Как я могу указать тайм-аут только для подключения при выполнении веб-запросов?
В настоящее время я использую код, который делает HTTP-запросы с использованием класса HttpClient. Хотя вы можете указать тайм-аут для запроса, это значение применяется ко всему запросу (который включает в себя разрешение имени хоста, установление соединения, отправку запроса и получение ответа).
Мне нужен способ, чтобы запросы быстро отказывали, если они не могут разрешить имя или установить соединение, но мне также иногда нужно получать большие объемы данных, поэтому нельзя просто уменьшить перерыв.
Есть ли способ достичь этого с помощью встроенного класса (BCL) или альтернативного стека HTTP-клиентов?
Я бегло просмотрел RestSharp и ServiceStack, но ни один из них не предоставляет тайм-аут только для части соединения (но поправьте меня, если я ошибаюсь).
7 ответов:
Вы можете использовать
Timerдля прерывания запроса, если соединение занимает слишком много времени. Добавьте событие, когда время истекло. Вы можете использовать что-то вроде этого:static WebRequest request; private static void sendAndReceive() { // The request with a big timeout for receiving large amout of data request = HttpWebRequest.Create("http://localhost:8081/index/"); request.Timeout = 100000; // The connection timeout var ConnectionTimeoutTime = 100; Timer timer = new Timer(ConnectionTimeoutTime); timer.Elapsed += connectionTimeout; timer.Enabled = true; Console.WriteLine("Connecting..."); try { using (var stream = request.GetRequestStream()) { Console.WriteLine("Connection success !"); timer.Enabled = false; /* * Sending data ... */ System.Threading.Thread.Sleep(1000000); } using (var response = (HttpWebResponse)request.GetResponse()) { /* * Receiving datas... */ } } catch (WebException e) { if(e.Status==WebExceptionStatus.RequestCanceled) Console.WriteLine("Connection canceled (timeout)"); else if(e.Status==WebExceptionStatus.ConnectFailure) Console.WriteLine("Can't connect to server"); else if(e.Status==WebExceptionStatus.Timeout) Console.WriteLine("Timeout"); else Console.WriteLine("Error"); } } static void connectionTimeout(object sender, System.Timers.ElapsedEventArgs e) { Console.WriteLine("Connection failed..."); Timer timer = (Timer)sender; timer.Enabled = false; request.Abort(); }Времена здесь просто для примера, вы должны настроить их в соответствии с вашими потребностями.
.NET HttpWebRequest предоставляет 2 свойства для указания времени ожидания соединения с удаленным HTTP-сервером:
- Timeout - возвращает или задает значение времени ожидания в миллисекундах для методов GetResponse и GetRequestStream.
- ReadWriteTimeout - количество миллисекунд до истечения времени записи или чтения. Значение по умолчанию-300 000 миллисекунд (5 минут).
Свойство
Timeoutближе всего к тому, что вы после, но это предполагает, что независимо от значения таймаута разрешение DNS может занять до 15 секунд:Запрос системы доменных имен (DNS) может занять до 15 секунд для возврата или тайм-аута. Если ваш запрос содержит имя хоста, требующее разрешения, и вы установили значение Timeout меньше 15 секунд, может потребоваться 15 секунд или больше, прежде чем WebException будет выдано для указания таймаута на ваш запрос.
Один из способов заранее установить более низкий тайм-аут, чем 15 секунд для DNS поиск заключается в том, чтобы искать имя хоста самостоятельно , но многие решения требуют P/Invoke для указания низкоуровневых настроек.
Указание таймаутов в HTTP-клиентах ServiceStack
Лежащий в основе
HttpWebRequestсвойства Timeout и ReadWriteTimeout также могут быть заданы в высокоуровневых HTTP-клиентах ServiceStack, т. е. в C# - клиентах с помощью:var client = new JsonServiceClient(BaseUri) { Timeout = TimeSpan.FromSeconds(30) };Или используя Сервисстак HTTP Utils с:
var timeoutMs = 30 * 1000; var response = url.GetStringFromUrl(requestFilter: req => req.Timeout = timeoutMs);
Я считаю, что RestSharp имеет свойства таймаута в RestClient.
var request = new RestRequest(); var client = new RestClient { Timeout = timeout, //Timeout in milliseconds to use for requests made by this client instance ReadWriteTimeout = readWriteTimeout //The number of milliseconds before the writing or reading times out. }; var response = client.Execute(request); //Handle response
Вы правы, вы не можете установить этот конкретный тайм-аут. У меня нет достаточной информации о том, как были построены библиотеки, но для той цели, для которой они предназначены, я считаю, что они подходят. Кто-то хочет сделать запрос и установить тайм-аут для всего.
Я предлагаю вам принять другой подход. Вы пытаетесь сделать здесь две разные вещи, которыеHttpRequestделают одновременно:
- Попробуйте найти хост/stabblish соединение;
- передача данных;
Вы могли бы попробовать разделите это на два этапа.
- используйте
Pingкласс (проверьте это), чтобы попытаться добраться до вашего хоста и установить тайм-аут для него;- используйте
HttpRequestЕсли он работает для ваших нужд (таймаута,Этот процесс не должен замедлять все, так как часть разрешения имен / маршрутов будет выполнена на первом этапе. Это не будет полностью одноразовым.
У этого решения есть недостаток: ваш удаленный хост должен принимать пинги. Надеюсь, что это помогает.
Я использовал этот метод, чтобы проверить, можно ли установить соединение. Это, однако, не гарантирует , что соединение может быть установлено последующим вызовом в
HttpWebRequest.private static bool CanConnect(string machine) { using (TcpClient client = new TcpClient()) { if (!client.ConnectAsync(machine, 443).Wait(50)) // Check if we can connect in 50ms { return false; } } return true; }
Если тайм - ауты вам не подходят-не используйте их. можно использовать обработчик, который ожидает завершения операции. когда вы получите ответ-остановите обработчик и продолжайте. таким образом, вы получите короткие запросы времени при сбое и длительные запросы времени для больших объемов данных.
Что-то вроде этого, возможно:
var handler = new ManualResetEvent(false); request = (HttpWebRequest)WebRequest.Create(url) { // initialize parameters such as method } request.BeginGetResponse(new AsyncCallback(delegate(IAsyncResult result) { try { var request = (HttpWebRequest)result.AsyncState; using (var response = (HttpWebResponse)request.EndGetResponse(result)) { using (var stream = response.GetResponseStream()) { // success } response.Close(); } } catch (Exception e) { // fail operations go here } finally { handler.Set(); // whenever i succeed or fail } }), request); handler.WaitOne(); // wait for the operation to complete
Как насчет запроса сначала только заголовка, а затем обычного ресурса, если он успешен,
webRequest.Method = "HEAD";
Comments