57

I believe after lengthy research and searching, I have discovered that what I want to do is probably better served by setting up an asynchronous connection and terminating it after the desired timeout... But I will go ahead and ask anyway!

Quick snippet of code:

HttpWebRequest webReq = (HttpWebRequest)HttpWebRequest.Create(url);
webReq.Timeout = 5000;
HttpWebResponse response = (HttpWebResponse)webReq.GetResponse(); 
// this takes ~20+ sec on servers that aren't on the proper port, etc.

I have an HttpWebRequest method that is in a multi-threaded application, in which I am connecting to a large number of company web servers. In cases where the server is not responding, the HttpWebRequest.GetResponse() is taking about 20 seconds to time out, even though I have specified a timeout of only 5 seconds. In the interest of getting through the servers on a regular interval, I want to skip those taking longer than 5 seconds to connect to.

So the question is: "Is there a simple way to specify/decrease a connection timeout for a WebRequest or HttpWebRequest?"

SteveC
  • 15,808
  • 23
  • 102
  • 173
JYelton
  • 35,664
  • 27
  • 132
  • 191

5 Answers5

59

I believe that the problem is that the WebRequest measures the time only after the request is actually made. If you submit multiple requests to the same address then the ServicePointManager will throttle your requests and only actually submit as many concurrent connections as the value of the corresponding ServicePoint.ConnectionLimit which by default gets the value from ServicePointManager.DefaultConnectionLimit. Application CLR host sets this to 2, ASP host to 10. So if you have a multithreaded application that submits multiple requests to the same host only two are actually placed on the wire, the rest are queued up.

I have not researched this to a conclusive evidence whether this is what really happens, but on a similar project I had things were horrible until I removed the ServicePoint limitation.

Another factor to consider is the DNS lookup time. Again, is my belief not backed by hard evidence, but I think the WebRequest does not count the DNS lookup time against the request timeout. DNS lookup time can show up as very big time factor on some deployments.

And yes, you must code your app around the WebRequest.BeginGetRequestStream (for POSTs with content) and WebRequest.BeginGetResponse (for GETs and POSTSs). Synchronous calls will not scale (I won't enter into details why, but that I do have hard evidence for). Anyway, the ServicePoint issue is orthogonal to this: the queueing behavior happens with async calls too.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • This is helpful: I believe the DNS lookup time may be where the excess time occurs. One clarification is that the requests are all made on separate threads to separate servers, and when all servers are responding, the time span including connection, download *and* processing is usually less than 4-5 seconds. It's just when a server is offline that the thread connecting to it takes a good deal more time to "realize" that the server is down. – JYelton Oct 01 '09 at 14:14
  • 2
    I know this question is old, but I hope you see this. Do you know if it's possible to change the DNS resolution timeout? See my problem here: http://stackoverflow.com/questions/5101507/c-web-calls-never-timing-out . I think it's very similar to this questions problem, and the DNS resolution never timing out would make sense. Any help is appreciated. – mike Mar 02 '11 at 07:40
  • 2
    @mrnye: I commented on that thread: there are system wide DNS resolution parameters: http://technet.microsoft.com/library/Cc977482 – Remus Rusanu Mar 02 '11 at 17:52
  • @JYelton: Did you manage to solve the problem with synchronous calls? I'm experiencing a similar issue (3 years after you posted the question :) ). I increased ServicePointManager.DefaultConnectionLimit to 15. The timeout values seem to be ignored. I tried setting both request.Timeout and request.ReadWriteTimeout to 1 second, and then to 60 seconds, but the call to GetRequestStream() takes about 21 seconds to complete in all the cases. Please note that I'm using an IP address for the server, so the DNS lookup time is not an issue here. Thanks. – Paul Sep 13 '13 at 08:36
  • @Paul I must have... the system has been running without any more attention to this particular issue for quite some time. Sorry I don't remember any further details without digging into the code again. – JYelton Sep 13 '13 at 19:05
  • 1
    @RemusRusanu : Not sure what you mean by 'Synchronous calls will not scale' which is very vague. Don't need hard evidence but can you be a little more specific? – public wireless Jan 30 '15 at 21:28
  • 1
    "Synchronous calls will not scale (I won't enter into details why, but that I do have hard evidence for)."...remind anyone else of Fermat's Last Theorem? – Darragh Feb 03 '15 at 11:53
39

Sorry for tacking on to an old thread, but I think something that was said above may be incorrect/misleading.

From what I can tell .Timeout is NOT the connection time, it is the TOTAL time allowed for the entire life of the HttpWebRequest and response. Proof:

I Set:

.Timeout=5000
.ReadWriteTimeout=32000

The connect and post time for the HttpWebRequest took 26ms

but the subsequent call HttpWebRequest.GetResponse() timed out in 4974ms thus proving that the 5000ms was the time limit for the whole send request/get response set of calls.

I didn't verify if the DNS name resolution was measured as part of the time as this is irrelevant to me since none of this works the way I really need it to work--my intention was to time out quicker when connecting to systems that weren't accepting connections as shown by them failing during the connect phase of the request.

For example: I'm willing to wait 30 seconds on a connection request that has a chance of returning a result, but I only want to burn 10 seconds waiting to send a request to a host that is misbehaving.

TechSavvySam
  • 1,382
  • 16
  • 28
  • 1
    Good find. So there's no easy way to set a connection timeout on an HttpWebRequest? That's very frustrating and seems like a big oversight... – craigrs84 Feb 13 '15 at 17:06
22

Something I found later which helped, is the .ReadWriteTimeout property. This, in addition to the .Timeout property seemed to finally cut down on the time threads would spend trying to download from a problematic server. The default time for .ReadWriteTimeout is 5 minutes, which for my application was far too long.

So, it seems to me:

.Timeout = time spent trying to establish a connection (not including lookup time) .ReadWriteTimeout = time spent trying to read or write data after connection established

More info: HttpWebRequest.ReadWriteTimeout Property

Edit:

Per @KyleM's comment, the Timeout property is for the entire connection attempt, and reading up on it at MSDN shows:

Timeout is the number of milliseconds that a subsequent synchronous request made with the GetResponse method waits for a response, and the GetRequestStream method waits for a stream. The Timeout applies to the entire request and response, not individually to the GetRequestStream and GetResponse method calls. If the resource is not returned within the time-out period, the request throws a WebException with the Status property set to WebExceptionStatus.Timeout.

(Emphasis mine.)

JYelton
  • 35,664
  • 27
  • 132
  • 191
  • 3
    Just noting that this answer is simply not true. As TechSavvySam says in an earlier response to this question, .Timeout is the limit for the FULL connection request and response. If your .Timeout is set to 5000ms and your data starts being sent at 1000ms, but takes 4999ms to send fully, guess what, you are going to time out. Regardless of what the API docs say, this is what real world testing shows. – KyleM Apr 02 '14 at 15:45
  • 3
    I can empirically state that in .NET 4.0, regardless of what the documentation stiffs say, the `.Timeout` property does not seem to actually encompass the full response. I had a reproducible subset of remote sites that allowed a connection to be established quickly and then dribbled out the page body data at a very slow rate. This caused connections to easily exceed several minutes in total despite me setting `.Timeout` to 10 seconds. But as soon as I **also** set `.ReadWriteTimeout` to 10 seconds, then the overall connection aborted as desired after 10 seconds had elapsed. – Special Sauce Nov 20 '16 at 06:04
14

From the documentation of the HttpWebRequest.Timeout property:

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.

Is it possible that your DNS query is the cause of the timeout?

GBegen
  • 6,107
  • 3
  • 31
  • 52
  • It's possible, however I am using IP addresses in a database to connect to servers, rather than URL's which require an address lookup. I suppose DNS could still be at fault if a particular IP address is not reachable, due to a VPN being down. When addresses are not reachable like that, that's when I run into the long timeouts and just a handful of servers can slow the program down, preventing it from moving on to other servers in the list. – JYelton Jan 28 '10 at 16:28
  • By the way +1 because this is a good find and clearly explains the DNS lookup timeout I was wondering about before. I must have read the documentation a dozen times but somehow never saw the mention of the DNS 15 second timeout. – JYelton Jan 28 '10 at 16:30
14

No matter what we tried we couldn't manage to get the timeout below 21 seconds when the server we were checking was down.

To work around this we combined a TcpClient check to see if the domain was alive followed by a separate check to see if the URL was active

public static bool IsUrlAlive(string aUrl, int aTimeoutSeconds)
{
    try
    {
        //check the domain first
        if (IsDomainAlive(new Uri(aUrl).Host, aTimeoutSeconds))
        {
            //only now check the url itself
            var request = System.Net.WebRequest.Create(aUrl);
            request.Method = "HEAD";
            request.Timeout = aTimeoutSeconds * 1000;
            var response = (HttpWebResponse)request.GetResponse();
            return response.StatusCode == HttpStatusCode.OK;
        }
    }
    catch
    {
    }
    return false;

}

private static bool IsDomainAlive(string aDomain, int aTimeoutSeconds)
{
    try
    {
        using (TcpClient client = new TcpClient())
        {
            var result = client.BeginConnect(aDomain, 80, null, null);

            var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(aTimeoutSeconds));

            if (!success)
            {
                return false;
            }

            // we have connected
            client.EndConnect(result);
            return true;
        }
    }
    catch
    {
    }
    return false;
}
Karl Glennon
  • 3,149
  • 4
  • 29
  • 31
  • Thanks a ton! This really helps to identify if the server is down before starting to wait for connection establishing. Save a lot of time. – Abhijith C R Dec 11 '19 at 07:06