Express是基于Node.js平台,快速、开发、极简的web开发框架
路由
路由:根据不同的方法和不同的路径返回不同的内容
我们可以定义任意定义路由规则,比如有些时候希望只匹配路径 ,不管什么方法都能处理
简单的路由方法
app.all('*', function (req, res) {
res.end('404');
});
app.get('/hello', function (req, res) {
res.end('hello');
});
中间件
使用use来定义一个中间件,next也是一个函数,调用它则意味着当前的中间件执行完毕,可以继续向下执行别的中间件了
简单的中间件方法
app.use(function (req, res, next) {
res.setHeader('Content-Type', 'text/html;charset=utf8');
console.log('没有路径的中间件');
next('我错了');
});
app.use('/water', function (req, res, next) {
console.log('过滤杂质');
next();
});
app.get('/water', function (req, res) {
res.end('water');
});
app.use('/hello', function (err, req, res, next) {
res.end('hello ' + err);
});
- 错误处理中间件有四个参数
err, req, res, next
- 如果调用
next
的时候如果传一个任意参数就表示此函数发生了错误,然后express
就会跳过后面所有的中间件和路由,交给错误处理中间件来处理
param
内置中间件,用来处理路径参数的,在此保存路径参数名和处理函数
const express = require('./express');
const app = express();
function getUser(userid) {
return { userid: 1, age: 8, name: 'zfpx' };
}
function setUser(user) {
//向数据库里保存用户
}
app.get('/username/:userid/:name', function (req, res) {
req.user.name = req.params.name;
setUser(req.user);
res.end('update name successfully');
});
app.get('/uesrage/:userid/:age', function (req, res) {
req.user.age = req.params.age;
setUser(req.user);
res.end('update age successfully');
});
app.param('userid', function (req, res, next, userid) {
req.user = getUser(userid);
next();
});
app.listen(8080);
模拟实现Express
项目结构:
application.js
中挂载了所有核心方法,实现Router和应用的分离
const Router = require('./router');
const http = require('http');
const methods = require('methods');//['get','post']
const slice = Array.prototype.slice;
function Application() {
this.settings = {};//用来保存参数
this.engines = {};//用来保存文件扩展名和渲染函数的函数
}
Application.prototype.lazyrouter = function () {
if (!this._router) {
this._router = new Router();
}
}
Application.prototype.param = function (name, handler) {
this.lazyrouter();
this._router.param.apply(this._router, arguments);
}
// 传二个参数表示设置,传一个参数表示获取
Application.prototype.set = function (key, val) {
if (arguments.length == 1) {
return this.settings[key];
}
this.settings[key] = val;
}
//规定何种文件用什么方法来渲染
Application.prototype.engine = function (ext, render) {
let extension = ext[0] == '.' ? ext : '.' + ext;
this.engines[extension] = render;
}
methods.forEach(function (method) {
Application.prototype[method] = function () {
if (method == 'get' && arguments.length == 1) {
return this.set(arguments[0]);
}
this.lazyrouter();
//这样写可以支持多个处理函数
this._router[method].apply(this._router, slice.call(arguments));
return this;
}
});
Application.prototype.route = function (path) {
this.lazyrouter();
//创建一个路由,然后创建一个layer ,layer.route = route.this.stack.push(layer)
this._router.route(path);
}
//添加中间件,而中间件和普通的路由都是放在一个数组中的,放在this._router.stack
Application.prototype.use = function () {
this.lazyrouter();
this._router.use.apply(this._router, arguments);
}
Application.prototype.listen = function () {
let self = this;
let server = http.createServer(function (req, res) {
function done() {//如果没有任何路由规则匹配的话会走此函数
res.end(`Cannot ${req.method} ${req.url}`);
}
//如果路由系统无法处理,也就是没有一条路由规则跟请求匹配,是会把请求交给done
self._router.handle(req, res, done);
});
server.listen(...arguments);
}
module.exports = Application;
param
内置中间件方法,批量设置参数set
传二个参数表示设置,传一个参数表示获取engine
规定何种文件用什么方法来渲染route
创建一个路由,然后创建一个layer ,layer.route = route.this.stack.push(layer)
use
添加中间件,而中间件和普通的路由都是放在一个数组中的,放在this._router.stack
listen
监听客户端发来请求的函数
express.js
导出创建的application实例
const http = require('http');
const url = require('url');
const Router = require('./router');
const Application = require('./application');
function createApplicaton() {
return new Application();
}
createApplicaton.Router = Router;
module.exports = createApplicaton;
html.js
实现模板引擎
const fs = require('fs');
function render(filepath, options, callback) {
fs.readFile(filepath, 'utf8', function (err, str) {
let head = "let tpl = ``;\nwith (obj) {\n tpl+=`";
str = str.replace(/<%=([\s\S]+?)%>/g, function () {
return "${" + arguments[1] + "}";
});
str = str.replace(/<%([\s\S]+?)%>/g, function () {
return "`;\n" + arguments[1] + "\n;tpl+=`";
});
let tail = "`}\n return tpl; ";
let html = head + str + tail;
let fn = new Function('obj', html);
let result = fn(options);
callback(null, result);
});
}
module.exports = render;
router中的文件关系如下:
* Router Layer 路径 处理函数(route.dispatch) 有一个特殊的route属性
* Route layer 路径 处理函数(真正的业务代码) 有一特殊的属性method
router/index
Router的stack数组存放子路由和中间件,handle方法,循环stack数组,根据规则执行回调函数
const Route = require('./route');
const Layer = require('./layer');
const url = require('url');
const methods = require('methods');
const init = require('../middle/init');
const slice = Array.prototype.slice;
function Router() {
function router(req, res, next) {
router.handle(req, res, next);
}
Object.setPrototypeOf(router, proto);
router.stack = [];
router.paramCallbacks = {};
router.use(init);
return router;
}
let proto = Object.create(null);
proto.route = function (path) {
let route = new Route(path);
let layer = new Layer(path, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
return route;
}
proto.use = function (path, handler) {
if (typeof handler != 'function') {
handler = path;
path = '/';
}
let layer = new Layer(path, handler);
layer.route = undefined;
this.stack.push(layer);
}
methods.forEach(function (method) {
proto[method] = function (path) {
let route = this.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;
}
});
proto.param = function (name, handler) {
if (!this.paramCallbacks[name]) {
this.paramCallbacks[name] = [];
}
this.paramCallbacks[name].push(handler);
}
proto.handle = function (req, res, out) {
let idx = 0, self = this, slashAdded = false, removed = '';
let { pathname } = url.parse(req.url, true);
function next(err) {
if (removed.length > 0) {
req.url = removed + req.url;
removed = '';
}
if (idx >= self.stack.length) {
return out(err);
}
let layer = self.stack[idx++];
if (layer.match(pathname)) {
if (!layer.route) {
removed = layer.path;
req.url = req.url.slice(removed.length);
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
} else {
if (layer.route && layer.route.handle_method(req.method)) {
req.params = layer.params;
self.process_params(layer, req, res, () => {
layer.handle_request(req, res, next);
});
} else {
next(err);
}
}
} else {
next(err);
}
}
next();
}
proto.process_params = function (layer, req, res, out) {
let keys = layer.keys;
let self = this;
let paramIndex = 0 ;
function param() {
if (paramIndex >= keys.length) {
return out();
}
key = keys[paramIndex++];
name = key.name;
val = layer.params[name];
callbacks = self.paramCallbacks[name];
if (!val || !callbacks) {
return param();
}
execCallback();
}
let callbackIndex = 0;
function execCallback() {
callback = callbacks[callbackIndex++];
if (!callback) {
return param();
}
callback(req, res, execCallback, val, name);
}
param();
}
module.exports = Router;
router/layer
把同一个path形成一层,并提供match方法进行匹配判断,handle方法来执行当前层的深入匹配methods
const pathToRegexp = require('path-to-regexp');
function Layer(path, handler) {
this.path = path;
this.handler = handler;
this.keys = [];
this.keys = [{name:'uid'}];
this.regexp = pathToRegexp(this.path, this.keys);
}
Layer.prototype.match = function (path) {
if (this.path == path) {
return true;
}
if (!this.route) {
return path.startsWith(this.path + '/');
}
if (this.route) {
let matches = this.regexp.exec(path);
if (matches) {
this.params = {};
for (let i = 1; i < matches.length; i++) {
let name = this.keys[i - 1].name;
let val = matches[i];
this.params[name] = val;
}
return true;
}
}
return false;
}
Layer.prototype.handle_request = function (req, res, next) {
this.handler(req, res, next);
}
Layer.prototype.handle_error = function (err, req, res, next) {
if (this.handler.length != 4) {
return next(err);
}
this.handler(err, req, res, next);
}
module.exports = Layer;
router/route.js
layer形成了层,每一层具体的事情有route来做
const Layer = require('./layer');
const methods = require('methods');
const slice = Array.prototype.slice;
function Route(path) {
this.path = path;
this.stack = [];
this.methods = {};
}
Route.prototype.handle_method = function (method) {
method = method.toLowerCase();
return this.methods[method];
}
methods.forEach(function (method) {
Route.prototype[method] = function () {
let handlers = slice.call(arguments);
this.methods[method] = true;
for (let i = 0; i < handlers.length; i++) {
let layer = new Layer('/', handlers[i]);
layer.method = method;
this.stack.push(layer);
}
return this;
}
});
Route.prototype.dispatch = function (req, res, out) {
let idx = 0, self = this;
function next(err) {
if (err) {
return out(err);
}
if (idx >= self.stack.length) {
return out();
}
let layer = self.stack[idx++];
if (layer.method == req.method.toLowerCase()) {
layer.handle_request(req, res, next);
} else {
next();
}
}
next();
}
module.exports = Route;
middle/init.js
是处理内置中间件的,主要是处理query path
const url = require('url');
module.exports = function (req, res, next) {
let { pathname, query } = url.parse(req.url, true);
req.path = pathname;
req.query = query;
res.json = function (obj) {
res.setHeader('Content-Type', 'application/json');
const str = JSON.stringify(obj);
res.end(str);
}
next();
}
以上就模拟实现了express的基本功能,结合用例进行测试,可使用