看了 koajs 的源码,意犹未尽,再看看几个其他的框架或者库.
简介
看了 koajs 的源码之后, 惊叹代码之少, 设计有趣. 看看其他几个框架. 看到了 fastify 有一个 benchmarks https://github.com/fastify/benchmarks , 那么就从这个 benchmarks 开始看吧.
从 npm 上看下载量如果下载量太少,说明没有人用, 就不看了.
polkadot
polkadot
The tiny HTTP server that gets out of your way! ・
七天下载量不到 1000, 但是和 Polka 是同门师兄弟, 所以看看.
在上面的 benchmarks 中, polkadot 排名第一. 自我描述是一个 thiny HTTP server. 源代码也很精简.
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 const { createServer } = require ('http' );const { parse } = require ('querystring' );const send = require ('@polka/send-type' );const url = require ('@polka/url' );function loop (res, out ) { if (res.finished ) return ; return out && out.then !== void 0 ? out.then (d => loop (res, d)) : send (res, res.statusCode || 200 , out); } module .exports = function (handler ) { const $ = { server : null , handler (req, res ) { const info = url (req); req.path = info.pathname ; req.query = info.query ? parse (info.query ) : {}; req.search = info.search ; return loop (res, handler (req, res)); }, listen ( ) { const handler = (r1, r2 ) => setImmediate ($.handler, r1, r2); ($.server = $.server || createServer ()).on ('request' , handler); return $.server.listen .apply ($.server, arguments ); } }; return $; }
短短几十行代码, 一个简单的封装, 并且实现了一个递归函数 loop 来处理 Promise. 不支持路由, 不支持中间件.
他这里使用 setTmmediate
看不出来有什么意义, (找到了作者解释 https://github.com/lukeed/polkadot/issues/5 ), 因为这里没有什么耗时操作, 或者会执行的东西.
Polka
Polka
A micro web server so fast, it'll make you dance! 👯
npm 七天下载量 8w 左右, 和上面 polkadot 是一个作者. 并且积极更新.
Essentially, Polka is just a native HTTP server with added support for routing, middleware, and sub-applications. That's it! 🎉
从描述看 polka 实现了中间件和 router
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const polka = require ('polka' );function one (req, res, next ) { req.hello = 'world' ; next (); } function two (req, res, next ) { req.foo = '...needs better demo 😔' ; next (); } polka () .use (one, two) .get ('/users/:id' , (req, res ) => { console .log (`~> Hello, ${req.hello} ` ); res.end (`User: ${req.params.id} ` ); }) .listen (3000 , () => { console .log (`> Running on localhost:3000` ); });
核心源代码也比较简单
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 const http = require ('http' );const Router = require ('trouter' );const { parse } = require ('querystring' );const parser = require ('@polka/url' );function lead (x ) { return x.charCodeAt (0 ) === 47 ? x : ('/' + x); } function value (x ) { let y = x.indexOf ('/' , 1 ); return y > 1 ? x.substring (0 , y) : x; } function mutate (str, req ) { req.url = req.url .substring (str.length ) || '/' ; req.path = req.path .substring (str.length ) || '/' ; } function onError (err, req, res, next ) { let code = (res.statusCode = err.code || err.status || 500 ); if (typeof err === 'string' || Buffer .isBuffer (err)) res.end (err); else res.end (err.message || http.STATUS_CODES [code]); } class Polka extends Router { constructor (opts={} ) { super (opts); this .apps = {}; this .wares = []; this .bwares = {}; this .parse = parser; this .server = opts.server ; this .handler = this .handler .bind (this ); this .onError = opts.onError || onError; this .onNoMatch = opts.onNoMatch || this .onError .bind (null , { code :404 }); } add (method, pattern, ...fns ) { let base = lead (value (pattern)); if (this .apps [base] !== void 0 ) throw new Error (`Cannot mount ".${method.toLowerCase()} ('${lead(pattern)} ')" because a Polka application at ".use('${base} ')" already exists! You should move this handler into your Polka application instead.` ); return super .add (method, pattern, ...fns); } use (base, ...fns ) { if (typeof base === 'function' ) { this .wares = this .wares .concat (base, fns); } else if (base === '/' ) { this .wares = this .wares .concat (fns); } else { base = lead (base); fns.forEach (fn => { if (fn instanceof Polka ) { this .apps [base] = fn; } else { let arr = this .bwares [base] || []; arr.length > 0 || arr.push ((r, _, nxt ) => (mutate (base, r),nxt ())); this .bwares [base] = arr.concat (fn); } }); } return this ; } listen ( ) { (this .server = this .server || http.createServer ()).on ('request' , this .handler ); this .server .listen .apply (this .server , arguments ); return this ; } handler (req, res, info ) { info = info || this .parse (req); let fns=[], arr=this .wares , obj=this .find (req.method , info.pathname ); req.originalUrl = req.originalUrl || req.url ; let base = value (req.path = info.pathname ); if (this .bwares [base] !== void 0 ) { arr = arr.concat (this .bwares [base]); } if (obj) { fns = obj.handlers ; req.params = obj.params ; } else if (this .apps [base] !== void 0 ) { mutate (base, req); info.pathname =req.path ; fns.push (this .apps [base].handler .bind (null , req, res, info)); } fns.push (this .onNoMatch ); req.search = info.search ; req.query = parse (info.query ); let i=0 , len=arr.length , num=fns.length ; if (len === i && num === 1 ) return fns[0 ](req, res); let next = err => err ? this .onError (err, req, res, next) : loop (); let loop = _ => res.finished || (i < len) && arr[i++](req, res, next); arr = arr.concat (fns); len += num; loop (); } }
可以看到, router 部分是使用 trouter 这个包来解析和匹配 path, 中间件的话, 使用了递归来处理这些中间件, 代码不如 koajs 的优美.
0http
0http
Cero friction HTTP requests router. The need for speed!
npm 下载量在 5000 左右
从 readme 的描述来看, 0http 也是使用 trouter 来解析和匹配 path 的, 但是他使用了 LRU (最近最少使用) 缓存 来缓存查询结果, 所以性能会好一些.
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 const Trouter = require ('trouter' )const next = require ('./../next' )const LRU = require ('lru-cache' )const { parse } = require ('regexparam' )const queryparams = require ('../utils/queryparams' )module .exports = (config = {} ) => { if (config.defaultRoute === undefined ) { config.defaultRoute = (req, res ) => { res.statusCode = 404 res.end () } } if (config.errorHandler === undefined ) { config.errorHandler = (err, req, res ) => { res.statusCode = 500 res.end (err.message ) } } if (config.cacheSize === undefined ) { config.cacheSize = 1000 } if (config.id === undefined ) { config.id = (Date .now ().toString (36 ) + Math .random ().toString (36 ).substr (2 , 5 )).toUpperCase () } const routers = {} const isCacheEnabled = config.cacheSize > 0 const cache = isCacheEnabled ? new LRU (config.cacheSize ) : null const router = new Trouter () router.id = config.id const _use = router.use router.use = (prefix, ...middlewares ) => { if (typeof prefix === 'function' ) { middlewares = [prefix] prefix = '/' } _use.call (router, prefix, middlewares) if (middlewares[0 ].id ) { const { pattern } = parse (prefix, true ) routers[middlewares[0 ].id ] = pattern } return this } router.lookup = (req, res, step ) => { if (!req.url ) { req.url = '/' } if (!req.originalUrl ) { req.originalUrl = req.url } queryparams (req, req.url ) let match if (isCacheEnabled) { const reqCacheKey = req.method + req.path match = cache.get (reqCacheKey) if (!match) { match = router.find (req.method , req.path ) cache.set (reqCacheKey, match) } } else { match = router.find (req.method , req.path ) } if (match.handlers .length !== 0 ) { const middlewares = [...match.handlers ] if (step !== undefined ) { middlewares.push ((req, res, next ) => { req.url = req.preRouterUrl req.path = req.preRouterPath delete req.preRouterUrl delete req.preRouterPath return step () }) } if (!req.params ) { req.params = {} } Object .assign (req.params , match.params ) return next (middlewares, req, res, 0 , routers, config.defaultRoute , config.errorHandler ) } else { config.defaultRoute (req, res) } } router.on = (method, pattern, ...handlers ) => router.add (method, pattern, handlers) return router }
核心就是 router 这部分代码. 和前面看的差不太多, 做了一个优化处理, 挺好的.