简体   繁体   English

C#IHttpHandler和将httpclient用于Web服务

[英]C# IHttpHandler and the use of httpclient for a web service

I have been tasked with maintaining an C# web service that pretty much boils down to the following code that is used in conjunction with IIS 8: 我的任务是维护一个C#Web服务,该服务大致可以归结为与IIS 8结合使用的以下代码:

public class Handler1 : IHttpHandler
{
    public void ProcessRequest(HttpContext context) 
    {
    // here we process the request and return it after fetching some data
    }
}

Now I need to create another web service, which in turn will request another web service for fetching data. 现在,我需要创建另一个Web服务,该Web服务又将请求另一个Web服务来获取数据。 What I've gathered is that the HttpClient() in C# is the new shiny, however finding out how to implement it in my case is not so easy (or if that would be a good solution). 我所收集的是C#中的HttpClient()是新的亮点,但是要找到在我的情况下如何实现它的方法并不那么容易(或者如果这是一个好的解决方案)。

My solution would something like 我的解决方案是这样的

public class Handler1 : IHttpHandler
{
    public void ProcessRequest(HttpContext context) 
    {
    // make sure that the incoming request is valid
    HttpClient client = new HttpClient();
    // do stuff with ^client and request the other service
    [..]
    // return the data from the response from ^client to the first request
    }
}

Am I on the right track or would this be a catastrophe? 我是在正确的轨道上还是这将是一场灾难? I would gladly take tips or pointers to relevant documentation. 我很乐意接受有关文档的提示或指示。

Better solution example: 更好的解决方案示例:

public class YourApp
{
    private static HttpClient Client = new HttpClient();
    public static void Main(string[] args)
    {
        Console.WriteLine("Requests about to start!");
        for(int i = 0; i < 5; i++)
        {
            var result = Client.GetAsync("http://www.stackoverflow.com").Result;
            Console.WriteLine(result);
        }
        Console.WriteLine("Requests are finished!");
        Console.ReadLine();
    }
}

There are two issues with your approach. 您的方法有两个问题。

First, as others have said, HttpClient must not be created for each request, as this would result in your server creating one new TCP connection for each request and not dispose it from your connection pool until they reach their MaxIdleTimeout. 首先,就像其他人所说的那样,一定不能为每个请求创建HttpClient,因为这将导致您的服务器为每个请求创建一个新的TCP连接, 并且直到它们达到其MaxIdleTimeout时才将其从连接池中释放。

This is because each instance of HttpClient (or rather, each instance of HttpClientHandler) creates a unique ConnectionGroup within the ConnectionPool, so two HttpClient (created in such a way) will not share the same connections. 这是因为每个HttpClient实例(或更确切地说,每个HttpClientHandler实例)在ConnectionPool中创建一个唯一的ConnectionGroup,所以两个HttpClient(以这种方式创建)将不会共享相同的连接。

You can quite easily exhaust your ports (because connections are kept alive for quite some time), and even without that, you'll have all the performance issues you might find when deactivating keepalive (typically when connecting on a HTTPs server). 您可以很容易地耗尽端口(因为连接保持活动了一段时间),即使没有此端口,在停用keepalive(通常在HTTPs服务器上进行连接)时,也会遇到所有性能问题。

As for a solution, you might add a private readonly HttpClient field in your handler that you would initialize as you've done in your code (I believe HttpHandlers are instanciated once by IIS, not per-request, as long as it says it's reusable). 对于解决方案,您可以在处理程序中添加一个私有的只读HttpClient字段,该字段将按照在代码中完成的操作进行初始化(我相信HttpHandlers是由IIS实例化的,而不是按请求实例化的,只要它表明它是可重用的即可)。 (Edit: note that HttpClient instances are thread-safe). (编辑:请注意,HttpClient实例是线程安全的)。

The other problem you'll find is that IHttpHandler are synchronous by design. 您会发现的另一个问题是IHttpHandler在设计上是同步的。 This means you'll have to return task.Result in your code. 这意味着您必须返回task.Result在您的代码中。 This will block the current thread (let's call it A), and all subsequent processing of the outbound request (sending the body of the request, reading the header of the response and reading the body of the response) will spawn use a thread from the thread pool to process this (let's call them B, C, and D, though they can all be the same thread since no step overlap in time). 这将阻塞当前线程(我们称其为A),出站请求的所有后续处理(发送请求的主体,读取响应的标头和读取响应的主体)将使用来自线程池来处理此操作(我们将它们称为B,C和D,尽管它们都可以是同一线程,因为时间上没有步骤重叠)。

But in Asp.net, you cannot safely do that. 但是在Asp.net中,您不能安全地做到这一点。 When your main thread A is processing an Asp.net inbound request, it will, simply put, lock the current context. 当您的主线程A处理Asp.net入站请求时,简单地说,它将锁定当前上下文。 And when threads B, C and D will be spawn to process the outbound request, they too, will try to acquire and lock the current context. 当生成线程B,C和D来处理出站请求时,它们也将尝试获取并锁定当前上下文。 But because your main thread A is blocked, the context has not been released. 但是由于您的主线程A被阻塞,因此上下文尚未释放。 This will effectively result in deadlocks (A locks the context and wait for B/C/D, B/C/D wait for the context). 这将有效导致死锁(A锁定上下文并等待B / C / D,B / C / D等待上下文)。

To work around this, I would advise you to use instead a IHttpAsyncHandler . 要解决此问题,我建议您改用IHttpAsyncHandler Async process are not made to be used synchronous, and HttpClient can only be used in an async way. 异步过程不能被同步使用,并且HttpClient只能以异步方式使用。

However, this might prove much more challenging for a first usage scenario. 但是,这对于首次使用场景可能会更具挑战性。 It would be much more easier to use a HttpClient from the context of a WebApi server. 从WebApi服务器的上下文中使用HttpClient会容易得多。 However, without any knowledge as to why you have to use a HttpHandler, I do not know if this would be in the realm of acceptable answers. 但是,在不知道为什么必须使用HttpHandler的情况下,我不知道这是否在可接受的范围之内。

There are other way to run asynchronous tasks and wait synchronously for them (such as changing temporary the CurrentContext), but they're all very dangerous. 还有其他方法可以运行异步任务并同步等待它们(例如临时更改CurrentContext),但是它们都非常危险。

As a side note, HttpClient is the new shiny thing, and works quite well with other new shiny things such as "all-the-way async infrastructure". 附带说明一下,HttpClient是新的亮点,并且可以与其他新亮点(例如“全程异步基础结构”)一起很好地工作。 But trying to use it HttpClient in an older infrastructure is not as easy. 但是,尝试在较旧的基础结构中使用HttpClient并不容易。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM