githtml
const express = require('express') // 导出的是一个函数
const app = express()
app.use('/', (req, res, next) => {
// 在中间件中处理公共路径
next()
})
// 内部不会将回调函数包装成promise,内部集成了路由
app.get('/', (req, res)=> {
res.end('ok')
})
app.listen(3000, () => {
console.log('start')
})
复制代码
// 主要代码
// 'application.js'
const http = require('http')
const Router = require('./router')
/*
@ 在初始化application的时候,须要建立一个router实例,中间的请求处理都让router去完成
@*/
function Application() {
// 在建立应用的时候,初始化一个router
this._router = new Router()
}
Application.prototype.get = function (path, ...handlers) {
this._router.get(path, handlers) //
},
Application.prototype.listen = function (...arg) {
const server = http.createServer((req, res) => {
function done() {
// 若是router处理不了,直接执行done
res.end('not xx `Found')
}
this._router.handle(req, res, done)
})
server.listen(...arg)
}
module.exports = Application
// 'layer.js'
function Layer(path, handle) {
this.path = path
this.handle = handle
}
module.exports = Layer
// 'route.js'
const Layer = require("./layer");
function Route() {
// route也有一个stack,存放内层的layer(方法类型与用户传入函数的对应)
this.stack = []
}
/*
@ route有一个diapatch属性,调用一次执行内层的stack
@ 还有一个get方法,用来标记内层layer函数与方法类型的对应关系
*/
Route.prototype.dispatch = function(req, res, out) {
let idx = 0
const next = () => {
if (idx >= this.stack.length) return out()
const routeLayer = this.stack[idx++]
// 比较方法
if (routeLayer.method === req.method.toLowerCase()) {
routeLayer.handle(req, res, next)
} else {
next()
}
}
next()
}
Route.prototype.get = function(handles) {
handles.forEach(handle => {
const layer = new Layer('', handle)
layer.method = 'get'
this.stack.push(layer)
});
}
module.exports = Route
// 'index.js'
/*
@router有route属性和layer属性,layer存路径和route.dispatch的对应关系,route存放用户真实的回调
@ router是真个路由系统,route是一条条的路由
@ 在express的路由系统中,又一个外层的栈,存放着路径与回调函数的对应关系,这个回调函数是route的diapatch,
@ 当调用外层的handle时,会让route.dispatch执行,dispatch找到route中存放的layer,根据方法进行匹配,一次执行
@ 内层的layer存放的是方法类型与真实回调的对应关系
*/
const Layer = require('./layer')
const Route = require('./route')
const url = require('url')
function Router() {
this.stack = []
}
Router.prototype.route = function (path) {
const route = new Route() // 建立route
const layer = new Layer(path, route.dispatch.bind(route)) // 建立外层layer
layer.route = route // 为layer设置route属性
// 将layer存放到路由系统的stack中
this.stack.push(layer)
return route
}
// router中有get方法与handle放法
Router.prototype.get = function (path, handlers) {
// 当调用get时咱们建立一个layer,给他添加对应关系
// 建立对应关系
const route = this.route(path)
// 让route调用get方法标记route中的每一个layer是何种方法
route.get(handlers)
}
Router.prototype.handle = function (req, res, done) {
/*
@ 当有请求过来时,咱们先拿到path找到外层的layer,而后执行他的handle,让内层的route,根据method依次执行内层的layer的handle
@
*/
let { pathname } = url.parse(req.url)
let idx = 0
let next = () => {
if (idx >= this.stack.length) return done() // 遍历完后仍是没找到,那就直接走出路由系统便可
let layer = this.stack[idx++]
// 须要查看 layer上的path 和 当前请求的路径是否一致,若是一致调用dispatch方法
if (layer.path === pathname) {
// 路径匹配到了 须要让layer上对应的dispatch执行
layer.handle(req, res, next) // 将遍历路由系统中下一层的方法传入
} else {
next()
}
}
next()
}
module.exports = Router
复制代码
function Application() {
// 在建立应用的时候,初始化一个router
}
Application.prototype.lazy_router = function() {
if (!this._router) {
this._router = new Router() // 在调用了对应的方法或者listen的建立路由
}
}
methods.forEach(method => {
Application.prototype[method] = function (path, ...handlers) {
this.lazy_router()
...
}
})
Application.prototype.listen = function (...arg) {
const server = http.createServer((req, res) => {
this.lazy_router() // 实现路由的懒加载
...
}
module.exports = Application
复制代码
咱们在拿到一个请求的路径和方法后,先去外层layer匹配path,path匹配以后,在去内层匹配methods,这就存在内层匹配不到对应的方法,形成浪费,所以咱们在外层layer的route属性中加一个methods属性,这个属性包括route中全部存在的方法,在当匹配到外层,先去methods映射去找是否存在,若是存在就触发route.dispatch,不存在则直接跳过,匹配下一个layergit
// index.js
Router.prototype.handle = function (req, res, done) {
...
let next = () => {
...
// 须要查看 layer上的path 和 当前请求的路径是否一致,若是一致调用dispatch方法
if (layer.match(pathname)) {
// 路径匹配到了 须要让layer上对应的dispatch执行
if (layer.route.methods[req.method.toLowerCase()]) {
layer.handle(req, res, next) // 将遍历路由系统中下一层的方法传入
} else {
next()
}
} else {
next()
}
}
next()
}
module.exports = Router
// route.js
function Route() {
...
this.methods = {}
}
...无用的代码省略
methods.forEach(method => {
Route.prototype[method] = function(handles) {
handles.forEach(handle => {
const layer = new Layer('', handle)
this.methods[method] = true
...
});
}
})
module.exports = Route
复制代码
...
app.get('/', function(req, res, next) {
console.log(1)
setTimeout(() => {
next();
console.log('xxx')
}, 6000);
}, function(req, res, next) {
next();
console.log(11)
}, function(req, res, next) {
next();
console.log(111);
})
app.get('/', function(req, res, next) {
console.log('2');
res.end('end')
})
app.post('/', function(req, res, next) {
res.end('post ok')
})
app.listen(3001, () => {
console.log('server start 3000');
})
// 1, 2, 111, 11,xxx
解析: 洋葱模型,其实就是从外面一层层的深刻,再一层层的穿出来,中间件的执行顺序严格遵循洋葱模型,next能够理解为先执行下一个中间件,next以后的代码会在执行完下一个中间件再回来执行
上述代码的输出:
+ 1输出毫无疑问,
+ next在setTimeOut中等待,到时间后先进入下一个中间件,在下一个中间件中next在输出之前,继续先执行下一个回调,输出2
+ 再回到上一层,输出111
+ 回到上一层输出11
+ 回到最开始的那一层,输出xxx
复制代码
// + 第一个参数,匹配以path开头的路径 ,第二个参数是回调
+ 中间件不匹配方法,只匹配路径
app.use('/', (req, res, next) => {
// todo
})
复制代码
// application.js
Application.prototype.use = function() {
// 中间件的实现逻辑,只要用到路由就要判断一次路由是否加载
this.lazy_router()
// 将逻辑交给路由系统中实现
this._router.use(...arguments)
}
// index.js
...
Router.prototype.use = function(path, ...handles) {
if(typeof path == 'function') {
// 只传递了一种参数
handles.unshift(path)
path = '/'
}
// 遍历handles建立layer
handles.forEach(handle => {
let layer = new Layer(path, handle)
// 对于中间件,没有route属性,为了区分,设置route属性为undeinfed,而且把这个layer放入到路由系统中
layer.route = undefined
this.stack.push(layer)
})
}
Router.prototype.handle = function (req, res, done) {
let { pathname } = url.parse(req.url)
let idx = 0
let next = () => {
...
if (layer.match(pathname)) {
// 当匹配到路径的后,可能为中间件,可能为路由
if (!layer.route) { // 判断是不是中间件
// 中间件的话,直接去执行
layer.handle_request(req, res, next)
} else {
// 路由
if (layer.route.methods[req.method.toLowerCase()]) {
layer.handle(req, res, next) // 将遍历路由系统中下一层的方法传入
} else {
next()
}
}
// 路径匹配到了 须要让layer上对应的dispatch执行
} else {
next()
}
}
next()
}
// layer.js
function Layer(path, handle) {
this.path = path
this.handle = handle
}
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
}
Layer.prototype.handle_request = function(req, res, next) {
this.handle(req, res, next)
}
module.exports = Layer
复制代码
const express = require('express');
const app = express();
app.use('/', (req, res, next) => {
// todo
console.log(1)
next('出错了')
})
app.use('/', (req, res, next) => {
// todo
console.log(2)
next()
})
app.get('/', function(req, res, next) {
console.log(1)
setTimeout(() => {
next();
console.log('xxx')
}, 10000);
}, function(req, res, next) {
next();
console.log(11)
}, function(req, res, next) {
next();
console.log(111);
})
复制代码
// route.js
...
Route.prototype.dispatch = function(req, res, out) {
let idx = 0
const next = (err) => {
if(err) { return out(err)} // out就是外层的next,若是内层有错误,直接跳出
if (idx >= this.stack.length) return out()
const routeLayer = this.stack[idx++]
// 比较方法
if (routeLayer.method === req.method.toLowerCase()) {
routeLayer.handle(req, res, next)
} else {
next()
}
}
next()
}
...
// index.js
Router.prototype.handle = function (req, res, done) {
/*
@ 当有请求过来时,咱们先拿到path找到外层的layer,而后执行他的handle,让内层的route,根据method依次执行内层的layer的handle
@
*/
let { pathname } = url.parse(req.url)
let idx = 0
let next = (err) => {
// 在这里进行统一的监听
if (idx >= this.stack.length) return done() // 遍历完后仍是没找到,那就直接走出路由系统便可
let layer = this.stack[idx++]
if (err) { //错误处理。通常放在最后
// 进入到错误能够是中间件也能够是路由
if (!layer.route) {
// 找中间件
layer.handle_error(err, req, res, next)
} else {
// 路由,继续携带错误信息匹配中间件
next(err)
}
} else {
if (layer.match(pathname)) {
// 当匹配到路径的后,可能为中间件,可能为路由
if (!layer.route) {
// 中间件的化,直接去执行
// 排除错误中间件
if (layer.handle.length !== 4) {
layer.handle_request(req, res, next) // 普通中间件
} else {
// 是错误中间件,跳出
next()
}
} else {
...
}
} else {
next()
}
}
}
next()
}
// layer.js
...
Layer.prototype.handle_error = function(err, req, res, next) {
// 找到错误处理中间件所在的layer,若是不是带着错误信息继续next
if(this.handle.length === 4) {
// 错误处理中间件,让handle执行
return this.handle(err, req, res, next)
}
next(err) // 普通的中间件
}
...
测试代码
server.js
app.use('/', (req, res, next) => {
// todo
console.log(1)
next()
})
app.use('/', (req, res, next) => {
// todo
console.log(2)
next()
})
app.use('/', (req, res, next) => {
// todo
console.log(3)
next()
})
// 路由的中间件 将处理逻辑 拆分红一个个的模块
app.get('/', function(req, res, next) {
console.log(1)
next()
}, function(req, res, next) {
next();
console.log(11)
}, function(req, res, next) {
next('出错了');
console.log('出错了');
})
app.get('/', function(req, res, next) {
console.log('2');
res.end('end')
})
app.use((err,req,res,next)=>{
next();
})
复制代码
// server.js
const express = require('express');
const LoginRouter =require('./routes/loginRouter');
const app = express();
app.use('/login',LoginRouter);
app.listen(3000);
// loginRouter.js
const express = require('express');
let router = express.Router(); // 是个构造函数
router.get('/add',function (req,res) {
res.end('/login-add')
})
router.get('/out',function (req,res) {
res.end('/login-out')
})
module.exports = router;
复制代码
// router/index.js
...
// 提供一个Router类,既能够new也能够执行
createApplication.Router = require('./router')
...
复制代码
function Router() {
this.stack = []
const router = (req, res, next) => {
router.handle(req, res, next)
}
router.stack = []
router.__proto__ = proto
return router
}
复制代码
这样改造后,经过new以后,this均指向router这个函数,在这个函数上没有以前的各类放法,所以以前写的逻辑均失效, 须要把这些属性放到原型链上es6
// index.js
proto.handle = function (req, res, done) {
/*
@ 当有请求过来时,咱们先拿到path找到外层的layer,而后执行他的handle,让内层的route,根据method依次执行内层的layer的handle
@
*/
let { pathname } = url.parse(req.url)
let idx = 0
let removed = '';
let next = (err) => {
// 在这里进行统一的监听
if (idx >= this.stack.length) return done() // 遍历完后仍是没找到,那就直接走出路由系统便可
let layer = this.stack[idx++]
// 出来中间件的时候将路径补气
if (removed) {
req.url = removed + pathname;// 增长路径 方便出来时匹配其余的中间件
removed = '';
}
if (err) { //错误处理。通常放在最后
...
} else {
if (layer.match(pathname)) {
if (!layer.route) {
if (layer.handle.length !== 4) {
if (layer.path !== '/') {
// 进入中间件的时候剪切路径
removed = layer.path // 中间件的路径
req.url = pathname.slice(removed.length);
}
layer.handle_request(req, res, next) // 普通中间件
} else {
// 是错误中间件,跳出
next()
}
} else {
...
} else {
next()
}
}
}
next()
}
复制代码
// applicatin
const EventEmitter = require('events')
const http = require('http')
const context = require('./context.js')
const request = require('./request.js')
const response = require('./response.js')
const Stream = require('stream')
class Application extends EventEmitter {
constructor() {
super()
// 为了实现每new一次有个全新的额context,所以须要使用object.create()赋值一份
this.context = Object.create(context)
this.response = Object.create(response)
this.request = Object.create(request)
this.middlewares = []
}
use(middleware) {
this.middlewares.push(middleware)
}
generateContext(req, res) {
....
}
compose(ctx) {
....
}
handleRequest(req, res) {
// 根据req,res以及新的context生成一个ctx
const ctx = this.generateContext(req, res)
// 执行中间件
this.compose(ctx).then(() => {
// 对返回的处理
let body = ctx.body; //当组合后的promise完成后,拿到最终的结果 响应回去
if(typeof body == 'string' || Buffer.isBuffer(body)){
res.end(body);
}else if(body instanceof Stream){
res.setHeader('Content-Disposition',`attachement;filename=${encodeURIComponent('下载1111')}`)
body.pipe(res);
}else if(typeof body == 'object'){
res.end(JSON.stringify(body));
}
})
}
listen(...args) {
const server = http.createServer(this.handleRequest.bind(this))
server.listen(...args)
}
}
module.exports = Application
复制代码
generateContext(req, res)
// 保证每次use都建立新的上下文
let context = Object.create(this.context);
let request = Object.create(this.request);
let response = Object.create(this.response);
// 上下文中有一个request对象 是本身封装的的对象
context.request = request;
context.response = response;
// 上下文中还有一个 req属性 指代的是原生的req
// 本身封装的request对象上有req属性
context.request.req = context.req = req;
context.response.res = context.res = res;
return context;
}
复制代码
异步回调使用next类型的
compose(ctx) {
const dispatch = i => {
try {
if(i >= this.middlewares.length) return Promise.reslove()
const currentMiddle = this.middlewares[i]
return Promise.reslove(currentMiddle(ctx, () => dispatch(i+1))
} catch(e) {
}
}
dispatch(0)
}
复制代码
const context = {
}
// 代理方法
function defineGetter(target, key) {
// 定义一个getter, context和this不是一个,this.__proto__.__proto__ = context
context.__defineGetter__(key, () => {
return this[target][key]
})
}
function defineSetter(target, key) {
context.__defineSetter__(key, (newValue) => {
this[target][key] = newValue
})
}
defineGetter('request', 'url')
defineGetter('request', 'path')
defineGetter('request', 'query') // ctx.query = ctx.requesrt.query
defineGetter('response','body');// ctx.body => ctx.response.body
defineSetter('response','body');// ctx.body => ctx.response.body
module.exports = context
复制代码
const url = require('url')
const request = {
// 属性访问器,相似Object.definedProperty()
get url() {
// 这里的this指的是ctx.request
return this.req.url
},
get path() {
return url.parse(this.req.url).pathname
},
// 但愿有什么属性就扩展
get query() {
return url.parse(this.req.url, true).query
}
}
module.exports = request
复制代码
const response = {
_body:'',
get body(){
return this._body;
},
set body(val){
this._body = val;
}
}
module.exports = response;
复制代码
const querystring = require('querystring');
const uuid = require('uuid');
const path = require('path');
const fs = require('fs');
// 中间件的功能能够扩展属性 / 方法
module.exports = function(uploadDir) {
return async (ctx, next) => {
await new Promise((resolve,reject)=>{
const arr = [];
ctx.req.on('data',function (chunk) {
arr.push(chunk);
})
ctx.req.on('end',function () {
if(ctx.get('content-type') === 'application/x-www-form-urlencoded'){
let result = Buffer.concat(arr).toString();
ctx.request.body = querystring.parse(result);
}
if(ctx.get('content-type').includes('multipart/form-data')){ // 二进制不能直接toString 可能会乱码
let result = Buffer.concat(arr); // buffer
let boundary = '--'+ ctx.get('Content-Type').split('=')[1];
let lines = result.split(boundary).slice(1,-1);
let obj = {}; // 服务器收到的结果所有放在这个对象中
lines.forEach(line=>{
let [head,body] = line.split('\r\n\r\n');
head = head.toString();
let key = head.match(/name="(.+?)"/)[1]
if(!head.includes('filename')){
obj[key] = body.toString().slice(0,-2);
}else{
// 是文件 文件上传 名字须要是随机的
let content = line.slice(head.length + 4,-2);
let filePath = path.join(uploadDir,uuid.v4());
obj[key] = {
filePath,
size:content.length
}
fs.writeFileSync(filePath,content);
}
});
ctx.request.body = obj;
}
resolve();
})
});
await next(); // 完成后须要继续向下执行
}
}
Buffer.prototype.split = function (sep) { // 分隔符多是中文的,我但愿将他转化成buffer来计数
let sepLen = Buffer.from(sep).length;
let arr = [];
let offset = 0;
let currentIndex = 0;
while((currentIndex = this.indexOf(sep,offset)) !== -1){
arr.push(this.slice(offset,currentIndex));
offset = currentIndex + sepLen;
}
arr.push(this.slice(offset));
return arr;
}
复制代码
项目目录 github
const Router = require('koa-router')
const routerA = new Router({prefix: '/login'}) // 划分接口以loagin开头的
复制代码
const combineRouters = require('koa-combine-routers')
combineRouters(A, B)// 将A和B路由合并,返回一个中间件
复制代码
const views = require('koa-views')// 在ctx加一个render方法,基于Promise
app.use(views(__dirname + './views', {
map: {
html: 'ejs' //使用ejs模版
}
}))
// 使用
class Controller {
async add(ctx, next) {
await ctc.render('a.html', { age: 11, name: 'xx'})
}
}
// 模版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<%=name%> <%=age%>
</body>
</html>
复制代码