#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? Или я думаю об этом совершенно неправильно?