6.16HttpContext 转发进阶

框架为 HttpContext 提供了多种扩展方法,旨在满足各种场景下的 HTTP 请求转发需求。

HttpContext 转发扩展方法#

框架为 HttpContext 提供了多种扩展方法,旨在满足各种场景下的 HTTP 请求转发需求。

cs
// 返回 HttpResponseMessage 对象httpContext.Forward(requestUri, configure, completionOption, forwardOptions);   // 自动获取 HttpContext.Request.Method 方法进行设置httpContext.Forward(httpMethod, requestUri, configure, completionOption, forwardOptions);await httpContext.ForwardAsync(requestUri, configure, completionOption, forwardOptions);    // 自动获取 HttpContext.Request.Method 方法进行设置await httpContext.ForwardAsync(httpMethod, requestUri, configure, completionOption, forwardOptions);// 返回 HttpRemoteResult<T>httpContext.Forward<T>(requestUri, configure, completionOption, forwardOptions);    // 自动获取 HttpContext.Request.Method 方法进行设置httpContext.Forward<T>(httpMethod, requestUri, configure, completionOption, forwardOptions);await httpContext.ForwardAsync<T>(requestUri, configure, completionOption, forwardOptions); // 自动获取 HttpContext.Request.Method 方法进行设置await httpContext.ForwardAsync<T>(httpMethod, requestUri, configure, completionOption, forwardOptions);// 返回目标 T 类型httpContext.ForwardAs<T>(requestUri, configure, completionOption, forwardOptions);  // 自动获取 HttpContext.Request.Method 方法进行设置httpContext.ForwardAs<T>(httpMethod, requestUri, configure, completionOption, forwardOptions);await httpContext.ForwardAsAsync<T>(requestUri, configure, completionOption, forwardOptions);   // 自动获取 HttpContext.Request.Method 方法进行设置await httpContext.ForwardAsAsync<T>(httpMethod, requestUri, configure, completionOption, forwardOptions);// 返回字符串类型httpContext.ForwardAsString(requestUri, configure, completionOption, forwardOptions);   // 自动获取 HttpContext.Request.Method 方法进行设置httpContext.ForwardAsString(httpMethod, requestUri, configure, completionOption, forwardOptions);await httpContext.ForwardAsStringAsync(requestUri, configure, completionOption, forwardOptions);    // 自动获取 HttpContext.Request.Method 方法进行设置await httpContext.ForwardAsStringAsync(httpMethod, requestUri, configure, completionOption, forwardOptions);// 返回字节数组类型httpContext.ForwardAsByteArray(requestUri, configure, completionOption, forwardOptions);   // 自动获取 HttpContext.Request.Method 方法进行设置httpContext.ForwardAsByteArray(httpMethod, requestUri, configure, completionOption, forwardOptions);await httpContext.ForwardAsByteArrayAsync(requestUri, configure, completionOption, forwardOptions);    // 自动获取 HttpContext.Request.Method 方法进行设置await httpContext.ForwardAsByteArrayAsync(httpMethod, requestUri, configure, completionOption, forwardOptions);// 返回 Stream 类型httpContext.ForwardAsStream(requestUri, configure, completionOption, forwardOptions);   // 自动获取 HttpContext.Request.Method 方法进行设置httpContext.ForwardAsStream(httpMethod, requestUri, configure, completionOption, forwardOptions);await httpContext.ForwardAsStreamAsync(requestUri, configure, completionOption, forwardOptions);    // 自动获取 HttpContext.Request.Method 方法进行设置await httpContext.ForwardAsStreamAsync(httpMethod, requestUri, configure, completionOption, forwardOptions);// 返回 IActionResult 类型httpContext.ForwardAsResult(requestUri, configure, completionOption, forwardOptions);   // 自动获取 HttpContext.Request.Method 方法进行设置httpContext.ForwardAsResult(httpMethod, requestUri, configure, completionOption, forwardOptions);await httpContext.ForwardAsResultAsync(requestUri, configure, completionOption, forwardOptions);    // 自动获取 HttpContext.Request.Method 方法进行设置await httpContext.ForwardAsResultAsync(httpMethod, requestUri, configure, completionOption, forwardOptions);// 返回 object 类型httpContext.ForwardAs(resultType, requestUri, configure, completionOption, forwardOptions);  // 自动获取 HttpContext.Request.Method 方法进行设置httpContext.ForwardAs(resultType, httpMethod, requestUri, configure, completionOption, forwardOptions);await httpContext.ForwardAsAsync(resultType, requestUri, configure, completionOption, forwardOptions);   // 自动获取 HttpContext.Request.Method 方法进行设置await httpContext.ForwardAsAsync(resultType, httpMethod, requestUri, configure, completionOption, forwardOptions);

HttpContext 转发的优势#

在对接第三方 API 接口时,通常的做法是创建一个入口程序,并在其中调用 HTTP 远程请求服务来发送请求到指定的第三方接口。假设第三方接口的控制器定义如下:

cs
[ApiController][Route("[controller]/[action]")]public class VendorController : ControllerBase{    [HttpPost]    public VendorModel Add(VendorModel model)    {        return model;    }}

传统的做法是使用 HTTP 远程请求服务来发送请求,例如:

cs
[ApiController][Route("[controller]/[action]")]public class YourController(IHttpRemoteService httpRemoteService) : ControllerBase{    [HttpPost]    public async Task<VendorModel> AddVendorAsync()    {        return await httpRemoteService.SendAsync<VendorModel>(            HttpRequestBuilder.Post("https://www.furion.net/vendor/add")                .SetJsonContent(new VendorModel())        );    }}

然而,使用 HttpContext 转发功能后,我们可以简化代码,只需创建与第三方接口一致的接口控制器声明,例如 VendorControllerAdd 方法,如下所示:

cs
[ApiController][Route("[controller]/[action]")]public class YourController(IHttpContextAccessor httpContextAccessor) : ControllerBase{    [HttpPost]    public async Task<VendorModel> AddAsync(VendorModel model)  // 同步和异步都行    {        // 自动转发 model,无需任何设置        return await httpContextAccessor.Context            .ForwardAsync<VendorModel>("https://www.furion.net/vendor/add");    }}

通过这种方式,代码变得更加简洁明了,无需手动构建和发送 HTTP 请求,而是利用 HttpContext 转发功能直接调用第三方接口。

HttpContext 转发在微服务中的应用#

在微服务架构中,HttpContext 转发功能展现出了极大的价值。微服务间的通信通常依赖于 HTTPgRPC,其中 HTTP 由于其出色的兼容性而被广泛应用。采用 HttpContext 转发不仅能够简化 HTTP 请求的代码量,还能使代码结构更加清晰,便于维护。对于大型项目或团队协作项目,这一优势尤为明显。

借助 HttpContext 转发功能,我们可以实现动态的请求分发。通过应用某种权重算法,系统能够自动将请求转发至不同的服务器,从而实现负载均衡和故障转移。例如:

cs
[ApiController][Route("[controller]/[action]")]public class YourController(IHttpContextAccessor httpContextAccessor) : ControllerBase{    [HttpPost]    public async Task<VendorModel> AddAsync(VendorModel model)    {        string targetUrl = "https://furion.net/"; // 默认服务器地址        // 根据某种算法(如负载均衡策略)选择目标服务器地址        // 在微服务架构中,这通常由服务注册与发现机制来确定        // 下面的代码仅为模拟示例        if (条件1)        {            targetUrl = "https://s1.furion.net/"; // s1 服务器地址        }        else if (条件2)        {            targetUrl = "https://s2.furion.net/"; // s2 服务器地址        }        else if (条件3)        {            targetUrl = "https://s3.furion.net/"; // s3 服务器地址        }        return await _httpContextAccessor.ForwardAsync<VendorModel>(targetUrl);    }}

此外,HttpContext 转发功能还使得构建网关中心成为可能。所有外部请求都可以先发送到网关中心,由网关进行鉴权、限流等处理后,再转发至目标服务。这极大地提升了系统的安全性和可管理性。总之,HttpContext 转发是微服务架构中一个不可或缺的组件,它为实现高效、灵活的微服务通信提供了有力支持。

转发忽略标头说明#

在使用 HttpContext 转发功能时,系统会自动忽略以下请求与响应标头,以确保转发的有效性和准确性:

  • 请求标头中将被忽略的

    • X-Forward-To
    • Host
    • Content-Length
  • 响应标头中将被忽略的

    • Content-Type
    • Transfer-Encoding
    • Keep-Alive
    • Upgrade
    • Proxy-Connection

若需添加更多忽略请求或响应标头的设置,可通过 HttpContextForwardOptions 进行配置。以下是两种配置方式:

  • 全局设置

在项目的 Startup.csProgram.cs 文件中,注册并配置该服务:

cs
services.Configure<HttpContextForwardOptions>(options =>{    // 忽略在转发时需要跳过的请求标头列表    options.IgnoreRequestHeaders = ["Framework"];    // 忽略在转发时需要跳过的响应标头列表    options.IgnoreResponseHeaders = ["Content-Length"];});
  • 单次转发设置

在单次转发时,可传递 HttpContextForwardOptions 进行配置:

cs
httpContext.ForwardAsResult("https://furion.net", forwardOptions: new HttpContextForwardOptions{    // 忽略在转发时需要跳过的请求标头列表    options.IgnoreRequestHeaders = ["Framework"];    // 忽略在转发时需要跳过的响应标头列表    options.IgnoreResponseHeaders = ["Content-Length"];});

ForwardAttribute 转发特性#

为了简化转发操作,框架提供了便捷的 [Forward] 控制器操作转发特性。相较于手动调用 HttpContextForward 扩展方法,该特性显著减少了重复的硬编码工作。以下是使用 [Forward] 特性的示例代码:

cs
[ApiController][Route("[controller]/[action]")]public class GetStartController : ControllerBase{    /// <summary>    ///     转发代理到网站    /// </summary>    /// <returns></returns>    [HttpGet]    [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] // 禁用浏览器缓存    [Forward("https://github.com", AllowedHosts = ["*"])]    public Task<IActionResult?> ForwardToWebSite()    {        throw new NotImplementedException();    }    /// <summary>    ///     转发代理到图片    /// </summary>    /// <returns></returns>    [HttpGet]    [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] // 禁用浏览器缓存    [Forward("https://img-s-msn-com.akamaized.net/tenant/amp/entityid/AA1u7RJI.img?w=584&h=326&m=6", AllowedHosts = ["*"])]    public Task<IActionResult?> ForwardToImage()    {        throw new NotImplementedException();    }    /// <summary>    ///     转发代理到文件    /// </summary>    /// <returns></returns>    [HttpGet]    [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] // 禁用浏览器缓存    [Forward("https://download.visualstudio.microsoft.com/download/pr/a17b907f-8457-45a8-90db-53f2665ee49e/49bccd33593ebceb2847674fe5fd768e/aspnetcore-runtime-8.0.10-win-x64.exe", AllowedHosts = ["*"])]    public Task<IActionResult?> ForwardToDownload()    {        throw new NotImplementedException();    }    /// <summary>    ///     转发代理到表单    /// </summary>    /// <param name="id"></param>    /// <param name="model"></param>    /// <returns></returns>    [HttpPost]    [Forward("https://localhost:7044/HttpRemote/AddForm", AllowedHosts = ["*"])]    public Task<YourRemoteFormResult?> ForwardToForm(int id, [FromForm] YourRemoteFormModel model)    {        throw new NotImplementedException();    }    /// <summary>    ///     转发代理到字符串    /// </summary>    /// <returns></returns>    [HttpGet]    [Forward("https://localhost:7044/GetStart/PostRawString", AllowedHosts = ["*"])]    public Task<string> ForwardToString()    {        throw new NotImplementedException();    }    /// <summary>    ///     转发代理到无返回值    /// </summary>    /// <returns></returns>    [HttpGet]    [Forward("https://localhost:7044/GetStart/PostRawString", AllowedHosts = ["*"])]    public Task ForwardToVoid()    {        throw new NotImplementedException();    }}

在上述代码中,我们只需为需要转发的控制器操作添加 [Forward] 特性,并指定目标 URL。框架会自动处理转发逻辑,因此方法体内无需编写任何实现代码(通常抛出 NotImplementedException 以表明这是一个由框架自动处理的转发操作)。这种方式在微服务应用中尤为便捷,极大地简化了代码编写和维护工作。

ForwardAttribute 包含以下属性:

  • 属性
    • RequestUri:转发地址(string 类型)。
    • Method:转发方式,若未设置,则自动采用当前请求方式作为转发方式(HttpMethod 类型)。
    • HttpClientNameHttpClient 实例的配置名称,默认值为 nullstring 类型)。
    • CompletionOption:指示响应内容操作方式,默认值为 ResponseHeadersReadHttpCompletionOption 类型)。
    • AllowedHosts:允许转发的目标主机白名单(string[]? 类型)。
      用于防范 服务端请求伪造(SSRF 攻击。仅当目标地址的主机(含端口、协议)与列表中的某一项匹配时,转发才会被允许。
      支持的格式(匹配均忽略大小写):
      • "furion.net" – 仅主机名,匹配任意协议(http/https)的默认端口(80/443)。
      • "furion.net:8080" – 主机+端口,匹配任意协议的指定端口。
      • "furion.net:*" – 主机+端口通配符,匹配任意协议下的任意端口。
      • "https://furion.net" – 协议+主机,只匹配指定协议的默认端口。
      • "http://furion.net:8080" – 协议+主机+端口,精确匹配。
      • "https://furion.net:*" – 协议+主机+端口通配符,只匹配指定协议的任意端口。
      • "*" – 全局通配符,允许任意主机和协议(完全绕过验证,高风险)。
        若未配置或为空,所有通过 X-Forward-To 请求头指定的目标地址都将被拒绝,以防止未经授权的转发。建议尽可能使用精确规则,仅对完全可信的来源开放通配符。
    • WithQueryParameters:是否转发查询参数(URL 参数),默认值为 truebool 类型)。
    • WithRequestHeaders:是否转发请求标头,默认值为 truebool 类型)。
    • WithResponseStatusCode:是否转发响应状态码,默认值为 truebool 类型)。
    • WithResponseHeaders:是否转发响应标头,默认值为 truebool 类型)。
    • WithResponseContentHeaders:是否转发响应内容标头,默认值为 truebool 类型)。
    • ResetHostRequestHeader:是否重新设置 Host 请求标头,默认值为 falsebool 类型)。
    • IgnoreQueryParameters:忽略在转发时需要跳过的查询参数(URL 参数)列表(string[]? 类型)。
    • IgnoreRequestHeaders:忽略在转发时需要跳过的请求标头列表(string[]? 类型)。
    • IgnoreResponseHeaders:忽略在转发时需要跳过的响应标头列表(string[]? 类型)。