PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Client.Silverlight/Connection/HttpJsonRequest.cs

http://github.com/ravendb/ravendb
C# | 346 lines | 268 code | 38 blank | 40 comment | 38 complexity | 0ae8697c2f104f4cf13b13518514ad2a MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  1. //-----------------------------------------------------------------------
  2. // <copyright file="HttpJsonRequest.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System;
  7. using System.Diagnostics;
  8. using System.Linq;
  9. using System.Collections.Generic;
  10. using System.IO;
  11. using System.Net;
  12. using System.Net.Browser;
  13. using System.Text;
  14. using System.Threading.Tasks;
  15. using Ionic.Zlib;
  16. using Newtonsoft.Json;
  17. using Newtonsoft.Json.Linq;
  18. using Raven.Abstractions.Data;
  19. using Raven.Abstractions.Extensions;
  20. using Raven.Client.Connection;
  21. using Raven.Client.Document;
  22. using Raven.Json.Linq;
  23. using Raven.Client.Extensions;
  24. using Raven.Abstractions.Connection;
  25. namespace Raven.Client.Silverlight.Connection
  26. {
  27. /// <summary>
  28. /// A representation of an HTTP json request to the RavenDB server
  29. /// Since we are using the ClientHttp stack for Silverlight, we don't need to implement
  30. /// caching, it is already implemented for us.
  31. /// Note: that the RavenDB server generates both an ETag and an Expires header to ensure proper
  32. /// Note: behavior from the silverlight http stack
  33. /// </summary>
  34. public class HttpJsonRequest
  35. {
  36. private readonly string url;
  37. private readonly DocumentConvention conventions;
  38. internal HttpWebRequest webRequest;
  39. private byte[] postedData;
  40. private int retries;
  41. private Task RecreateWebRequest(Action<HttpWebRequest> result)
  42. {
  43. retries++;
  44. // we now need to clone the request, since just calling GetRequest again wouldn't do anything
  45. var newWebRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(new Uri(url));
  46. newWebRequest.Method = webRequest.Method;
  47. HttpJsonRequestHelper.CopyHeaders(webRequest, newWebRequest);
  48. newWebRequest.Credentials = webRequest.Credentials;
  49. result(newWebRequest);
  50. webRequest = newWebRequest;
  51. if (postedData == null)
  52. {
  53. var taskCompletionSource = new TaskCompletionSource<object>();
  54. taskCompletionSource.SetResult(null);
  55. return taskCompletionSource.Task;
  56. }
  57. else return WriteAsync(postedData);
  58. }
  59. private HttpJsonRequestFactory factory;
  60. /// <summary>
  61. /// Gets or sets the response headers.
  62. /// </summary>
  63. /// <value>The response headers.</value>
  64. public IDictionary<string, IList<string>> ResponseHeaders { get; set; }
  65. internal HttpJsonRequest(string url, string method, RavenJObject metadata, DocumentConvention conventions, HttpJsonRequestFactory factory)
  66. {
  67. this.url = url;
  68. this.conventions = conventions;
  69. this.factory = factory;
  70. webRequest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(new Uri(url));
  71. var tcs = new TaskCompletionSource<object>();
  72. tcs.SetResult(null);
  73. WaitForTask = tcs.Task;
  74. WriteMetadata(metadata);
  75. webRequest.Method = method;
  76. if (method != "GET")
  77. webRequest.ContentType = "application/json; charset=utf-8";
  78. if( (method == "POST" || method == "PUT") &&
  79. factory.DisableRequestCompression == false)
  80. webRequest.Headers["Content-Encoding"] = "gzip";
  81. }
  82. public Task<RavenJToken> ReadResponseJsonAsync()
  83. {
  84. return ReadResponseStringAsync()
  85. .ContinueWith(task => RavenJToken.Parse(task.Result));
  86. }
  87. public Task ExecuteRequest()
  88. {
  89. return ReadResponseStringAsync();
  90. }
  91. /// <summary>
  92. /// Begins the read response string.
  93. /// </summary>
  94. private Task<string> ReadResponseStringAsync()
  95. {
  96. return WaitForTask.ContinueWith(_ => webRequest
  97. .GetResponseAsync()
  98. .ConvertSecurityExceptionToServerNotFound()
  99. .AddUrlIfFaulting(webRequest.RequestUri)
  100. .ContinueWith(t => ReadStringInternal(() => t.Result))
  101. .ContinueWith(task => RetryIfNeedTo(task, ReadResponseStringAsync))
  102. .Unwrap())
  103. .Unwrap();
  104. }
  105. private Task<T> RetryIfNeedTo<T>(Task<T> task, Func<Task<T>> generator)
  106. {
  107. var exception = task.Exception.ExtractSingleInnerException() as WebException;
  108. if (exception == null || retries >= 3)
  109. return task;
  110. var webResponse = exception.Response as HttpWebResponse;
  111. if (webResponse == null || webResponse.StatusCode != HttpStatusCode.Unauthorized)
  112. return task;
  113. var authorizeResponse = HandleUnauthorizedResponseAsync(webResponse);
  114. if (authorizeResponse == null)
  115. return task; // effectively throw
  116. return authorizeResponse
  117. .ContinueWith(task1 =>
  118. {
  119. task1.Wait();// throw if error
  120. return generator();
  121. })
  122. .Unwrap();
  123. }
  124. public Task HandleUnauthorizedResponseAsync(HttpWebResponse unauthorizedResponse)
  125. {
  126. if (conventions.HandleUnauthorizedResponseAsync == null)
  127. return null;
  128. var unauthorizedResponseAsync = conventions.HandleUnauthorizedResponseAsync(unauthorizedResponse);
  129. if (unauthorizedResponseAsync == null)
  130. return null;
  131. return unauthorizedResponseAsync.ContinueWith(task => RecreateWebRequest(task.Result)).Unwrap();
  132. }
  133. public Task<byte[]> ReadResponseBytesAsync()
  134. {
  135. return WaitForTask.ContinueWith(_ => webRequest
  136. .GetResponseAsync()
  137. .ConvertSecurityExceptionToServerNotFound()
  138. .AddUrlIfFaulting(webRequest.RequestUri)
  139. .ContinueWith(t => ReadResponse(() => t.Result, ConvertStreamToBytes))
  140. .ContinueWith(task => RetryIfNeedTo(task, ReadResponseBytesAsync))
  141. .Unwrap())
  142. .Unwrap();
  143. }
  144. static byte[] ConvertStreamToBytes(Stream input)
  145. {
  146. var buffer = new byte[16 * 1024];
  147. using (var ms = new MemoryStream())
  148. {
  149. int read;
  150. while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
  151. {
  152. ms.Write(buffer, 0, read);
  153. }
  154. return ms.ToArray();
  155. }
  156. }
  157. private string ReadStringInternal(Func<WebResponse> getResponse)
  158. {
  159. return ReadResponse(getResponse, responseStream =>
  160. {
  161. var reader = new StreamReader(responseStream);
  162. var text = reader.ReadToEnd();
  163. return text;
  164. }
  165. );
  166. }
  167. private T ReadResponse<T>(Func<WebResponse> getResponse, Func<Stream, T> handleResponse)
  168. {
  169. WebResponse response;
  170. try
  171. {
  172. response = getResponse();
  173. }
  174. catch (WebException e)
  175. {
  176. var httpWebResponse = e.Response as HttpWebResponse;
  177. if (httpWebResponse == null ||
  178. httpWebResponse.StatusCode == HttpStatusCode.NotFound ||
  179. httpWebResponse.StatusCode == HttpStatusCode.Conflict)
  180. throw;
  181. using (var sr = new StreamReader(e.Response.GetResponseStream()))
  182. {
  183. throw new InvalidOperationException(sr.ReadToEnd(), e);
  184. }
  185. }
  186. ResponseHeaders = new Dictionary<string, IList<string>>(StringComparer.InvariantCultureIgnoreCase);
  187. foreach (var key in response.Headers.AllKeys)
  188. {
  189. ResponseHeaders[key] = new List<string>
  190. {
  191. response.Headers[key]
  192. };
  193. }
  194. ResponseStatusCode = ((HttpWebResponse)response).StatusCode;
  195. using (var responseStream = response.GetResponseStreamWithHttpDecompression())
  196. {
  197. return handleResponse(responseStream);
  198. }
  199. }
  200. /// <summary>
  201. /// Gets or sets the response status code.
  202. /// </summary>
  203. /// <value>The response status code.</value>
  204. public HttpStatusCode ResponseStatusCode { get; set; }
  205. /// <summary>
  206. /// The task to wait all other actions on
  207. /// </summary>
  208. public Task WaitForTask { get; set; }
  209. private void WriteMetadata(RavenJObject metadata)
  210. {
  211. foreach (var prop in metadata)
  212. {
  213. if (prop.Value == null)
  214. continue;
  215. string value;
  216. switch (prop.Value.Type)
  217. {
  218. case JTokenType.Array:
  219. value = prop.Value.Value<RavenJArray>().ToString(Formatting.None);
  220. break;
  221. case JTokenType.Object:
  222. value = prop.Value.Value<RavenJObject>().ToString(Formatting.None);
  223. break;
  224. default:
  225. value = prop.Value.Value<object>().ToString();
  226. break;
  227. }
  228. var headerName = prop.Key;
  229. if (headerName == "ETag")
  230. headerName = "If-None-Match";
  231. if(headerName.StartsWith("@") ||
  232. headerName == Constants.LastModified)
  233. continue;
  234. switch (headerName)
  235. {
  236. case "Content-Length":
  237. break;
  238. case "Content-Type":
  239. webRequest.ContentType = value;
  240. break;
  241. default:
  242. webRequest.Headers[headerName] = value;
  243. break;
  244. }
  245. }
  246. }
  247. /// <summary>
  248. /// Begins the write operation
  249. /// </summary>
  250. public Task WriteAsync(string data)
  251. {
  252. return WaitForTask.ContinueWith(_ =>
  253. webRequest.GetRequestStreamAsync()
  254. .ContinueWith(task =>
  255. {
  256. Stream dataStream = factory.DisableRequestCompression == false ?
  257. new GZipStream(task.Result, CompressionMode.Compress) :
  258. task.Result;
  259. var streamWriter = new StreamWriter(dataStream, Encoding.UTF8);
  260. return streamWriter.WriteAsync(data)
  261. .ContinueWith(writeTask =>
  262. {
  263. streamWriter.Dispose();
  264. dataStream.Dispose();
  265. task.Result.Dispose();
  266. return writeTask;
  267. }).Unwrap();
  268. }).Unwrap())
  269. .Unwrap();
  270. }
  271. /// <summary>
  272. /// Begins the write operation
  273. /// </summary>
  274. public Task WriteAsync(byte[] byteArray)
  275. {
  276. postedData = byteArray;
  277. return WaitForTask.ContinueWith(_ => webRequest.GetRequestStreamAsync().ContinueWith(t =>
  278. {
  279. var dataStream =new GZipStream(t.Result,CompressionMode.Compress);
  280. using (dataStream)
  281. {
  282. dataStream.Write(byteArray, 0, byteArray.Length);
  283. dataStream.Close();
  284. }
  285. })).Unwrap();
  286. }
  287. /// <summary>
  288. /// Adds the operation headers.
  289. /// </summary>
  290. /// <param name="operationsHeaders">The operations headers.</param>
  291. public HttpJsonRequest AddOperationHeaders(IDictionary<string, string> operationsHeaders)
  292. {
  293. foreach (var header in operationsHeaders)
  294. {
  295. webRequest.Headers[header.Key] = header.Value;
  296. }
  297. return this;
  298. }
  299. /// <summary>
  300. /// Adds the operation header
  301. /// </summary>
  302. public HttpJsonRequest AddOperationHeader(string key, string value)
  303. {
  304. webRequest.Headers[key] = value;
  305. return this;
  306. }
  307. }
  308. }