一、web服务器
1.1、介绍
Web服务器一般指的是网站服务器,是指驻留因特网上某一台或N台计算机的程序,可以处理浏览器等Web客户端的请求并返回相应响应,目前最主流的三个Web服务器是Apache、 Nginx 、IIS、Tomcat。
1.2、服务器相关概念
ip地址:IP地址就是互联网上每台计算机的唯一地址,因此IP地址具有唯一性。在开发期间,自己的电脑既是一台服务器,也是一个客户端,可以在本机浏览器中输入127.0.0.1进行访问。
域名:尽管 IP地址能够唯一地标记网络上的计算机,但IP地址是一长串数字,不直观,而且不便于记忆,于是人们又发明了另一套字符型的地址方案,叫域名地址。IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS)的电脑中。在开发测试期间, 127.0.0.1 对应的域名是 localhost。
网络协议:网络上的计算机之间交换信息,就像我们说话用某种语言一样,在网络上的各台计算机之间也有一种语言,这就是网络协议,不同的计算机之间必须使用相同的网络协议才能进行通信。如:TCP、UDP、HTTP、FTP等等。
端口:务器的端口号就像是现实生活中的门牌号一样。通过门牌号,外卖员就可以准确把外卖送到你的手中。同样的道理,在一台电脑中,可以运行N多个web 服务。每个 web 服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的 web 服务进行处理。端口号总数:(2的16次方) 65535 其中1-1024系统保留
注:服务器上的端口号是不可以重复的,必须是独一无二
http服务默认端口号为 80 https默认端口号443
1.3、创建web服务器
NodeJs是通过官方提供的http模块来创建 web服务器的模块。通过几行简单的代码,就能轻松的手写一个web服务,从而对外提供 web 服务。
//①、导入http模块
const http = require('http')
//②、创建web服务对象实例
const server = http.createServer()
//③、绑定监听客户端请求事件request
server.on('request', (request, response) => {})
//request: 接受客户端请求对象,它包含了与客户端相关的数据和属性
request.url //客户端当前请求的uri地址
request.method //客户端请求的方式 get或post
req.headers //客户端请求头信息
//response:服务器对客户端的响应对象
//设置响应头信息 ,用于响应时有中文时乱码解决处理
response.setHeader('content-type', 'text/html;charset=utf-8')
//设置状态码
res.statusCod = 200
//向客户端发送响应数据,并结束本次请求的处理过程
res.end('hello world')
//④、启动服务
// 在window中查看当前电脑中是否占用此端口命令
// 如果返回为空,表示此端口没有表占用,有值则为占用
// netstat -ano | findstr 8080
// mac => sudo lsof -i tcp:port
server.listen(8080, '0.0.0.0',() => {
console.log('服务已启动')
})
1.4、静态资源服务器
实现思路:
客户端请求的每个资源uri地址,作为在本机服务器指定目录中的文件。
之后在通过相关模块进行读取文件中数据进行响应给客户端,从而实现静态服务器
实现步骤:
1、导入模块:
const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
2、使用http模块创建web服务器
const server = http.createServer();
3、将资源的请求uri地址映射为文件的存放路径
//事件监听
server.on('request',(req,res) => {
//得到请求的uri
let pathname = req.url;
pathname = pathname === '/' ? '/index.html' : pathname;
if(pathname !== '/favicon.ico'){
` //请求静态地址
let filepath = path.join(__dirname, 'public', pathname)
}
})
4、读取文件内容并相应给客户端
fs.readFile(filepath,(err, data) => {
if(err){
res.statusCode = 500;
res.end('服务器内部错误');
}else{
res.end(data);
}
})
一个综合的案例:(完成下面的代码,就可以搭建出一个静态服务器,用Nodejs运行下面代码,在打开页面时在IP后加对应的端口号即可)
const { join, extname } = require('path'); // 导入path 处理路径的模块
const fs = require('fs'); // 导入fs 文件模块
const http = require('http'); // 导入http web模块
const { createGzip } = require('zlib'); // 导入zlib 压缩模块
const mimes = require('./libs/mime'); // 关于扩展名的一个类型对象(第三方也有)
// 网站根目录 url地址中的 / => www目录
const webRoot = join(__dirname, 'www');
// req 是对象也是流 res是对象也是流
http.createServer((req, res) => {
if (req.url != '/favicon.ico') {
// 得到客户请求的pathname,并且做了缺省页的处理
let pathname = req.url === '/' ? '/index.html' : req.url
// 得到请求的对象服务器中真实的文件路径
let filepath = join(webRoot, pathname)
// 判断文件是否存在,如果不存在则返回404
if (fs.existsSync(filepath)) { // 文件存在
// 得到请求文件的扩展名
const ext = extname(filepath).slice(1)
// 得到扩展名
const mime = mimes[ext]
// 设置响应头
res.setHeader('content-type', mime)
res.setHeader('Content-Encoding', 'gzip')
// 读取文件并给响应
// res.end(fs.readFileSync(filepath))
fs.createReadStream(filepath).pipe(createGzip()).pipe(res)
} else {
res.statusCode = 404
res.end('not found')
}
}
}).listen(3000, '0.0.0.0')
1.5、get数据获取
get数据通过地址栏使用query方式进行传递的数据 例?id=1&name=zhangsan
const http = require('http');
const url = require('url');
http.createServer((req, res) => {
// 获取地址栏中 query数据
let { query } = url.parse(req.url, true);
console.log(query);
}).listen(8080)
const { join, extname } = require('path')
const fs = require('fs')
const http = require('http')
const { createGzip } = require('zlib')
const url = require('url')
const mimes = require('./libs/mime')
// 网站根目录 url地址中的 / => www目录
const webRoot = join(__dirname, 'www')
// get数据获取
http.createServer((req, res) => {
if (req.url != '/favicon.ico') {
// query,就是浏览器访问的get参数集合
let { pathname, query } = url.parse(req.url, true)
pathname = pathname === '/' ? '/index.html' : pathname
// 得到请求的对象服务器中真实的文件路径
let filepath = join(webRoot, pathname)
if (fs.existsSync(filepath)) {
// 得到请求文件的扩展名
const ext = extname(filepath).slice(1)
// 得到扩展名
const mime = mimes[ext]
// 设置响应头
res.setHeader('content-type', mime)
// res.setHeader('Content-Encoding', 'gzip')
let html;
if ('html' === ext) {
html = fs.readFileSync(filepath, 'utf-8');
// html = html.replace('{{id}}', query.id)
html = html.replace(/\{\{\s*(\w+)\s*\}\}/g, (preg,match) => {
return query[match]
})
} else {
html = fs.readFileSync(filepath)
}
res.end(html)
// fs.createReadStream(filepath).pipe(createGzip()).pipe(res)
} else {
res.statusCode = 404
res.end('not found')
}
}
}).listen(3000, '0.0.0.0')
1.6、post数据获取
表单数据多数为post进行提交到服务器端。
const http = require('http');
const queryString = require('querystring');
http.createServer((req, res) => {
let data = '';
// 数据接受中
req.on('data', res => {
data += res
});
// 数据传输结束了 接受到的所有数据
req.on('end', () => {
// post数据
let post = queryString.parse(data)
console.log(post);
});
}).listen(8080)
let postData = []
// 有数据流入
req.on('data', buffer => postData.push(buffer))
// 接受完毕
req.on('end', () => {
console.log(qs.parse(Buffer.concat(postData).toString()));
res.end('ok')
})
const { join, extname } = require('path')
const fs = require('fs')
const http = require('http')
const { createGzip } = require('zlib')
const url = require('url')
const qs = require('querystring')
const mimes = require('./libs/mime')
// 网站根目录 url地址中的 / => www目录
const webRoot = join(__dirname, 'www')
// get数据获取
http.createServer((req, res) => {
let { pathname, query } = url.parse(req.url, true)
// post处理
if (req.method === 'POST') {
// 路由 post登录处理 流
if (pathname == '/login') {
// application/x-www-form-urlencoded
/* let postData = ''
// 有数据流入
req.on('data', chunk => postData += chunk)
// 接受完毕
req.on('end', () => {
console.log(qs.parse(postData));
res.end('ok')
}) */
let postData = []
// 有数据流入
req.on('data', buffer => postData.push(buffer))
// 接受完毕
req.on('end', () => {
console.log(qs.parse(Buffer.concat(postData).toString()));
res.end('ok')
})
}
} else {
if (req.url != '/favicon.ico') {
// query,就是浏览器访问的get参数集合
pathname = pathname === '/' ? '/index.html' : pathname
// 得到请求的对象服务器中真实的文件路径
let filepath = join(webRoot, pathname)
if (fs.existsSync(filepath)) {
// 得到请求文件的扩展名
const ext = extname(filepath).slice(1)
// 得到扩展名
const mime = mimes[ext]
// 设置响应头
res.setHeader('content-type', mime)
// res.setHeader('Content-Encoding', 'gzip')
let html;
if ('html' === ext) {
html = fs.readFileSync(filepath, 'utf-8');
// html = html.replace('{{id}}', query.id)
html = html.replace(/\{\{\s*(\w+)\s*\}\}/g, (preg, match) => {
return query[match]
})
} else {
html = fs.readFileSync(filepath)
}
res.end(html)
// fs.createReadStream(filepath).pipe(createGzip()).pipe(res)
} else {
res.statusCode = 404
res.end('not found')
}
}
}
}).listen(3000, '0.0.0.0')
1.7、文件上传
异步文件上传,通过文件流方式进行文件上传
html5中提供一个js的api方法 ,得到文件域中的对象 File
const { join, extname } = require('path')
const fs = require('fs')
const http = require('http')
const { createGzip } = require('zlib')
const url = require('url')
const qs = require('querystring')
const mimes = require('./libs/mime')
// 网站根目录 url地址中的 / => www目录
const webRoot = join(__dirname, 'www')
// get数据获取
http.createServer((req, res) => {
let { pathname, query } = url.parse(req.url, true)
// post处理
if (req.method === 'POST') {
// 路由 post登录处理 流
if (pathname == '/upload') {
// 文件名称
let filename = Date.now() + extname(req.headers.filename);
// 实现文件上传
req.pipe(fs.createWriteStream(join(webRoot, 'uploads', filename)))
res.end(JSON.stringify({ code: 0, url: 'http://localhost:3000/uploads/' + filename }))
}
} else {
if (req.url != '/favicon.ico') {
// query,就是浏览器访问的get参数集合
pathname = pathname === '/' ? '/index.html' : pathname
// 得到请求的对象服务器中真实的文件路径
let filepath = join(webRoot, pathname)
if (fs.existsSync(filepath)) {
// 得到请求文件的扩展名
const ext = extname(filepath).slice(1)
// 得到扩展名
const mime = mimes[ext]
// 设置响应头
res.setHeader('content-type', mime)
// res.setHeader('Content-Encoding', 'gzip')
let html;
if ('html' === ext) {
html = fs.readFileSync(filepath, 'utf-8');
// html = html.replace('{{id}}', query.id)
html = html.replace(/\{\{\s*(\w+)\s*\}\}/g, (preg, match) => {
return query[match]
})
} else {
html = fs.readFileSync(filepath)
}
res.end(html)
// fs.createReadStream(filepath).pipe(createGzip()).pipe(res)
} else {
res.statusCode = 404
res.end('not found')
}
}
}
}).listen(3000, '0.0.0.0')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<img src="" alt="" id="pic">
</div>
<div>
<input type="file" id="file">
</div>
<script>
const fileDom = document.querySelector('#file')
// 监听文件域表单项改变事件,如果有改变则触发,从而得到一个File对象
fileDom.onchange = function () {
// 文件对象
const file = fileDom.files[0];
// html5提供一个api
const fileReader = new FileReader()
// 把得到的File对象转为流 stream可以通过ajax发送
fileReader.readAsArrayBuffer(file)
fileReader.onloadend = function () {
const xhr = new XMLHttpRequest;
xhr.open('POST', '/upload', true);
xhr.setRequestHeader('filename', file.name)
xhr.send(fileReader.result)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let json = eval('(' + xhr.responseText + ')')
pic.src = json.url
}
}
}
}
</script>
</body>
</html>
1.8、路由
var http = require('http')
var fs = require('fs')
http.createServer( function ( req, res ) {
switch ( req.url ) {
case '/home':
res.write('home')
res.end()
break
case '/mine':
res.write('mine')
res.end()
break
case '/login':
fs.readFile( './static/login.html',function ( error , data ) {
if ( error ) throw error
res.write( data )
res.end()
})
break
case '/fulian.jpg':
fs.readFile( './static/fulian.jpg', 'binary', function( error , data ) {
if( error ) throw error
res.write( data, 'binary' )
res.end()
})
break
default:
break
}
}).listen( 3000, '0.0.0.0', function () {
console.log( '服务器运行在: http://localhost:3000' )
})
1.9、跨域问题解决
同源策略
是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
如果两个 URL 的 协议、端口和主机地址都相同的话,则这两个 URL 是同源
为什么浏览器不支持跨域?
跨域的访问会带来许多安全性的问题,比如,cookie一般用于状态控制,常用于存储登录的信息,如果允许跨域访问,那么别的网站只需要一段脚本就可以获取你的cookie,从而冒充你的身份去登录网站,造成非常大的安全问题,因此,现代浏览器均推行同源策略。
1.9.1、jsonp
jsonp不是ajax,它就是一个网络请求,利用 script标签中的src属性可以跨域,然后了务器端返回要执行函数
客户端
<script>
function 函数(){}
</script>
<script src=’http://xxx.com:3000/?cb=函数名’></script>
服务器
返回这个函数
1.9.2、cors
html5提出的解决跨域的方案,通过给响应头设置信息,让浏览器允许跨域。
//服务端
// 通过设置响应头信息,来允许ajax跨域请求
// * 表示允许所有域名来跨域请求
// * 如果你所写为 * 则cookie将无法跨域
if (allowDomain.includes(req.headers.origin)) {
// res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4000')
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
res.setHeader('Access-Control-Allow-Credentials', 'true')
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS')
res.setHeader('Access-Control-Allow-Headers', 'username')
res.setHeader('Access-Control-Expose-Headers', 'uuuu')
}
res.setHeader('content-type', 'application/json;charset=utf-8')
res.setHeader('uuuu', 'admin888')
res.end(jsonFn(user))
//客户端
<script>
const xhr = new XMLHttpRequest()
document.cookie = 'name=abc;path=/'
xhr.open('put', 'http://localhost:3000/', true)
xhr.withCredentials = true;
xhr.setRequestHeader('username', 'aaa')
xhr.send()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.getResponseHeader('uuuu'))
console.log(xhr.responseText)
}
}
</script>
1.9.3、服务器代理
跨域它是浏览的同源策略导致,但是服务器对服务器通信,是没有跨域限制
中间代理服务器的配置:
// 接口服务器,没有静态资源
const http = require('http')
// 代理
const { createProxyMiddleware: proxy } = require('http-proxy-middleware')
const server = http.createServer((req, res) => {
// cors
res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
let proxyFn = proxy('/api', {
// 请求的目标地址
target: 'http://localhost:5000',
// 取消客户请求过来的前缀
pathRewrite: {
'^/api': ''
},
// 修改代理请求到的服务器中的请求头中Host字段为本机字段
changeOrigin: true
})
proxyFn(req, res)
/* let proxyFn2 = proxy('/aaa', {
// 请求的目标地址
target: 'http://localhost:6000',
// 取消客户请求过来的前缀
pathRewrite: {
'^/aaa': ''
}
})
proxyFn2(req, res) */
/* if ('/home' === req.url) {
res.end(JSON.stringify({ code: 0, msg: '4000接口', data: { title: 'aaaa' } }))
return;
} */
}).listen(4000, '0.0.0.0')
1.9.4、postMessage
<!-- a.html 3000-->
<iframe src="http://localhost:4000/" frameborder="0" id="frame" onload="load()"></iframe>
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('aaa', 'http://localhost:4000')
}
window.onmessage = function (e) {
console.log(e.data)
}
</script>
<!-- b.html 4000-->
<script>
window.onmessage = function (e) {
console.log(e.data)
e.source.postMessage('bbb', e.origin)
}
</script>
1.10、Node数据获取(爬虫)
纯node获取:
const https = require('https')
// 数据抓取
https.get('https://news.ke.com/bj/baike/0725210.html', res => {
let html = ''
res.on('data', chunk => html += chunk)
res.on('end', () => {
const preg = /<title>(.*)<\/title>/i
let arr = html.match(preg)
console.log(arr[1]);
})
})
使用第三方插件在nodejs中使用像jquery选择器一样的功能:
安装:
npm i -S cheerio
const https = require('https')
const http = require('http')
const cheerio = require('cheerio')
const path = require('path')
const fs = require('fs')
const dirpath = path.join(__dirname, 'pics')
// 数据抓取
http.get('http://www.jj20.com/bz/nxxz/', res => {
let html = ''
res.on('data', chunk => html += chunk)
res.on('end', () => {
// 把当前html内容,使用cheerio来完成选择加载s
const $ = cheerio.load(html)
const imgs = $('.g-box-1200 > .picbz > li > a > img')
imgs.each((index, el) => {
let src = $(el).attr('src')
if (src) {
http.get(src, ret => {
let name = path.basename(src)
ret.pipe(fs.createWriteStream(dirpath + '/' + name))
})
}
})
})
})