内容简介:上面的文字看起来可能不够直观,所以可以自己发送请求来查看:将login.html的form表单的enctype属性分别改成text/plain、application/x-www-form-urlencoded、multipart/form-data:从上面的两张图可以看到请求头中contentType中存在一个boundary属性,而请求体中的数据正是用这个boundary属性的值隔开的,去除里面的内容可以用分割的方法。由于文件是二进制的,需要分割buffer,但是Buffer的原型上没有这个方法,所以
- koa-session:让无状态的http拥有状态,基于cookie实现的后台保存信息的session
- koa-mysql:封装了需要用到的 SQL 语句
- koa-mysql-session:当不想让session存储到内存,而想让session存储到 mysql 数据库中时使用
- koa-router:后台会接受到各种请求的url,路由会根据不同的url来使用不同的处理逻辑。
- koa-view:请求html页面时,后台会用模板引擎渲染数据到模板上,然后返回给后台
- koa-static:请求img、js、css等文件时,不需要其他逻辑,只需要读取文件
- koa-better-body:post上传文件时,解析请求体
koa系列文章:
- koa框架会用也会写—(koa的实现)
- koa框架会用也会写—(koa-router)
- koa框架会用也会写—(koa-view、koa-static)
- koa框架会用也会写—(koa-bodyparser、koa-better-body)
form标签的enctype属性
- application/x-www-form-urlencoded:在发送前编码所有字符(不设置默认)
- multipart/form-data:不对字符编码,在使用包含文件上传控件的表单时,必须使用该值。
- text/plain:空格转换为 "+" 加号,但不对特殊字符编码。
上面的文字看起来可能不够直观,所以可以自己发送请求来查看:
//前台页面login.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> <link rel="stylesheet" href="/bootstrap/dist/css/bootstrap.css"> </head> <body> <form action="/fileupload" enctype="text/plain" method="POST"> <div class="form-group"> <label for="username" class="control-label">用户名</label> <input type="text" class="form-control" id="username" name="username"> </div> <div class="form-group"> <label for="password" class="control-label">密码</label> <input type="text" class="form-control" id="password" name="password"> </div> <div class="form-group"> <label for="avatar" class="control-label">头像</label> <input type="file" multiple class="form-control" id="avatar" name="avatar"> </div> <div class="form-group"> <button type="submit" class="btn btn-danger">登录</button> </div> </form> </body> </html> 复制代码
//后台服务 const Koa = require('koa'); const path = require('path'); const Router = require('koa-router'); const static = require('koa-static'); const session = require('koa-session'); let app = new Koa(); let router = new Router(); app.keys = ['zfpx','jw']; app.use(session({ maxAge:5*1000, }, app)); app.use(static(path.resolve(__dirname))); app.use(static(path.resolve(__dirname,'node_modules'))); router.post('/login',async(ctx,next)=>{ // 获取用户的账号及密码 ctx.session.user = ctx.request.body ctx.body = ctx.session.user; }); router.get('/home',async (ctx,next)=>{ if (ctx.session.user){ ctx.body = { status:1, username: ctx.session.user.username } }else{ ctx.body = { status: 0, username: null } } }) router.post('/fileupload',async(ctx,next)=>{ console.log('upload') }) app.use(router.routes()); app.listen(3000); 复制代码
将login.html的form表单的enctype属性分别改成text/plain、application/x-www-form-urlencoded、multipart/form-data:
- text/plain的请求体:空格转换为 "+" 加号,但不对特殊字符编码
- application/x-www-form-urlencoded的请求体:在发送前编码所有字符(不设置默认)
- multipart/form-data:不对字符编码,在使用包含文件上传控件的表单时,必须使用该值。 从上面的图片可以看出来:
- 一般的表单提交使用的application/x-www-form-urlencoded,比text/plain多了字符串进行编码
- application/x-www-form-urlencoded、text/plain提交文件时会把只把文件名提交,内容在请求体中看不到。但是multipart/form-data却可以
koa-bodyparser和koa-better-body的区别
- koa-bodyparser没有处理文件上传的功能,而koa-better-body处理了文件上传功能
- koa-bodyparserh会将请求体挂载在ctx.request.body,而koa-better-body将请求体挂载在ctx.request.fields
koa-bodyparser的原理
//利用buffer来缓存数据,kao的中间件使用async和await function bodyParser() { return async (ctx, next) => { await new Promise((resolve, reject) => { let arr = []; ctx.req.on('data', function (data) { arr.push(data); }); ctx.req.on('end', function () { let r = Buffer.concat(arr).toString(); ctx.request.body = r; resolve(); }) }); await next(); } } 复制代码
koa-better-body的原理
从上面的两张图可以看到请求头中contentType中存在一个boundary属性,而请求体中的数据正是用这个boundary属性的值隔开的,去除里面的内容可以用分割的方法。由于文件是二进制的,需要分割buffer,但是Buffer的原型上没有这个方法,所以要扩展这个split方法:
Buffer.prototype.split = function (sep) { let len = Buffer.from(sep).length; //分隔符的字节长度 let pos = 0; let index = 0; let arr = []; //判断pos位后面是否还存在boundary //截取boundary前面的内容放在数组中 while (-1 != (index = this.indexOf(sep,pos))) { arr.push(this.slice(pos,index)); pos= len + index;//每个(**)b的位置 } arr.push(this.slice(pos));//将最后boundary后面的内容放进数组 return arr; } //b代表boundary,**代表获取的内容 let buffer = Buffer.form('b**b**b**b--').split('b') console.log(buffer); //[<Buffer >,<Buffer 2a 2a>,<Buffer 2a 2a>,<Buffer 2a 2a>,<Buffer 2d 2d>] 复制代码
截取了需要的内容之后就可以做进一步的处理:
- 内容中包含filename的获取除绿色部分的内容
- 内容中不包含filename的组成ctx.request.fields.xx = xx
function betterBody({uploadDir}) { return async (ctx,next)=>{ await new Promise((resolve,reject)=>{ let arr = []; ctx.req.on('data', function (data) { arr.push(data); }); ctx.req.on('end', function () { if (ctx.get('content-type').includes('multipart')) { let r = Buffer.concat(arr); //请求体中的内容 //获取的boundary少了--,加上后用于截取内容 let boundary = '--' + ctx.get('content-type').split('=')[1]; // 去除头尾取到中间有用的部分 //[<Buffer 2a 2a>,<Buffer 2a 2a>,<Buffer 2a 2a>] let lines = r.split(boundary).slice(1, -1); let fileds = {}; //处理含filename和不含filename的内容 lines.forEach((line) => { let [head, content] = line.split('\r\n\r\n'); head = head.toString(); if (head.includes('filename')) { // 是文件取出除head+'\r\n\r\n'后的内容 //-2表示去除最后空白行\r\n let content = line.slice(head.length + 4, -2); let uuid = require('uuid/v4'); let fs = require('fs'); let p = uuid(); fs.writeFileSync(path.resolve(__dirname, uploadDir, p), content); fileds['path'] = p; } else { // 不是文件,挂载参数例如:username let key = head.match(/name="([\s\S]*)"/im)[1]; //-2表示去除最后空白行\r\n let value = content.toString().slice(0, -2); fileds[key] = value; }; }) ctx.request.fields = fileds; resolve(); } }) }) await next() } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 基于Google MVVM框架的baseMVVM框架
- Spring 框架是怎么出生的(二):重构提炼出框架
- Spring 框架是怎么出生的(二):重构提炼出框架
- Genesis框架从入门到精通(7): 框架的过滤器
- 如何打造自己的POC框架-Pocsuite3-框架篇
- 如何打造自己的PoC框架-Pocsuite3-框架篇
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。