【问题标题】:C# IHttpHandler and the use of httpclient for a web serviceC# IHttpHandler 和 httpclient 用于 Web 服务
【发布时间】:2016-10-21 15:18:59
【问题描述】:

我的任务是维护一个 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
    }
}

现在我需要创建另一个 Web 服务,它会请求另一个 Web 服务来获取数据。我收集到的是 C# 中的 HttpClient() 是新的亮点,但是在我的情况下找出如何实现它并不是那么容易(或者这是否是一个好的解决方案)。

我的解决方案类似于

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
    }
}

我是在正确的轨道上还是这会是一场灾难?我很乐意接受相关文档的提示或指示。

【问题讨论】:

  • 我会重用 HttpClient,每次调用都重新实例化它是非常低效的。
  • @FailedUnitTest 这不是低效的,但如果它被大量使用它可能可能杀死你的服务器。
  • 将其设为静态并将其置于 ProcessRequest 方法之外会更好吗?
  • @Steve 如果你使用 async 你不应该有这个问题
  • @Steve 一点也不,HttpClientfully re-entrant

标签: c# web-services dotnet-httpclient


【解决方案1】:

您的方法存在两个问题。

首先,正如其他人所说,不得为每个请求创建 HttpClient,因为这会导致您的服务器为每个请求创建一个新的 TCP 连接并且不要将其从连接池中释放,直到他们达到了 MaxIdleTimeout。

这是因为 HttpClient 的每个实例(或者更确切地说,HttpClientHandler 的每个实例)在 ConnectionPool 中创建一个唯一的 ConnectionGroup,因此两个 HttpClient(以这种方式创建)不会共享相同的连接。

您可以很容易地耗尽您的端口(因为连接会保持活动相当长的一段时间),即使没有这些,您也会遇到停用 keepalive 时可能会发现的所有性能问题(通常是在连接到 HTTPs 服务器时) .

至于解决方案,您可以在处理程序中添加一个私有的只读 HttpClient 字段,您可以像在代码中那样初始化它(我相信 HttpHandlers 由 IIS 实例化一次,而不是每个请求,只要它说它是可重复使用的)。 (编辑:请注意 HttpClient 实例是线程安全的)。

您会发现的另一个问题是 IHttpHandler 在设计上是同步的。这意味着您必须在代码中返回 task.Result。这将阻塞当前线程(我们称其为 A),并且所有后续处理出站请求(发送请求的主体、读取响应的标头和读取响应的主体)都将使用来自线程池来处理这个(我们称它们为 B、C 和 D,尽管它们都可以是同一个线程,因为在时间上没有步骤重叠)。

但在 Asp.net 中,您不能安全地做到这一点。当您的主线程 A 正在处理 Asp.net 入站请求时,简单地说,它会锁定当前上下文。当线程 B、C 和 D 将被派生来处理出站请求时,它们也会尝试获取并锁定当前上下文。但是因为你的主线程A被阻塞了,所以上下文并没有被释放。这将有效地导致死锁(A 锁定上下文并等待 B/C/D,B/C/D 等待上下文)。

要解决这个问题,我建议您改用 IHttpAsyncHandler。异步进程不是同步使用的,HttpClient 只能以异步方式使用。

但是,对于第一个使用场景,这可能更具挑战性。在 WebApi 服务器的上下文中使用 HttpClient 会容易得多。但是,在不知道为什么必须使用 HttpHandler 的情况下,我不知道这是否在可接受的答案范围内。

还有其他方法可以运行异步任务并同步等待它们(例如临时更改 CurrentContext),但它们都非常危险。

附带说明,HttpClient 是新的闪亮事物,并且与其他新闪亮事物(例如“一路异步基础架构”)配合得很好。但是尝试在旧的基础架构中使用 HttpClient 并不容易。

【讨论】:

  • HttpHandler 的使用只是遗留问题。在研究我们应该改用什么时,似乎有很多关于华丽的 restful api 之类的文档,我认为这不是我们需要的。但是从您的帖子中得出的结论是,我认为以这种方式使用 httpclient 并不是最佳的。
  • 澄清一下——我认为我们的过程是同步的,因为我们必须等待第二个请求完成的响应才能响应对我们处理程序的初始请求。为此,我认为 WebRequest 类可能对我们更有用?还是我认为这完全错误?
  • 在 .net 的上下文中,同步意味着启动操作的线程完全专用于它,直到完成。因此,当某些 IO 发生时,例如读取文件系统或发送 HTTP 请求,专用线程会等待它们完成,进入挂起状态。另一方面,异步意味着启动操作的线程在第一个 IO 时被释放。当 IO 完成时,会发送一个回调来恢复另一个线程上的操作。
  • 这与http调用的同步性完全无关。您可以让您的入站请求到达,由异步处理程序中的线程处理。发送出站查询时,将释放处理线程。此时,您的响应尚未发送,因此消费者应用程序仍在等待来自您的入站请求的响应,但在内部,没有线程阻塞,因此从服务器的角度来看,这是异步的。当您的出站响应被触发时,将执行一个回调,并将响应提供给入站套接字。所以它可以是异步的。
  • 现在,如果您仍然喜欢坚持使用同步模型,直接使用 WebRequest 会更安全。它已经过时了,但毕竟同步模型也是如此
【解决方案2】:

更好的解决方案示例:

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();
    }
}

【讨论】:

  • 是的,这有点类似于我使用 httpclient 的原型,但我需要将来自 httpclient 的响应推送到原始请求 - IHttpHandler。我是否应该将我的 httpclient 代码放在一个每次都在 ProcessRequest 方法中启动的类中?
  • 也许我理解错了,但为什么不包括“私有静态HttpClient Client = new HttpClient();”同班吗?
  • 我认为我们的过程是同步的,因为我们必须等待第二个请求的响应完成才能响应对我们处理程序的初始请求。为此,我认为 WebRequest 类可能比 httpclient 更适合我们?还是我认为这完全错误?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多