# 请求生命周期

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

下面通过一个 HTTP 请求的示例解释请求的完整生命周期。

- **接收请求：** 每个请求在请求本身和将要发送给客户端的响应之间创建一个 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/)。
    - 外部请求验证：对请求进行 [HttpServerConfiguration.RemoteRequestsAction](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.RemoteRequestsAction.md) 的验证。
        - 如果请求是外部的且属性为 `Drop`，则在不向客户端返回响应的情况下关闭连接，`HttpServerExecutionStatus = RemoteRequestDropped`。
    - 转发解析器配置：如果配置了 [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 匹配：在解析出主机且配置了多个 [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` 状态。
    - 路由绑定：将相应 ListeningHost 的路由器与接收到的 HTTP 服务器关联。
        - 如果路由器已经关联到另一个 HTTP 服务器，则会抛出 `InvalidOperationException`，因为路由器会主动使用服务器的配置资源，这在 HTTP 服务器初始化期间才会发生，而不会在创建 HTTP 上下文时发生。
    - 预定义头部：
        - 如果已配置，则在响应中预定义 `X-Request-Id` 头部。
        - 如果已配置，则在响应中预定义 `X-Powered-By` 头部。
    - 内容大小验证：仅当配置值大于零时，验证请求内容是否小于 [HttpServerConfiguration.MaximumContentLength](https://docs.sisk-framework.org/api/Sisk.Core.Http.HttpServerConfiguration.MaximumContentLength.md)。
        - 如果请求发送的 `Content-Length` 大于配置的值，服务器返回 413 Payload Too Large 响应给客户端，并向 HTTP 上下文返回 `HttpServerExecutionStatus = ContentTooLarge` 状态。
    - `OnHttpRequestOpen` 事件会对所有已配置的 HTTP 服务器处理程序触发。
- **路由操作：** 服务器为接收到的请求调用路由器。
    - 如果路由器未找到匹配请求的路由：
        - 如果已配置 [Router.NotFoundErrorHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.Router.NotFoundErrorHandler.md) 属性，则调用该操作，并将操作的响应转发给 HTTP 客户端。
        - 如果该属性为 null，则返回默认的 404 Not Found 响应给客户端。
    - 如果路由器找到匹配的路由，但该路由的方法与请求的方法不匹配：
        - 如果已配置 [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 HTTP 响应，`Location` 头部指向相同路径并在末尾添加 `/`，返回给客户端。
    - `OnContextBagCreated` 事件会对所有已配置的 HTTP 服务器处理程序触发。
    - 执行所有全局的带有 `BeforeResponse` 标记的 [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) 实例。
        - 如果任意处理程序返回非 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](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) 实例。
        - 如果任意处理程序返回非 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。
    - 调用路由器的操作并将其转换为 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。
    - 执行所有全局的带有 `AfterResponse` 标记的 [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) 实例。
        - 如果任意处理程序返回非 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。
    - 执行路由中定义的且带有 `AfterResponse` 标记的所有 [IRequestHandler](https://docs.sisk-framework.org/api/Sisk.Core.Routing.IRequestHandler.md) 实例。
        - 如果任意处理程序返回非 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。
- **处理响应：** 当响应准备好后，服务器会为发送给客户端做准备。
    - 根据当前 [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) 的对象。
    - `OnHttpRequestClose` 事件会对所有已配置的 HTTP 服务器处理程序触发。
    - 如果服务器抛出异常，则 `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) 等待请求，则释放互斥锁并使上下文对用户可用。
