cURL-запрос с HttpClient - как установить время ожидания соединения с сервером (WinInet) - PullRequest
1 голос
/ 29 мая 2019

Я отправляю запрос cURL с использованием HttpClient по методу, описанному здесь.

Параметр, используемый для этого метода:

SelectedProxy = пользовательский класс, в котором хранятся параметры моего прокси

Parameters.WcTimeout = время ожидания

url, header, content = запрос cURL (на основе этого инструмента для преобразования в C # https://curl.olsh.me/).

        const SslProtocols _Tls12 = (SslProtocols)0x00000C00;
        const SecurityProtocolType Tls12 = (SecurityProtocolType)_Tls12;
        ServicePointManager.SecurityProtocol = Tls12;
        string source = "";

        using (var handler = new HttpClientHandler())
        {
            handler.UseCookies = usecookies;
            WebProxy wp = new WebProxy(SelectedProxy.Address);
            handler.Proxy = wp;

            using (var httpClient = new HttpClient(handler))
            {
                httpClient.Timeout = Parameters.WcTimeout;

                using (var request = new HttpRequestMessage(new HttpMethod(HttpMethod), url))
                {
                    if (headers != null)
                    {
                        foreach (var h in headers)
                        {
                            request.Headers.TryAddWithoutValidation(h.Item1, h.Item2);
                        }
                    }
                    if (content != "")
                    {
                        request.Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
                    }

                    HttpResponseMessage response = new HttpResponseMessage();
                    try
                    {
                        response = await httpClient.SendAsync(request);
                    }
                    catch (Exception e)
                    {
                        //Here the exception happens
                    }
                    source = await response.Content.ReadAsStringAsync();
                }
            }
        }
        return source;

Если я запускаю это без прокси, это работает как шарм. Когда я отправляю запрос с использованием прокси-сервера, который я сначала тестировал в Chrome, у меня возникает следующая ошибка при попытке {} catch {}. Вот дерево ошибок

{"An error occurred while sending the request."}
    InnerException {"Unable to connect to the remote server"}
        InnerException {"A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond [ProxyAdress]"}
        SocketErrorCode: TimedOut

Используя секундомер, я вижу, что тайм-аут произошел примерно через 30 секунд.


Я пробовал несколько разных обработчиков, основываясь на следующих ссылках В чем разница между HttpClient.Timeout и использованием свойств тайм-аута WebRequestHandler? , Путаница с тайм-аутом HttpClient или с WinHttpHandler.

Стоит отметить, что WinHttpHandler допускает использование другого кода ошибки, то есть ошибка 12002, вызывающая WINHTTP_CALLBACK_STATUS_REQUEST_ERROR, «Тайм-аут операции». Основная причина та же самая, хотя она помогла нацелиться на места ошибок (например, WinInet), что подтверждает также то, что @DavidWright говорил относительно того, что таймауты от HttpClient управляют другой частью отправки запроса.

Следовательно, моя проблема связана со временем, которое требуется для установления соединения с сервером, которое запускает 30-секундный таймаут из WinInet.

Мой вопрос: как изменить время ожидания?

Стоит отметить, что Chrome, использующий WinInet, не страдает от этого тайм-аута, как и Cefsharp, на котором основана большая часть моего приложения, и с помощью которого те же прокси-серверы могут правильно отправлять запросы. .

Ответы [ 2 ]

0 голосов
/ 31 мая 2019

Итак, благодаря @DavidWright я понимаю несколько вещей:

  1. Перед отправкой HttpRequestMessage и началом тайм-аута от HttpClient устанавливается TCP-соединение с сервером
  2. У TCP-соединения есть собственный тайм-аут, определенный на уровне операционной системы, и мы не определили способ изменить его во время выполнения с C # (вопрос в ожидании, если кто-то захочет внести свой вклад)
  3. Настаивать на попыткеподключение работает, так как каждая попытка извлекает выгоду из предыдущих попыток, хотя необходимо реализовать надлежащее управление исключениями и счетчик времени ожидания вручную (я фактически рассмотрел несколько попыток в своем коде, предполагая, что каждая попытка составляет около 30 секунд)

Все это вместе закончилось следующим кодом:

        const SslProtocols _Tls12 = (SslProtocols)0x00000C00;
        const SecurityProtocolType Tls12 = (SecurityProtocolType)_Tls12;
        ServicePointManager.SecurityProtocol = Tls12;
        var sp = ServicePointManager.FindServicePoint(endpoint);

        sp.ConnectionLeaseTimeout = (int)Parameters.ConnectionLeaseTimeout.TotalMilliseconds;


        string source = "";

        using (var handler = new HttpClientHandler())
        {
            handler.UseCookies = usecookies;
            WebProxy wp = new WebProxy(SelectedProxy.Address);
            handler.Proxy = wp;

            using (var client = new HttpClient(handler))
            {
                client.Timeout = Parameters.WcTimeout;

                int n = 0;
                back:
                using (var request = new HttpRequestMessage(new HttpMethod(HttpMethod), endpoint))
                {

                    if (headers != null)
                    {
                        foreach (var h in headers)
                        {
                            request.Headers.TryAddWithoutValidation(h.Item1, h.Item2);
                        }
                    }
                    if (content != "")
                    {
                        request.Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
                    }
                    HttpResponseMessage response = new HttpResponseMessage();

                    try
                    {
                        response = await client.SendAsync(request);
                    }
                    catch (Exception e)
                    {
                        if(e.InnerException != null)
                        {
                            if(e.InnerException.InnerException != null)
                            {
                                if (e.InnerException.InnerException.Message.Contains("A connection attempt failed because the connected party did not properly respond after"))
                                {
                                    if (n <= Parameters.TCPMaxTries)
                                    {
                                        n++;
                                        goto back;
                                    }
                                }
                            }
                        }
                        // Manage here other exceptions
                    }
                    source = await response.Content.ReadAsStringAsync();
                }
            }
        }
        return source;

Кстати, моя текущая реализация HttpClient может оказаться проблематичной в будущем.Будучи одноразовым, HttpClient должен быть определен на уровне приложения с помощью статики, а не внутри оператора using.Чтобы узнать больше об этом, зайдите здесь или туда .

Моя проблема заключается в том, что я хочу обновлять прокси-сервер при каждом запросе, и он не установлен набаза запроса.Хотя он объясняет причину нового параметра ConnectionLeaseTimeout (чтобы минимизировать время, в течение которого аренда остается открытой), это другая тема

0 голосов
/ 29 мая 2019

У меня была такая же проблема с HttpClient. Чтобы SendAsync мог вернуться, необходимо выполнить две вещи: во-первых, настроить канал TCP, по которому происходит обмен данными (рукопожатие SYN, SYN / ACK, ACK, если вам это знакомо), и, во-вторых, получить обратно данные, составляющие HTTP-ответ по этому каналу TCP. Тайм-аут HttpClient относится только ко второй части. Время ожидания для первой части определяется сетевой подсистемой ОС, и изменить время ожидания в коде .NET довольно сложно.

(Вот как вы можете воспроизвести этот эффект. Установите работающее соединение клиент / сервер между двумя компьютерами, чтобы вы знали, что разрешение имен, доступ к порту, прослушивание и логика клиента и сервера все работает. Затем отключите сетевой кабель от сети. сервер и повторно запустите клиентский запрос. Он истечет с сетевым тайм-аутом по умолчанию ОС, независимо от того, какой тайм-аут вы установили на HttpClient.)

Единственный способ, который я знаю об этом, - запустить собственный таймер задержки в другом потоке и отменить задачу SendAsync, если таймер завершит работу первым. Вы можете сделать это с помощью Task.Delay и Task.WaitAny или путем создания CancellationTokenSource с желаемым временем (которое, по сути, просто делает первый шаг под капотом). В любом случае вам нужно быть осторожным в отмене и чтении исключений из задачи, которая проигрывает гонку.

...