koa-static
koa-static文件在100行之内,非常简洁。koa-static就是对koa-send的浅封装,koa-static只支持get head;默认添加了index.html
// 来源于koa-static源码
if (opts.index !== false) opts.index = opts.index || 'index.html'// 默认添加了index.html
if (ctx.method === 'HEAD' || ctx.method === 'GET') { // 支持的方法
try {
done = await send(ctx, ctx.path, opts)
} catch (err) {
if (err.status !== 404) {
throw err
}
}
}
用法:
const Koa = require('koa')
const path = require('path')
const static = require('koa-static')
const app = new Koa()
const staticPath = './static'
app.use(static(
path.join( __dirname, staticPath)
))
app.use( async ( ctx ) => {
ctx.body = 'hello world'
})
app.listen(3000, () => {
console.log('static-use-middleware is starting at port 3000')
})
koa-send:
最重要的一行:😂
ctx.body = fs.createReadStream(path)
path是静态资源的路径;通过fs的流处理方式读取,然后把静态资源返回给浏览器,
其余的是一些完善处理:
隐藏文件。。。;path解码 ;File type;Last-Modified和Cache-Control处理;max-age=处理;path路径是不是文件夹等处理;setHeaders;gzip等等
koa-send的部分源码:
// stream
ctx.set('Content-Length', stats.size)
if (!ctx.response.get('Last-Modified')) ctx.set('Last-Modified', stats.mtime.toUTCString())
if (!ctx.response.get('Cache-Control')) {
const directives = ['max-age=' + (maxage / 1000 | 0)]
if (immutable) {
directives.push('immutable')
// Cache-Control immutable表示静态资源不可变,可以不发送请求直接使用缓存,
// 有这个请求是为了更好的用户体验,同时减小服务端压力;(可见基础多嘛重要)
}
ctx.set('Cache-Control', directives.join(','))
}
if (!ctx.type) ctx.type = type(path, encodingExt)
ctx.body = fs.createReadStream(path)
return path
非常简单,koa-send也就100来行,主要是做一些其他边界条件处理。
koa-views
koa-views 是模版渲染的中间件,是和koa2搭配使用的;koa-views 底层使用的是 consolidate这个npm包,支持很多种模版引擎,在使用koa-views的时候,还要装上相应模版引擎的npm包,koa-views里边也用了koa-send这个包;当
app.use(views(__dirname))
没map的时候直接返回静态文件,是不会渲染数据的。所以map选项是必不可少的。
if (isHtml(suffix) && !map) {
return send(ctx, paths.rel, {
root: path
})
}
/*suffix一般都是html,因为
extension = 'html'
getPaths(path, relPath, extension).then(paths => {
const suffix = paths.ext
......
}
除非对extension赋值;
*/
consolidate这个npm包对应的渲染方式;
const consolidate = require('consolidate')
engineSource = consolidate
const engineName = map && map[suffix] ? map[suffix] : suffix
const render = engineSource[engineName]
return render(resolve(path, paths.rel), state).then(html => {
// since pug has deprecated `pretty` option
// we will use the `pretty` package in the meanwhile
if (locals.pretty) {
debug('using `pretty` package to beautify HTML')
html = pretty(html)
}
if (autoRender) {
ctx.body = html
} else {
return Promise.resolve(html)
}
})
用法:
//template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<%arr.forEach(a=>{%>
<li><%=a%></li>
<%})%>
</body>
</html>
const Koa = require('koa');
const path = require('path');
const Router = require('koa-router')
const views = require('koa-views');
const app = new Koa();
const router = new Router();
app.use(router.routes())
app.use(views(path.resolve(__dirname), {
map: { html: 'ejs' }
}));
router.get('/',async (ctx,next)=> {
await ctx.render('template.html',{arr:[1,2,3]})
})
app.listen(3000);
ejs中render函数简化版本
123
koa-bodyparser
首先要说一下post提交数据的方式,这样才能更进一步了解解析body的包。
- application/x-www-form-urlencoded;浏览器的原生form表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据,这种数据方式最为常见;
- multipart/form-data;我们使用表单上传文件时,必须让form表单的enctype 等于multipart/form-data,这种方式一般用来上传文件;
- application/json;这种方式的数据格式就是最为常见的json格式;
koa-bodyparser基于co-body。支持 json,默认的form,text,xml类型的post请求。不支持multipart format data,其实就是没有处理文件上传的功能;残废包😂
;koa-bodyparser基于co-body,co-body基于raw-body,raw-body用的是buffer的流处理方式,先存起来,再吐出来,用的on('data',onData),on('end',onEnd)做监听处理;
另外:对Koa1也支持;
koa-body
和koa-bodyparser相比支持文件上传
;
json处理方式:
bodyPromise = require('co-body').json(ctx, {
encoding: opts.encoding,
limit: opts.jsonLimit,
strict: opts.jsonStrict,
returnRawBody: opts.includeUnparsed
});
普通form处理方式
bodyPromise = require('co-body').form(ctx, {
encoding: opts.encoding,
limit: opts.formLimit,
queryString: opts.queryString,
returnRawBody: opts.includeUnparsed
});
上传文件/图片处理方式
const forms = require('formidable');
function formy(ctx, opts) {
return new Promise(function (resolve, reject) {
var fields = {};
var files = {};
var form = new forms.IncomingForm(opts);
form.on('end', function () {
return resolve({
fields: fields,
files: files
});
}).on('error', function (err) {
return reject(err);
}).on('field', function (field, value) {
if (fields[field]) {
if (Array.isArray(fields[field])) {
fields[field].push(value);
} else {
fields[field] = [fields[field], value];
}
} else {
fields[field] = value;
}
}).on('file', function (field, file) {
if (files[field]) {
if (Array.isArray(files[field])) {
files[field].push(file);
} else {
files[field] = [files[field], file];
}
} else {
files[field] = file;
}
});
if (opts.onFileBegin) {
form.on('fileBegin', opts.onFileBegin);
}
form.parse(ctx.req);
});
}
bodyPromise = formy(ctx, opts.formidable);
前两种方式和koa-bodyparser相似都是引用的co-body,第三种处理方式是引用了formidable,formidable封装方法还是用的es5,很多方法挂载到原型链上😂;
koa-better-body
相比于前两个,这个比较全面,json,buffer,text,文件上传都支持,用的generator的方式,还支持koa1;
koa-router
koa-router源码中方法还是挂载在原型上,没有采用es6的class写法,失望😢; 包括的方法有7种:'HEAD', 'OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE' 通过for循环的方式将7种方法挂载到原型上,然后拿到register方法去注册;
for (var i = 0; i < methods.length; i++) {
function setMethodVerb(method) {
Router.prototype[method] = function(name, path, middleware) {
var middleware;
if (typeof path === "string" || path instanceof RegExp) {
middleware = Array.prototype.slice.call(arguments, 2);
} else {
middleware = Array.prototype.slice.call(arguments, 1);
path = name;
name = null;
}
this.register(path, [method], middleware, {
name: name
});
return this;
};
}
setMethodVerb(methods[i]);
}
register方法里边的:
var route = new Layer(path, methods, middleware, {
end: opts.end === false ? opts.end : true,
name: opts.name,
sensitive: opts.sensitive || this.opts.sensitive || false,
strict: opts.strict || this.opts.strict || false,
prefix: opts.prefix || this.opts.prefix || "",
ignoreCaptures: opts.ignoreCaptures // 忽视大小写
});
stack.push(route);
return route;
主要核心在new Layer里边;
设置前缀的逻辑
if (router.opts.prefix) cloneLayer.setPrefix(router.opts.prefix);
// 其实就是下边的逻辑
Layer.prototype.setPrefix = function (prefix) {
if (this.path) {
if (this.path !== '/' || this.opts.strict === true) {
this.path = prefix + this.path;
} else {
this.path = prefix;
}
this.paramNames = [];
this.regexp = pathToRegexp(this.path, this.paramNames, this.opts);
}
return this;
};
del是delete的一个映射
// Alias for `router.delete()` because delete is a reserved word
Router.prototype.del = Router.prototype['delete'];
提到allowedMethods的作用,首先先说一下http options的作用
- 检测服务器所支持的请求方法。 可以使用 OPTIONS 方法对服务器发起请求,以检测服务器支持哪些 HTTP 方法
- CORS 中的预检请求。 在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求,以检测实际请求是否可以被服务器所接受
allowedMethods的作用
- 没有加allowedMethods的情况下,给接口发送options请求,结果返回404,加上allowedMethods,就返回200,并且通过Allow字段告诉他所支持的请求方法
- 相应的返回405(不允许)和501(没实现);比如url只是实现了get方法,但是没实现post方法,用post方法访问该接口就返回405;但是用LINK方法就会返回501,因为koa这个框架不支持LINK这个 生僻方法;
// 对应源代码
return function allowedMethods(ctx, next) {
return next().then(function() {
var allowed = {};
if (!ctx.status || ctx.status === 404) {
for (var i = 0; i < ctx.matched.length; i++) {
var route = ctx.matched[i];
for (var j = 0; j < route.methods.length; j++) {
var method = route.methods[j];
allowed[method] = method
}
}
var allowedArr = Object.keys(allowed);
if (!~implemented.indexOf(ctx.method)) {
if (options.throw) {
var notImplementedThrowable;
if (typeof options.notImplemented === 'function') {
notImplementedThrowable = options.notImplemented(); // set whatever the user returns from their function
} else {
notImplementedThrowable = new HttpError.NotImplemented();
}
throw notImplementedThrowable;
} else {
ctx.status = 501;
ctx.set('Allow', allowedArr.join(', '));
}
} else if (allowedArr.length) {
if (ctx.method === 'OPTIONS') {
ctx.status = 200;
ctx.body = '';
ctx.set('Allow', allowedArr.join(', '));
} else if (!allowed[ctx.method]) {
if (options.throw) {
var notAllowedThrowable;
if (typeof options.methodNotAllowed === 'function') {
notAllowedThrowable = options.methodNotAllowed(); // set whatever the user returns from their function
} else {
notAllowedThrowable = new HttpError.MethodNotAllowed();
}
throw notAllowedThrowable;
} else {
ctx.status = 405;
ctx.set('Allow', allowedArr.join(', '));
}
}
}
}
});
};
koa-router里边还引用了一个koa-compose, compose 是一个工具函数,通过这个工具函数组合后,按 app.use() 的顺序同步执行,也就是形成了 洋葱圈 式的调用。 这个函数的源代码不长,不到50行, 利用递归实现了 Promise 的链式执行,不管中间件中是同步还是异步都通过 Promise 转成异步链式执行。