Промежуточное ПО, обычно используемое в Koa:
- koa-session: позволить HTTP без сохранения состояния иметь состояние, а сеанс, основанный на cookie, для сохранения информации в фоновом режиме.
- koa-mysql: инкапсулирует операторы SQL, которые необходимо использовать.
- koa-mysql-session: используется, когда вы не хотите хранить сеанс в памяти, но хотите сохранить сеанс в базе данных mysql.
- koa-router: фон будет получать URL-адреса различных запросов, а маршрутизация будет использовать различную логику обработки в соответствии с разными URL-адресами.
- koa-view: при запросе html-страницы фон будет использовать механизм шаблонов для рендеринга данных в шаблон, а затем вернуть их в фон
- koa-static: При запросе img, js, css и других файлов никакой другой логики не требуется, нужно только прочитать файл
- koa-better-body: когда сообщение загружает файл, анализировать тело запроса
коа серия статей:
- Будет использоваться и написана структура koa - (реализация koa)
- Будет использоваться и написана структура koa — (koa-router)
- Будет использоваться и написана структура koa — (koa-view, koa-static)
- Фреймворк koa можно использовать и писать — (koa-bodyparser, koa-better-body)
Атрибут 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 = ['jx','kbz'];
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);
Измените атрибут enctype формы формы login.html на 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-bodyparser смонтирует тело запроса в 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();
}
}
Принцип коа-лучшее тело
Из двух приведенных выше рисунков видно, что в contentType заголовка запроса есть граничный атрибут, а данные в теле запроса разделены значением этого граничного атрибута.Чтобы удалить содержимое внутри, вы можете использовать метод сегментации. Так как файл бинарный, буфер нужно разбить, а на прототипе буфера такого метода нет, поэтому этот метод разбиения нужно расширить:
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));
// 每个(**)b的位置
pos= len + index;
}
// 将最后boundary后面的内容放进数组
arr.push(this.slice(pos));
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>]
После перехвата требуемого контента можно производить дальнейшую обработку:
- Если содержимое содержит имя файла, получить содержимое, кроме зеленой части
- Композиция 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()
}
}
Эпилог
Будет использоваться структура koa, и содержание серии будет в основном представлено Добро пожаловать, чтобы исправить меня!