Sisk

Http 服务器处理程序

本页由英文自动翻译。 阅读原文

在 Sisk 0.16 版本中,我们引入了 HttpServerHandler 类,旨在扩展 Sisk 的整体行为并为 Sisk 提供额外的事件处理程序,例如处理 Http 请求、路由器、上下文包等。

该类集中处理整个 HTTP 服务器以及单个请求生命周期中发生的事件。Http 协议本身没有会话概念,因此无法在请求之间保留信息。Sisk 目前提供了一种方式,让你实现会话、上下文、数据库连接以及其他有用的提供程序,以帮助你的工作。

请参阅 this page 了解每个事件何时触发以及其目的。你也可以查看 lifecycle of an HTTP request 以了解请求的处理过程以及事件的触发位置。HTTP 服务器允许同时使用多个处理程序。每次事件调用都是同步的,即它会阻塞当前线程,直至与该函数关联的所有处理程序执行完毕。

与 RequestHandlers 不同,这些处理程序不能应用于某些路由组或特定路由,而是作用于整个 HTTP 服务器。你可以在 Http Server Handler 中添加条件。此外,每个 Sisk 应用程序只会为每个 HttpServerHandler 定义一个单例,因此每种 HttpServerHandler 只会有一个实例。

使用 HttpServerHandler 的一个实际例子是:在请求结束时自动释放数据库连接。

C#
// DatabaseConnectionHandler.cs

public class DatabaseConnectionHandler : HttpServerHandler
{
    protected override void OnHttpRequestClose(HttpServerExecutionResult result)
    {
        var requestBag = result.Request.Context.RequestBag;

        // 检查请求是否已定义 DbContext
        // 在其上下文包中
        if (requestBag.IsSet<DbContext>())
        {
            var db = requestBag.Get<DbContext>();
            db.Dispose();
        }
    }
}

public static class DatabaseConnectionHandlerExtensions
{
    public static DbContext GetDbContext(this HttpRequest request)
    {
        return request.Bag.GetOrAdd(() => new DbContext());
    }
}

有了上述代码,GetDbContext 扩展方法可以直接从 HttpRequest 对象创建连接上下文。未释放的连接在使用数据库时可能导致问题,因此在 OnHttpRequestClose 中将其终止。

你可以在构建器中或直接使用 HttpServer.RegisterHandler 在 Http 服务器上注册处理程序。

C#
// Program.cs

class Program
{
    static void Main(string[] args)
    {
        using var app = HttpServer.CreateBuilder()
            .UseHandler<DatabaseConnectionHandler>()
            .Build();

        app.Router.MapInstance(new UserController());
        app.Start();
    }
}

这样,UsersController 类就可以这样使用数据库上下文:

C#
// UserController.cs

[RoutePrefix("/users")]
public class UserController : ApiController
{
    [RouteGet()]
    public async Task<HttpResponse> List(HttpRequest request)
    {
        var db = request.GetDbContext();
        var users = db.Users.ToArray();

        return JsonOk(users);
    }

    [RouteGet("<id>")]
    public async Task<HttpResponse> View(HttpRequest request)
    {
        var db = request.GetDbContext();

        int userId = request.RouteParameters["id"].GetInteger();
        var user = db.Users.FirstOrDefault(u => u.Id == userId);

        return JsonOk(user);
    }

    [RoutePost]
    public async Task<HttpResponse> Create(HttpRequest request)
    {
        var db = request.GetDbContext();
        var user = await request.GetJsonContentAsync<User>();

        ArgumentNullException.ThrowIfNull(user);

        db.Users.Add(user);
        await db.SaveChangesAsync();

        return JsonMessage("User added.");
    }
}

上述代码使用了 JsonOk 和 JsonMessage 方法,这些方法内置于 ApiController,而 ApiController 继承自 RouterController:

C#
// ApiController.cs

public class ApiController : RouterModule
{
    public HttpResponse JsonOk(object value)
    {
        return new HttpResponse(200)
            .WithContent(JsonContent.Create(value, null, new JsonSerializerOptions()
            {
                PropertyNameCaseInsensitive = true
            }));
    }

    public HttpResponse JsonMessage(string message, int statusCode = 200)
    {
        return new HttpResponse(statusCode)
            .WithContent(JsonContent.Create(new
            {
                Message = message
            }));
    }
}

处理程序抛出的异常 #

自 Sisk 1.7 起,服务器处理程序抛出的异常处理方式如下:

  • 在 OnServerStarting、OnServerStarted 和 OnSetupRouter 中抛出的异常会阻止服务器启动。它们会被包装在 InvalidOperationException 中,原始异常位于 InnerException。
  • 在其他事件中抛出的异常会写入 ErrorsLogsStream,其余处理程序仍会被调用,除非已启用 ThrowExceptions。启用 ThrowExceptions 时,这些异常同样会被包装在 InvalidOperationException 中。
  • 在 OnHttpRequestClose、OnException 中抛出的异常,或在格式化访问日志和错误日志时抛出的异常,永远不会向 HTTP 引擎泄漏,即使启用了 ThrowExceptions。它们会写入 ErrorsLogsStream。在早期版本中,这类异常可能导致进程终止或连接保持打开状态。

开发者可以使用此类实现会话、上下文和数据库连接。提供的代码展示了一个使用 DatabaseConnectionHandler 的实际示例,实现了在每个请求结束时自动释放数据库连接。

集成非常简便,只需在服务器设置期间注册处理程序。HttpServerHandler 类为在 HTTP 应用中管理资源和扩展 Sisk 行为提供了强大的工具集。

输入关键词以搜索文档和 API 参考。