koa-body 中间件解析原理

常见的请求数据类型:

  1. application/json  常见于post请求 未经过任何处理 以json的格式通过body传输

  2. application/x-www-form-urlencoded 提交的表单数据会转换为键值对并按照key1=val&key2=val2的方式进行编码,常见于POST提交表单以及原生的处理方式。

  3. multipart/form-data 多媒体类型 多用于上传图片文件等 以boundary作为分隔。

  4. text/xml 以xml的格式传输数据 多用于文本传输

Koa-body 原理解析:

  1.  核心主要依赖于co-body 做上述4种不同的数据格式的转换。
  2.   通过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 }

 

posted @ 2023-05-15 21:02  TangTaue  阅读(305)  评论(0编辑  收藏  举报