Cancelling an http request after programmed connection timeout

darfur

Member
Sep 27, 2004
48
0
0
I'm writing an application that simplifies some web site interfaces I use at work, and one of them in particular is causing problems. It uses a persistent connection to keep you logged in. I have a relatively short timeout set on the http request since normally the website is very fast, but it is known for its bad days as well. I'd like to keep the time out low because I don't want people using the program to have to sit there forever waiting for it to do nothing.

The problem is, from what I can tell, when my program produces a time out exception, the server does not know to close the connection with my client. After two time outs, the program more or less becomes hosed, and will do nothing but time out on that web site until it's restarted or sometimes even restarting the computer. I've looked everywhere (although I'll admit I'm not very familiar with web requests so I'm probably searching the wrong things) and am having very little luck finding anything helpful on this. I need to be able to somehow tell the server that I don't want that request anymore so it'll free up a spot to avoid permanent time outs.

The few things I've known/found to try and closing or disposing the read/request streams and and aborting or closing the request and response. I've even tried setting KeepAlive to false, and that did not work. I've tried putting these in the try/catch and just for kicks in a finally too. I have also tried starting another thread, that if allowed to run long enough, will close all the streams/connections/blah and abort the requesting thread (thread where the function below runs) a little before the 10/30 second time out would happen. This also did not work, I suspect, because the requesting thread was "locked up" trying to get the request. It couldn't be aborted until it unlocked when it hit its timeout, if that would even make a difference for this.

I've included code just for completion, though really I think this is more of a problem of knowing some command to send to the server (at least I hope it's that easy) opposed to a C# issue.

HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(destination);
webRequest.Method = method;
webRequest.Accept = "*/*";
webRequest.AllowAutoRedirect = false;
webRequest.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12";
webRequest.CookieContainer = new CookieContainer();
webRequest.ContentLength = requestBytes.Length;
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.PreAuthenticate = true;
webRequest.Referer = refer;
//webRequest.KeepAlive = false;
if (source == ThreadSource.WebCT || source == ThreadSource.Directory || source == ThreadSource.DLS || source == ThreadSource.VistaKeepAlive)
webRequest.Timeout = 10000;

if (source == ThreadSource.Vista)
webRequest.Timeout = 30000;

webRequest.Credentials = credentials;
webRequest.CookieContainer.Add(cookies.GetCookies(destination));

Stream reqStream = null;
StreamReader stream = null;
HttpWebResponse webResponse = null;

try
{
if (method == "post")
{
reqStream = webRequest.GetRequestStream();
reqStream.Write(requestBytes, 0, requestBytes.Length);
reqStream.Close();
}

webResponse = (HttpWebResponse)webRequest.GetResponse();
if (webRequest.HaveResponse)
{
foreach (Cookie retCookie in webResponse.Cookies)
{
bool cookieFound = false;
foreach (Cookie oldCookie in cookies.GetCookies(destination))
{
if (retCookie.Name.Equals(oldCookie.Name))
{
oldCookie.Value = retCookie.Value;
cookieFound = true;
}
}
if (!cookieFound)
cookies.Add(retCookie);
}

if ((webResponse.StatusCode == HttpStatusCode.Found) || (webResponse.StatusCode == HttpStatusCode.Redirect) || (webResponse.StatusCode == HttpStatusCode.Moved) || (webResponse.StatusCode == HttpStatusCode.MovedPermanently))
{
WebHeaderCollection headers = webResponse.Headers;
return SendRequestTo(method, requestBytes, new Uri(headers["location"]), refer, credentials, source);
}

stream = new StreamReader(webResponse.GetResponseStream());
String responseString = stream.ReadToEnd();
stream.Close();
return responseString;
}
}
catch (WebException e)
{
throw new Exception("Exception occured while sending request.", e);
}

throw new Exception("No response received from host.");
 

darfur

Member
Sep 27, 2004
48
0
0
I have tried that. if anything it seemed to make the problem worse, although that's hard to say for sure because of how the system behaves after you get these timeouts.
 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
The other possibility is to implement it as asynch and use a timeout callback, then call Abort() from within the callback. That's how MS does it in their examples.
 

imported_Dhaval00

Senior member
Jul 23, 2004
573
0
0
Well, the first issue I see is the way you're handling the reqStream and stream objects. Sure, you close the objects, but they never get disposed/closed in case of an exception. I would recommend you use either "using" blocks around these objects or dispose them in a finally block. The Framework cleans up behind you automatically, but if your server is too busy, the Garbage Collector might not be able to clean up appropriately (performance-wise).

The next issue is you're throwing an exception in your catch block. I think (I could be wrong, but I am sure I have seen this before) this will cause IIS to bring down the entire AppDomain in which your application/Website is running. IMO, this is the underlying cause as to why the error doesn't go away until you restart the process. Never, ever throw exceptions directly in an IIS application - this includes Websites, SMTP Sinks, etc. The first thing I would try is to remove the "uncatched" throw statements. After all, if you're going to throw it, why are you catching it? LOL.

See if these pointers remedy your situation.
 

darfur

Member
Sep 27, 2004
48
0
0
Earlier I had tried disposing the streams in a finally, but it did not help. I tried usings as you suggested, but I'm still getting the same problems. As far as the exceptions go, the code I posted above is a function and that function call is in a try catch, so they are caught by the calling function. I didn't understand 100% what you posted, but it seems this should be ok as long as they are caught?

I haven't had a chance to try using the async methods, but I will as soon as I can find the time.

Edit: I did try removing those throws, and actually the program acts a little different. I can't tell yet for better or for worse.
 

darfur

Member
Sep 27, 2004
48
0
0
These are the functions that send and receive the requests. I realize there's a lot of things in here that may not be done entirely properly :eek: so unless they could be causing my problem, please overlook them :)

In the even bigger picture (the entire project is close to 6000 lines, with most of it working just fine =\), an event happens and starts a new thread whos entry point is one of many functions. That function builds a request, then calls the dorequest() function.

public String dorequest(String method, String request, Uri destination, String refer, NetworkCredential credientials, ThreadSource source)
{
String result = "";

try
{

threadlist.Add(Thread.CurrentThread);

result = SendRequest(method, request, destination, refer, credientials, source);

if (result.Contains("Invalid password") || result.Contains("Please log on") && source != ThreadSource.AMSTestLogin)
{
writetodebug('(' + mainform.txtEUID.Text + ')' + " Logging in. . .");
SendRequest("post", "=%2cLogin&euid=" + mainform.txtEUID.Text + "&password=" + password, destination, "", credientials, source);
result = SendRequest(method, request, destination, refer, credientials, source);
//File.WriteAllText("test.htm", result);
}
else if (result.Contains("Click the name of the Institution you want to access.")) // logged out of Vista
{
// logged out of vista
}
else if (result.Contains("Please choose a question and provide a secret answer."))
{
changeqaform = new ChangeSecretQA(mainform);
if (changeqaform.ShowDialog() == DialogResult.OK)
{
result = SendRequest(method, request, destination, refer, credientials, source);
}
else
throw new Exception("Failed to set a new question and answer");
}
/*else
File.WriteAllText("test.htm", result);*/
}
catch (Exception ex)
{
if (!ex.Message.Contains("aborted"))
{
if (ex.InnerException != null)
{
if (!ex.InnerException.Message.Contains("aborted"))
MessageBox.Show(ex.Message + " " + ex.InnerException.Message, "Error with " + source.ToString() + " request", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
}
else
{
if (ex.Message.ToString() != "Rerun")
MessageBox.Show(ex.Message, "Error with " + source.ToString() + " request", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
else
rerun = true;
}
}
}
finally
{
threadlist.Remove(Thread.CurrentThread);

try
{
apptitleCallback d = new apptitleCallback(mainform.setapptitle);
mainform.Invoke(d, new object[] { });
}
catch (Exception)
{
}
}

return result;
}




private String SendRequest(String method, String request, Uri destination, String refer, NetworkCredential credentials, ThreadSource source)
{
byte[] xmlBytes = Encoding.UTF8.GetBytes(request);
return SendRequestTo(method, xmlBytes, destination, refer, credentials, source);
}




private String SendRequestTo(String method, Byte[] requestBytes, Uri destination, String refer, NetworkCredential credentials, ThreadSource source)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(destination);
webRequest.Method = method;
webRequest.Accept = "*/*";
webRequest.AllowAutoRedirect = false;
webRequest.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12";
webRequest.CookieContainer = new CookieContainer();
webRequest.ContentLength = requestBytes.Length;
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.PreAuthenticate = true;
webRequest.Referer = refer;
//webRequest.KeepAlive = false;
if (source == ThreadSource.WebCT || source == ThreadSource.Directory || source == ThreadSource.DLS || source == ThreadSource.VistaKeepAlive)
webRequest.Timeout = 10000;

if (source == ThreadSource.Vista)
webRequest.Timeout = 30000;

webRequest.Credentials = credentials;
webRequest.CookieContainer.Add(cookies.GetCookies(destination));

Stream reqStream = null;
StreamReader stream = null;
HttpWebResponse webResponse = null;

try
{
if (method == "post")
{
using (reqStream = webRequest.GetRequestStream())
{
reqStream.Write(requestBytes, 0, requestBytes.Length);
}
}

using (webResponse = (HttpWebResponse)webRequest.GetResponse())
{
if (webRequest.HaveResponse)
{
foreach (Cookie retCookie in webResponse.Cookies)
{
bool cookieFound = false;
foreach (Cookie oldCookie in cookies.GetCookies(destination))
{
if (retCookie.Name.Equals(oldCookie.Name))
{
oldCookie.Value = retCookie.Value;
cookieFound = true;
}
}
if (!cookieFound)
cookies.Add(retCookie);
}

if ((webResponse.StatusCode == HttpStatusCode.Found) || (webResponse.StatusCode == HttpStatusCode.Redirect) || (webResponse.StatusCode == HttpStatusCode.Moved) || (webResponse.StatusCode == HttpStatusCode.MovedPermanently))
{
WebHeaderCollection headers = webResponse.Headers;
return SendRequestTo(method, requestBytes, new Uri(headers["location"]), refer, credentials, source);
}

using (stream = new StreamReader(webResponse.GetResponseStream()))
{
String responseString = stream.ReadToEnd();
return responseString;
}
}
}
}
catch (WebException e)
{
webRequest.Abort();
throw new Exception("Exception occured while sending request.", e);
//MessageBox.Show("Exception occured while sending request." + e.Message);
//return "";
}

//return "";
webRequest.Abort();
throw new Exception("No response received from host.");
}
 

imported_Dhaval00

Senior member
Jul 23, 2004
573
0
0
I don't know if I am reading it wrong, but what are the last 2 lines for? Every time you call SendRequestTo, an Exception will be thrown...

Correct me if I read it wrong.
 

darfur

Member
Sep 27, 2004
48
0
0
A few lines above that right before the 4 closing braces it will return responseString if the request was successful. Hard to follow becuase of the formatting.

I tried changing the request to be an asynchttp request. Assuming I've implemented it correclty, this does not appear to be working, although I'm still playing around with it.
 

imported_Dhaval00

Senior member
Jul 23, 2004
573
0
0
You get a RequestStream only if the method is POST. Nevertheless, you always end up getting a response? Logically speaking, I don't know if you can have a response without issuing a request to the server. If there is no data to send, I would still get a RequestStream and close it/dispose it before calling GetResponse.

Also, in all those (using) blocks, can you call .Close() on each of the object... I remember reading a bug in one of the NetworkStream objects that how Dispose() doesn't call Close() - I don't think this is an issue here.

Post your results. Also I use http://www.rafb.net/paste/ to paste code chucks :)
 

darfur

Member
Sep 27, 2004
48
0
0
That's correct. If the method was get it didn't require (in fact would give me an error "cannot send content body with this verb type") a request, only to get the response. However, for some reason with my code that does an async request, it will only work if I do send something. So if the method is get, I temporarily change it to post, make the request, then change the method back to get before I get the resposne.

Calling close in the using blocks did not work.

It may be worth noting, with the Async requests, I now sometimes get an error saying a connection that was exepcted to be kept alive was unexpectadly closed by the server. It didn't seem to work any better after that happened, but could this be a step in the right direction?

Great site btw :) Here's a post with both versions of my code, the async request and the non.

http://rafb.net/p/g0R5ew93.html

The try/catch at 124 does not need to be there. I was experimenting with things. The results are the same with or without it. Also, line 79 never executes.

Given the number of things I've tried (before posting here even) and the number of examples I've looked at that say "make sure you close the streams and abort otherwise the connection will stay open", part of me thinks that the system this part of the program is accessing is just not going to let me do this without restarting the program.
 

imported_Dhaval00

Senior member
Jul 23, 2004
573
0
0
I don't know if you didn't post the relevant code, or reverted all your changes, but going back to the SendRequestTo function (line 234), I don't see where you considered the logic of calling GetRequestStream() every time before calling GetResponse() - line 265 onwards.

I would say concentrate on making the synchronous requests work... in your desperation to get to a solution, the asynchronous stuff may end up inducing threading issues. I say this because the issue is reoccurring and the only solution is to restart IIS or your system. Additionally, I would also keep an eye out on the memory usage of either the inetinfo.exe (IIS 5.1) process or the w3wp.exe process (W2K3). Usually, if there are threading issues, this process's memory usage will be quite high.