1. Express
- Express 是一个第三方模块,对原生模块封装了一套更灵活、更简洁的应用框架,其在 Node.js 环境的地位和作用好比 jQuery 在前端的地位和作用。
- 使用 Express 可以快速地搭建一个完整功能的网站
1.1 安装
npm install express
1.2 使用
//引入模块 var express = require('express'); var app = express(); //开启服务器,定义端口8080: app.listen(8080, _ => { console.log('Server running on http://localhost:8080') });
1.3 Express中间件(middleware)
- 中间件是一个封装了某些处理数据功能的函数,在request或response调用之前执行,从本质上来说,一个Express应用其实就是在调用各种中间件
1.3.1 使用中间件
- 格式:app.use([path],...middlewares)
1.3.2 内置中间件
express.static(root, [options])
基于 server-static 开发的中间件,负责托管 Express 应用内的静态资源,如:图片, CSS, JavaScript 等,一般用于实现静态资源服务器
- root 参数指的是静态资源文件所在的根目录。
- options 对象是可选的,支持以下属性:
- maxAge
app.use(express.static('./public'));
1.3.3 自定义中间件
- 格式为:function(request, response, next) {}
- next():next是一个方法,因为一个应用中可以使用多个中间件,而要想运行下一个中间件,那么上一个中间件必须运行
app.use((req,res,next)=>{ // 任何请求都进入此中间件 }); app.use('/goods',(req,res,next)=>{ //只有请求地址为/goods时才进入此中间件 })
1.3.4 常用第三方中间件
- body-parser:解析body中的数据到request.body
- multer:用于处理FormData数据(表单的 enctype="multipart/form-data"属性)
- cookie-parser:解析客户端cookie中的数据到request.cookies
- express-session:解析服务端生成的sessionid对应的session数据到request.session属性
- http-proxy-middleware: 服务器代理中间件
1.4 定义路由
-
服务器知识
- 请求request
- 相应response
- 前后端分离(BSR)与耦合架构(SSR)的概念等
-
RESTfulAPI
- 请求类型
- 请求路径
-
获取请求参数
- 参数通过请求路径传递(get):request.query
- 参数通过请求体/formData(post):request.body
- 动态路由:request.params
1.4.1 GET
// 定义主页路由,当我们访问:`http://localhost:8080/`时触发 app.get('/', function(request, response){ response.send('首页'); }); //定义任意路由(如:cart) ,当我们访问:`http://localhost:8080/cart`时触发 app.get('/cart', function(request, response){ response.send('购物车页面'); }) //访问地址:http://localhost:8080/search?keyword=iphonX app.get('/search', function(request, response){ var params = { username: request.query.keyword } response.send(params); }); //访问地址:http://localhost:8080/goods/10086, app.get('/goods/:id', function(request, response){ var params = { username: request.params.id } response.send(params); });
1.4.2 POST
- post 参数接收,可依赖第三方模块 body-parser 作为__express中间件__进行转换会更方便、更简单
post传参:body-parser
- string:bodyParser.urlencoded()
- json:bodyParser.json()
//参数接受和 GET 基本一样,不同的在于 GET 是 request.query 而 POST 的是 request.body var bodyParser = require('body-parser'); // 创建urlencoded 编码解析(把请求头content-type值为application/x-www-form-urlencoded时的数据格式化到request.body中) var urlencodedParser = bodyParser.urlencoded({ extended: false }); // 创建json编码解析(把请求头content-type值为application/json时的数据格式化到request.body中) const jsonParser = bodyParser.json(); app.post('/getUsers', urlencodedParser, function (request, response) { var params = { username: request.body.username, age: request.body.age } response.send(params); });
1.4.3 图片上传:multer
- multipart/form-data
// @文件上传 const multer = require('multer') const path = require('path'); // 配置上传参数 let storage = multer.diskStorage({ // 上传文件保存目录,无则自动创建 destination:'./uploads/', // 格式化文件名 filename: function (req, file, cb) { // 获取文件后缀名 let ext = path.extname(file.originalname); cb(null, file.fieldname + '-' + Date.now() + ext); } }) // 设置文件保存目录 let upload = multer({storage}); Router.post('/avatar',upload.single('avatar'),async (req,res)=>{ // 后端如何接收文件格式的数据 // post -> req.body // 文件 -> multer中间件格式化到req.file res.send(formatData({data:req.file})) })
1.4.4 跨域支持
- 把这个路由配置放在所有路由的前面,方便调用next操作
app.all('*', function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"); res.header("Access-Control-Allow-Methods","PUT,POST,GET,PATCH,DELETE,OPTIONS"); // 跨域请求CORS中的预请求 if(req.method=="OPTIONS") { res.sendStatus(200);/*让options请求快速返回*/ } else{ next(); } });
- 复杂跨域:
- 非GET、HEAD、POST请求。
- POST请求的Content-Type不是application/x-www-form-urlencoded, multipart/form-data, 或text/plain。
- 添加了自定义header,例如Token。
1.4.5 代理服务器
- 代理服务器最关键和主要的作用就是请求转发,即代理服务器将实际的浏览器请求转发至目标服务器,
实现这个功能,关键就在下面两点:
- 请求转发至目标服务器。
- 响应转发至浏览器。
无论请求还是响应,转发有需要关注两个点:
- 请求或响应头。
- http请求是无状态的,我们使用session的方式验证用户权限,session等经常保存在cookie中,所以,头的转发是必须的。
- 请求或响应实体数据。
- 利用http-proxy-middleware实现代理
- webpack代理服务器使用的中间件
测试api: http://52.198.113.252/country
2 request模块
可用于发起 http 或 https 请求,可理解成服务端的 ajax 请求。可用于简单的服务器代理,用法和 ajax 类似。
- 安装: npm install request --save
2.1 GET 请求
const request = require('request'); request.get('https://cnodejs.org/api/v1/topics?page=1&limit=10', (error, response, body) => { // error: 错误信息,默认null // response: 相应对象 // body: 请求响应内容 console.log(body) }); //or request('https://cnodejs.org/api/v1/topics?page=1&limit=10', (error, response, body) => { console.log(body) });
2.2 参数设置
const request = require('request'); request({ url: url, method: 'get', timeout: 10000, headers: {}, proxy: {} , agentOptions: {}, params: {} }, function(err, res, body) {});
3 cheerio模块
一个专门为服务器设计包含jquery核心库的第三方模块
3.1 cheerio.load(html)
- 用于在于html代码以便进一步的精确选取,它不会执行html代码中的css和js
3.2 爬虫应用(爬取图片到本地)
- 又被称为网页蜘蛛,网络机器人,主要是在服务端去请求外部的 url 拿到对方的资源,然后进行分析并抓取有效数据。
//这里用 request 实现一个简单的图片抓取的小爬虫 const request = require('request'); const fs = require('fs'); const cheerio = require('cheerio');// cheerio为包含jQuery 核心的子集 const path = require('path'); request('http://www.lanrentuku.com/', (error, response, body) => { // 执行load方法载入获取到的html结构 let $ = cheerio.load(body); // 利用jquery的核心方法获取html代码中的具体元素 $('img', '.in-ne').each((i, e) => { let src = $(e).attr('src'); let filename = path.basename(src); // 利用request与stream数据流保存爬取到的图片到本地硬盘 request(src).pipe(fs.createWriteStream(filename)) }) });
3.3 stream流
-
Stream 是一个抽象接口,往往用于打开大型的文本文件,创建一个读取操作的数据流。Node 中有很多对象实现了这个接口。如http 服务器发起请求的request 对象就是一个 Stream。所有的 Stream 对象都是 EventEmitter 的实例
-
所谓大型文本文件,指的是文本文件的体积很大,读取操作的缓存装不下,只能分成几次发送,每次发送会触发一个data事件,发送结束会触发end事件。
3.3.1 读取流(以流的方式读取文件内容)
- setEncoding():设置读取编码格式
- data事件:读取数据中
- end事件:数据读取完毕
var fs = require("fs"); var data = ''; // 创建可读流 // 以流的方式读取input.txt中的内容 var readerStream = fs.createReadStream('input.txt'); // 设置编码为 utf8 readerStream.setEncoding('UTF8'); // 处理流事件 --> data, end, and error readerStream.on('data', function(chunk) { data += chunk; }); readerStream.on('end',function(){ console.log(data); }); readerStream.on('error', function(err){ console.log(err.stack); });
3.3.2 写入流(以流的方式写入文件)
- write()方法:写入内容方法
- end()方法:标记写入结束
- finish事件:写入完成后执行
//创建一个可以写入的流,写入到文件 output.txt 中 var fs = require("fs"); var data = '中国'; // 创建一个可以写入的流,写入到文件 output.txt 中 var writerStream = fs.createWriteStream('output.txt'); // 使用 utf8 编码写入数据 writerStream.write(data,'UTF8'); // 标记文件末尾 writerStream.end(); // 处理流事件 --> data, end, and error writerStream.on('finish', function() { console.log("写入完成。"); }); writerStream.on('error', function(err){ console.log(err.stack); });
3.3.3 管道流pipe
- 管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。
var fs = require("fs"); // 创建一个可读流 var readerStream = fs.createReadStream('input.txt'); // 创建一个可写流 var writerStream = fs.createWriteStream('output.txt'); // 管道读写操作 // 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中 readerStream.pipe(writerStream);
3.3.4 链式流(多个pipe)
- 链式是通过连接输出流到另外一个流并创建多个对个流操作链的机制。链式流一般用于管道操作。接下来我们就是用管道和链式来压缩和解压文件。
4 WebSocket
ws模块
4.1 协议
4.1.1 HTTP/HTTPS 协议
http/https协议可以总结几个特点:
- 一次性的、无状态的短连接:客户端发起请求、服务端响应、结束。
- 被动性响应:只有当客户端请求时才被执行,给予响应,不能主动向客户端发起响应。
- 信息安全性:得在服务器添加 SSL 证书,访问时用 HTTPS。
- 跨域:服务器默认__不支持跨域__,可在服务端设置支持跨域的代码或对应的配置。
4.1.2 TCP 协议
TCP 协议可以总结几个特点:
- 有状态的长连接:客户端发起连接请求,服务端响应并建立连接,连接会一直保持直到一方主动断开。
- 主动性:建立起与客户端的连接后,服务端可主动向客户端发起调用。
- 信息安全性:同样可以使用 SSL 证书进行信息加密,访问时用 WSS 。
- 跨域:默认支持跨域。
4.1.3 WebSocket 协议
- WebSocket目前由 W3C 进行标准化。WebSocket已经受到Firefox 4、Chrome 4、Opera 10.70 以及Safari 5 等浏览器的支持。
- 如果在前端我们可以把 AJAX 请求当作一个 HTTP 协议的实现,那么,WebSocket 就是 TCP 协议的一种实现。
4.2 使用Socket
4.2.1 服务端
1. 安装ws模块
npm install ws
2. 开启WebSocket服务器
let socketServer = require('ws').Server; let wss = new socketServer({ port: 1001 });
3. 配合express开启服务器
let express = require('express'); let http = require('http'); let ws = require('ws'); let app = express(); let server = http.Server(app); let SocketServer = ws.Server; let wss = new SocketServer({ server, port:1001 });
4. 用 on 来进行事件监听
- connection:连接监听,当客户端连接到服务端时触发该事件,返回连接客户端对象
- close:连接断开监听,当客户端断开与服务器的连接时触发
- message:消息接受监听,当客户端向服务端发送信息时触发该事件
- send: 向客户端推送信息
4.2.2 客户端
WebSocket是HTML5开始提供的一种基于 TCP 的协议,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据
- 实例化 WebSocket ,参数为 WebSocket 服务器地址,建立与服务器的连接
事件
- onopen:当网络连接建立时触发该事件
- onclose:当服务端关闭时触发该事件
- onerror:当网络发生错误时触发该事件
- onmessage:当接收到服务器发来的消息的时触发的事件,也是通信中最重要的一个监听事件
方法
- close(): 在客户端断开与服务端的连接 socket.close()
- send():向服务端推送消息
//连接 socket 服务器 var socket = new WebSocket('ws://localhost:1001'); //监听 socket 的连接 socket.onopen = function(){ document.write("服务已连接 ws://localhost:1001"); } //监听服务端断开 socket.onclose = function(){ document.write("服务已断开"); socket = null; } //监听服务端异常 socket.onerror = function(){ document.write("服务错误"); socket = null; } //监听服务端广播过来的消息 socket.onmessage = function(msg){ var msgObj = JSON.parse(msg.data); if(msgObj.status == 0){ $('<p>' + msgObj.nickname + '[' + msgObj.time + ']退出聊天</p>').appendTo('.msgList'); } else{ $('<p>' + msgObj.nickname + '[' + msgObj.time + ']:' + msgObj.message + '</p>').appendTo('.msgList'); } } var sendMessage = function(_mess){ if(socket){ var myDate = new Date(); var now = myDate.getMonth() + '-' + myDate.getDate() + ' ' + myDate.getHours() + ':' + myDate.getMinutes() + ':' + myDate.getSeconds(); var mesObj = { nickname: $('#nickName').val(), message: _mess || $('#mesBox').val(), time: now } //向服务端发送消息 socket.send(JSON.stringify(mesObj)); } }
【案例】
- 爬虫的实现
- 爬取网站图片并保存到本地
- 利用express实现静态资源服务器
- 利用路由编写RESTful规范的数据接口
- 实现图片上传效果
- 实现多人聊天室
【练习】
- 爬取某个网站的数据并写入数据库
- 注册登录接口
- 加密解密知识