- 后端
- 微服务
- 云原生
【免费下载链接】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. 🌈
在 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— 完整请求 URLctx.path— 请求路径ctx.ip— 客户端 IPctx.query— 解析后的查询字符串ctx.get()— 获取指定请求头字段
Response 别名
以下属性均来自response对象的代理:
ctx.body=— 设置响应体ctx.status=— 设置状态码(response.statusCode别名)ctx.type=— 设置 Content-Typectx.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 为textBuffer:默认 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() {} }从中可以总结出几个关键实践:
- 读请求:通过
@Query()等参数装饰器或ctx.query/ctx.params读取请求参数;在非 Controller 类函数中,直接使用ctx.params.id、ctx.headers即可。 - 写响应:
return返回值与ctx.body = xxx等价(HTTP 函数场景);需要定制状态码与响应头时使用ctx.status = 404、ctx.set('X-FaaS-Duration', 2100),或使用@HttpCode()、@SetHeader()等装饰器,后者在底层同样通过context.status/context.set生效(见 framework.ts)。 - 取服务:在需要手动获取 IoC 实例时使用
await ctx.requestContext.getAsync(UserService),实现请求级共享。 - 记日志:统一使用
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. 🌈
相关推荐
Midway Serverless 函数上下文(Function Context)完全指南:从 Event 统一到 Koa 风格 API
Midway Serverless 函数上下文(Function Context)完全指南:从 Event 统一到 Koa 风格 API Midway Serv
后端微服务云原生Midway Serverless 函数上下文(Context)全面解析:从 Event 转换到 FaaSHTTPContext 的 Koa 风格统一封装
Midway Serverless 函数上下文(Context)全面解析:从 Event 转换到 FaaSHTTPContext 的 Koa 风格统一封装 Mi
后端微服务云原生RedwoodJS 自定义 Serverless Function 完全指南:从 `generate function` 到请求方法过滤与 CORS 实战
RedwoodJS 自定义 Serverless Function 完全指南:从 generate function 到请求方法过滤与 CORS 实战 导读 R
后端前端Web框架开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考