Express
路由机制
一个简单的express使用例子如下:
1 2 3 4 5 6 7 8 9 10
| const app = express(); app.get("/home", (req, res) => { res.end("home"); }); app.get("/users", (req, res) => { res.end("users"); }); app.listen(3000, () => { console.log("listening"); });
|
分析app应该是一个构造函数的实例,身上应该有get,listen方法.而且我们可以定义多个路由,应该单独抽离出来管理这些路由。因此需要两个构造函数( Application 负责新增路由,启动服务,Router 管理路由 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| const http = require("http"); const Router = require("./router/index");
function Application() { this.router = new Router(); }
Application.prototype.listen = function () { const _this = this;
function done(req, res) { res.end(`Connot ${req.method} ${req.url}`); }
let server = http.createServer(function (req, res) { _this.router.handle(req, res, done); }); server.listen.apply(server, arguments); };
Application.prototype.get = function (path, handler) { this.router.get(path, handler); };
const url = require("url");
function Router() { this.stack = []; } Router.prototype.get = function (path, handler) { this.stack.push({ path, method: "get", handler, }); }; Router.prototype.handle = function (req, res, out) { const { pathname } = url.parse(req.url); const reqMethod = req.method.toLowerCase(); for (let i = 0; i < this.stack.length; i++) { const { path, method, handler } = this.stack[i]; if (path === pathname && method === reqMethod) { return handler(req, res); } } out(req, res); }; module.exports = Router;
|
而有时候我们还会这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| app.get('/',function(req,res,next){ console.log(1); next('wrong'); },function(req,res,next){ console.log(11); next(); }).get('/',function(req,res,next){ console.log(2); next(); }).get('/',function(req,res,next){ console.log(3); res.end('ok'); }).get('/',function(err,req,res,next){ res.end('catch: '+err); }); app.listen(3000);
|
显然之前的架构处理这样的书写就会很臃肿,因此我们还需要单独再抽象出两个类,Layer (负责管理路由) 和 Route(负责管理一个路由下的所有函数)类。同时原来的Router类也需要进行改进。
Router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
let Layer = require('./layer'); let Route = require('./route'); function Router(){ this.stack = []; } Router.prototype.route = function(path){ let route = new Route(); let layer = new Layer(path,route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; }
Router.prototype.get = function(path,...handlers){ let route = this.route(path); route.get(handlers); }
Router.prototype.handle = function (req, res, out) { let { pathname } = url.parse(req.url); let idx = 0; let next = () => { if (idx >= this.stack.length) return out(); let layer = this.stack[idx++]; if (layer.match(pathname)) { layer.handle_request(req, res, next); } else { next(); } }; next(); };
module.exports = Router;
|
Route.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| let Layer = require("./layer"); function Route() { this.stack = []; } Route.prototype.get = function (handlers) { handlers.forEach((handler) => { let layer = new Layer("/", handler); layer.method = "get"; this.stack.push(layer); }); }; Route.prototype.dispatch = function (req, res, out) { let idx = 0; let next = () => { if (idx >= this.stack.length) return out(); let layer = this.stack[idx++]; if (layer.method === req.method.toLowerCase()) { layer.handle_request(req, res, next); } else { next(); } }; next(); }; module.exports = Route;
|
Layer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function Layer(path,handler){ this.path = path; this.handler = handler; }
Layer.prototype.match = function(pathname){ return this.path == pathname } Layer.prototype.handle_request = function(req,res,next){ this.handler(req,res,next); } module.exports = Layer;
|
中间件原理
其实已经在路由中用到过,只不过Express中中间件又分为好几种:
1 2 3 4 5 6 7
| app.get("/home", (req, res, next) => { console.log(123); next() }); app.get("/home", (req, res, next) => { res.end("home"); });
|
1 2 3
| app.use((req,res)=>{ res.status(200).send("这个是404 没有路由匹配到!") })
|
1
| app.use('/static',express.static("public"));
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| Application.prototype.use = function () { if (!this._router) { this._router = new Router(); } this._router.use(...arguments); };
Router.prototype.use = function (path, handler) { if (typeof handler !== "function") { handler = path; path = "/"; } let layer = new Layer(path, handler); layer.route = undefined; this.stack.push(layer); };
Router.prototype.handle = function (req, res, out) { let { pathname } = url.parse(req.url); let idx = 0; letnext = () => { if (idx >= this.stack.length) returnout(); let layer = this.stack[idx++];
if (layer.match(pathname)) { if (!layer.route) {
layer.handle_request(req, res, next); } else {
if (layer.route.methods[req.method.toLowerCase()]) { layer.handle_request(req, res, next); } else { next(); } } } else { next(); } }; };
|
params解析
当我们编写路由的时候会有如下写法
1 2 3 4 5
| const express = require('express') const app = express() app.get('/user/:name/:id',(req,res)=>{ res.end('OK') })
|
而当我们在浏览器输入路由是这样的
1
| localhost:3000/user/hxh/0522
|
首先会将app.get定义的路由用正则表达式进行替换,将:xxx部分替换成正则
1 2 3 4 5 6 7 8
| const path = "/user/:name/:id"; const paramsName = []; const regStr = path.replace(/:(\w+)/g, function (matchChar, group1) { paramsName.push(group1); return ('(\\w+)') }); console.log(regStr); console.log(regStr);
|
这样就得到了一个匹配url的正则字符串,再用这个字符串生成正则去匹配我们的路由,就能提取出params,最后把提取出来的值跟变量对应起来就好。
1 2 3 4 5 6 7 8
| const reg = new RegExp(regStr); const url = "localhost:3000/user/hxh/0522"; const res = url.match(reg); const params = {} for (let i = 0; i < paramsName.length; i++) { params[paramsName[i]] = res[i + 1]; } console.log(params);
|