# 请求生命周期

Source: https://docs.sisk-framework.org/zh-cn/docs/advanced/request-lifecycle.html

下面解释了请求的整个生命周期，以一个 HTTP 请求的示例为例。

- **Receiving the request:** 每个请求都会在请求本身和将要发送给客户端的响应之间创建一个 HTTP 上下文。该上下文来自 Sisk 内置的监听器，可以是 [HttpListener](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-9.0)、[Kestrel](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-9.0) 或 [Cadente](https://blog.sisk-framework.org/posts/2025-01-29-cadente-experiment/)。
    - External request validation: 对请求进行 [HttpServerConfiguration.RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) 的验证。
        - 如果请求是外部的且属性为 `Drop`，则在不向客户端返回响应的情况下关闭连接，并将 `HttpServerExecutionStatus = RemoteRequestDropped`。
    - Forwarding Resolver configuration: 如果配置了 [ForwardingResolver](https://docs.sisk-framework.org/zh-cn/docs/advanced/forwarding-resolvers.md)，它将在请求的原始主机上调用 [OnResolveRequestHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ForwardingResolver.OnResolveRequestHost.md) 方法。
    - DNS matching: 在解析出主机且配置了多个 [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md) 时，服务器会为请求查找对应的主机。
        - 如果没有匹配的 ListeningHost，返回 400 Bad Request 响应给客户端，并在 HTTP 上下文中返回 `HttpServerExecutionStatus = DnsUnknownHost` 状态。
        - 如果匹配到 ListeningHost，但其 [Router](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.Router.md) 尚未初始化，返回 503 Service Unavailable 响应给客户端，并在 HTTP 上下文中返回 `HttpServerExecutionStatus = ListeningHostNotReady` 状态。
    - Router binding: 将对应 ListeningHost 的路由器关联到接收到的 HTTP 服务器。
        - 如果路由器已经关联到另一个 HTTP 服务器（这不被允许，因为路由器会主动使用服务器的配置资源），则抛出 `InvalidOperationException`。此异常仅在 HTTP 服务器初始化期间出现，而不会在创建 HTTP 上下文时出现。
    - Pre-definition of headers:
        - 如果已配置，则在响应中预定义 `X-Request-Id` 头。
        - 如果已配置，则在响应中预定义 `X-Powered-By` 头。
    - Content size validation: 仅当请求内容长度大于零时，验证请求内容是否小于 [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md)（默认 100 MB）。
        - 如果请求发送的 `Content-Length` 超过配置的上限，返回 413 Payload Too Large 响应给客户端，并在 HTTP 上下文中返回 `HttpServerExecutionStatus = ContentTooLarge` 状态。
    - The `OnHttpRequestOpen` event is invoked for all configured HTTP server handlers.
- **Routing the action:** 服务器为接收到的请求调用路由器。
    - 如果路由器未找到匹配请求的路由：
        - 如果已配置 [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md) 属性，则调用该操作，并将操作的响应转发给 HTTP 客户端。
        - 如果该属性为 null，则返回默认的 404 Not Found 响应给客户端。
    - 如果路由器找到了匹配的路由，但路由的 HTTP 方法与请求的方法不匹配：
        - 如果已配置 [Router.MethodNotAllowedErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.MethodNotAllowedErrorHandler.md) 属性，则调用该操作，并将操作的响应转发给 HTTP 客户端。
        - 如果该属性为 null，则返回默认的 405 Method Not Allowed 响应给客户端。
    - 如果请求使用 `OPTIONS` 方法：
        - 仅当没有路由匹配请求方法（路由的方法未显式标记为 [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md)）时，路由器返回 200 Ok 响应给客户端。
    - 如果启用了 [HttpServerConfiguration.ForceTrailingSlash](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ForceTrailingSlash.md) 属性，且匹配的路由不是正则表达式，请求路径未以 `/` 结尾，并且请求方法为 `GET`：
        - 返回 307 Temporary Redirect 响应，`Location` 头指向同一路径加上结尾的 `/`（包括查询字符串），发送给客户端。
    - The `OnContextBagCreated` event is invoked for all configured HTTP server handlers.
    - 所有全局的 [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) 实例（带有 `BeforeResponse` 标记）将被执行。
        - 如果任意处理器返回非 null 响应，则该响应被转发给 HTTP 客户端，随后关闭上下文。
        - 如果此步骤抛出错误且 [HttpServerConfiguration.ThrowExceptions](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ThrowExceptions.md) 为禁用：
            - 如果已启用 [Router.CallbackErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.CallbackErrorHandler.md) 属性，则调用它并将产生的响应返回给客户端。
            - 如果未定义前述属性，则返回空响应给服务器，服务器会根据抛出的异常类型转发响应，通常为 500 Internal Server Error。
    - 所有在路由中定义且带有 `BeforeResponse` 标记的 [IRequestHandler] 实例将被执行。
        - 如果任意处理器返回非 null 响应，则该响应被转发给 HTTP 客户端，随后关闭上下文。
        - 如果此步骤抛出错误且 [HttpServerConfiguration.ThrowExceptions] 为禁用：
            - 如果已启用 [Router.CallbackErrorHandler] 属性，则调用它并将产生的响应返回给客户端。
            - 如果未定义前述属性，则返回空响应给服务器，服务器会根据抛出的异常类型转发响应，通常为 500 Internal Server Error。
    - 调用路由器的操作并将其转换为 HTTP 响应。
        - 如果此步骤抛出错误且 [HttpServerConfiguration.ThrowExceptions] 为禁用：
            - 如果已启用 [Router.CallbackErrorHandler] 属性，则调用它并将产生的响应返回给客户端。
            - 如果未定义前述属性，则返回空响应给服务器，服务器会根据抛出的异常类型转发响应，通常为 500 Internal Server Error。
    - 所有全局的 [IRequestHandler] 实例（带有 `AfterResponse` 标记）将被执行。
        - 如果任意处理器返回非 null 响应，则该处理器的响应替换之前的响应，并立即转发给 HTTP 客户端。
        - 如果此步骤抛出错误且 [HttpServerConfiguration.ThrowExceptions] 为禁用：
            - 如果已启用 [Router.CallbackErrorHandler] 属性，则调用它并将产生的响应返回给客户端。
            - 如果未定义前述属性，则返回空响应给服务器，服务器会根据抛出的异常类型转发响应，通常为 500 Internal Server Error。
    - 所有在路由中定义且带有 `AfterResponse` 标记的 [IRequestHandler] 实例将被执行。
        - 如果任意处理器返回非 null 响应，则该处理器的响应替换之前的响应，并立即转发给 HTTP 客户端。
        - 如果此步骤抛出错误且 [HttpServerConfiguration.ThrowExceptions] 为禁用：
            - 如果已启用 [Router.CallbackErrorHandler] 属性，则调用它并将产生的响应返回给客户端。
            - 如果未定义前述属性，则返回空响应给服务器，服务器会根据抛出的异常类型转发响应，通常为 500 Internal Server Error。
- **Processing the response:** 当响应准备就绪后，服务器会准备将其发送给客户端。
    - 根据当前 [ListeningHost.CrossOriginResourceSharingPolicy](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.CrossOriginResourceSharingPolicy.md) 的配置，在响应中定义跨域资源共享（CORS）头。
    - 将响应的状态码和头部发送给客户端。
    - 将响应内容发送给客户端：
        - 如果响应内容是 [ByteArrayContent](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.bytearraycontent) 的子类，则直接将响应字节复制到响应输出流。
        - 如果不满足前述条件，则将响应序列化为流并复制到响应输出流。
    - 关闭流并丢弃响应内容。
    - 如果启用了 [HttpServerConfiguration.DisposeDisposableContextValues](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues.md)，则会丢弃请求上下文中所有继承自 [IDisposable](https://learn.microsoft.com/en-us/dotnet/api/system.idisposable) 的对象。
    - The `OnHttpRequestClose` event is invoked for all configured HTTP server handlers.
    - 如果服务器上抛出了异常，则调用 `OnException` 事件，通知所有已配置的 HTTP 服务器处理器。
    - 如果路由允许访问日志且 [HttpServerConfiguration.AccessLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.AccessLogsStream.md) 不为 null，则向日志输出写入一行日志。
    - 如果路由允许错误日志、出现异常且 [HttpServerConfiguration.ErrorsLogsStream](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.ErrorsLogsStream.md) 不为 null，则向错误日志输出写入一行日志。
    - 如果服务器正通过 [HttpServer.WaitNext](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServer.WaitNext.md) 等待请求，则释放互斥锁并使上下文对用户可用。
