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);