using System;
using System.IO;
using System.Net;
#if !MONOTOUCH
using System.Web;
#endif
using ServiceStack.Common;
using ServiceStack.Logging;
using ServiceStack.Service;
using ServiceStack.ServiceHost;
using ServiceStack.Text;
namespace ServiceStack.ServiceClient.Web
{
/**
* Need to provide async request options
* http://msdn.microsoft.com/en-us/library/86wf6409(VS.71).aspx
*/
public abstract class ServiceClientBase
#if !SILVERLIGHT
: IServiceClient, IRestClient
#else
: IServiceClient
#endif
{
private static readonly ILog log = LogManager.GetLogger(typeof(ServiceClientBase));
///
/// The request filter is called before any request.
/// This request filter is executed globally.
///
private static Action httpWebRequestFilter;
public static Action HttpWebRequestFilter
{
get
{
return httpWebRequestFilter;
}
set
{
httpWebRequestFilter = value;
AsyncServiceClient.HttpWebRequestFilter = value;
}
}
///
/// The response action is called once the server response is available.
/// It will allow you to access raw response information.
/// This response action is executed globally.
/// Note that you should NOT consume the response stream as this is handled by ServiceStack
///
private static Action httpWebResponseFilter;
public static Action HttpWebResponseFilter
{
get
{
return httpWebResponseFilter;
}
set
{
httpWebResponseFilter = value;
AsyncServiceClient.HttpWebResponseFilter = value;
}
}
public const string DefaultHttpMethod = "POST";
readonly AsyncServiceClient asyncClient;
protected ServiceClientBase()
{
this.HttpMethod = DefaultHttpMethod;
this.CookieContainer = new CookieContainer();
asyncClient = new AsyncServiceClient
{
ContentType = ContentType,
StreamSerializer = SerializeToStream,
StreamDeserializer = StreamDeserializer,
CookieContainer = this.CookieContainer,
UserName = this.UserName,
Password = this.Password,
LocalHttpWebRequestFilter = this.LocalHttpWebRequestFilter,
LocalHttpWebResponseFilter = this.LocalHttpWebResponseFilter
};
this.StoreCookies = true; //leave
#if SILVERLIGHT
asyncClient.HandleCallbackOnUIThread = this.HandleCallbackOnUIThread = true;
asyncClient.UseBrowserHttpHandling = this.UseBrowserHttpHandling = false;
asyncClient.ShareCookiesWithBrowser = this.ShareCookiesWithBrowser = true;
#endif
}
protected ServiceClientBase(string syncReplyBaseUri, string asyncOneWayBaseUri)
: this()
{
this.SyncReplyBaseUri = syncReplyBaseUri;
this.AsyncOneWayBaseUri = asyncOneWayBaseUri;
}
///
/// Sets all baseUri properties, using the Format property for the SyncReplyBaseUri and AsyncOneWayBaseUri
///
/// Base URI of the service
public void SetBaseUri(string baseUri)
{
this.BaseUri = baseUri;
this.asyncClient.BaseUri = baseUri;
this.SyncReplyBaseUri = baseUri.WithTrailingSlash() + Format + "/syncreply/";
this.AsyncOneWayBaseUri = baseUri.WithTrailingSlash() + Format + "/asynconeway/";
}
///
/// Sets all baseUri properties allowing for a temporary override of the Format property
///
/// Base URI of the service
/// Override of the Format property for the service
//Marked obsolete on 4/11/2012
[Obsolete("Please call the SetBaseUri(string baseUri) method, which uses the specific implementation's Format property.")]
public void SetBaseUri(string baseUri, string format)
{
this.BaseUri = baseUri;
this.asyncClient.BaseUri = baseUri;
this.SyncReplyBaseUri = baseUri.WithTrailingSlash() + format + "/syncreply/";
this.AsyncOneWayBaseUri = baseUri.WithTrailingSlash() + format + "/asynconeway/";
}
private bool _disableAutoCompression;
///
/// Whether to Accept Gzip,Deflate Content-Encoding and to auto decompress responses
///
public bool DisableAutoCompression
{
get { return _disableAutoCompression; }
set
{
_disableAutoCompression = value;
asyncClient.DisableAutoCompression = value;
}
}
private string _username;
///
/// The user name for basic authentication
///
public string UserName
{
get { return _username; }
set
{
_username = value;
asyncClient.UserName = value;
}
}
private string _password;
///
/// The password for basic authentication
///
public string Password
{
get { return _password; }
set
{
_password = value;
asyncClient.Password = value;
}
}
///
/// Sets the username and the password for basic authentication.
///
public void SetCredentials(string userName, string password)
{
this.UserName = userName;
this.Password = password;
}
public string BaseUri { get; set; }
public abstract string Format { get; }
public string SyncReplyBaseUri { get; set; }
public string AsyncOneWayBaseUri { get; set; }
private TimeSpan? timeout;
public TimeSpan? Timeout
{
get { return this.timeout; }
set
{
this.timeout = value;
this.asyncClient.Timeout = value;
}
}
public abstract string ContentType { get; }
public string HttpMethod { get; set; }
#if !SILVERLIGHT
public IWebProxy Proxy { get; set; }
#endif
#if SILVERLIGHT
private bool handleCallbackOnUiThread;
public bool HandleCallbackOnUIThread
{
get { return this.handleCallbackOnUiThread; }
set { asyncClient.HandleCallbackOnUIThread = this.handleCallbackOnUiThread = value; }
}
private bool useBrowserHttpHandling;
public bool UseBrowserHttpHandling
{
get { return this.useBrowserHttpHandling; }
set { asyncClient.UseBrowserHttpHandling = this.useBrowserHttpHandling = value; }
}
private bool shareCookiesWithBrowser;
public bool ShareCookiesWithBrowser
{
get { return this.shareCookiesWithBrowser; }
set { asyncClient.ShareCookiesWithBrowser = this.shareCookiesWithBrowser = value; }
}
#endif
private ICredentials credentials;
///
/// Gets or sets authentication information for the request.
/// Warning: It's recommened to use and for basic auth.
/// This property is only used for IIS level authentication.
///
public ICredentials Credentials
{
set
{
this.credentials = value;
this.asyncClient.Credentials = value;
}
}
///
/// Determines if the basic auth header should be sent with every request.
/// By default, the basic auth header is only sent when "401 Unauthorized" is returned.
///
public bool AlwaysSendBasicAuthHeader { get; set; }
///
/// Specifies if cookies should be stored
///
private bool storeCookies;
public bool StoreCookies
{
get { return storeCookies; }
set { asyncClient.StoreCookies = storeCookies = value; }
}
public CookieContainer CookieContainer { get; set; }
///
/// Called before request resend, when the initial request required authentication
///
private Action onAuthenticationRequired { get; set; }
public Action OnAuthenticationRequired
{
get
{
return onAuthenticationRequired;
}
set
{
onAuthenticationRequired = value;
asyncClient.OnAuthenticationRequired = value;
}
}
///
/// The request filter is called before any request.
/// This request filter only works with the instance where it was set (not global).
///
private Action localHttpWebRequestFilter { get; set; }
public Action LocalHttpWebRequestFilter
{
get
{
return localHttpWebRequestFilter;
}
set
{
localHttpWebRequestFilter = value;
asyncClient.LocalHttpWebRequestFilter = value;
}
}
///
/// The response action is called once the server response is available.
/// It will allow you to access raw response information.
/// Note that you should NOT consume the response stream as this is handled by ServiceStack
///
private Action localHttpWebResponseFilter { get; set; }
public Action LocalHttpWebResponseFilter
{
get
{
return localHttpWebResponseFilter;
}
set
{
localHttpWebResponseFilter = value;
asyncClient.LocalHttpWebResponseFilter = value;
}
}
public abstract void SerializeToStream(IRequestContext requestContext, object request, Stream stream);
public abstract T DeserializeFromStream(Stream stream);
public abstract StreamDeserializerDelegate StreamDeserializer { get; }
#if !SILVERLIGHT
public virtual TResponse Send(object request)
{
var requestUri = this.SyncReplyBaseUri.WithTrailingSlash() + request.GetType().Name;
var client = SendRequest(requestUri, request);
try
{
var webResponse = client.GetResponse();
ApplyWebResponseFilters(webResponse);
using (var responseStream = webResponse.GetResponseStream())
{
var response = DeserializeFromStream(responseStream);
return response;
}
}
catch (Exception ex)
{
TResponse response;
if (!HandleResponseException(ex, Web.HttpMethod.Post, requestUri, request, out response))
{
throw;
}
return response;
}
}
private bool HandleResponseException(Exception ex, string httpMethod, string requestUri, object request, out TResponse response)
{
try
{
if (WebRequestUtils.ShouldAuthenticate(ex, this.UserName, this.Password))
{
var client = SendRequest(httpMethod, requestUri, request);
client.AddBasicAuth(this.UserName, this.Password);
if (OnAuthenticationRequired != null)
{
OnAuthenticationRequired(client);
}
try
{
var webResponse = client.GetResponse();
ApplyWebResponseFilters(webResponse);
using (var responseStream = webResponse.GetResponseStream())
{
response = DeserializeFromStream(responseStream);
return true;
}
}
catch { /* Ignore deserializing error exceptions */ }
}
}
catch (Exception subEx)
{
// Since we are effectively re-executing the call,
// the new exception should be shown to the caller rather
// than the old one.
// The new exception is either this one or the one thrown
// by the following method.
HandleResponseException(subEx, requestUri);
throw;
}
// If this doesn't throw, the calling method
// should rethrow the original exception upon
// return value of false.
HandleResponseException(ex, requestUri);
response = default(TResponse);
return false;
}
private void HandleResponseException(Exception ex, string requestUri)
{
var webEx = ex as WebException;
if (webEx != null && webEx.Status == WebExceptionStatus.ProtocolError)
{
var errorResponse = ((HttpWebResponse)webEx.Response);
log.Error(webEx);
log.DebugFormat("Status Code : {0}", errorResponse.StatusCode);
log.DebugFormat("Status Description : {0}", errorResponse.StatusDescription);
var serviceEx = new WebServiceException(errorResponse.StatusDescription)
{
StatusCode = (int)errorResponse.StatusCode,
StatusDescription = errorResponse.StatusDescription,
};
try
{
using (var stream = errorResponse.GetResponseStream())
{
serviceEx.ResponseDto = DeserializeFromStream(stream);
}
}
catch (Exception innerEx)
{
// Oh, well, we tried
throw new WebServiceException(errorResponse.StatusDescription, innerEx)
{
StatusCode = (int)errorResponse.StatusCode,
StatusDescription = errorResponse.StatusDescription,
};
}
//Escape deserialize exception handling and throw here
throw serviceEx;
}
var authEx = ex as AuthenticationException;
if (authEx != null)
{
throw WebRequestUtils.CreateCustomException(requestUri, authEx);
}
}
private WebRequest SendRequest(string requestUri, object request)
{
return SendRequest(HttpMethod ?? DefaultHttpMethod, requestUri, request);
}
private WebRequest SendRequest(string httpMethod, string requestUri, object request)
{
if (httpMethod == null)
throw new ArgumentNullException("httpMethod");
if (httpMethod == Web.HttpMethod.Get && request != null)
{
var queryString = QueryStringSerializer.SerializeToString(request);
if (!string.IsNullOrEmpty(queryString))
{
requestUri += "?" + queryString;
}
}
var client = (HttpWebRequest)WebRequest.Create(requestUri);
try
{
client.Accept = ContentType;
client.Method = httpMethod;
if (Proxy != null) client.Proxy = Proxy;
if (this.Timeout.HasValue) client.Timeout = (int)this.Timeout.Value.TotalMilliseconds;
if (this.credentials != null) client.Credentials = this.credentials;
if (this.AlwaysSendBasicAuthHeader) client.AddBasicAuth(this.UserName, this.Password);
if (!DisableAutoCompression)
{
client.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
client.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
if (StoreCookies)
{
client.CookieContainer = CookieContainer;
}
ApplyWebRequestFilters(client);
if (httpMethod != Web.HttpMethod.Get
&& httpMethod != Web.HttpMethod.Delete)
{
client.ContentType = ContentType;
using (var requestStream = client.GetRequestStream())
{
SerializeToStream(null, request, requestStream);
}
}
}
catch (AuthenticationException ex)
{
throw WebRequestUtils.CreateCustomException(requestUri, ex) ?? ex;
}
return client;
}
private void ApplyWebResponseFilters(WebResponse webResponse)
{
if (!(webResponse is HttpWebResponse)) return;
if (HttpWebResponseFilter != null)
HttpWebResponseFilter((HttpWebResponse)webResponse);
if (LocalHttpWebResponseFilter != null)
LocalHttpWebResponseFilter((HttpWebResponse)webResponse);
}
private void ApplyWebRequestFilters(HttpWebRequest client)
{
if (LocalHttpWebRequestFilter != null)
LocalHttpWebRequestFilter(client);
if (HttpWebRequestFilter != null)
HttpWebRequestFilter(client);
}
#else
private void SendRequest(string requestUri, object request, Action callback)
{
var isHttpGet = HttpMethod != null && HttpMethod.ToUpper() == "GET";
if (isHttpGet)
{
var queryString = QueryStringSerializer.SerializeToString(request);
if (!string.IsNullOrEmpty(queryString))
{
requestUri += "?" + queryString;
}
}
SendRequest(HttpMethod ?? DefaultHttpMethod, requestUri, request, callback);
}
private void SendRequest(string httpMethod, string requestUri, object request, Action callback)
{
if (httpMethod == null)
throw new ArgumentNullException("httpMethod");
var client = (HttpWebRequest)WebRequest.Create(requestUri);
try
{
client.Accept = ContentType;
client.Method = httpMethod;
if (this.credentials != null) client.Credentials = this.credentials;
if (this.AlwaysSendBasicAuthHeader) client.AddBasicAuth(this.UserName, this.Password);
if (StoreCookies)
{
client.CookieContainer = CookieContainer;
}
if (this.LocalHttpWebRequestFilter != null)
LocalHttpWebRequestFilter(client);
if (HttpWebRequestFilter != null)
HttpWebRequestFilter(client);
if (httpMethod != Web.HttpMethod.Get
&& httpMethod != Web.HttpMethod.Delete)
{
client.ContentType = ContentType;
client.BeginGetRequestStream(delegate(IAsyncResult target)
{
var webReq = (HttpWebRequest)target.AsyncState;
var requestStream = webReq.EndGetRequestStream(target);
SerializeToStream(null, request, requestStream);
callback(client);
}, null);
}
}
catch (AuthenticationException ex)
{
throw WebRequestUtils.CreateCustomException(requestUri, ex) ?? ex;
}
}
#endif
private string GetUrl(string relativeOrAbsoluteUrl)
{
return relativeOrAbsoluteUrl.StartsWith("http:")
|| relativeOrAbsoluteUrl.StartsWith("https:")
? relativeOrAbsoluteUrl
: this.BaseUri.CombineWith(relativeOrAbsoluteUrl);
}
#if !SILVERLIGHT
private byte[] DownloadBytes(string requestUri, object request)
{
var webRequest = SendRequest(requestUri, request);
using (var response = webRequest.GetResponse())
{
ApplyWebResponseFilters(response);
using (var stream = response.GetResponseStream())
return stream.ReadFully();
}
}
#else
private void DownloadBytes(string requestUri, object request, Action callback = null)
{
SendRequest(requestUri, request, webRequest => webRequest.BeginGetResponse(delegate(IAsyncResult result)
{
var webReq = (HttpWebRequest)result.AsyncState;
var response = (HttpWebResponse)webReq.EndGetResponse(result);
using (var stream = response.GetResponseStream())
{
var bytes = stream.ReadFully();
if (callback != null)
{
callback(bytes);
}
}
}, null));
}
#endif
public virtual void SendOneWay(object request)
{
var requestUri = this.AsyncOneWayBaseUri.WithTrailingSlash() + request.GetType().Name;
DownloadBytes(requestUri, request);
}
public virtual void SendOneWay(string relativeOrAbsoluteUrl, object request)
{
var requestUri = GetUrl(relativeOrAbsoluteUrl);
DownloadBytes(requestUri, request);
}
public virtual void SendAsync(object request, Action onSuccess, Action onError)
{
var requestUri = this.SyncReplyBaseUri.WithTrailingSlash() + request.GetType().Name;
asyncClient.SendAsync(Web.HttpMethod.Post, requestUri, request, onSuccess, onError);
}
public virtual void GetAsync(string relativeOrAbsoluteUrl, Action onSuccess, Action onError)
{
asyncClient.SendAsync(Web.HttpMethod.Get, GetUrl(relativeOrAbsoluteUrl), null, onSuccess, onError);
}
public virtual void DeleteAsync(string relativeOrAbsoluteUrl, Action onSuccess, Action onError)
{
asyncClient.SendAsync(Web.HttpMethod.Delete, GetUrl(relativeOrAbsoluteUrl), null, onSuccess, onError);
}
public virtual void PostAsync(string relativeOrAbsoluteUrl, object request, Action onSuccess, Action onError)
{
asyncClient.SendAsync(Web.HttpMethod.Post, GetUrl(relativeOrAbsoluteUrl), request, onSuccess, onError);
}
public virtual void PutAsync(string relativeOrAbsoluteUrl, object request, Action onSuccess, Action onError)
{
asyncClient.SendAsync(Web.HttpMethod.Put, GetUrl(relativeOrAbsoluteUrl), request, onSuccess, onError);
}
public virtual void CancelAsync()
{
asyncClient.CancelAsync();
}
#if !SILVERLIGHT
public virtual TResponse Send(string httpMethod, string relativeOrAbsoluteUrl, object request)
{
var requestUri = GetUrl(relativeOrAbsoluteUrl);
var client = SendRequest(httpMethod, requestUri, request);
try
{
var webResponse = client.GetResponse();
ApplyWebResponseFilters(webResponse);
using (var responseStream = webResponse.GetResponseStream())
{
var response = DeserializeFromStream(responseStream);
return response;
}
}
catch (Exception ex)
{
TResponse response;
if (!HandleResponseException(ex, httpMethod, requestUri, request, out response))
{
throw;
}
return response;
}
}
public virtual TResponse Get(string relativeOrAbsoluteUrl)
{
return Send(Web.HttpMethod.Get, relativeOrAbsoluteUrl, null);
}
public virtual TResponse Delete(string relativeOrAbsoluteUrl)
{
return Send(Web.HttpMethod.Delete, relativeOrAbsoluteUrl, null);
}
public virtual TResponse Post(string relativeOrAbsoluteUrl, object request)
{
return Send(Web.HttpMethod.Post, relativeOrAbsoluteUrl, request);
}
public virtual TResponse Put(string relativeOrAbsoluteUrl, object request)
{
return Send(Web.HttpMethod.Put, relativeOrAbsoluteUrl, request);
}
public virtual TResponse PostFileWithRequest(string relativeOrAbsoluteUrl, FileInfo fileToUpload, object request)
{
return PostFileWithRequest(relativeOrAbsoluteUrl, fileToUpload.OpenRead(), fileToUpload.Name, request);
}
public virtual TResponse PostFileWithRequest(string relativeOrAbsoluteUrl, Stream fileToUpload, string fileName, object request)
{
var requestUri = GetUrl(relativeOrAbsoluteUrl);
var webRequest = (HttpWebRequest)WebRequest.Create(requestUri);
webRequest.Method = Web.HttpMethod.Post;
webRequest.Accept = ContentType;
if (Proxy != null) webRequest.Proxy = Proxy;
try
{
ApplyWebRequestFilters(webRequest);
var queryString = QueryStringSerializer.SerializeToString(request);
#if !MONOTOUCH
var nameValueCollection = HttpUtility.ParseQueryString(queryString);
#endif
var boundary = DateTime.Now.Ticks.ToString();
webRequest.ContentType = "multipart/form-data; boundary=" + boundary;
boundary = "--" + boundary;
var newLine = Environment.NewLine;
using (var outputStream = webRequest.GetRequestStream())
{
#if !MONOTOUCH
foreach (var key in nameValueCollection.AllKeys)
{
outputStream.Write(boundary + newLine);
outputStream.Write("Content-Disposition: form-data;name=\"{0}\"{1}{2}".FormatWith(key, newLine, newLine));
outputStream.Write(nameValueCollection[key] + newLine);
}
#endif
outputStream.Write(boundary + newLine);
outputStream.Write("Content-Disposition: form-data;name=\"{0}\";filename=\"{1}\"{2}{3}".FormatWith("upload", fileName, newLine, newLine));
var buffer = new byte[4096];
int byteCount;
while ((byteCount = fileToUpload.Read(buffer, 0, 4096)) > 0)
{
outputStream.Write(buffer, 0, byteCount);
}
outputStream.Write(newLine);
outputStream.Write(boundary + "--");
}
var webResponse = webRequest.GetResponse();
ApplyWebResponseFilters(webResponse);
using (var responseStream = webResponse.GetResponseStream())
{
var response = DeserializeFromStream(responseStream);
return response;
}
}
catch (Exception ex)
{
HandleResponseException(ex, requestUri);
throw;
}
}
public virtual TResponse PostFile(string relativeOrAbsoluteUrl, FileInfo fileToUpload, string mimeType)
{
var requestUri = GetUrl(relativeOrAbsoluteUrl);
var webRequest = (HttpWebRequest)WebRequest.Create(requestUri);
webRequest.Method = Web.HttpMethod.Post;
webRequest.Accept = ContentType;
if (Proxy != null) webRequest.Proxy = Proxy;
try
{
ApplyWebRequestFilters(webRequest);
var webResponse = webRequest.UploadFile(fileToUpload, mimeType);
ApplyWebResponseFilters(webResponse);
using (var responseStream = webResponse.GetResponseStream())
{
var response = DeserializeFromStream(responseStream);
return response;
}
}
catch (Exception ex)
{
HandleResponseException(ex, requestUri);
throw;
}
}
public virtual TResponse PostFile(string relativeOrAbsoluteUrl, Stream fileToUpload, string fileName, string mimeType)
{
var requestUri = GetUrl(relativeOrAbsoluteUrl);
var webRequest = (HttpWebRequest)WebRequest.Create(requestUri);
webRequest.Method = Web.HttpMethod.Post;
webRequest.Accept = ContentType;
if (Proxy != null) webRequest.Proxy = Proxy;
try
{
ApplyWebRequestFilters(webRequest);
webRequest.UploadFile(fileToUpload, fileName, mimeType);
var webResponse = webRequest.GetResponse();
ApplyWebResponseFilters(webResponse);
using (var responseStream = webResponse.GetResponseStream())
{
var response = DeserializeFromStream(responseStream);
return response;
}
}
catch (Exception ex)
{
HandleResponseException(ex, requestUri);
throw;
}
}
#endif
public void Dispose() { }
}
}