☰
Midway Serverless Function Context 完全指南:从 Event 到 FaaSHTTPContext 的请求处理模型
2026/10/9 17:51:50 网站建设 项目流程
  • 后端
  • 微服务
  • 云原生

【免费下载链接】midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

在 Midway Serverless(@midwayjs/faas)框架中,函数上下文(Function Context)是每一次函数调用的核心载体:它承载着平台传入的原始event/context,并经过框架统一包装后对外提供一套近似 Koa 的编程 API。本文围绕 serverless_context.md 展开,结合packages/faas与packages-serverless/serverless-http-parser的源码实现,系统讲解事件转换机制、Context与FaaSHTTPContext的完整 API 结构及底层原理。读完本文,你将能够在 Serverless 函数中熟练使用ctx读写请求参数、设置响应状态码与响应头,并理解这些 API 在不同平台触发(API 网关、HTTP 触发器)下的行为差异与适用范围。

一、事件转换:统一不同平台的输入参数

Midway Serverless 针对不同云平台(阿里云函数计算 FC、腾讯云 SCF、AWS Lambda 等)的差异化输入参数做了统一包装。当函数使用 API 网关(apigw)和 HTTP(阿里云)触发器时,框架对输入参数(event)做了特殊处理:将 event 统一、规范化成类似 Koa 的写法,以简化并统一函数代码的书写方式。

普通触发器场景

普通触发器(非 HTTP/API 网关)下,ctx可以直接注入到类属性中,handler方法可以接收原始event参数:

import { Context } from '@midwayjs/faas'; import { Provide } from '@midwayjs/core'; @Provide() export class Index { @Inject() ctx: Context; @ServerlessTrigger(...) async handler(event) { return 'hello world'; } }

HTTP 与 API 网关触发器场景

在 HTTP 与 API 网关触发器下,两种返回值写法等价——既可以像 Koa 一样通过this.ctx.body赋值,也可以直接return返回值,框架会统一将返回值写入ctx.body:

import { Context } from '@midwayjs/faas'; import { Provide } from '@midwayjs/core'; @Provide() export class Index { @Inject() ctx: Context; @ServerlessTrigger(...) async handler() { // The following two writing methods are the same // this.ctx.body = 'hello world'; return 'hello world'; } }

从源码看,这一行为由 framework.ts 中的invokeTriggerFunction保证:对于 HTTP 函数,当result !== undefined时,若非null则直接执行ctx.body = result,若为null则绕过 Koa 的_explicitStatus赋值机制将_body置空。也就是说,直接return与显式赋值ctx.body在 HTTP 场景下最终走的是同一条路径。

二、Context:每次调用的请求作用域容器

每调用一次函数,框架就会创建一个全新的ctx(函数上下文)。对于ctx上的属性和方法,框架都提供了 TypeScript 类型定义。

:::info 在 Serverless v1 时代,这个定义被命名为FaaSContext;到 v2 之后,定义与应用被统一,写法更加一致。 :::

在 interface.ts 中可以看到,FaaSContext继承自IMidwayContext<FaaSHTTPContext>,并声明了logger、env、requestContext、originContext四个基础成员;而对外导出的Context类型就是FaaSContext的别名(见 interface.ts)。

ctx.logger

  • 返回类型:ILogger
  • 语义:运行时传入的每次请求对应的日志对象,默认值为console
ctx.logger.info('hello'); ctx.logger.warn('hello'); ctx.logger.error('hello');

在 framework.ts 的getContext方法中可以看到底层细节:当设置了环境变量MIDWAY_SERVERLESS_REPLACE_LOGGER === 'true',或者平台没有提供context.logger时,框架会通过createContextLogger生成_serverlessLogger,并通过Object.defineProperty重新定义logger访问器。注释指出:由于 FC 公有云环境的 logger 存在已知 bug,默认会替换为框架日志,其他平台视情况而定。

ctx.env

  • 返回类型:string
  • 语义:当前启动环境,即NODE_ENV或MIDWAY_SERVER_ENV的值,默认值为prod
ctx.env; //default prod

源码层面,getContext中有一个兜底逻辑:如果平台传入的context上没有env字段,则调用this.environmentService.getCurrentEnvironment()填充(见 framework.ts),保证任意平台下ctx.env始终有值。

ctx.requestContext

  • 返回类型:MidwayRequestContainer
  • 语义:Midway FaaS 的 IoC 请求作用域容器,用于获取其他 IoC 容器中的对象实例
const userService = await ctx.requestContext.getAsync(UserService);

这一容器与 Midway 的依赖注入体系打通:函数处理器(handler)自身由context.requestContext.getAsync(routerInfo.controllerId)实例化(见 framework.ts),因此你在 handler 中通过@Inject()注入的服务与通过ctx.requestContext.getAsync()获取的实例来自同一个请求作用域,可以安全地在一次请求内共享状态。

三、FaaSHTTPContext:Koa 风格的应用开发体验

Context的定义继承自FaaSHTTPContext。Context保留了后者的全部能力,在大多数场景下可以直接使用Context;FaaSHTTPContext仅在 API 网关(apigw)和 HTTP(阿里云)触发器下可用。对普通用户而言,直接使用Context定义即可:

import { Context } from '@midwayjs/faas'; @Inject() ctx: Context;

在ctx对象中,框架提供了大量与传统 Koa Web 应用相似的 API。这样设计的好处是:降低用户的学习成本,并在一定程度上兼容原有的传统代码与社区中间件。

注意:不同平台提供的 API 可能不完全相同,下文会指出具体 API 的适用范围。

ctx.request

  • 返回类型:FaaSHTTPRequest
  • 语义:FaaS 模拟的 HTTP Request 对象

ctx.response

  • 返回类型:FaaSHTTPResponse
  • 语义:FaaS 模拟的 HTTP Response 对象

从类型定义看,FaaSHTTPContext同时继承ContextDelegatedRequest与ContextDelegatedResponse(见 interface.ts),这意味着它把请求侧的属性和响应侧的属性全部委托到同一个ctx上。此外它还提供req/res(原生 mock 对象,不建议直接使用)、originEvent(原始 event 对象)、cookies(Cookie 对象)与state(请求状态存储)。

ctx.params

代理的是request.pathParameters,仅在 HTTP 触发器(阿里云)和 API 网关触发器下可用。

// /api/user/[id] /api/user/faas ctx.params.id; // faas

底层实现中,request.params的 getter 直接返回this.req.pathParameters || {}(见 request.ts),而pathParameters由 http/req.ts 从 event 中读取并支持 setter 覆写。在 HTTP 路由匹配时,框架会用PathToRegexpUtil.match将路径参数写入context.req.pathParameters(见 framework.ts),因此ctx.params.id能取到/api/user/faas中的faas。

ctx.set

设置响应头,是response.setHeader的代理。

ctx.set('X-FaaS-Duration', 2100);

底层实现位于 response.ts:set方法支持「字段 + 值」与「整个对象批量设置」两种形态,最终调用this.res.setHeader(field, val);当传入数组时会统一转为字符串数组。

ctx.status

设置返回状态码,是response.statusCode的代理。

ctx.status = 404;

在 response.ts 中,statussetter 会校验状态码必须为 100~999 之间的整数,并置位_explicitStatus标记;如果当前状态码对应空响应语义且已有 body,则自动清空 body。

Request 别名

以下属性均来自request对象的代理:

  • ctx.headers— 请求头对象
  • ctx.method— 请求方法
  • ctx.url— 完整请求 URL
  • ctx.path— 请求路径
  • ctx.ip— 客户端 IP
  • ctx.query— 解析后的查询字符串
  • ctx.get()— 获取指定请求头字段

Response 别名

以下属性均来自response对象的代理:

  • ctx.body=— 设置响应体
  • ctx.status=— 设置状态码(response.statusCode别名)
  • ctx.type=— 设置 Content-Type
  • ctx.set()— 设置响应头(response.setHeader别名)

补充说明:原文档中列出的 Request/Response 锚点链接在版本化文档中已失效,此处不再引用。你可以在 interface.ts 中查看ContextDelegatedRequest与ContextDelegatedResponse的完整成员定义。

四、FaaSHTTPRequest:从 event 转换出的请求对象

FaaSHTTPRequest对象由函数的event和context输入参数转换而来。类型定义见 interface.ts,实现逻辑见 request.ts 与 http/req.ts。

request.headers

包含所有请求头的对象,以键值对存储。注意 http/req.ts 中会对原始 event 的 headers 做key 小写化处理,保证ctx.headers['Content-Type']与ctx.headers['content-type']均能命中。

request.ip

获取客户端请求 IP 地址。

:::info 在阿里云 FC 上,只有 HTTP 触发器可以获取到该值,API 网关暂无法获取。 :::

实现上,request.ip优先读取this.req?.clientIP || this.req.ip(见 request.ts),而HTTPRequest.ip则从event.clientIP || event.requestContext?.sourceIp中取值(见 http/req.ts)——这也解释了为何只有携带这些字段的 HTTP 触发器才能取到 IP。

request.url

客户端请求的完整 URL。若 event 中缺失url,框架会用path + querystring拼接生成(见 http/req.ts)。

request.path

客户端请求路径。

request.method

请求方法。实现上兼容event.method与event.httpMethod两种字段名(见 http/req.ts),这也是不同云平台 event 结构差异被抹平的一个典型例子。

request.body

POST 请求体,已被解析为 JSON。解析逻辑分层完成:

  • http/req.ts 负责从 event 中提取原始 body:GET/HEAD/OPTIONS 请求返回undefined;支持isBase64Encoded的 base64 解码;bodyParsed标记用于判断 body 是否已被上层解析。
  • request.ts 负责按 Content-Type 解析:json类型执行JSON.parse(解析失败抛出invalid json received),urlencoded类型用querystring.parse解析,其余类型原样返回。

五、FaaSHTTPResponse:模拟的响应对象

FaaSHTTPResponse同样由event和context转换而来,提供三个核心能力。

response.setHeader

设置响应头。通过ctx.set()调用,底层委托给原生res.setHeader(见 response.ts)。

response.statusCode

设置返回状态码。通过ctx.status = code调用,见上文第三节对statussetter 的说明。

response.body

设置响应体内容,类型为string或buffer。setter 逻辑(见 response.ts)会根据值的类型自动处理:

  • string:自动计算并设置Content-Length,默认 Content-Type 为text
  • Buffer:默认 Content-Type 为bin
  • 对象(JSON):移除Content-Length并设置 Content-Type 为json
  • null:若当前状态码不是空响应语义,则自动改为 204

响应在返回平台之前,会经过 framework.ts 的formatHttpResponse统一格式化:当 body 为null/undefined且未显式设置状态码时,输出 204;字符串默认text/plain,Buffer 在未开启supportBufferResponse时转 base64 并标记isBase64Encoded,对象默认application/json并序列化为字符串。最终组装为{ isBase64Encoded, statusCode, headers, body }的标准网关响应结构。

六、实战:把 API 串起来写一个 HTTP 函数

结合仓库中的测试示例 base-app-controller/src/controller/api.ts,可以看到ctx相关 API 在真实 Controller 中的组合用法:

import { Controller, Post, Get, Provide, Inject, Query, Body, HttpCode, SetHeader, Logger } from '@midwayjs/core'; @Provide() @Controller('/api') export class APIController { @Inject() ctx: any; @Logger() logger; @Get('/', { middleware: [] }) @HttpCode(201) async home(@Query('name') name: string, @Query('age') age: number) { this.ctx.logger.info('my home router'); this.logger.warn('my home warn router') return 'hello world,' + name + age; } @Get('/set_header') @SetHeader('bbb', 'aaa') @SetHeader({ 'ccc': 'ddd' }) async homeSet() { return 'bbb'; } @Get('/ctx-body') async getCtxBody() { this.ctx.body = 'ctx-body'; } @Get('/login') @Redirect('/') async redirect() {} }

从中可以总结出几个关键实践:

  1. 读请求:通过@Query()等参数装饰器或ctx.query/ctx.params读取请求参数;在非 Controller 类函数中,直接使用ctx.params.id、ctx.headers即可。
  2. 写响应:return返回值与ctx.body = xxx等价(HTTP 函数场景);需要定制状态码与响应头时使用ctx.status = 404、ctx.set('X-FaaS-Duration', 2100),或使用@HttpCode()、@SetHeader()等装饰器,后者在底层同样通过context.status/context.set生效(见 framework.ts)。
  3. 取服务:在需要手动获取 IoC 实例时使用await ctx.requestContext.getAsync(UserService),实现请求级共享。
  4. 记日志:统一使用ctx.logger(或@Logger()注入),避免直接console.log,保证日志对象与请求生命周期、平台日志能力对齐。

七、小结

Midway Serverless 通过「事件转换 + Context 包装」两层设计,把千差万别的云平台 event 归一为熟悉的 Koa 式编程模型:

  • 普通触发器:handler(event)直接拿到原始 event,ctx提供logger、env、requestContext等运行时能力;
  • HTTP/API 网关触发器:ctx额外具备完整的FaaSHTTPContext能力——ctx.request/ctx.response模拟对象、ctx.params路径参数、ctx.set/ctx.status响应控制,以及一整套 Request/Response 别名;
  • 底层实现:请求对象转换在packages-serverless/serverless-http-parser的HTTPRequest/request中完成,响应格式化与 HTTP 函数调用链在packages/faas/src/framework.ts中完成,类型定义集中在packages/faas/src/interface.ts,可供进一步阅读源码验证。

在实际开发中,只需记住一个原则:普通场景直接用Context,HTTP 场景优先使用return返回值,其余细节(参数解析、状态码、Content-Type、Buffer 编码)都可以放心交给框架处理。

  • 后端
  • 微服务
  • 云原生

【免费下载链接】midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈

项目地址:https://gitcode.com/gh_mirrors/mi/midway
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询