Skip to content

Cell - Web 组件 ​

Cell 的 HTTP 层:服务器、中间件链、上下文、请求体、cookie、会话、CORS 和路由匹配,基于 node:http,无第三方运行时依赖。控制器来自 @celljs/mvc;指南是 HTTP API。

中间件 ​

中间件是绑定到 Middleware 的组件;每个请求按 priority 从高到低顺序通过所有中间件,然后到达处理器(MVC、RPC)。

ts
import { Component } from '@celljs/core';
import { Context, HTTP_MIDDLEWARE_PRIORITY, Middleware } from '@celljs/web/lib/node';

@Component(Middleware)
export class TimingMiddleware implements Middleware {
    readonly priority = HTTP_MIDDLEWARE_PRIORITY - 10;

    async handle(ctx: Context, next: () => Promise<void>): Promise<void> {
        const start = Date.now();
        await next();
        ctx.response.setHeader('server-timing', `app;dur=${Date.now() - start}`);
    }
}

内置的从高到低:跟踪(TRACE_MIDDLEWARE_PRIORITY,2200)、CORS(2100)、请求体和 HttpRequest(2000)、cookie 和健康(1900)、会话(1800);@celljs/security 在其下添加自己的。抛出的中间件将错误交给错误处理器。

错误 ​

抛出 HttpError(status, message?, { cause?, expose? }) 或 NotFoundError(message) 以答状态;其他任何东西答 500。没有消息时,消息是状态文本(Not Found)。堆栈与每个 cause 被记录在错误级别,永远不发送;一个 4xx HttpError,或任何处理器答重定向或 4xx 的错误(需要登录、拒绝访问),是答覆盖而不是失败,仅在调试级别记录。

客户端看到的由 ErrorMessageResolver 决定:有 expose 的 HttpError 的消息(4xx 的默认值);对于其他的,当 web.errors.exposeMessages 为 true 时的消息,否则状态文本(Internal Server Error)。exposeMessages 在生产外默认为 true(NODE_ENV 不是 production),所以内部错误的消息(SQL、主机、文件路径)在生产中保持在日志中。

错误处理器是绑定到 ErrorHandler 的组件,有 priority、canHandle(ctx, err) 和 handle(ctx, err):能处理错误的最高优先级处理器答覆盖,当其 handle 抛出时下一个被尝试;当没有答覆盖时,响应以裸 500 结束,所以没有请求挂起。ErrorResponseUtil.end(ctx, status, body, contentType?) 结束失败的响应(仅在头发送时结束)。内置处理器的优先级高达 2000。

请求上下文 ​

Context(@celljs/web/lib/node)是请求期间任何代码运行的当前请求,用 AsyncLocalStorage 跨 await 传递:Context.getRequest()、getResponse()、getCookies()、getSession()、getTraceId(),以及用 Context.setAttr(key, value) / Context.getAttr(key) 的属性(默认请求作用域,AttributeScope.App 用于应用)。

跟踪 ID 与健康 ​

每个响应都在 X-Cell-Trace-ID 中携带跟踪 ID(web.trace.responseField;requestField 命名请求头可从它来)。GET /health(web.health.url、web.health.method)答 HealthIndicator 组件的聚合状态为 JSON。

会话(web.session) ​

会话存储在签名的 cookie(CookieSessionStore)中,当改变时在请求后保存(autoCommit)。maxAge 默认是一天。用 Context.getSession() 或控制器中的 @Session() 读它;绑定你自己的 SessionStore 以将会话保留在其他地方。

请求与响应 ​

Request 是 Node 的 IncomingMessage 加上 path(无查询,未解码)、query(带括号,如 express 解析:?a[b]=1&c=2&c=3 → { a: { b: '1' }, c: ['2', '3'] })、protocol / secure(从连接;不读 X-Forwarded-Proto)、hostname、ip、originalUrl、body 和 get(header)。Response 是 Node 的 ServerResponse 加上 body,http 中间件在处理器后写;没有 res.status() / res.send():设置 statusCode 和 body,或调用 end()。

HttpServerUtil.createHandler(container) 是应用的 (req, res) 处理器,HttpServerUtil.createHttpServer(handler) 是一个请求是 HttpRequest 的服务器;另一服务器的请求(开发服务器、测试、无服务器代理)用 HttpRequest.enhance(req) 获得成员。Express 中间件当仅需要 Node 的 req / res 时仍用 convertExpressMiddleware() 挂载。

参数类型 ​

TypeConversionPipe 将请求数据转换为 String、Number、Boolean 和 Date 参数,如 class-transformer 做的('5' → 5)。其他类的参数获得数据原样;添加 @celljs/validation 以用 class-validator 转成实例和验证它们。

请求体(web.body) ​

request.body 是 JSON(application/json)、文本(text/plain)、Buffer(application/octet-stream)或表单(application/x-www-form-urlencoded,带括号:a[b]=1 → { a: { b: '1' } });任何其他请求获得 {}。gzip、deflate 和 br 体被解压。

KeyDefault
limit100kb解压后最大体,字节或 '5mb'
inflatetruefalse 拒绝压缩体
stricttrueJSON 必须是对象或数组
parameterLimit1000表单中最多参数
depth32表单中括号最多级别

格式错误的体答 400,大的答 413,不支持的字符集或编码答 415。

带 keys 时,cookie 被签名(一个 <name>.sig cookie,用第一个键的 HMAC-SHA1,cookies/keygrip 包的格式,所以现有的签名 cookie 保持有效);任何键验证,所以键可以通过把新的放在前面来轮转。Cookies.get(name, { signed: true }) 检查签名;get(name) 无选项不。

默认 keys 是占位符(['abcdef']):在生产中设置你自己的,例如 keys: ["${env.COOKIE_KEY}"],因为它们也签署会话 cookie。CookiesSecurityCheck 报告占位符(CELL_SECURITY_DEFAULT_COOKIE_KEYS)、无键(CELL_SECURITY_COOKIE_KEYS_MISSING:会话 cookie 然后可由客户端编辑)和短于 16 个字符的键(CELL_SECURITY_WEAK_COOKIE_KEYS)给 cell check 和生产启动(见 @celljs/core 中的 securityChecks)。

CORS(web.cors) ​

express cors 包的选项:origin(默认 '*';字符串、请求来源的 true、RegExp 或列表、无的 false)、methods、allowedHeaders、exposedHeaders、credentials、maxAge、preflightContinue、optionsSuccessStatus。

路由模式 ​

RequestMatcher.match(pattern) 接受 PathPattern::name 是一个分段,* 是任何东西(进 _),(...) 是可选,\\ 使下一个字符普通。/users/:id 在 /users/42 给 { id: '42' };无匹配给 null。