express与koa对比
- express处理请求的时候全部采用的回调函数的方式, koa采用的是promise + async + await
- express内部采用的是es5语法, koa采用的是es6来编写
- express比koa功能多,多了一些内置的中间件(路由,静态服务,模版渲染)代码体积比koa大
- koa中为了扩展采用的是ctx扩展了request/response对象,express直接在原生的req/res的基础上进行了扩展
- express中的特点内部采用的是回调 (组合 内部不支持promise串联) koa支持promise串联
express基本实现
const express = require('express');
const app = express();
app.get("/", function(req,res){
res.end('home');
})
app.get("/hello", function(req, res) {
res.end('hello');
})
app.all('*', function(req, res) {
res.end('all');
})
app.listen(3000, function() {
console.log('server start on 3000')
});
const http = require("http");
const url = require('url');
let routers = [
{
method: "all",
path: "*",
handler(req, res){
res.end(`Cannot ${req.method}/${req.url}`)
}
}
];
function createApplication() {
return {
get(path, handler) {
routers.push({
method: "get",
path,
handler
})
},
listen(...args) {
const server = http.createServer(function(req, res) {
let {pathname, query} = url.parse(req.url, true);
let requestMethod = req.method.toLowerCase();
for(let i = 1; i < routes.length; i++) {
let {method, path, handler} = routes[i];
if(pathname == path && method == requestMethod){
return handler(req, res);
}
}
return routers[0].handler(req, res);
});
server.listen(...args);
}
}
}
- 代码重构
- 每次express()都创建一个应用
- 将应用和路由系统进行拆分
function Router() {
this.stack = []
}
Router.prototype.get = function(path, handler) {
this.stack.push({
method: "get",
path,
handler
})
}
Router.prototype.handle = function(req, res, done) {
let {pathname, query} = url.parse(req.url, true);
let requestMethod = req.method.toLowerCase();
for(let i = 0; i < this.stack.length; i++) {
let {method, path, handler} = this.stack[i];
if(pathname == path && method == requestMethod){
return handler(req, res);
}
}
return done();
}
module.exports = Router;
const http = require("http");
const url = require('url');
const Router = require("./router");
function Application() {
this.router = new Router();
}
Application.prototype.get = function(path, handler) {
this.router.get(path, handler);
}
Application.prototype.listen = function(...args) {
const server = http.createServer(function(req, res) {
function done() {
res.end(`Connot ${req.url}/${req.method}`)
}
this.router.handle(req, res, done);
});
server.listen(...args);
}
module.exports = Application;
const Application = require("./application");
function createApplication() {
new Application();
}
module.exports = createApplication;
路由功能
const express = require('express');
const app = express();
app.get("/", function(req,res,next){
console.log(1);
next();
console.log(5);
}, function(req,res,next){
console.log(2);
next();
console.log(6);
}, function(req,res,next){
console.log(3);
next();
console.log(7)
})
app.route('/').post(function(req, res){
res.end('post')
}).get(function(req, res){
res.end('get')
})

- 两个优化
- 应用的路由懒加载
- 路由的匹配, 外层要匹配路径和粗略看一下route中是否有此方法,有再调用dispatch,没有就跳过,执行后续逻辑
- 路由的完整实现
function Layer(path, handler) {
this.path = path;
thia.handler = handler;
}
Layer.prototype.match = function(pathname) {
return this.path = pathname;
}
Layer.prototype.handle_request = function(req, res, next) {
return this.handler(req, res, next);
}
module.exports = Layer;
const Layer = require("./layer");
function Route() {
this.stack = [];
this.methods = {};
}
Route.prototype.dispatch = function(req,res,out) {
let i = 0;
let next = () => {
if(i === this.stack.length) {
return out();
}
let layer = this.stack[i++];
if(layer.method === req.method.toLowerCase()) {
layer.handle_request(req,res,next)
}else{
next();
}
}
next();
}
methods.forEach(method => {
Route.prototype[method] = function(handlers) {
handlers.forEach(handler => {
let layer = new Layer("路径不在意", handler);
layer.method = method;
this.methods[method] = true;
this.stack.push(layer);
})
}
})
module.exports = Route;
const Layer = require('./layer')
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;
}
methods.forEach(method => {
Router.prototype[method] = function(path, handlers) {
let route = this.route(path);
route[method](handlers);
}
})
Router.prototype.handle = function(req, res, done) {
const {pathname} = url.parse(req.url, true);
let i = 0;
const next = () => {
if(i === this.stack.length) {
return done();
}
let layer = this.stack[i++];
if(layer.match(pathname)) {
if(layer.route.methods[req.method.toLowerCase()]) {
layer.handle_request(req, res, next);
}else{
next();
}
}else{
next();
}
}
next();
}
module.exports = Router;
const http = require("http");
const url = require('url');
const Router = require("./router");
const methods = require("methods");
function Application() {
}
Application.prototype.lazy_route = function() {
if(!this.router) {
this.router = new Router();
}
}
methods.forEach(method => {
Application.prototype[method] = function(path, ...handlers) {
this.lazy_route();
this.router[method](path, handlers);
}
})
Application.prototype.listen = function(...args) {
const server = http.createServer(function(req, res) {
function done() {
res.end(`Connot ${req.url}/${req.method}`)
}
this.lazy_route();
this.router.handle(req, res, done);
});
server.listen(...args);
}
module.exports = Application;
const Application = require("./application");
function createApplication() {
new Application();
}
module.exports = createApplication;
中间件的实现
const express = require("express");
const app = express();
app.use("/user", function(req, res, next){
console.log("user middleware");
next();
})
app.get("/user", function(req, res) {
res.end('user');
})
app.get("/user/admin", function(req, res) {
res.end('user admin');
})
app.get("/admin", function(req, res) {
res.end('admin');
})
app.listen(3000, function(req, res) {
console.log('start listen on 3000')
})
ApplicationCache.prototype.use = function() {
this.lazy_route();
this.router.use(...arguments);
}
Router.prototype.use = function(path) {
let args = Array.from(arguments);
let handlers = [];
if(typeof path === 'function'){
path = '/';
handlers = [...args];
}else{
handlers = args.slice(1);
}
handlers.forEach(handler => {
let layer = new Layer(path, handler);
layer.route = undefined;
this.stack.push(layer);
});
}
Layer.prototype.match = function(pathname) {
if(this.path === pathname) {
return true;
}
if(!this.route) {
if(this.path === '/') {
return true;
}
return pathname.startsWith(this.path);
}
return false;
}
Router.prototype.handle = function(req, res, done) {
const {pathname} = url.parse(req.url, true);
let i = 0;
const next = () => {
if(i === this.stack.length) {
return done();
}
let layer = this.stack[i++];
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();
}
}
next();
}
错误处理中间件
- next()中传递参数表示是一个错误
- 错误中间件一般会放到最底部,比普通中间件会多出一个参数 (err, req, res, next)=>{}
- 当next传递了参数时会跳过其他正常中间件的执行 直接执行错误中间件
- 当中间件的参数为4个时即为错误中间件
const express = require("express");
const app = express();
app.use("/user", function(req, res, next){
console.log('user middleware');
next('error')
})
app.get("/user", function(req,res){
res.end('user');
})
app.use((error, req, res, next) => {
console.log(error);
res.end(error)
})
app.listen(3000, function() {
console.log('server start on 3000')
})
Route.prototype.dispatch = function(req,res,out) {
let i = 0;
let next = (err) => {
+ if(err) return out(err);
if(i === this.stack.length) {
return out();
}
let layer = this.stack[i++];
if(layer.method === req.method.toLowerCase()) {
layer.handle_request(req,res,next)
}else{
next();
}
}
next();
}
Router.prototype.handle = function(req, res, done) {
const {pathname} = url.parse(req.url, true);
let i = 0;
const next = (err) => {
if(i === this.stack.length) {
return done();
}
let layer = this.stack[i++];
+ if(err){
+
+ if(!layer.route){
+ if(layer.handler.length === 4){
+ layer.handler(err, req, res, next);
+ }else{
+ next(err);
+ }
+ }else{
+ next(err);
+ }
}else{
if(layer.match(pathname)) {
if(!layer.route) {
+ if(layer.handler.length === 4){
+ next();
}else{
layer.handle_request(req, res, next);
}
}else{
if(layer.route.methods[req.method.toLowerCase()]) {
layer.handle_request(req, res, next);
}else{
next();
}
}
}else{
next();
}
}
}
next();
}
正则路由
- path-to-regexp 把路径转化成正则,和请求来的路径做匹配 获得对象
- req.params
app.get('/user/:id/:name', function(req,res) {
res.end(JSON.stringify(req.params));
})
function pathToRegExp(str, keys){
str = str.replace(/:([^\/]+)/g, function(){
keys.push(arguments[1]);
return '([^\/]+)'
})
return new RegExp(str);
}
let p = '/user/:id/:name/xxx';
let keys = [];
let reg = pathToRegExp(p, keys);
let url = '/user/111/haha/xxx';
console.log(url.match(reg).slice(1), keys);
二级路由
- 每次执行 express.Router() 都会单独创建一个路由系统
- 在进去路由系统匹配时删除前缀 从那个路由系统出来再加上前缀
- 进去和出来调用的都是next 在next的前后进行截取和拼接
const express = require('express');
const router = express.Router();
router.get('/add', function(req, res){
res.end('user add')
})
router.get('/remove', function(req, res){
res.end('user remove')
})
module.exports = router;
const express = require('express');
const router = express.Router();
router.get('/add', function(req, res){
res.end('article add')
})
router.get('/remove', function(req, res){
res.end('article remove')
})
module.exports = router;
const express = require('express');
const user = require('./routes/user.js');
const article = require('./routes/article.js');
const app = express();
app.use('/user', user);
app.use('/article', article);
app.listen(3000, function(){
console.log(`server start on 3000`)
})
路由参数
const express = require('express');
const app = express();
app.param('id', function(req, res, next, value, key){
console.log(value);
next()
})
app.param('id', function(req, res, next, value, key){
console.log(value);
next()
})
app.param('name', function(req, res, next, value, key){
console.log(value);
next()
})
app.param('name', function(req, res, next, value, key){
console.log(value);
next()
})
app.get('/user/:id/:name/xxx', function(req, res){
res.end('user')
})
app.listen(3000, function(){
console.log('server start on 3000')
})
中间件注册
app.use((req, res, next) => {
res.send = function(data) {
if(typeof data === 'object') {
res.end(JSON.stringify(data));
}else if(typeof data === 'string' || Buffer.isBuffer(data)){
res.end(data);
}
}
res.sendFile = function(filePath){
fs.createReadStream(filePath).pipe(res);
}
next();
})