如何实现Http请求报头的自动转发[应用篇]

如下所示的是作为下游应用的WebApp2的定义。如代码片段所示,为了验证指定的跟踪报头是否在WebApp1中被我们的组件成功转发,我们将接收到的所有请求报头拼接成一个字符串作为响应内容。

public class Program

{

    public static void Main()

    {

        Host.CreateDefaultBuilder()

            .ConfigureWebHostDefaults(web => web.Configure(app=>app.Run(Process)))

            .Build()

            .Run();

        static Task Process(HttpContext httpContext)

        {

            var headerString = string.Join(";", httpContext.Request.Headers.Select(it => $"{it.Key}={it.Value}"));

            return httpContext.Response.WriteAsync(headerString);

        }

    }

}

WebApp1的所有代码定义如下。HeaderForwarder组件通过调用IHostBuilder的扩展方法UseHeaderForwarder进行注册,在调用该方法的时候我们指定了需要转发的请求报头名称(foo和bar)。在接收到请求之后,WebApp1会利用HttpClient调用WebApp2,并将得到结果作为相应的内容。

public class Program

{

    public static void Main()

    {

        Host.CreateDefaultBuilder()

            .UseHeaderForwarder(forwarder=>forwarder.AddHeaderNames("foo", "bar"))

            .ConfigureWebHostDefaults(web => web

                .ConfigureServices(svcs=>svcs.AddHttpClient())

                .Configure(app => app.Run(Process)))

            .Build()

            .Run();

        static async Task Process(HttpContext httpContext)

        {

            var httpClient = httpContext.RequestServices.GetRequiredService<IHttpClientFactory>().CreateClient();

            var headerString = await httpClient.GetStringAsync("http://localhost:6000");

            await httpContext.Response.WriteAsync(headerString);

        }

    }

}

作为上游应用的App具有如下所示的定义。它直接利用HttpClient向WebApp1发送了一个请求,该请求携带了foo和bar这两个需要WebApp1转发的报头。如果WebApp1完成了针对这两个请求报头的转发,那么得到的响应内容将包含这两个报头的值,我们将这一验证逻辑体现在两个调试断言中。

class Program

{

    static async Task Main(string[] args)

    {

        var httpClient = new HttpClient();

        var request = new HttpRequestMessage

        {

            RequestUri = new Uri("http://localhost:5000"),

            Method = HttpMethod.Get

        };

        request.Headers.Add("foo", "123");

        request.Headers.Add("bar", "456");

        var response = await httpClient.SendAsync(request);

        var headers = (await response.Content.ReadAsStringAsync()).Split(";");

        Debug.Assert(headers.Contains("foo=123"));

        Debug.Assert(headers.Contains("bar=456"));

    }

}

二、添加任意需要转发的请求报头

上面我们演示了HeaderForwarder组件自动提取指定的报头并自动转发的功能,实际上该组件还可以帮助我们将任意的报头添加到由HttpClient发出的请求消息中。假设WebApp1除了自动转发的foo和bar报头之外,还需要额外添加一个baz报头,我们可以对程序作如下的修改。

public class Program

{

    public static void Main()

    {

        Host.CreateDefaultBuilder()

            .UseHeaderForwarder(forwarder => forwarder.AddHeaderNames("foo", "bar"))

            .ConfigureWebHostDefaults(web => web

                .ConfigureServices(svcs => svcs.AddHttpClient())

                .Configure(app => app.Run(Process)))

            .Build()

            .Run();

        static async Task Process(HttpContext httpContext)

        {

            using (new HttpInvocationContextScope())

            {

                HttpInvocationContext.Current.AddOutgoingHeader("baz", "789");

                var httpClient = httpContext.RequestServices.GetRequiredService<IHttpClientFactory>().CreateClient();

                var headerString = await httpClient.GetStringAsync("http://localhost:6000");

                await httpContext.Response.WriteAsync(headerString);

            }

        }

    }

}

如上面的代码片段所示,我们将针对HttpClient的调用置于HttpInvocationContextScope对象构建的上下文范围中。在调用HttpClient发送请求之前,我们通过Current静态属性得到当前的HttpInvocationContext上下文,并通过调用其AddOutgoingHeader方法设置待转发的baz报头。为了验证WebApp1针对baz报头的转发,我们将App的程序进行如下的改写。

class Program

{

    static async Task Main(string[] args)

    {

        var httpClient = new HttpClient();

        var request = new HttpRequestMessage

        {

            RequestUri = new Uri("http://localhost:5000"),

            Method = HttpMethod.Get

        };

        request.Headers.Add("foo", "123");

        request.Headers.Add("bar", "456");

        var response = await httpClient.SendAsync(request);

        var headers = (await response.Content.ReadAsStringAsync()).Split(";");

        Debug.Assert(headers.Contains("foo=123"));

        Debug.Assert(headers.Contains("bar=456"));

        Debug.Assert(headers.Contains("baz=789"));

    }

}

如果涉及到多个HTTP调用都需要对相同的报头进行转发,上面介绍的这种基于HttpInvocationContextScope/HttpInvocationContext的编程模式会变得很方便。

using (new HttpInvocationContextScope())

{

      HttpInvocationContext.Current

          .AddOutgoingHeader("foo", "123")

          .AddOutgoingHeader("bar", "456")

          .AddOutgoingHeader("baz", "789");

      var result1 = await httpClient.GetStringAsync("http://www.foo.com/");

      var result2 = await httpClient.GetStringAsync("http://www.bar.com/");

      var result3 = await httpClient.GetStringAsync("http://www.baz.com/");

}

三、在非ASP.NET Core应用中使用

在ASP.NET Core应用中,HeaderForwarder是通过调用IHostBuilder的扩展方法UseHeaderForwarder进行注册的,如果在控制台应用又该如何使用。其实很简单,HeaderForwarder针对请求(通过HttpClient发送)报头的添加是通过该注册提供的一个HttpClientObserver对象提供的,它实现了IObserver<DiagnosticListener>接口,我们只需要对该对象进行注册就可以了。

class Program

{

    static async Task Main()

    {

        var httpClientObserver = new ServiceCollection()

            .AddHeaderForwarder()

            .BuildServiceProvider()

            .GetRequiredService<HttpClientObserver>();

        DiagnosticListener.AllListeners.Subscribe(httpClientObserver);

        using (new HttpInvocationContextScope())

        {

            HttpInvocationContext.Current

                    .AddOutgoingHeader("foo", "123")

                    .AddOutgoingHeader("bar", "456");

            var headers = (await new HttpClient().GetStringAsync("http://locahost:5000")).Split(";");

            Debug.Assert(headers.Contains("foo=123"));

            Debug.Assert(headers.Contains("bar=456"));

            Debug.Assert(headers.Contains("baz=789"));

        }

    }

}

龙华大道1号 http://www.kinghill.cn/Dynamics/2106.html

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容