express基础
express.js是nodejs的框架。它将http-server进行了封装。
1. express基础源码
let http = require('http'); let methods = require('methods');//express依赖的第三方库,包含所有的方法 let url = require('url'); let fs = require('fs'); let path = require('path'); function application() { function app(req, res) {// http.createServer(function(){})中的监听函数 let { pathname } = url.parse(req.url); let reqMethod = req.method.toLowerCase(); let index = 0; function next(err) {//如果next传参,表示抛出异常 // 递归终止条件;即遍历完app.routes仍然调用了next方法 if (index === app.routes.length) { res.statusCode = 404; // 任何路由都不匹配 return res.end(`Cannot ${reqMethod.toUpperCase()} ${pathname}`); }; const layer = app.routes[index++]; const { path, method, handler } = layer; if (err) { // 错误捕获中间件 if (method === 'middleware' && handler.length === 4) { return handler(err, req, res) } else { next(); } } else { if (method === 'middleware') {// 中间件 // 中间件的路由匹配 if(path === pathname || path === '/' || path.startsWith(path+'/')) { return handler(req,res,next); } else {//如果不能匹配中间件的路由 next(); } } else { if(path.params) {// 带参数的路径 let matches = pathname.match(path);// 取出路径中对应的参数 if (matches && method === reqMethod) { let [, ...args] = matches; req.params = path.params.reduce((memo, curr, index) => (memo[curr] = args[index],memo), {}); return handler(req, res); } } if((pathname === path || pathname === '*') && (method === reqMethod || method === 'all')) { return handler(req,res); } next(); } } } next(); } app.routes = []; [...methods, 'all'].forEach(item => { app[item] = function(path, handler) { if(path.includes(':')) {// 路径含有路径参数 let params = []; let str = path.replace(/:([^\/]*)/g, function() { params.push(arguments[1]); return '([^\/]*)';// 用于匹配用户输入的带参数的路由 }) path = new RegExp(str); path.params = params; } const layer = { method: item, path, handler } app.routes.push(layer); } }) // app中使用中间件; app.use = function(path, handler) { if(typeof handler !== 'function') {// 如果有路由 handler = path; path = '/'; } const layer = { path, method: 'middleware', handler } app.routes.push(layer); } //内置中间件执行;添加公共属性和方法 app.use(function(req, res, next) { const { query, path } = url.parse(req.url, true); req.query = query; req.path = path; res.send = function(value) { if(typeof value === 'object') {//json对象 res.setHeader('Content-Type', 'application/json;charset=utf-8'); res.end(JSON.stringify(value)); } else if(typeof value === 'number') {//校验码 const status = require('_http_servet').status_CODES; res.end(status[value]); } else if(typeof value === 'string' || Buffer.isBuffer(value)) { res.setHeader('Content-Type', 'text/html;charset=utf-8'); res.end(value); } } res.sendFile = function(pathname) { return fs.createReadStream(pathname).pipe(res); } next(); }) app.listen = function(port) { let server = http.createServer(app); server.listen(port); } return app; } // 高阶函数 application.static = function(root) { return function(req, res, next) { let absPath = path.join(root, req.url); fs.stat(absPath, function(err, statObj) { if(err) { return next(); } if (statObj.isDirectory()) { absPath = absPath + 'index.html'; } fs.createReadStream(absPath).pipe(res); }) } } module.exports = application;
2. express中间件
请求到路径处理之前,在中间执行的内容,就是中间件。
中间件的应用:
1. 可以在中间件中添加权限校验
2. 可以在中间件中添加公共属性和方法
3. 中间件的回调函数中有三个参数(req, res, next), 其中用户可以通过next方法决定是否继续执行
中间件分为三类:
1. 自定义中间件
app.use(path, function(req,res,next) { // 向下执行 next(); })
2. 特殊中间件
1. 静态服务中间件
静态服务中间件用于服务端提供静态资源服务。
const app = express(); const root = path.join(__dirname, './index.html') // 如果root=__dirname表示提供基于根目录的静态服务 app.use(express.static(root));
2. 内置中间件
内置中间件主要提供了公共属性(req.query, req.path)和公共方法res.send, res.sendFile。
1)公共属性
const { query, path } = url.parse(req.url, true); req.query = query; req.path = path;
2)公共方法
// send方法根据传递的参数类型不同,返回不同的结果 res.send = function(value) { if(typeof value === 'object') {//json对象 res.setHeader('Content-Type', 'application/json;charset=utf-8'); res.end(JSON.stringify(value)); } else if(typeof value === 'number') {//校验码 const status = require('_http_servet').status_CODES; res.end(status[value]); } else if(typeof value === 'string' || Buffer.isBuffer(value)) { res.setHeader('Content-Type', 'text/html;charset=utf-8'); res.end(value); } } // sendFile接受一个路径参数,返回路径对应的文件内容 res.sendFile = function(pathname) { return fs.createReadStream(pathname).pipe(res); }
3. 错误捕获中间件
next方法传递的参数即抛出的错误信息。通过错误捕获中间件进行捕获。
错误捕获中间件的特点是,回调函数含有4个参数,第一个参数是获取到的error信息。
app.use('/user/', function(req, res, next) { const absPath = path.resolve(__dirname, './index.html'); res.sendFile(absPath); next('dddd'); //抛出错误信息; }); // 由于上面抛出错误,该步不执行 app.get('/read', function(req, res) { res.send(req.session); }); // 错误捕获中间件; app.use(function(err, req, res, next) { console.log(err); // dddd });
3. 第三方中间件
1. body-parser
根据请求头的Content-Type获取请求体的数据类型,然后将其解析后的数据赋值到req.body中。
app.use(bodyParser.json());// application/json // extended=true,表示使用qs库;=false表示使用querystring库 app.use(bodyParser.urlencoded({extended: true}));// application/x-www-form-urlencoded
2. cookie-parser
1) 接受一个参数,该参数是签名cookie的密钥。并且可以通过req.secret获取该密钥。
参数存在时express中的API中res.cookie(key, value, options)中options中设置signed: true生效,
写入客户端的cookie是签名后的cookie。
2)解析cookie, 通过req.cookies可以获取所有的非签名的cookie。
req.signedCookies获取到的是签名的cookie
app.use(cookieParser('lyra'));
3. express-session
1)向客户端写入sessionId对应的cookie, session的数据存在服务器端。
可以通过服务器端设置req.session[key] = value进行数据添加和读取。
2)接受一个对象,用于设置参数。
其中secret对应的参数,如果使用可cookie-parser,则需要保持两者一致。
app.use(session({ // 会向客户端发送一个key为connect.id, value为唯一标识的cookie。 resave: true, saveUninitialized: true, cookie: {httpOnly: true}, //设置connect.id对应的cookie的属性 secret: 'lyra' //加密和cookieParser的密钥一致 }));
3. 示例
let express = require('express'); let path = require('path'); let bodyParser = require('body-parser'); let cookieParser = require('cookie-parser'); let session = require('express-session'); let app = express(); // app本质是监听函数 /** * 自定义中间件: * 1. 可以在中间件中进行权限校验 * 2. 可以在中间件中添加公共方法和属性 * 3. 中间件的回调函数有next方法,可以由用户调用与否,决定是否向下执行 * 特殊中间件: * 1. 错误捕获中间件: next方法传递的参数视为抛出的错误信息 * 2. 内置中间件: 添加公共属性和方法(path, query, send, sendFile) * 3. 静态服务中间件: 启动静态服务(高阶函数) * 第三方中间件: * 1. body-parser: 解析请求体req.body(json/x-www-form-urlencoded) * 2. cookie-parser: 1)解析cookie, 赋值req.cookies返回未签名cookie * req.signedCookies返回签过名的cookie;示例: {name1: lyra} * 2)支持cookie签名,使得express的res.cookie的signed参数生效 * cookieParser传递的参数是密钥,通过req.secret获取 * 3. express-session: session数据存储在服务端,客户端存储的是sessionId * 1)在req上提供一个session对象;session * 2)提供一个密钥 */ // 静态服务中间件 app.use(express.static(__dirname)); app.use(function(req, res, next) { next(); }); app.use(bodyParser.json());// application/json // extended=true,表示使用qs库;=false表示使用querystring库 app.use(bodyParser.urlencoded({extended: true}));// application/x-www-form-urlencoded app.use(cookieParser('lyra')); app.use(session({ // 会向客户端发送一个key为connect.id, value为唯一标识的cookie。 resave: true, saveUninitialized: true, secret: 'lyra' //加密和cookieParser的密钥一致 })); app.use('/user/', function(req, res, next) { const absPath = path.resolve(__dirname, './index.html'); res.sendFile(absPath); next('dddd'); }); app.get('/read', function(req, res) { res.send(req.session); }); app.get('/write', function(req, res) { // 手动设置session req.session.a = 'hello'; //express设置cookie;时间为ms res.cookie('name', 'lyra', {maxAge: 20000, signed: false}); res.cookie('name1', 'lyra', {maxAge: 20000, signed: true}); res.end(); }); app.get('/user/:name/:id', function(req, res) { // console.log(req.params); }); app.post('/post', function(req, res) { res.send(req.body); }); app.use(function(err, req, res, next) { console.log(err); }); app.listen(3000);