koa-body 中间件解析原理
常见的请求数据类型:
-
application/json 常见于post请求 未经过任何处理 以json的格式通过body传输
-
application/x-www-form-urlencoded 提交的表单数据会转换为键值对并按照key1=val&key2=val2的方式进行编码,常见于POST提交表单以及原生的处理方式。
-
multipart/form-data 多媒体类型 多用于上传图片文件等 以boundary作为分隔。
-
text/xml 以xml的格式传输数据 多用于文本传输
Koa-body 原理解析:
- 核心主要依赖于co-body 做上述4种不同的数据格式的转换。
- 通过patchNodeAndKoa 进行不同数据格式的判断。最终返回响应体。
1 import { KoaBodyMiddlewareOptionsSchema } from './types';
2 import type { KoaBodyMiddlewareOptions } from './types';
3 import type { Context, Middleware, Next } from 'koa';
4 import * as Koa from 'koa';
5 import type { Files } from 'formidable';
6 import coBody from 'co-body';
7 import toHttpMethod from './utils/string-method-to-enum-method';
8 import throwableToError from './utils/throwable-to-error';
9 import { isJsonBody, isMultipartBody, isTextBody, isUrlencodedBody } from './utils/body-type-util';
10 import parseWithFormidable from './utils/parse-with-formidable';
11 import { patchNodeAndKoa } from './utils/patch-util';
12 import type { ContextWithBodyAndFiles } from './utils/patch-util';
13
14 export * from './types';
15
16 declare module 'koa' {
17 interface Request extends Koa.BaseRequest {
18 body?: any;
19 files?: Files;
20 }
21 }
22
23 export function koaBody(options: Partial<KoaBodyMiddlewareOptions> = {}): Middleware {
24 const validatedOptions = KoaBodyMiddlewareOptionsSchema.parse(options);
25 const optionsToUse = { ...options, ...validatedOptions };
26 return async (ctx: Context, next: Next) => {
27 const isJson = isJsonBody(ctx, optionsToUse);
28 const isText = isTextBody(ctx, optionsToUse);
29 const isUrlencoded = isUrlencodedBody(ctx, optionsToUse);
30 const isMultipart = isMultipartBody(ctx, optionsToUse);
31 const {
32 encoding,
33 jsonStrict,
34 jsonLimit,
35 includeUnparsed: returnRawBody,
36 formLimit,
37 textLimit,
38 queryString,
39 formidable,
40 onError,
41 patchNode,
42 patchKoa,
43 } = optionsToUse;
44 // only parse the body on specifically chosen methods 只解析body中指定的请求方法
45 if (validatedOptions.parsedMethods.includes(toHttpMethod(ctx.method.toUpperCase()))) {
46 try {
47 if (isJson) { // application/json 数据格式
48 const jsonBody = await coBody.json(ctx, {
49 encoding,
50 limit: jsonLimit,
51 strict: jsonStrict,
52 returnRawBody,
53 });
54 patchNodeAndKoa(ctx as ContextWithBodyAndFiles, jsonBody, {
55 isText,
56 includeUnparsed: returnRawBody,
57 isMultipart,
58 patchKoa,
59 patchNode,
60 });
61 } else if (isUrlencoded) { // urlencode数据格式 key=value value默认编码 get和post请求都可使用
62 const urlEncodedBody = await coBody.form(ctx, {
63 encoding,
64 limit: formLimit,
65 queryString: queryString,
66 returnRawBody,
67 });
68 patchNodeAndKoa(ctx as ContextWithBodyAndFiles, urlEncodedBody, {
69 isText,
70 includeUnparsed: returnRawBody,
71 isMultipart,
72 patchKoa,
73 patchNode,
74 });
75 } else if (isText) { // 文本数据格式
76 const textBody = await coBody.text(ctx, {
77 encoding,
78 limit: textLimit,
79 returnRawBody,
80 });
81 patchNodeAndKoa(ctx as ContextWithBodyAndFiles, textBody, {
82 isText,
83 includeUnparsed: returnRawBody,
84 isMultipart,
85 patchKoa,
86 patchNode,
87 });
88 } else if (isMultipart) { // 多媒体数据格式
89 const multipartBody = await parseWithFormidable(ctx, formidable || {});
90 patchNodeAndKoa(ctx as ContextWithBodyAndFiles, multipartBody, {
91 isText,
92 includeUnparsed: returnRawBody,
93 isMultipart,
94 patchKoa,
95 patchNode,
96 });
97 }
98 } catch (parsingError) {
99 const error = throwableToError(parsingError);
100 if (typeof onError === 'function') {
101 onError(error, ctx);
102 } else {
103 throw error;
104 }
105 }
106 } else { // 处理未指定的请求方法
107 patchNodeAndKoa(
108 ctx as ContextWithBodyAndFiles,
109 {},
110 {
111 isText,
112 includeUnparsed: returnRawBody,
113 isMultipart,
114 patchKoa,
115 patchNode,
116 },
117 );
118 }
119
120 return next();
121 };
122 }
123
124 export default koaBody;
1 export function patchNodeAndKoa(ctx: ContextWithBodyAndFiles, body: any, options: PatchOptions) {
2 const { patchKoa, patchNode, isMultipart, includeUnparsed, isText } = options;
3 // 请求放置在Node
4 if (patchNode) {
5 if (isMultipart) {
6 ctx.req.body = body.fields;
7 ctx.req.files = body.files;
8 } else if (includeUnparsed) {
9 ctx.req.body = body.parsed || {};
10 if (!isText) {
11 ctx.req.body[symbolUnparsed] = body.raw;
12 }
13 } else {
14 ctx.req.body = body;
15 }
16 }
17 if (patchKoa) { // 请求放置在Koa
18 if (isMultipart) {
19 ctx.request.body = body.fields;
20 ctx.request.files = body.files;
21 } else if (includeUnparsed) {
22 ctx.request.body = body.parsed || {};
23 if (!isText) {
24 ctx.request.body[symbolUnparsed] = body.raw;
25 }
26 } else {
27 ctx.request.body = body;
28 }
29 }
30 }
分类:
Node
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构