C # IHttpHandler и использование httpclient для веб-службы

#c# #web-services #dotnet-httpclient

#c# #веб-сервисы #dotnet-httpclient

Вопрос:

Мне было поручено поддерживать веб-службу C #, которая в значительной степени сводится к следующему коду, используемому совместно с IIS 8:

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

Теперь мне нужно создать другой веб-сервис, который, в свою очередь, будет запрашивать другой веб-сервис для извлечения данных. Я понял, что HttpClient() в C # — это новый shiny, однако выяснить, как его реализовать в моем случае, не так просто (или это было бы хорошим решением).

Мое решение было бы чем-то вроде

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

Я на правильном пути или это было бы катастрофой? Я бы с удовольствием воспользовался советами или указателями на соответствующую документацию.

Комментарии:

1. Я бы повторно использовал HttpClient, повторная установка его при каждом вызове очень неэффективна.

2. @FailedUnitTest Это не неэффективно, но это может потенциально привести к сбою вашего сервера, если он интенсивно используется.

3. Было бы лучше сделать его статичным и поместить вне метода ProcessRequest?

4. @Steve если вы используете async, у вас не должно возникнуть этой проблемы

5. @Steve Вовсе нет, HttpClient он полностью повторно участвует .

Ответ №1:

В вашем подходе есть две проблемы.

Во-первых, как говорили другие, HttpClient не должен создаваться для каждого запроса, поскольку это приведет к тому, что ваш сервер создаст одно новое TCP-соединение для каждого запроса и не удалит его из вашего пула подключений, пока не достигнет максимального времени ожидания.

Это связано с тем, что каждый экземпляр HttpClient (или, скорее, каждый экземпляр HttpClientHandler) создает уникальную группу соединений в пределах ConnectionPool, поэтому два HttpClient (созданных таким образом) не будут совместно использовать одни и те же соединения.

Вы можете довольно легко исчерпать свои порты (поскольку соединения поддерживаются активными в течение достаточно долгого времени), и даже без этого у вас возникнут все проблемы с производительностью, которые вы можете обнаружить при деактивации keepalive (обычно при подключении к серверу HTTPs).

Что касается решения, вы могли бы добавить в свой обработчик частное поле HttpClient, доступное только для чтения, которое вы бы инициализировали, как вы это сделали в своем коде (я полагаю, что IIS создает экземпляры HttpHandlers один раз, а не для каждого запроса, если указано, что его можно использовать повторно). (Редактировать: обратите внимание, что экземпляры 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 может использоваться только асинхронным способом.

Однако это может оказаться гораздо более сложным для первого сценария использования. Было бы намного проще использовать HttpClient из контекста сервера WebAPI. Однако, без каких-либо знаний о том, почему вы должны использовать HttpHandler, я не знаю, будет ли это в сфере приемлемых ответов.

Существуют другие способы запуска асинхронных задач и их синхронного ожидания (например, временное изменение CurrentContext), но все они очень опасны.

В качестве дополнительного примечания, HttpClient — это новая блестящая вещь, которая довольно хорошо работает с другими новыми блестящими вещами, такими как «универсальная асинхронная инфраструктура». Но попытаться использовать его HttpClient в старой инфраструктуре не так просто.

Комментарии:

1. Использование HttpHandler является просто устаревшим. При рассмотрении того, что мы должны использовать вместо этого, кажется, что существует много документации по кричащему restful api и тому подобному, что, я думаю, не то, что нам нужно. Но вывод из вашего поста заключается в том, что использование httpclient таким образом, который я считал, не является оптимальным.

2. И просто для пояснения — я думаю, что наш процесс синхронен, поскольку мы должны дождаться ответа на второй запрос для завершения, чтобы иметь возможность ответить на первоначальный запрос нашему обработчику. Я думаю, что для этого класс WebRequest мог бы быть более полезным для нас? Или я думаю об этом совершенно неправильно?

3. В контексте .net синхронный означает, что поток, который запускает операцию, полностью посвящен ей до ее завершения. Итак, когда происходит некоторый ввод-вывод, например, чтение файловой системы или отправка HTTP-запроса, выделенный поток ожидает их завершения, переходя в приостановленное состояние. Асинхронность, с другой стороны, означает, что поток, запустивший операцию, освобождается при первом вводе-выводе. Когда ввод-вывод завершается, отправляется обратный вызов для возобновления операции в другом потоке.

4. Это совершенно не связано с синхронностью http-вызова. Вы могли бы получить свой входящий запрос, который был бы обработан потоком в асинхронном обработчике. При отправке исходящего запроса поток обработки будет освобожден. На этом этапе ваш ответ еще не отправлен, поэтому приложение-потребитель все еще ожидает ответа от вашего входящего запроса, но внутренне поток не заблокирован, так что с точки зрения сервера это асинхронно. Когда запускается ваш исходящий ответ, выполняется обратный вызов, и он передает ответ входящему сокету. Поэтому он может быть асинхронным.

5. Теперь, если вы все еще предпочитаете придерживаться синхронной модели, прямое использование 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").Resu<
            Console.WriteLine(result);
        }
        Console.WriteLine("Requests are finished!");
        Console.ReadLine();
    }
}
  

Комментарии:

1. Да, это было чем-то похоже на мой прототип, играющий с httpclient, но мне нужно отправить ответ из httpclient get на исходный запрос — IHttpHandler. Должен ли я предпочитать, чтобы мой код httpclient находился в классе, который инициируется каждый раз в методе ProcessRequest?

2. Возможно, я неправильно понял, но почему бы не включить «частный статический клиент HttpClient = новый HttpClient();» в тот же класс?

3. Я думаю, что наш процесс синхронен, поскольку мы должны дождаться ответа на второй запрос для завершения, чтобы иметь возможность ответить на первоначальный запрос нашему обработчику. Для этого, я думаю, класс WebRequest мог бы быть более полезным для нас вместо httpclient? Или я думаю об этом совершенно неправильно?