koa-static
koa-static
能够解决动态资源,参数是动态资源文件夹门路,官网的实现蕴含了更多的参数配,具体能够查看 koajs/static
实现剖析:
- 获取申请url门路,查找动态文件夹下的门路是否能匹配
- 如果是门路是文件夹,查找文件夹下
index.html
文件 - 设置响应头文件类型(mime)
gzip
压缩,设置压缩类型并返回可读流- 出错或文件不存在
next
持续下一个中间件
const fs = require("fs")const path = require("path")const mime = require("mime")const zlib = require("zlib")function static(dir) { return async (ctx, next) => { try { let reqUrl = ctx.path let abspath = path.join(dir, reqUrl) let statObj = fs.statSync(abspath) // 如果是文件夹,拼接上index.html if (statObj.isDirectory()) { abspath = path.join(abspath, "index.html") } // 判断门路是否精确 fs.accessSync(abspath); // 设置文件类型 ctx.set("Content-Type", mime.getType(abspath) + ";charset=utf8") // 客户端容许的编码格局,判断是否须要gzip压缩 const encoding = ctx.get("accept-encoding") if (/\bgzip\b/.test(encoding)) { ctx.set("Content-Encoding", "gzip") ctx.body = fs.createReadStream(abspath).pipe(zlib.createGzip()) } else if (/\bdeflate\b/.test(encoding)) { ctx.set("Content-Encoding", "bdeflate") ctx.body = fs.createReadStream(abspath).pipe(zlib.createDeflate()) } else { ctx.body = fs.createReadStream(abspath) } } catch (error) { await next() } }}module.exports = static
koa-bodyparser
koa-bodyparser
能够解决POST申请的数据,将form-data
数据解析到ctx.request.body
,官网地址:koa-bodyparser
实现剖析:
- 读取申请数据
- 设置响应头类型
application/json
- 判断客户端申请头
content-type
类型 - 解析数据并绑定到
ctx.request.body
function bodyParser() { return async (ctx, next) => { await new Promise((resolve, reject) => { let data = [] ctx.req.on("data", (chunk) => { data.push(chunk) }) ctx.req.on("end", () => { let ct = ctx.get("content-type") let body = {} ctx.set("Content-Type", "application/json") if (ct === "application/x-www-form-urlencoded") { body = require("querystring").parse(Buffer.concat(data).toString()) } if (ct === "application/json") { body = JSON.parse(Buffer.concat(data).toString()) } ctx.request.body = body resolve() }) ctx.req.on("error", (error) => { reject(error) }) }) await next() }}module.exports = bodyParser
koa-router
koa-router
能够让koa像express一样管制路由,源码koa-router实现比较复杂,这里实现一个简略版本。
实现剖析:
- 先将申请保留到数组
middlewares
中 routes
返回中间件函数,获取ctx
和next
- 从数组
middlewares
过滤出门路/办法
雷同的数据 - 中间件解决,传入
ctx
和封装后的next
,解决完结才调用真正的next
办法
class Router { constructor() { // 保留中间件办法的数组 this.middlewares = [] } get(path, handler) { // get、post、delete、put这些办法都要解决,这里只实现get this.middlewares.push({ path, handler }) } compose(routes, ctx,next) { // 派发每一个存储的中间件办法 const dispatch = (index) => { // 解决实现才执行上下文的next if (routes.length === index) return next(); // 将中间件的next包装成下个执行的dispath,避免屡次执行上下文的next办法 routes[index].handler(ctx, () => dispatch(++index)) } dispatch(0) } routes() { return async (ctx, next) => { // 过滤出雷同的存储对象 let routes = this.middlewares.filter((item) => item.path === ctx.url) this.compose(routes, ctx,next) } }}module.exports = Router;
koa-better-body
koa外面解决文件上传应用的是koa-better-body
,这里须要保障表单中带有multipart/form-data
:
<form action="/submiturl" method="POST" enctype="multipart/form-data">
上面是通过表单enctype为multipart/form-data
提交后盾拿到的数据:
------WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition: form-data; name="username"chenwl------WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition: form-data; name="password"1234567------WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition: form-data; name="avatar"; filename="test.txt"Content-Type: text/plain这里是文件内容------WebKitFormBoundaryfCunWPksjjur83I5--
实现剖析:
- 获取申请信息,申请头须要有
multipart/form-data
- 切割申请信息,提取有用的信息
- 蕴含
filename
的为文件,写入到对应门路 - 提取其它信息保留到
ctx.request.fields
const fs = require("fs");const path = require("path");Buffer.prototype.split = function(sep){ let arr = []; let offset = 0; let len = Buffer.from(sep).length; let current = this.indexOf(sep,offset); while (current !== -1) { let data=this.slice(offset,current) arr.push(data); offset = current+len; current = this.indexOf(sep, offset) } arr.push(this.slice(offset)); return arr;}module.exports = function ({ uploadDir }) { return async (ctx, next) => { // 后果放到 req.request.fields await new Promise((resolve, reject) => { let data = [] ctx.req.on("data", (chunk) => { data.push(chunk) }) ctx.req.on("end", () => { // multipart/form-data; boundary=----WebKitFormBoundaryvFyQ9QW1McYTqHkp const contentType = ctx.get("content-type") if (contentType.includes("multipart/form-data")) { const boundary = "--"+contentType.split("=")[1]; const r = Buffer.concat(data); const arr = r.split(boundary).slice(1,-1); const fields = {}; arr.forEach(line=>{ let [head,body] = line.split("\r\n\r\n"); body = body.slice(0,-2); // 取出无效的内容 head = head.toString(); if(head.includes("filename")){ // 解决文件 // 申请头长度 = 总共的内容长度 - 头部长度 - 4个换行符长度 const filecontent = line.slice(head.length+4,-2); const filenanme = head.match(/filename="(.*?)"/)[1] || uid(); const uploadPath = path.join(uploadDir, filenanme) fs.writeFileSync(uploadPath, filecontent) }else{ fields[head.match(/name="(.*?)"/)[1]] = body.toString(); } }) ctx.request.fields = fields } resolve() }) ctx.req.on("error", (error) => { reject(error) }) }) await next() }}function uid(){ return Math.random().toString(32).slice(2);}