欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET Core 2.1 中的 HttpClientFactory (Part 3) 使用Handler实现传出请求中间件

程序员文章站 2022-08-29 23:47:47
原文:https://www.stevejgordon.co.uk/httpclientfactory-aspnetcore-outgoing-request-middleware-pipeline-delegatinghandlers 发表于:2018年4月 原文:https://www.stev ......

原文:https://www.stevejgordon.co.uk/httpclientfactory-aspnetcore-outgoing-request-middleware-pipeline-delegatinghandlers  
发表于:2018年4月

    先前的系列文章中我介绍了一些核心概念,并且展示了asp.net core 2.1中新的ihttpclientfactory的一些示例。前面两个帖子开始已经有一段时间了,我想通过讨论带有handler的“传出请求中间件”的概念来继续本系列。

delegatinghandlers

    首先我们要知道,这部分功能中涉及的许多部件已经存在了很长时间。httpclientfactory通过更加灵活和清晰的api简化了这些部件的使用。

    在发起http请求时,您可能希望通过给定的httpclient将所有请求实现cross cutting concerns(aop基于切面的设计)。诸如处理重试失败的信息,记录诊断信息或者实现一个缓存层以减少http的调用次数。

    对于熟悉asp.net core的人,您也可能熟悉中间件概念。 delegatinghandlers提供了几乎相同的概念,但相反,是在发出传出请求时。

    您可以将一组处理程序(handlers)定义为管道,在发送之前,它们(handlers)会处理传出的http请求。这些处理程序可以以编程方式修改标头,检查请求的主体或者记录有关请求的一些信息。

    httprequestmessage在它到达最终内部处理程序(final inner handler)之前依次流经每个处理程序。这个处理程序实际上将通过网络分派http请求。这个内部处理程序也将是第一个接收响应的人。此时,响应以相反的顺序通过处理程序的回切线传回。同样,每个处理程序都可以根据需要检查,修改或使用响应。例如,对于某些请求路径,您可能希望应用返回数据的缓存。

ASP.NET Core 2.1 中的 HttpClientFactory (Part 3) 使用Handler实现传出请求中间件

图中您可以看到可视化的管道

    与asp.net core中间件非常相似,处理程序也可以使流程短路来立即返回响应。这在强制执行某些规则时比较有用。例如,您可以创建一个处理程序,检查传出请求的中是否存在api密钥头(key header)。如果缺少这个,程序将不会把请求传递给下一个处理程序(避免实际的http调用),程序会生成一个失败响应返回给调用者。

    在使用ihttpclientfactory之前,您需要将处理程序实例(可以是多个)传递到httpclient实例的构造函数中。然后,httpclient将通过这些处理程序处理传出请求。

    我们可以为不同的“命名化客户端”或“类型化客户端”使用不同的处理程序配置。

创建处理程序(creating a handler

    我们先定义两个handler,为了保持代码简单,它们的功能不会特别逼真,仅为了展示关键概念。后面的例子中有一些方法可以实现类似的结果,而无需编写我们自己的处理程序。

    要创建一个处理程序,我们可以简单地创建一个继承于delegatinghandler抽象类的类。我们可以覆盖sendasync方法来添加我们自己的功能。

public class timinghandler : delegatinghandler
{
    private readonly ilogger<timinghandler> _logger;

    public timinghandler(ilogger<timinghandler> logger)
    {
        _logger = logger;
    }

    protected override async task<httpresponsemessage> sendasync(httprequestmessage request, 
        cancellationtoken cancellationtoken)
    {
        var sw = stopwatch.startnew();

        _logger.loginformation("starting request");

        var response = await base.sendasync(request, cancellationtoken);

        _logger.loginformation($"finished request in {sw.elapsedmilliseconds}ms");

        return response;
    }
}

    我们的例子展示的是传出请求。在发起请求前,stopwatch开始执行,接下来异步执行基类的sendasync方法并返回一个httpresponsemessage。

    为了让事情变得有趣,让我们创建第二个处理程序。它将检查是否存在特定的header,如果没有,则返回立即响应,通过“短路”来避免不必要的http调用。

public class validateheaderhandler : delegatinghandler
{
    protected override async task<httpresponsemessage> sendasync(httprequestmessage request,
        cancellationtoken cancellationtoken)
    {
        if (!request.headers.contains("x-api-key"))
        {
            return new httpresponsemessage(httpstatuscode.badrequest)
            {
                content = new stringcontent("you must supply an api key header called x-api-key")
            };
        }

        return await base.sendasync(request, cancellationtoken);
    }
}

注册处理程序(registering handlers

    现在我们准备好了处理程序,最后一步是使用依赖注入容器注册它们并定义一个客户端。我们在startup类中的configureservices方法做这些工作。

public void configureservices(iservicecollection services)
{
    services.addtransient<timinghandler>();
    services.addtransient<validateheaderhandler>();

    services.addhttpclient("github", c =>
    {
        c.baseaddress = new uri("https://api.github.com/");
        c.defaultrequestheaders.add("accept", "application/vnd.github.v3+json");
        c.defaultrequestheaders.add("user-agent", "httpclientfactory-sample");
    })
    .addhttpmessagehandler<timinghandler>() // this handler is on the outside and executes first on the way out and last on the way in.
    .addhttpmessagehandler<validateheaderhandler>(); // this handler is on the inside, closest to the request.
}

    前两行将处理程序分别注册到service collection,并且使用 transient,以便在每次创建新的httpclient时都会提供一个新实例。

    接下来定义一个客户端。为方便演示,我们使用命名化客户端(named client)。在这个例子中,addhttpclient方法返回一个ihttpclientbuilder。我们再调用ihttpclientbuilder的泛型扩展方法addhttpmessagehandler,此方法将处理程序的类型作为泛型参数。

    注册顺序很重要。我们首先注册最外层的处理程序(timinghandler)。该处理程序将第一个检查请求,并最后一个检查响应。在我们的例子中,我们希望timing handler能够记录整个请求流(request flow)的完整时间,包括任何内部处理程序所用时间,因此我们首先添加它。接着,我们再次调用addhttpmessagehandler,注册validateheaderhandler。它是内部httpclienthandler传递请求之前最后的自定义处理程序。

    至此,我们在“github”客户端(named client)上定义了一个传出中间件管道。当发起请求时,首先经过timinghandler,然后是validateheaderhandler。如果请求中包含指定的header,则它将被允发送到请求中的uri。当响应返回时,它将首先经过validateheaderhandler,然后传递给timinghandler记录用时,最后返回调用的代码。

总结

    虽然我已经展示了创建delegatinghandler并将其添加到httpclient是多么容易,但在大多数情况下,团队并未意识到可以这样做。一些诸如日志记录的常见需求,已经在ihttpclientfactory中考虑到了(将在后面文章中介绍)。对于更复杂的需求,例如失败后重试,更好的选择是使用名叫polly的第三方库。微软团队已决定与polly实现整合。