diff --git a/RestSharp/Enum.cs b/RestSharp/Enum.cs index 02651944b..407788a75 100644 --- a/RestSharp/Enum.cs +++ b/RestSharp/Enum.cs @@ -74,6 +74,8 @@ public enum ResponseStatus { None, Completed, - Error + Error, + TimedOut, + NoConnectivity } } diff --git a/RestSharp/Http.Async.cs b/RestSharp/Http.Async.cs index f943b6057..2b49f94ed 100644 --- a/RestSharp/Http.Async.cs +++ b/RestSharp/Http.Async.cs @@ -20,6 +20,7 @@ using System.Linq; using System.Net; using System.Text; +using System.Threading; using RestSharp.Extensions; #if SILVERLIGHT @@ -43,6 +44,8 @@ namespace RestSharp /// public partial class Http { + private TimeOutState timeoutState = null; + public void DeleteAsync(Action action) { GetStyleMethodInternalAsync("DELETE", action); @@ -79,9 +82,11 @@ private void GetStyleMethodInternalAsync(string method, Action cal { var url = Url; var webRequest = ConfigureAsyncWebRequest(method, url); - webRequest.BeginGetResponse(result => ResponseCallback(result, callback), webRequest); + timeoutState = new TimeOutState { Request = webRequest }; + var asyncResult = webRequest.BeginGetResponse(result => ResponseCallback(result, callback), webRequest); + SetTimeout(asyncResult, webRequest, timeoutState); } - catch (Exception ex) + catch(Exception ex) { var response = new HttpResponse(); response.ErrorMessage = ex.Message; @@ -99,7 +104,7 @@ private void PutPostInternalAsync(string method, Action callback) PreparePostBody(webRequest); WriteRequestBodyAsync(webRequest, callback); } - catch (Exception ex) + catch(Exception ex) { var response = new HttpResponse(); response.ErrorMessage = ex.Message; @@ -111,125 +116,177 @@ private void PutPostInternalAsync(string method, Action callback) private void WriteRequestBodyAsync(HttpWebRequest webRequest, Action callback) { - if (HasBody || HasFiles) + IAsyncResult asyncResult; + timeoutState = new TimeOutState { Request = webRequest }; + + if(HasBody || HasFiles) { #if !WINDOWS_PHONE webRequest.ContentLength = CalculateContentLength(); #endif - webRequest.BeginGetRequestStream(result => RequestStreamCallback(result, callback), webRequest); + asyncResult = webRequest.BeginGetRequestStream(result => RequestStreamCallback(result, callback), webRequest); } + else { - webRequest.BeginGetResponse(r => ResponseCallback(r, callback), webRequest); + asyncResult = webRequest.BeginGetResponse(r => ResponseCallback(r, callback), webRequest); } + + SetTimeout(asyncResult, webRequest, timeoutState); } - private long CalculateContentLength () + private long CalculateContentLength() { - if (!HasFiles) + if(!HasFiles) { return RequestBody.Length; } // calculate length for multipart form long length = 0; - foreach (var file in Files) { - length += GetMultipartFileHeader (file).Length; + foreach(var file in Files) + { + length += GetMultipartFileHeader(file).Length; length += file.ContentLength; length += Environment.NewLine.Length; } - foreach (var param in Parameters) { - length += GetMultipartFormData (param).Length; + foreach(var param in Parameters) + { + length += GetMultipartFormData(param).Length; } length += GetMultipartFooter().Length; return length; } - private void WriteMultipartFormDataAsync (Stream requestStream) + private void WriteMultipartFormDataAsync(Stream requestStream) { var encoding = Encoding.UTF8; - foreach (var file in Files) + foreach(var file in Files) { // Add just the first part of this param, since we will write the file data directly to the Stream - var header = GetMultipartFileHeader (file); - requestStream.Write (encoding.GetBytes (header), 0, header.Length); + var header = GetMultipartFileHeader(file); + requestStream.Write(encoding.GetBytes(header), 0, header.Length); // Write the file data directly to the Stream, rather than serializing it to a string. - file.Writer (requestStream); + file.Writer(requestStream); var lineEnding = Environment.NewLine; - requestStream.Write (encoding.GetBytes (lineEnding), 0, lineEnding.Length); + requestStream.Write(encoding.GetBytes(lineEnding), 0, lineEnding.Length); } - foreach (var param in Parameters) + foreach(var param in Parameters) { - var postData = GetMultipartFormData (param); - requestStream.Write (encoding.GetBytes (postData), 0, postData.Length); + var postData = GetMultipartFormData(param); + requestStream.Write(encoding.GetBytes(postData), 0, postData.Length); } - var footer = GetMultipartFooter (); - requestStream.Write (encoding.GetBytes (footer), 0, footer.Length); + var footer = GetMultipartFooter(); + requestStream.Write(encoding.GetBytes(footer), 0, footer.Length); } - - private void RequestStreamCallback (IAsyncResult result, Action callback) + + private void RequestStreamCallback(IAsyncResult result, Action callback) { var webRequest = result.AsyncState as HttpWebRequest; - + // write body to request stream - using (var requestStream = webRequest.EndGetRequestStream (result)) + using(var requestStream = webRequest.EndGetRequestStream(result)) { - if (HasFiles) + if(HasFiles) { - WriteMultipartFormDataAsync (requestStream); + WriteMultipartFormDataAsync(requestStream); } + + + else { var encoding = Encoding.UTF8; - requestStream.Write (encoding.GetBytes (RequestBody), 0, RequestBody.Length); + requestStream.Write(encoding.GetBytes(RequestBody), 0, RequestBody.Length); } } - + webRequest.BeginGetResponse(r => ResponseCallback(r, callback), webRequest); } - private void GetRawResponseAsync(IAsyncResult result, Action callback) + private void SetTimeout(IAsyncResult asyncResult, HttpWebRequest request, TimeOutState timeOutState) { - var response = new HttpResponse(); - response.ResponseStatus = ResponseStatus.None; + ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), timeOutState, Timeout, true); + } - HttpWebResponse raw = null; + private void TimeoutCallback(object state, bool timedOut) + { + Console.WriteLine("Timed out: " + timedOut.ToString()); + if(timedOut) + { + TimeOutState timeoutState = state as TimeOutState; + + if(timeoutState == null) + { + return; + } + + lock(timeoutState) + { + timeoutState.TimedOut = timedOut; + } + + if(timeoutState.Request != null) + { + timeoutState.Request.Abort(); + } + } + } + private void GetRawResponseAsync(IAsyncResult result, HttpResponse response, Action callback) + { + HttpWebResponse raw = null; + try { var webRequest = (HttpWebRequest)result.AsyncState; raw = webRequest.EndGetResponse(result) as HttpWebResponse; } - catch (WebException ex) + catch(WebException ex) { - if (ex.Response is HttpWebResponse) + raw = ex.Response as HttpWebResponse; + + if(ex.Status == WebExceptionStatus.ConnectFailure || + ex.Status == WebExceptionStatus.NameResolutionFailure || + ex.Status == WebExceptionStatus.ProxyNameResolutionFailure) { - raw = ex.Response as HttpWebResponse; + response.ResponseStatus = ResponseStatus.NoConnectivity; } } - + callback(raw); + raw.Close(); } private void ResponseCallback(IAsyncResult result, Action callback) { var response = new HttpResponse(); response.ResponseStatus = ResponseStatus.None; - + try { - GetRawResponseAsync(result, webResponse => - { - ExtractResponseData(response, webResponse); - ExecuteCallback(response, callback); - }); + if(timeoutState.TimedOut) + { + response.ResponseStatus = ResponseStatus.TimedOut; + ExecuteCallback(response, callback); + return; + } + + GetRawResponseAsync(result, response, webResponse => + { + if(response.ResponseStatus == ResponseStatus.None) + { + ExtractResponseData(response, webResponse); + } + ExecuteCallback(response, callback); + }); } - catch (Exception ex) + catch(Exception ex) { response.ErrorMessage = ex.Message; response.ErrorException = ex; @@ -242,11 +299,10 @@ private void ExecuteCallback(HttpResponse response, Action callbac { #if WINDOWS_PHONE var dispatcher = Deployment.Current.Dispatcher; - dispatcher.BeginInvoke(() => { #endif callback(response); #if WINDOWS_PHONE - }); + dispatcher.BeginInvoke(() => { callback(response); }); #endif } @@ -270,56 +326,62 @@ private HttpWebRequest ConfigureAsyncWebRequest(string method, Uri url) #endif var webRequest = (HttpWebRequest)WebRequest.Create(url); webRequest.UseDefaultCredentials = false; - + AppendHeaders(webRequest); AppendCookies(webRequest); - + webRequest.Method = method; - + // make sure Content-Length header is always sent since default is -1 #if !WINDOWS_PHONE // WP7 doesn't as of Beta doesn't support a way to set this value either directly // or indirectly - if (!HasFiles) + if(!HasFiles) { webRequest.ContentLength = 0; } #endif - if (Credentials != null) + if(Credentials != null) { webRequest.Credentials = Credentials; } - + #if !SILVERLIGHT - if (UserAgent.HasValue()) + if(UserAgent.HasValue()) { webRequest.UserAgent = UserAgent; } #endif - + #if FRAMEWORK ServicePointManager.Expect100Continue = false; - - if (Timeout != 0) + + if(Timeout != 0) { webRequest.Timeout = Timeout; } - - if (Proxy != null) + + if(Proxy != null) { webRequest.Proxy = Proxy; } - - if (FollowRedirects && MaxRedirects.HasValue) + + if(FollowRedirects && MaxRedirects.HasValue) { - webRequest.MaximumAutomaticRedirections = MaxRedirects.Value; + webRequest.MaximumAutomaticRedirections = MaxRedirects.Value; } #endif - + #if !SILVERLIGHT webRequest.AllowAutoRedirect = FollowRedirects; #endif return webRequest; } + + private class TimeOutState + { + public bool TimedOut { get; set; } + public HttpWebRequest Request { get; set; } + } } }