5
using System.Threading;
6
using ServiceStack.Logging;
7
using ServiceStack.ServiceHost;
8
using ServiceStack.Text;
9
using ServiceStack.Common.Web;
11
namespace ServiceStack.ServiceClient.Web
14
* Need to provide async request options
15
* http://msdn.microsoft.com/en-us/library/86wf6409(VS.71).aspx
18
public class AsyncServiceClient
20
private static readonly ILog Log = LogManager.GetLogger(typeof(AsyncServiceClient));
21
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60);
22
private HttpWebRequest _webRequest = null;
25
/// The request filter is called before any request.
26
/// This request filter is executed globally.
28
public static Action<HttpWebRequest> HttpWebRequestFilter { get; set; }
31
/// The response action is called once the server response is available.
32
/// It will allow you to access raw response information.
33
/// This response action is executed globally.
34
/// Note that you should NOT consume the response stream as this is handled by ServiceStack
36
public static Action<HttpWebResponse> HttpWebResponseFilter { get; set; }
39
/// Called before request resend, when the initial request required authentication
41
public Action<WebRequest> OnAuthenticationRequired { get; set; }
43
const int BufferSize = 4096;
45
public ICredentials Credentials { get; set; }
47
public bool StoreCookies { get; set; }
49
public CookieContainer CookieContainer { get; set; }
52
/// The request filter is called before any request.
53
/// This request filter only works with the instance where it was set (not global).
55
public Action<HttpWebRequest> LocalHttpWebRequestFilter { get; set; }
58
/// The response action is called once the server response is available.
59
/// It will allow you to access raw response information.
60
/// Note that you should NOT consume the response stream as this is handled by ServiceStack
62
public Action<HttpWebResponse> LocalHttpWebResponseFilter { get; set; }
64
public string BaseUri { get; set; }
66
internal class RequestState<TResponse> : IDisposable
68
private bool _timedOut; // Pass the correct error back even on Async Calls
72
BufferRead = new byte[BufferSize];
73
TextData = new StringBuilder();
74
BytesData = new MemoryStream(BufferSize);
76
ResponseStream = null;
79
public string HttpMethod;
83
public StringBuilder TextData;
85
public MemoryStream BytesData;
87
public byte[] BufferRead;
89
public object Request;
91
public HttpWebRequest WebRequest;
93
public HttpWebResponse WebResponse;
95
public Stream ResponseStream;
99
public int RequestCount;
103
public Action<TResponse> OnSuccess;
105
public Action<TResponse, Exception> OnError;
108
public bool HandleCallbackOnUIThread { get; set; }
111
public void HandleSuccess(TResponse response)
113
if (this.OnSuccess == null)
117
if (this.HandleCallbackOnUIThread)
118
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => this.OnSuccess(response));
120
this.OnSuccess(response);
122
this.OnSuccess(response);
126
public void HandleError(TResponse response, Exception ex)
128
if (this.OnError == null)
131
Exception toReturn = ex;
135
WebException we = new WebException("The request timed out", ex, WebExceptionStatus.RequestCanceled, null);
137
WebException we = new WebException("The request timed out", ex, WebExceptionStatus.Timeout, null);
143
if (this.HandleCallbackOnUIThread)
144
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => this.OnError(response, toReturn));
146
this.OnError(response, toReturn);
148
OnError(response, toReturn);
152
public void StartTimer(TimeSpan timeOut)
154
this.Timer = new Timer(this.TimedOut, this, (int)timeOut.TotalMilliseconds, System.Threading.Timeout.Infinite);
157
public void TimedOut(object state)
159
if (Interlocked.Increment(ref Completed) == 1)
161
if (this.WebRequest != null)
164
this.WebRequest.Abort();
167
this.Timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
168
this.Timer.Dispose();
172
public void Dispose()
174
if (this.BytesData == null) return;
175
this.BytesData.Dispose();
176
this.BytesData = null;
180
public bool DisableAutoCompression { get; set; }
182
public string UserName { get; set; }
184
public string Password { get; set; }
186
public void SetCredentials(string userName, string password)
188
this.UserName = userName;
189
this.Password = password;
192
public TimeSpan? Timeout { get; set; }
194
public string ContentType { get; set; }
196
public StreamSerializerDelegate StreamSerializer { get; set; }
198
public StreamDeserializerDelegate StreamDeserializer { get; set; }
201
public bool HandleCallbackOnUIThread { get; set; }
203
public bool UseBrowserHttpHandling { get; set; }
205
public bool ShareCookiesWithBrowser { get; set; }
208
public void SendAsync<TResponse>(string httpMethod, string absoluteUrl, object request,
209
Action<TResponse> onSuccess, Action<TResponse, Exception> onError)
211
SendWebRequest(httpMethod, absoluteUrl, request, onSuccess, onError);
214
public void CancelAsync()
216
if (_webRequest != null)
218
// Request will be nulled after it throws an exception on its async methods
219
// See - http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.abort
225
internal static void AllowAutoCompression(HttpWebRequest webRequest)
227
webRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
228
webRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
232
private RequestState<TResponse> SendWebRequest<TResponse>(string httpMethod, string absoluteUrl, object request,
233
Action<TResponse> onSuccess, Action<TResponse, Exception> onError)
235
if (httpMethod == null) throw new ArgumentNullException("httpMethod");
237
var requestUri = absoluteUrl;
238
var httpGetOrDelete = (httpMethod == "GET" || httpMethod == "DELETE");
239
var hasQueryString = request != null && httpGetOrDelete;
242
var queryString = QueryStringSerializer.SerializeToString(request);
243
if (!string.IsNullOrEmpty(queryString))
245
requestUri += "?" + queryString;
251
var creator = this.UseBrowserHttpHandling
252
? System.Net.Browser.WebRequestCreator.BrowserHttp
253
: System.Net.Browser.WebRequestCreator.ClientHttp;
255
var webRequest = (HttpWebRequest) creator.Create(new Uri(requestUri));
257
if (StoreCookies && !UseBrowserHttpHandling)
259
if (ShareCookiesWithBrowser)
261
if (CookieContainer == null)
262
CookieContainer = new CookieContainer();
263
CookieContainer.SetCookies(new Uri(BaseUri), System.Windows.Browser.HtmlPage.Document.Cookies);
266
webRequest.CookieContainer = CookieContainer;
270
_webRequest = (HttpWebRequest)WebRequest.Create(requestUri);
274
_webRequest.CookieContainer = CookieContainer;
279
if (!DisableAutoCompression)
281
_webRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
282
_webRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
286
var requestState = new RequestState<TResponse>
288
HttpMethod = httpMethod,
291
WebRequest = webRequest,
293
WebRequest = _webRequest,
296
OnSuccess = onSuccess,
299
HandleCallbackOnUIThread = HandleCallbackOnUIThread,
302
requestState.StartTimer(this.Timeout.GetValueOrDefault(DefaultTimeout));
305
SendWebRequestAsync(httpMethod, request, requestState, webRequest);
307
SendWebRequestAsync(httpMethod, request, requestState, _webRequest);
313
private void SendWebRequestAsync<TResponse>(string httpMethod, object request,
314
RequestState<TResponse> requestState, HttpWebRequest webRequest)
316
var httpGetOrDelete = (httpMethod == "GET" || httpMethod == "DELETE");
317
webRequest.Accept = string.Format("{0}, */*", ContentType);
320
webRequest.Method = httpMethod;
322
//Methods others than GET and POST are only supported by Client request creator, see
323
//http://msdn.microsoft.com/en-us/library/cc838250(v=vs.95).aspx
325
if (this.UseBrowserHttpHandling && httpMethod != "GET" && httpMethod != "POST")
327
webRequest.Method = "POST";
328
webRequest.Headers[HttpHeaders.XHttpMethodOverride] = httpMethod;
332
webRequest.Method = httpMethod;
336
if (this.Credentials != null)
338
webRequest.Credentials = this.Credentials;
341
ApplyWebRequestFilters(webRequest);
345
if (!httpGetOrDelete && request != null)
347
webRequest.ContentType = ContentType;
348
webRequest.BeginGetRequestStream(RequestCallback<TResponse>, requestState);
352
requestState.WebRequest.BeginGetResponse(ResponseCallback<TResponse>, requestState);
357
// BeginGetRequestStream can throw if request was aborted
358
HandleResponseError(ex, requestState);
362
private void RequestCallback<T>(IAsyncResult asyncResult)
364
var requestState = (RequestState<T>)asyncResult.AsyncState;
367
var req = requestState.WebRequest;
369
var postStream = req.EndGetRequestStream(asyncResult);
370
StreamSerializer(null, requestState.Request, postStream);
372
requestState.WebRequest.BeginGetResponse(ResponseCallback<T>, requestState);
376
HandleResponseError(ex, requestState);
380
private void ResponseCallback<T>(IAsyncResult asyncResult)
382
var requestState = (RequestState<T>)asyncResult.AsyncState;
385
var webRequest = requestState.WebRequest;
387
requestState.WebResponse = (HttpWebResponse)webRequest.EndGetResponse(asyncResult);
389
ApplyWebResponseFilters(requestState.WebResponse);
391
// Read the response into a Stream object.
392
var responseStream = requestState.WebResponse.GetResponseStream();
393
requestState.ResponseStream = responseStream;
395
responseStream.BeginRead(requestState.BufferRead, 0, BufferSize, ReadCallBack<T>, requestState);
400
var firstCall = Interlocked.Increment(ref requestState.RequestCount) == 1;
401
if (firstCall && WebRequestUtils.ShouldAuthenticate(ex, this.UserName, this.Password))
405
requestState.WebRequest = (HttpWebRequest)WebRequest.Create(requestState.Url);
407
requestState.WebRequest.AddBasicAuth(this.UserName, this.Password);
409
if (OnAuthenticationRequired != null)
411
OnAuthenticationRequired(requestState.WebRequest);
415
requestState.HttpMethod, requestState.Request,
416
requestState, requestState.WebRequest);
418
catch (Exception /*subEx*/)
420
HandleResponseError(ex, requestState);
425
HandleResponseError(ex, requestState);
429
private void ReadCallBack<T>(IAsyncResult asyncResult)
431
var requestState = (RequestState<T>)asyncResult.AsyncState;
434
var responseStream = requestState.ResponseStream;
435
int read = responseStream.EndRead(asyncResult);
439
requestState.BytesData.Write(requestState.BufferRead, 0, read);
440
responseStream.BeginRead(
441
requestState.BufferRead, 0, BufferSize, ReadCallBack<T>, requestState);
446
Interlocked.Increment(ref requestState.Completed);
448
var response = default(T);
451
requestState.BytesData.Position = 0;
452
using (var reader = requestState.BytesData)
454
response = (T)this.StreamDeserializer(typeof(T), reader);
458
if (this.StoreCookies && this.ShareCookiesWithBrowser && !this.UseBrowserHttpHandling)
460
// browser cookies must be set on the ui thread
461
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(
464
var cookieHeader = this.CookieContainer.GetCookieHeader(new Uri(BaseUri));
465
System.Windows.Browser.HtmlPage.Document.Cookies = cookieHeader;
470
requestState.HandleSuccess(response);
474
Log.Debug(string.Format("Error Reading Response Error: {0}", ex.Message), ex);
475
requestState.HandleError(default(T), ex);
479
responseStream.Close();
485
HandleResponseError(ex, requestState);
489
private void HandleResponseError<TResponse>(Exception exception, RequestState<TResponse> requestState)
491
var webEx = exception as WebException;
494
&& webEx.Status == WebExceptionStatus.ProtocolError
498
var errorResponse = ((HttpWebResponse)webEx.Response);
500
Log.DebugFormat("Status Code : {0}", errorResponse.StatusCode);
501
Log.DebugFormat("Status Description : {0}", errorResponse.StatusDescription);
503
var serviceEx = new WebServiceException(errorResponse.StatusDescription)
505
StatusCode = (int)errorResponse.StatusCode,
510
using (var stream = errorResponse.GetResponseStream())
512
//Uncomment to Debug exceptions:
513
//var strResponse = new StreamReader(stream).ReadToEnd();
514
//Console.WriteLine("Response: " + strResponse);
515
//stream.Position = 0;
517
serviceEx.ResponseDto = this.StreamDeserializer(typeof(TResponse), stream);
518
requestState.HandleError((TResponse)serviceEx.ResponseDto, serviceEx);
521
catch (Exception innerEx)
523
// Oh, well, we tried
524
Log.Debug(string.Format("WebException Reading Response Error: {0}", innerEx.Message), innerEx);
525
requestState.HandleError(default(TResponse), new WebServiceException(errorResponse.StatusDescription, innerEx)
527
StatusCode = (int)errorResponse.StatusCode,
533
var authEx = exception as AuthenticationException;
536
var customEx = WebRequestUtils.CreateCustomException(requestState.Url, authEx);
538
Log.Debug(string.Format("AuthenticationException: {0}", customEx.Message), customEx);
539
requestState.HandleError(default(TResponse), authEx);
542
Log.Debug(string.Format("Exception Reading Response Error: {0}", exception.Message), exception);
543
requestState.HandleError(default(TResponse), exception);
548
private void ApplyWebResponseFilters(WebResponse webResponse)
550
if (!(webResponse is HttpWebResponse)) return;
552
if (HttpWebResponseFilter != null)
553
HttpWebResponseFilter((HttpWebResponse)webResponse);
554
if (LocalHttpWebResponseFilter != null)
555
LocalHttpWebResponseFilter((HttpWebResponse)webResponse);
558
private void ApplyWebRequestFilters(HttpWebRequest client)
560
if (LocalHttpWebRequestFilter != null)
561
LocalHttpWebRequestFilter(client);
563
if (HttpWebRequestFilter != null)
564
HttpWebRequestFilter(client);
567
public void Dispose() { }
b'\\ No newline at end of file'