HttpClient 从不在 IIS 上发送请求
HttpClient never sends request on IIS
我遇到了一个奇怪的问题并尝试了各种方法来让它工作。
我的 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 检查)并最终超时并抛出带有子 TaskCanceledException
的 AggregateException
。
在 task.Wait
上设置断点,我注意到,由于某种原因,断点被命中 10 次,而不是 运行 时命中一次通过 IIS Express。
我尝试了各种方法来尝试让它工作,包括大量搜索 Google 和 SO,但似乎没有任何效果。
有人知道为什么会这样吗?或者可以解释我做错了什么?
想通了。必须根据请求更改 Host
header 才能正确发送。它基本上忽略了 RequestUri 并使用 Host
header 来决定实际发送请求的位置。
forwardRequest.Headers.Host = forwardRequest.RequestUri.Host;
现在工作正常,IIS 现在将适当地发送请求。仍然让我想知道为什么 IIS Express 似乎不需要 Host
header 改变!
完整代码...添加了 X-Forwarded-For
和 X-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())
};
}
}
}
}
我遇到了一个奇怪的问题并尝试了各种方法来让它工作。
我的 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 检查)并最终超时并抛出带有子 TaskCanceledException
的 AggregateException
。
在 task.Wait
上设置断点,我注意到,由于某种原因,断点被命中 10 次,而不是 运行 时命中一次通过 IIS Express。
我尝试了各种方法来尝试让它工作,包括大量搜索 Google 和 SO,但似乎没有任何效果。
有人知道为什么会这样吗?或者可以解释我做错了什么?
想通了。必须根据请求更改 Host
header 才能正确发送。它基本上忽略了 RequestUri 并使用 Host
header 来决定实际发送请求的位置。
forwardRequest.Headers.Host = forwardRequest.RequestUri.Host;
现在工作正常,IIS 现在将适当地发送请求。仍然让我想知道为什么 IIS Express 似乎不需要 Host
header 改变!
完整代码...添加了 X-Forwarded-For
和 X-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())
};
}
}
}
}