【问题标题】:HttpClient never sends request on IISHttpClient 从不在 IIS 上发送请求
【发布时间】:2015-10-08 11:15:32
【问题描述】:

我有一个奇怪的问题,并尝试了各种方法来解决这个问题。

我的 Web API 项目中有一个反向代理委托处理程序,该处理程序用于拦截对内部资源、文件等的请求,从我们的外部站点到我们 DMZ 内的内部站点...

using System;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;

namespace Resources.API
{
    public class ProxyHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var routes = new[]{
                "/api/videos",
                "/api/documents"
            };

            // check whether we need to proxy this request
            var passThrough = !routes.Any(route => request.RequestUri.LocalPath.StartsWith(route));
            if (passThrough)
                return await base.SendAsync(request, cancellationToken);

            // got a hit forward the request to the proxy Web API
            return await ForwardRequest(request, cancellationToken);
        }

        private static async Task<HttpResponseMessage> ForwardRequest(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            //Clone the request and forward to the internal proxy site
            var proxyUrl = ConfigurationManager.AppSettings["ProxyUrl"];
            var baseUri = new UriBuilder(proxyUrl);

            //clone the requestUri and point it at the proxy site
            var forwardedUri = new UriBuilder(request.RequestUri)
            {
                Scheme = baseUri.Scheme,
                Host = baseUri.Host,
                Port = baseUri.Port
            };

            var forwardRequest = new HttpRequestMessage(request.Method, forwardedUri.Uri);

            if (request.Method == HttpMethod.Post || request.Method == HttpMethod.Put)
            {
                var stream = new MemoryStream();
                await request.Content.CopyToAsync(stream);
                stream.Seek(0, SeekOrigin.Begin);
                forwardRequest.Content = new StreamContent(stream);

                //copy the content headers
                foreach (var header in request.Content.Headers)
                {
                    forwardRequest.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
                }
            };

            forwardRequest.Version = request.Version;

            foreach (var prop in request.Properties)
            {
                forwardRequest.Properties.Add(prop);
            }

            foreach (var header in request.Headers)
            {
                forwardRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }

            var client = new HttpClient(new HttpClientHandler(), disposeHandler: false);
            var task = await Task.Factory
               .StartNew(async () => await client.SendAsync(forwardRequest, HttpCompletionOption.ResponseHeadersRead,
                   cancellationToken),
                   CancellationToken.None,
                   TaskCreationOptions.LongRunning,
                   TaskScheduler.Default);
            try
            {
                task.Wait(cancellationToken);
            }
            catch (Exception e)
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    Content =
                        new ObjectContent<HttpError>(new HttpError(e, includeErrorDetail: true),
                            new JsonMediaTypeFormatter())
                };
            }

            return task.Result;
        }
    }
}

编辑:还尝试等待并返回任务...

            try
            {
                return await task;
            }

这适用于 IIS Express 8.0,但不适用于 Windows 7 Professional(我的开发机器)上的 IIS 7.5 或 Windows Server 2012 上的 IIS 8.0。

创建的 HttpClient 从未真正通过网络发送请求(由 Fiddler 检查)并最终超时并抛出带有子 TaskCanceledExceptionAggregateException

task.Wait 上设置断点,我注意到,由于某种原因,断点被命中 10 次,而不是在通过 IIS Express 运行时被命中一次。

我已经尝试了各种方法来尝试使其正常工作,包括大量搜索 Google 和 SO,但似乎没有任何工作。

有人知道为什么会这样吗?或者可以解释我做错了什么?

【问题讨论】:

  • 你为什么要启动一个Task来调用已经异步的SendAsync方法?如果您不想检查回复,您可以简单地写var response=client.SendAsync(...);return response; 甚至return client.SendAsync(...)。您还可以将所有return await xyz() 行更改为return xyz();。如果父方法返回另一个方法创建的Task,则无需等待它完成
  • @PanagiotisKanavos 我想我最初尝试过,但还是再次尝试了。 return await client.SendAsync(...) 适用于 IIS Express 8.0 但不适用于 IIS 7.5 - 仍然不发送请求并最终超时。我缺少的两个环境之间有什么根本不同吗???
  • 不,除了 Express 是仅限开发人员的版本。与其寻找隐蔽的错误,不如尝试清理代码然后调试它。另一种可能性是您正在达到每个域限制的两个并发请求。或者您可能会将调用重定向到服务器本身,本质上是创建无限递归。添加日志记录或使用 Parallel Stacks 调试窗口查看断点第 N 次命中时的什么
  • @PanagiotisKanavos 在您提到它之前从未听说过并行堆栈调试窗口。甜的!确实设法弄清楚发生了什么。
  • 可能有人不知道 HOST 标头,或者不知道如何使用它们在端口 80 上为多个站点提供服务。无论如何,回答的赞成票比问题的反对票更重要

标签: c# asp.net iis asp.net-web-api reverse-proxy


【解决方案1】:

想通了。必须更改请求上的 Host 标头才能正确发送。它本质上忽略了 RequestUri 并使用 Host 标头来决定实际发送请求的位置。

forwardRequest.Headers.Host = forwardRequest.RequestUri.Host;

现在就像一个魅力,IIS 现在将适当地发送请求。仍然让我想知道为什么 IIS Express 似乎不需要更改 Host 标头!

完整的代码...添加了 X-Forwarded-ForX-Forwarded-Host 以及更好的衡量标准。

using System;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

namespace Resources.API
{
    public class ProxyHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var routes = new[]{
                "/api/videos",
                "/api/documents"
            };

            // check whether we need to proxy this request
            var passThrough = !routes.Any(route => request.RequestUri.LocalPath.StartsWith(route));
            if (passThrough)
                return await base.SendAsync(request, cancellationToken);

            // got a hit forward the request to the proxy Web API
            //return GetResponseFromProxy(request);

            //Nicer method using HttpClient - but it doesn't work on IIS!
            return await ForwardRequest(request, cancellationToken);
        }

        private static async Task<HttpResponseMessage> ForwardRequest(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            //Clone the request and forward to the internal proxy site
            var proxyUrl = ConfigurationManager.AppSettings["ProxyUrl"];
            var baseUri = new UriBuilder(proxyUrl);

            //clone the requestUri and point it at the proxy site
            var forwardedUri = new UriBuilder(request.RequestUri)
            {
                Scheme = baseUri.Scheme,
                Host = baseUri.Host,
                Port = baseUri.Port
            };

            var forwardRequest = new HttpRequestMessage(request.Method, forwardedUri.Uri);

            if (request.Method == HttpMethod.Post || request.Method == HttpMethod.Put)
            {
                var stream = new MemoryStream();
                await request.Content.CopyToAsync(stream);
                stream.Seek(0, SeekOrigin.Begin);
                forwardRequest.Content = new StreamContent(stream);

                //copy the content headers
                foreach (var header in request.Content.Headers)
                {
                    forwardRequest.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
                }
            };

            forwardRequest.Version = request.Version;

            foreach (var prop in request.Properties)
            {
                forwardRequest.Properties.Add(prop);
            }

            foreach (var header in request.Headers)
            {
                forwardRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }

            // Don't forget to change the Host header to refer to the proxy
            forwardRequest.Headers.Host = forwardRequest.RequestUri.Host;

            //Add the relevant X-Forwarded headers
            var xForwardedHost = request.Headers.Host;
            forwardRequest.Headers.Add("X-Forwarded-Host", xForwardedHost);

            var xForwardedFor = HttpContext.Current.Request.UserHostAddress;
            forwardRequest.Headers.Add("X-Forwarded-For", xForwardedFor);

            var client = new HttpClient(new HttpClientHandler(), disposeHandler: false);

            try
            {
                return await client.SendAsync(forwardRequest, HttpCompletionOption.ResponseHeadersRead,
                    cancellationToken);
            }
            catch (Exception e)
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError)
                {
                    Content =
                        new ObjectContent<HttpError>(new HttpError(e, includeErrorDetail: true),
                            new JsonMediaTypeFormatter())
                };
            }
        }
    }
}

【讨论】:

  • 这是因为 HOST 标头优先于 URL。 HOST 标头用于通过同一端口为多个站点提供服务。在 DNS 中配置不同的名称以指向相同的 IP。 Web 服务器(包括 IIS)通过查看 HOST 标头来确定要访问的正确站点。例如,托管商就是这样使用同一台机器在端口 80 上托管多个站点
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多