14

I'm currently using code that makes HTTP requests using the HttpClient class. Although you can specify a timeout for the request, the value applies to the entirety of the request (which includes resolving the host name, establishing a connection, sending the request and receiving the response).

I need a way to make requests fail fast if they cannot resolve the name or establish a connection, but I also sometimes need to receive large amounts of data, so cannot just reduce the timeout.

Is there a way to achieve this using either a built in (BCL) class or an alternative HTTP client stack?

I've looked briefly at RestSharp and ServiceStack, but neither seems to provide a timeout just for the connection part (but do correct me if I am wrong).

Morten Mertner
  • 9,414
  • 4
  • 39
  • 56
  • I am not sure if you looked at the following if so please ignore.. [HttpWebRequest.Timeout](http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.timeout(v=vs.110).aspx) – MethodMan Nov 16 '14 at 22:13
  • @DJKRAZE The Timeout on HttpWebRequest "applies to the entire request and response" (from the MSDN page). – Morten Mertner Nov 16 '14 at 22:17
  • Is changing the Timeout value after the connection has been established an option? – MrPaulch Nov 16 '14 at 22:19
  • how about showing the code I wonder if there is a `Close() or Dispose()` issue or lack of `to the HttpWebRespose and Stream` – MethodMan Nov 16 '14 at 22:20
  • @MrPaulch "The Timeout property must be set before the GetRequestStream or GetResponse method is called." (again from the MSDN page for HttpWebRequest) – Morten Mertner Nov 16 '14 at 23:09
  • 1
    Why don't you just perform (asynchronous) DNS resolution beforehand, which is the main bugaboo? I would not try establishing connectivity on the port separately as well -- let the regular timeout take care of this, because a good firewall is indistinguishable from a slow server -- but the DNS resolution can just be factored out. – Jeroen Mostert Nov 21 '14 at 12:45
  • 1
    "if they cannot resolve the name or establish a connection" There are two parts to this A) The DNS resolve and B) the Socket open to that port. No DNS resolve usually is because 1) A DNS server or it's other neighbors cannot resolve the name or 2) The DNS server is down. The second part is a desire to send a socket open request, this is what a port scanner does, it see's if the socket (address/port pair) is responding. It does it simply by starting a session via a Socket.Open(). The client either responds or doesn't and the time it takes if you have the DNS resolve is minimal. – JWP Nov 26 '14 at 17:00

7 Answers7

8

You can use a Timer to abort the request if the connection take too much time. Add an event when the time is elapsed. You can use something like this:

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

Times here are just for example, you have to adjust them to your needs.

Ludovic Feltz
  • 11,416
  • 4
  • 47
  • 63
  • 2
    You're the first with something that would actually solve the problem. It's not exactly pretty, but useful nonetheless! – Morten Mertner Nov 21 '14 at 23:00
  • You can specify `AutoReset = false` when initializing the timer to remove the `timer.Enabled = false` in the handler. Also, you can just make the `Elapsed` look like this: `timer.Elapsed += (o, e) => { try { request?.Abort(); } catch { } };` Doing it inline lets you avoid the global `request` and extra handler method. – Chris Benard Mar 10 '20 at 21:09
2

.NET's HttpWebRequest exposes 2 properties for specifying a Timeout for connecting with a remote HTTP Server:

  • Timeout - Gets or sets the time-out value in milliseconds for the GetResponse and GetRequestStream methods.
  • ReadWriteTimeout - The number of milliseconds before the writing or reading times out. The default value is 300,000 milliseconds (5 minutes).

The Timeout property is the closest to what you're after, but it does suggest that regardless of the Timeout value the DNS resolution may take up to 15 seconds:

A Domain Name System (DNS) query may take up to 15 seconds to return or time out. If your request contains a host name that requires resolution and you set Timeout to a value less than 15 seconds, it may take 15 seconds or more before a WebException is thrown to indicate a timeout on your request.

One way to prempt a lower timeout than 15s for DNS lookups is to lookup the hostname yourself, but many solutions requires P/Invoke to specify low-level settings.

Specifying timeouts in ServiceStack HTTP Clients

The underlying HttpWebRequest Timeout and ReadWriteTimeout properties can also be specified in ServiceStack's high-level HTTP Clients, i.e. in C# Service Clients with:

var client = new JsonServiceClient(BaseUri) {
    Timeout = TimeSpan.FromSeconds(30)
};

Or using ServiceStack's HTTP Utils with:

var timeoutMs = 30 * 1000;
var response = url.GetStringFromUrl(requestFilter: req => 
    req.Timeout = timeoutMs);
Community
  • 1
  • 1
mythz
  • 141,670
  • 29
  • 246
  • 390
  • Thank you for your response. However, unless I misunderstand something, all you do is point out the limitations of HttpWebRequest. What I'm really looking for are alternatives that do not have these limitations. – Morten Mertner Nov 17 '14 at 21:14
  • 1
    @MortenMertner I'm suggesting out of the only Timeout options available, `Timeout` is the closest to what you're looking for. In addition all clients built on .NET's underlying `HttpWebRequest` is also only going to have the same API's at best as the only workaround for finer-grained control is to P/Invoke into Win32 system API's. – mythz Nov 17 '14 at 22:19
  • Right. I'm already using Timeout, but am not happy with it, so still looking for an alternative .NET library. I could end up writing one myself (using the system APIs), but that is more work than I can squeeze into the current cycle. – Morten Mertner Nov 17 '14 at 23:28
  • @MortenMertner Have you looked at using HttpWebRequest asynchronously and implementing your own timeout logic? – JamieSee Nov 25 '14 at 19:32
1

I believe RestSharp does have timeout properties in 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
cubski
  • 3,218
  • 1
  • 31
  • 33
  • 1
    RestSharp uses HttpWebRequest internally, so the Timeout behaves no differently there than it does with HttpClient. – Morten Mertner Nov 19 '14 at 22:53
  • @MortenMertner, HttpWebRequest has two different Timeouts, one Timeout is for entire connection life cycle and ReadWriteTimeout for Stream flush timeout. It is design flaw that HttpClient does not specify both, however HttpClient is asynchronous so you can and you should implement your own Cancellation Token Source to supply cancelling (timing out) of read/write stream operation. – Akash Kava Nov 24 '14 at 19:49
  • 1
    @AkashKava It is not so much a problem that is does not implement both (see 2nd answer to this SO question: http://stackoverflow.com/questions/1500955/adjusting-httpwebrequest-connection-timeout-in-c-sharp?rq=1), but rather that neither has a ConnectionTimeout. I'll probably go for using timers and abort the request manually (per Ludovic's suggestion), but it smells. – Morten Mertner Nov 25 '14 at 10:43
0

You right, you are unable to set this specific timeout. I don't have enough information about how the libraries were built, but for the purpose they are meant to, I believe they fit. Someone wants to do a request and set a timeout for everything.

I suggest you take a different approach. You are trying to do two different things here that HttpRequest do at once:

  1. Try to find the host/stabblish a connection;
  2. Transfer data;

You could try to separate this in two stages.

  1. Use Ping class (check this out) to try to get to your host and set a timeout for it;
  2. Use the HttpRequest IF it works for your needs (of timeout,

This process should not slow down everything, since part of resolving names/routes would be done at the first stage. This would not be totally disposable.

There's a drawback on this solution: your remote host must accept pings. Hope this helps.

rodrigogq
  • 1,943
  • 1
  • 16
  • 25
  • Don't use PING for network wide client or host availability, because Routers often disallow ICMP packets. – JWP Nov 26 '14 at 16:57
0

I used this method to check if the connection can be established. This however doesn't guarantee that the connection can be established by the subsequent call in 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;
}
tcb
  • 4,408
  • 5
  • 34
  • 51
-1

if timeouts does not suits your need - don't use them. you can use a handler which waits for the operation to complete. when you get a response - stop the handler and proceed. that way you will get short time requests when failing and long time requests for large amounts of data.

something like this maybe:

 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
ymz
  • 6,602
  • 1
  • 20
  • 39
-1

What about asking for only the header at first, and then the usual resource if it is successful,

webRequest.Method = "HEAD";
Baris Demiray
  • 1,539
  • 24
  • 35
  • To see why this is not an answer to the question, consider the case where the requests are already all `HEAD` requests. The original problem of having the timeout apply to making the connection would not disappear -- they would not "fail fast". – Jeroen Mostert Nov 21 '14 at 17:57
  • 1
    I did not see that the OP was about all head requests. To solve the OP of "I need a way to make requests fail fast if they cannot resolve the name or establish a connection, but I also sometimes need to receive large amounts of data, so cannot just reduce the timeout". Head only would let you know if you have a 404, 500 or something you are not able to program around. – Andy Powell Nov 21 '14 at 20:32