PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/Raven.Database/Server/Controllers/StreamsController.cs

https://github.com/nwendel/ravendb
C# | 402 lines | 350 code | 49 blank | 3 comment | 17 complexity | b6c0a9cbe94506a6299a273beec1c63b MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Net;
  7. using System.Net.Http;
  8. using System.Net.Http.Headers;
  9. using System.Security.Authentication.ExtendedProtection;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using System.Web.Http;
  14. using Raven.Abstractions;
  15. using Raven.Abstractions.Data;
  16. using Raven.Abstractions.Extensions;
  17. using Raven.Abstractions.Util;
  18. using Raven.Database.Actions;
  19. using Raven.Database.Extensions;
  20. using Raven.Database.Impl;
  21. using Raven.Database.Storage;
  22. using Raven.Imports.Newtonsoft.Json;
  23. using Raven.Imports.Newtonsoft.Json.Linq;
  24. using Raven.Json.Linq;
  25. using System.Linq;
  26. namespace Raven.Database.Server.Controllers
  27. {
  28. public class StreamsController : RavenDbApiController
  29. {
  30. [HttpGet]
  31. [Route("streams/docs")]
  32. [Route("databases/{databaseName}/streams/docs")]
  33. public HttpResponseMessage StreamDocsGet()
  34. {
  35. var start = GetStart();
  36. var etag = GetEtagFromQueryString();
  37. var startsWith = GetQueryStringValue("startsWith");
  38. var pageSize = GetPageSize(int.MaxValue);
  39. var matches = GetQueryStringValue("matches");
  40. var nextPageStart = GetNextPageStart();
  41. if (string.IsNullOrEmpty(GetQueryStringValue("pageSize")))
  42. pageSize = int.MaxValue;
  43. var skipAfter = GetQueryStringValue("skipAfter");
  44. return new HttpResponseMessage(HttpStatusCode.OK)
  45. {
  46. Content = new PushStreamContent((stream, content, transportContext) =>
  47. StreamToClient(stream, startsWith, start, pageSize, etag, matches, nextPageStart, skipAfter))
  48. {
  49. Headers =
  50. {
  51. ContentType = new MediaTypeHeaderValue("application/json") { CharSet = "utf-8" }
  52. }
  53. }
  54. };
  55. }
  56. private void StreamToClient(Stream stream, string startsWith, int start, int pageSize, Etag etag, string matches, int nextPageStart, string skipAfter)
  57. {
  58. using (var cts = new CancellationTokenSource())
  59. using (var timeout = cts.TimeoutAfter(DatabasesLandlord.SystemConfiguration.DatabaseOperationTimeout))
  60. using (var writer = new JsonTextWriter(new StreamWriter(stream)))
  61. {
  62. writer.WriteStartObject();
  63. writer.WritePropertyName("Results");
  64. writer.WriteStartArray();
  65. Database.TransactionalStorage.Batch(accessor =>
  66. {
  67. // we may be sending a LOT of documents to the user, and most
  68. // of them aren't going to be relevant for other ops, so we are going to skip
  69. // the cache for that, to avoid filling it up very quickly
  70. using (DocumentCacher.SkipSettingDocumentsInDocumentCache())
  71. {
  72. if (string.IsNullOrEmpty(startsWith))
  73. Database.Documents.GetDocuments(start, pageSize, etag, cts.Token, doc =>
  74. {
  75. timeout.Delay();
  76. doc.WriteTo(writer);
  77. writer.WriteRaw(Environment.NewLine);
  78. });
  79. else
  80. {
  81. var nextPageStartInternal = nextPageStart;
  82. Database.Documents.GetDocumentsWithIdStartingWith(startsWith, matches, null, start, pageSize, cts.Token, ref nextPageStartInternal, doc =>
  83. {
  84. timeout.Delay();
  85. doc.WriteTo(writer);
  86. writer.WriteRaw(Environment.NewLine);
  87. },skipAfter: skipAfter);
  88. nextPageStart = nextPageStartInternal;
  89. }
  90. }
  91. });
  92. writer.WriteEndArray();
  93. writer.WritePropertyName("NextPageStart");
  94. writer.WriteValue(nextPageStart);
  95. writer.WriteEndObject();
  96. writer.Flush();
  97. }
  98. }
  99. [HttpGet]
  100. [Route("streams/query/{*id}")]
  101. [Route("databases/{databaseName}/streams/query/{*id}")]
  102. public HttpResponseMessage SteamQueryGet(string id)
  103. {
  104. var cts = new CancellationTokenSource();
  105. var timeout = cts.TimeoutAfter(DatabasesLandlord.SystemConfiguration.DatabaseOperationTimeout);
  106. var msg = GetEmptyMessage();
  107. var index = id;
  108. var query = GetIndexQuery(int.MaxValue);
  109. if (string.IsNullOrEmpty(GetQueryStringValue("pageSize"))) query.PageSize = int.MaxValue;
  110. var isHeadRequest = InnerRequest.Method == HttpMethod.Head;
  111. if (isHeadRequest) query.PageSize = 0;
  112. var accessor = Database.TransactionalStorage.CreateAccessor(); //accessor will be disposed in the StreamQueryContent.SerializeToStreamAsync!
  113. try
  114. {
  115. var queryOp = new QueryActions.DatabaseQueryOperation(Database, index, query, accessor, cts);
  116. queryOp.Init();
  117. msg.Content = new StreamQueryContent(InnerRequest, queryOp, accessor, timeout,
  118. mediaType => msg.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType) {CharSet = "utf-8"});
  119. msg.Headers.Add("Raven-Result-Etag", queryOp.Header.ResultEtag.ToString());
  120. msg.Headers.Add("Raven-Index-Etag", queryOp.Header.IndexEtag.ToString());
  121. msg.Headers.Add("Raven-Is-Stale", queryOp.Header.IsStale ? "true" : "false");
  122. msg.Headers.Add("Raven-Index", queryOp.Header.Index);
  123. msg.Headers.Add("Raven-Total-Results", queryOp.Header.TotalResults.ToString(CultureInfo.InvariantCulture));
  124. msg.Headers.Add(
  125. "Raven-Index-Timestamp", queryOp.Header.IndexTimestamp.ToString(Default.DateTimeFormatsToWrite, CultureInfo.InvariantCulture));
  126. if (IsCsvDownloadRequest(InnerRequest))
  127. {
  128. msg.Content.Headers.Add("Content-Disposition", "attachment; filename=export.csv");
  129. }
  130. }
  131. catch (Exception)
  132. {
  133. accessor.Dispose();
  134. throw;
  135. }
  136. return msg;
  137. }
  138. [HttpPost]
  139. [Route("streams/query/{*id}")]
  140. [Route("databases/{databaseName}/streams/query/{*id}")]
  141. public async Task<HttpResponseMessage> SteamQueryPost(string id)
  142. {
  143. if ("true".Equals(GetQueryStringValue("postQuery"), StringComparison.InvariantCultureIgnoreCase))
  144. {
  145. var postedQuery = await ReadStringAsync();
  146. SetPostRequestQuery(postedQuery);
  147. return SteamQueryGet(id);
  148. }
  149. return GetMessageWithString("Not idea how to handle a POST on " + id + " without a posted query", HttpStatusCode.BadRequest);
  150. }
  151. public class StreamQueryContent : HttpContent
  152. {
  153. private readonly HttpRequestMessage req;
  154. private readonly QueryActions.DatabaseQueryOperation queryOp;
  155. private readonly IStorageActionsAccessor accessor;
  156. private readonly CancellationTimeout _timeout;
  157. private readonly Action<string> outputContentTypeSetter;
  158. public StreamQueryContent(HttpRequestMessage req, QueryActions.DatabaseQueryOperation queryOp, IStorageActionsAccessor accessor, CancellationTimeout timeout, Action<string> contentTypeSetter)
  159. {
  160. this.req = req;
  161. this.queryOp = queryOp;
  162. this.accessor = accessor;
  163. _timeout = timeout;
  164. outputContentTypeSetter = contentTypeSetter;
  165. }
  166. protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
  167. {
  168. using (queryOp)
  169. using (accessor)
  170. using(_timeout)
  171. using (var writer = GetOutputWriter(req, stream))
  172. {
  173. outputContentTypeSetter(writer.ContentType);
  174. writer.WriteHeader();
  175. try
  176. {
  177. queryOp.Execute(o =>
  178. {
  179. _timeout.Delay();
  180. writer.Write(o);
  181. });
  182. }
  183. catch (Exception e)
  184. {
  185. writer.WriteError(e);
  186. }
  187. }
  188. return Task.FromResult(true);
  189. }
  190. protected override bool TryComputeLength(out long length)
  191. {
  192. length = -1;
  193. return false;
  194. }
  195. }
  196. private static IOutputWriter GetOutputWriter(HttpRequestMessage req, Stream stream)
  197. {
  198. var useExcelFormat = "excel".Equals(GetQueryStringValue(req, "format"), StringComparison.InvariantCultureIgnoreCase);
  199. return useExcelFormat ? (IOutputWriter)new ExcelOutputWriter(stream) : new JsonOutputWriter(stream);
  200. }
  201. private static Boolean IsCsvDownloadRequest(HttpRequestMessage req)
  202. {
  203. return "true".Equals(GetQueryStringValue(req, "download"), StringComparison.InvariantCultureIgnoreCase)
  204. && "excel".Equals(GetQueryStringValue(req, "format"), StringComparison.InvariantCultureIgnoreCase);
  205. }
  206. public interface IOutputWriter : IDisposable
  207. {
  208. string ContentType { get; }
  209. void WriteHeader();
  210. void Write(RavenJObject result);
  211. void WriteError(Exception exception);
  212. }
  213. private class ExcelOutputWriter : IOutputWriter
  214. {
  215. private const string CsvContentType = "text/csv";
  216. private readonly Stream stream;
  217. private StreamWriter writer;
  218. public ExcelOutputWriter(Stream stream)
  219. {
  220. this.stream = stream;
  221. }
  222. public string ContentType
  223. {
  224. get { return CsvContentType; }
  225. }
  226. public void Dispose()
  227. {
  228. if (writer == null)
  229. return;
  230. writer.Flush();
  231. writer.Close();
  232. }
  233. public void WriteHeader()
  234. {
  235. writer = new StreamWriter(stream, Encoding.UTF8);
  236. }
  237. public void Write(RavenJObject result)
  238. {
  239. if (properties == null)
  240. {
  241. GetPropertiesAndWriteCsvHeader(result);
  242. Debug.Assert(properties != null);
  243. }
  244. foreach (var property in properties)
  245. {
  246. var token = result.SelectToken(property);
  247. if (token != null)
  248. {
  249. switch (token.Type)
  250. {
  251. case JTokenType.Null:
  252. break;
  253. case JTokenType.Array:
  254. case JTokenType.Object:
  255. OutputCsvValue(token.ToString(Formatting.None));
  256. break;
  257. default:
  258. OutputCsvValue(token.Value<string>());
  259. break;
  260. }
  261. }
  262. writer.Write(',');
  263. }
  264. writer.WriteLine();
  265. }
  266. public void WriteError(Exception exception)
  267. {
  268. writer.WriteLine();
  269. writer.WriteLine();
  270. writer.WriteLine(exception.ToString());
  271. }
  272. private void GetPropertiesAndWriteCsvHeader(RavenJObject result)
  273. {
  274. properties = DocumentHelpers.GetPropertiesFromJObject(result,
  275. parentPropertyPath: "",
  276. includeNestedProperties: true,
  277. includeMetadata: false,
  278. excludeParentPropertyNames: true).ToList();
  279. foreach (var property in properties)
  280. {
  281. OutputCsvValue(property);
  282. writer.Write(',');
  283. }
  284. writer.WriteLine();
  285. }
  286. private static readonly char[] RequireQuotesChars = { ',', '\r', '\n', '"' };
  287. private IEnumerable<string> properties;
  288. private void OutputCsvValue(string val)
  289. {
  290. var needsQuoutes = val.IndexOfAny(RequireQuotesChars) != -1;
  291. if (needsQuoutes)
  292. writer.Write('"');
  293. writer.Write(needsQuoutes ? val.Replace("\"", "\"\"") : val);
  294. if (needsQuoutes)
  295. writer.Write('"');
  296. }
  297. }
  298. public class JsonOutputWriter : IOutputWriter
  299. {
  300. private const string JsonContentType = "application/json";
  301. private readonly Stream stream;
  302. private JsonWriter writer;
  303. private bool closedArray = false;
  304. public JsonOutputWriter(Stream stream)
  305. {
  306. this.stream = stream;
  307. }
  308. public string ContentType
  309. {
  310. get { return JsonContentType; }
  311. }
  312. public void WriteHeader()
  313. {
  314. writer = new JsonTextWriter(new StreamWriter(stream));
  315. writer.WriteStartObject();
  316. writer.WritePropertyName("Results");
  317. writer.WriteStartArray();
  318. }
  319. public void Dispose()
  320. {
  321. if (writer == null)
  322. return;
  323. if (closedArray == false)
  324. writer.WriteEndArray();
  325. writer.WriteEndObject();
  326. writer.Flush();
  327. writer.Close();
  328. }
  329. public void Write(RavenJObject result)
  330. {
  331. result.WriteTo(writer, Default.Converters);
  332. writer.WriteRaw(Environment.NewLine);
  333. }
  334. public void WriteError(Exception exception)
  335. {
  336. closedArray = true;
  337. writer.WriteEndArray();
  338. writer.WritePropertyName("Error");
  339. writer.WriteValue(exception.ToString());
  340. }
  341. }
  342. }
  343. }