PageRenderTime 88ms CodeModel.GetById 22ms RepoModel.GetById 10ms app.codeStats 0ms

/Raven.Database/Server/Controllers/RavenBaseApiController.cs

https://github.com/nwendel/ravendb
C# | 643 lines | 526 code | 114 blank | 3 comment | 99 complexity | 19b0007c5d80b26ff00e4efb12b1ae03 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.IO.Compression;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Net.Http.Headers;
  11. using System.Security.Principal;
  12. using System.Text;
  13. using System.Text.RegularExpressions;
  14. using System.Threading.Tasks;
  15. using System.Web.Http;
  16. using System.Web.Http.Controllers;
  17. using Mono.CSharp;
  18. using Raven.Abstractions.Connection;
  19. using Raven.Abstractions.Data;
  20. using Raven.Abstractions.Exceptions;
  21. using Raven.Abstractions.Extensions;
  22. using Raven.Abstractions.Json;
  23. using Raven.Abstractions.Logging;
  24. using Raven.Abstractions.Util;
  25. using Raven.Client.Connection;
  26. using Raven.Database.Config;
  27. using Raven.Database.Server.Abstractions;
  28. using Raven.Database.Server.WebApi;
  29. using Raven.Imports.Newtonsoft.Json;
  30. using Raven.Imports.Newtonsoft.Json.Bson;
  31. using Raven.Imports.Newtonsoft.Json.Linq;
  32. using Raven.Json.Linq;
  33. namespace Raven.Database.Server.Controllers
  34. {
  35. public abstract class RavenBaseApiController : ApiController
  36. {
  37. protected static readonly ILog Log = LogManager.GetCurrentClassLogger();
  38. public abstract LogTenantType TenantType { get; }
  39. private HttpRequestMessage request;
  40. public HttpRequestMessage InnerRequest
  41. {
  42. get
  43. {
  44. return Request ?? request;
  45. }
  46. }
  47. public HttpHeaders InnerHeaders
  48. {
  49. get
  50. {
  51. var headers = new Headers();
  52. foreach (var header in InnerRequest.Headers)
  53. {
  54. if (header.Value.Count() == 1)
  55. headers.Add(header.Key, header.Value.First());
  56. else
  57. headers.Add(header.Key, header.Value.ToList());
  58. }
  59. if (InnerRequest.Content == null)
  60. return headers;
  61. foreach (var header in InnerRequest.Content.Headers)
  62. {
  63. if (header.Value.Count() == 1)
  64. headers.Add(header.Key, header.Value.First());
  65. else
  66. headers.Add(header.Key, header.Value.ToList());
  67. }
  68. return headers;
  69. }
  70. }
  71. public new IPrincipal User { get; set; }
  72. public bool WasAlreadyAuthorizedUsingSingleAuthToken { get; set; }
  73. protected virtual void InnerInitialization(HttpControllerContext controllerContext)
  74. {
  75. request = controllerContext.Request;
  76. User = controllerContext.RequestContext.Principal;
  77. }
  78. public async Task<T> ReadJsonObjectAsync<T>()
  79. {
  80. using (var stream = await InnerRequest.Content.ReadAsStreamAsync())
  81. using(var gzipStream = new GZipStream(stream, CompressionMode.Decompress))
  82. using (var streamReader = new StreamReader(stream, GetRequestEncoding()))
  83. {
  84. using (var jsonReader = new JsonTextReader(streamReader))
  85. {
  86. var result = JsonExtensions.CreateDefaultJsonSerializer();
  87. return (T)result.Deserialize(jsonReader, typeof(T));
  88. }
  89. }
  90. }
  91. public async Task<RavenJObject> ReadJsonAsync()
  92. {
  93. using (var stream = await InnerRequest.Content.ReadAsStreamAsync())
  94. using (var streamReader = new StreamReader(stream, GetRequestEncoding()))
  95. using (var jsonReader = new RavenJsonTextReader(streamReader))
  96. return RavenJObject.Load(jsonReader);
  97. }
  98. public async Task<RavenJArray> ReadJsonArrayAsync()
  99. {
  100. using (var stream = await InnerRequest.Content.ReadAsStreamAsync())
  101. using (var streamReader = new StreamReader(stream, GetRequestEncoding()))
  102. using (var jsonReader = new RavenJsonTextReader(streamReader))
  103. return RavenJArray.Load(jsonReader);
  104. }
  105. public async Task<string> ReadStringAsync()
  106. {
  107. using (var stream = await InnerRequest.Content.ReadAsStreamAsync())
  108. using (var streamReader = new StreamReader(stream, GetRequestEncoding()))
  109. return streamReader.ReadToEnd();
  110. }
  111. public async Task<RavenJArray> ReadBsonArrayAsync()
  112. {
  113. using (var stream = await InnerRequest.Content.ReadAsStreamAsync())
  114. using (var jsonReader = new BsonReader(stream))
  115. {
  116. var jObject = RavenJObject.Load(jsonReader);
  117. return new RavenJArray(jObject.Values<RavenJToken>());
  118. }
  119. }
  120. private Encoding GetRequestEncoding()
  121. {
  122. if (InnerRequest.Content.Headers.ContentType == null || string.IsNullOrWhiteSpace(InnerRequest.Content.Headers.ContentType.CharSet))
  123. return Encoding.GetEncoding(Constants.DefaultRequestEncoding);
  124. return Encoding.GetEncoding(InnerRequest.Content.Headers.ContentType.CharSet);
  125. }
  126. public int GetStart()
  127. {
  128. int start;
  129. int.TryParse(GetQueryStringValue("start"), out start);
  130. return Math.Max(0, start);
  131. }
  132. public int GetPageSize(int maxPageSize)
  133. {
  134. int pageSize;
  135. if (int.TryParse(GetQueryStringValue("pageSize"), out pageSize) == false)
  136. pageSize = 25;
  137. if (pageSize < 0)
  138. return 0;
  139. if (pageSize > maxPageSize)
  140. pageSize = maxPageSize;
  141. return pageSize;
  142. }
  143. public bool MatchEtag(Etag etag)
  144. {
  145. return EtagHeaderToEtag() == etag;
  146. }
  147. internal Etag EtagHeaderToEtag()
  148. {
  149. var responseHeader = GetHeader("If-None-Match");
  150. if (string.IsNullOrEmpty(responseHeader))
  151. return Etag.InvalidEtag;
  152. if (responseHeader[0] == '\"')
  153. return Etag.Parse(responseHeader.Substring(1, responseHeader.Length - 2));
  154. return Etag.Parse(responseHeader);
  155. }
  156. public string GetQueryStringValue(string key)
  157. {
  158. return GetQueryStringValue(InnerRequest, key);
  159. }
  160. public static string GetQueryStringValue(HttpRequestMessage req, string key)
  161. {
  162. var value = req.GetQueryNameValuePairs().Where(pair => pair.Key == key).Select(pair => pair.Value).FirstOrDefault();
  163. if (value != null)
  164. value = Uri.UnescapeDataString(value);
  165. return value;
  166. }
  167. public string[] GetQueryStringValues(string key)
  168. {
  169. var items = InnerRequest.GetQueryNameValuePairs().Where(pair => pair.Key == key);
  170. return items.Select(pair => (pair.Value != null) ? Uri.UnescapeDataString(pair.Value) : null ).ToArray();
  171. }
  172. public Etag GetEtagFromQueryString()
  173. {
  174. var etagAsString = GetQueryStringValue("etag");
  175. return etagAsString != null ? Etag.Parse(etagAsString) : null;
  176. }
  177. public void WriteETag(Etag etag, HttpResponseMessage msg)
  178. {
  179. if (etag == null)
  180. return;
  181. WriteETag(etag.ToString(), msg);
  182. }
  183. public void WriteETag(string etag, HttpResponseMessage msg)
  184. {
  185. if (string.IsNullOrWhiteSpace(etag))
  186. return;
  187. msg.Headers.ETag = new EntityTagHeaderValue("\"" + etag + "\"");
  188. }
  189. public void WriteHeaders(RavenJObject headers, Etag etag, HttpResponseMessage msg)
  190. {
  191. foreach (var header in headers)
  192. {
  193. if (header.Key.StartsWith("@"))
  194. continue;
  195. switch (header.Key)
  196. {
  197. case "Content-Type":
  198. var headerValue = header.Value.Value<string>();
  199. string charset = null;
  200. if (headerValue.Contains("charset="))
  201. {
  202. var splits = headerValue.Split(';');
  203. headerValue = splits[0];
  204. charset = splits[1].Split('=')[1];
  205. }
  206. msg.Content.Headers.ContentType = new MediaTypeHeaderValue(headerValue) { CharSet = charset };
  207. break;
  208. default:
  209. if (header.Value.Type == JTokenType.Date)
  210. {
  211. var rfc1123 = GetDateString(header.Value, "r");
  212. var iso8601 = GetDateString(header.Value, "o");
  213. msg.Content.Headers.Add(header.Key, rfc1123);
  214. if (header.Key.StartsWith("Raven-") == false)
  215. msg.Content.Headers.Add("Raven-" + header.Key, iso8601);
  216. }
  217. else if (header.Value.Type == JTokenType.Boolean)
  218. {
  219. msg.Content.Headers.Add(header.Key, header.Value.ToString());
  220. }
  221. else
  222. {
  223. var value = UnescapeStringIfNeeded(header.Value.ToString(Formatting.None));
  224. msg.Content.Headers.Add(header.Key, value);
  225. }
  226. break;
  227. }
  228. }
  229. if (headers["@Http-Status-Code"] != null)
  230. {
  231. msg.StatusCode = (HttpStatusCode)headers.Value<int>("@Http-Status-Code");
  232. msg.Content.Headers.Add("Temp-Status-Description", headers.Value<string>("@Http-Status-Description"));
  233. }
  234. WriteETag(etag, msg);
  235. }
  236. public void AddHeader(string key, string value, HttpResponseMessage msg)
  237. {
  238. if (msg.Content == null)
  239. msg.Content = JsonContent();
  240. // Ensure we haven't already appended these values.
  241. IEnumerable<string> existingValues;
  242. var hasExistingHeaderAppended = msg.Content.Headers.TryGetValues(key, out existingValues) && existingValues.Any(v => v == value);
  243. if (!hasExistingHeaderAppended)
  244. {
  245. msg.Content.Headers.Add(key, value);
  246. }
  247. }
  248. private string GetDateString(RavenJToken token, string format)
  249. {
  250. var value = token as RavenJValue;
  251. if (value == null)
  252. return token.ToString();
  253. var obj = value.Value;
  254. if (obj is DateTime)
  255. return ((DateTime)obj).ToString(format);
  256. if (obj is DateTimeOffset)
  257. return ((DateTimeOffset)obj).ToString(format);
  258. return obj.ToString();
  259. }
  260. private static string UnescapeStringIfNeeded(string str)
  261. {
  262. if (str.StartsWith("\"") && str.EndsWith("\""))
  263. str = Regex.Unescape(str.Substring(1, str.Length - 2));
  264. if (str.Any(ch => ch > 127))
  265. {
  266. // contains non ASCII chars, needs encoding
  267. return Uri.EscapeDataString(str);
  268. }
  269. return str;
  270. }
  271. public virtual HttpResponseMessage GetMessageWithObject(object item, HttpStatusCode code = HttpStatusCode.OK, Etag etag = null)
  272. {
  273. var token = item as RavenJToken;
  274. if (token == null && item != null)
  275. {
  276. token = RavenJToken.FromObject(item);
  277. }
  278. bool metadataOnly;
  279. if (bool.TryParse(GetQueryStringValue("metadata-only"), out metadataOnly) && metadataOnly)
  280. token = Extensions.HttpExtensions.MinimizeToken(token);
  281. var msg = new HttpResponseMessage(code)
  282. {
  283. Content = JsonContent(token),
  284. };
  285. WriteETag(etag, msg);
  286. return msg;
  287. }
  288. public virtual HttpResponseMessage GetMessageWithString(string msg, HttpStatusCode code = HttpStatusCode.OK, Etag etag = null)
  289. {
  290. var resMsg = new HttpResponseMessage(code)
  291. {
  292. Content = JsonContent(msg)
  293. };
  294. WriteETag(etag, resMsg);
  295. return resMsg;
  296. }
  297. public virtual HttpResponseMessage GetEmptyMessage(HttpStatusCode code = HttpStatusCode.OK, Etag etag = null)
  298. {
  299. var resMsg = new HttpResponseMessage(code)
  300. {
  301. Content = JsonContent()
  302. };
  303. WriteETag(etag, resMsg);
  304. return resMsg;
  305. }
  306. public virtual Task<HttpResponseMessage> GetMessageWithObjectAsTask(object item, HttpStatusCode code = HttpStatusCode.OK, Etag etag = null)
  307. {
  308. return new CompletedTask<HttpResponseMessage>(GetMessageWithObject(item, code, etag));
  309. }
  310. public Task<HttpResponseMessage> GetMessageWithStringAsTask(string msg, HttpStatusCode code = HttpStatusCode.OK, Etag etag = null)
  311. {
  312. return new CompletedTask<HttpResponseMessage>(GetMessageWithString(msg, code, etag));
  313. }
  314. public Task<HttpResponseMessage> GetEmptyMessageAsTask(HttpStatusCode code = HttpStatusCode.OK, Etag etag = null)
  315. {
  316. return new CompletedTask<HttpResponseMessage>(GetEmptyMessage(code, etag));
  317. }
  318. public HttpResponseMessage WriteData(RavenJObject data, RavenJObject headers, Etag etag, HttpStatusCode status = HttpStatusCode.OK, HttpResponseMessage msg = null)
  319. {
  320. if (msg == null)
  321. msg = GetEmptyMessage(status);
  322. var jsonContent = ((JsonContent)msg.Content);
  323. WriteHeaders(headers, etag, msg);
  324. var jsonp = GetQueryStringValue("jsonp");
  325. if (string.IsNullOrEmpty(jsonp) == false)
  326. jsonContent.Jsonp = jsonp;
  327. jsonContent.Data = data;
  328. return msg;
  329. }
  330. public Etag GetEtag()
  331. {
  332. var etagAsString = GetHeader("If-None-Match") ?? GetHeader("If-Match");
  333. if (etagAsString != null)
  334. {
  335. // etags are usually quoted
  336. if (etagAsString.StartsWith("\"") && etagAsString.EndsWith("\""))
  337. etagAsString = etagAsString.Substring(1, etagAsString.Length - 2);
  338. Etag result;
  339. if (Etag.TryParse(etagAsString, out result))
  340. return result;
  341. throw new BadRequestException("Could not parse If-None-Match or If-Match header as Guid");
  342. }
  343. return null;
  344. }
  345. public string GetHeader(string key)
  346. {
  347. if (InnerHeaders.Contains(key) == false)
  348. return null;
  349. return InnerHeaders.GetValues(key).FirstOrDefault();
  350. }
  351. public List<string> GetHeaders(string key)
  352. {
  353. if (InnerHeaders.Contains(key) == false)
  354. return null;
  355. return InnerHeaders.GetValues(key).ToList();
  356. }
  357. public bool HasCookie(string key)
  358. {
  359. return InnerRequest.Headers.GetCookies(key).Count != 0;
  360. }
  361. public string GetCookie(string key)
  362. {
  363. var cookieHeaderValue = InnerRequest.Headers.GetCookies(key).FirstOrDefault();
  364. if (cookieHeaderValue != null)
  365. {
  366. var cookie = cookieHeaderValue.Cookies.FirstOrDefault();
  367. if (cookie != null)
  368. return cookie.Value;
  369. }
  370. return null;
  371. }
  372. public HttpResponseMessage WriteEmbeddedFile(string ravenPath, string embeddedPath, string zipPath, string docPath)
  373. {
  374. var filePath = Path.Combine(ravenPath, docPath);
  375. if (File.Exists(filePath))
  376. return WriteFile(filePath);
  377. filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../Raven.Studio.Html5/", docPath);
  378. if (File.Exists(filePath))
  379. return WriteFile(filePath);
  380. filePath = Path.Combine("~/../../../../Raven.Studio.Html5", docPath);
  381. if (File.Exists(filePath))
  382. return WriteFile(filePath);
  383. if (string.IsNullOrEmpty(zipPath) == false)
  384. {
  385. var fullZipPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, zipPath + ".zip");
  386. if (File.Exists(fullZipPath) == false)
  387. fullZipPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin", zipPath + ".zip");
  388. if (File.Exists(fullZipPath))
  389. {
  390. return WriteFileFromZip(fullZipPath, docPath);
  391. }
  392. }
  393. return WriteEmbeddedFileOfType(embeddedPath, docPath);
  394. }
  395. private HttpResponseMessage WriteFileFromZip(string zipPath, string docPath)
  396. {
  397. var etagValue = GetHeader("If-None-Match") ?? GetHeader("If-Match");
  398. var currentFileEtag = EmbeddedLastChangedDate + docPath;
  399. if (etagValue == currentFileEtag)
  400. return GetEmptyMessage(HttpStatusCode.NotModified);
  401. var fileStream = new FileStream(zipPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  402. var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read, false);
  403. var zipEntry = zipArchive.Entries.FirstOrDefault(a => a.FullName.Equals(docPath, StringComparison.OrdinalIgnoreCase));
  404. if (zipEntry == null)
  405. return EmbeddedFileNotFound(docPath);
  406. var entry = zipEntry.Open();
  407. var msg = new HttpResponseMessage
  408. {
  409. Content = new CompressedStreamContent(entry, false)
  410. {
  411. Disposables = { zipArchive }
  412. },
  413. };
  414. WriteETag(currentFileEtag, msg);
  415. var type = GetContentType(docPath);
  416. msg.Content.Headers.ContentType = new MediaTypeHeaderValue(type);
  417. return msg;
  418. }
  419. public HttpResponseMessage WriteFile(string filePath)
  420. {
  421. var etagValue = GetHeader("If-None-Match") ?? GetHeader("If-Match");
  422. var fileEtag = File.GetLastWriteTimeUtc(filePath).ToString("G");
  423. if (etagValue == fileEtag)
  424. return GetEmptyMessage(HttpStatusCode.NotModified);
  425. var msg = new HttpResponseMessage
  426. {
  427. Content = new CompressedStreamContent(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), false)
  428. };
  429. WriteETag(fileEtag, msg);
  430. var type = GetContentType(filePath);
  431. msg.Content.Headers.ContentType = new MediaTypeHeaderValue(type);
  432. return msg;
  433. }
  434. private HttpResponseMessage WriteEmbeddedFileOfType(string embeddedPath, string docPath)
  435. {
  436. var etagValue = GetHeader("If-None-Match") ?? GetHeader("If-Match");
  437. var currentFileEtag = EmbeddedLastChangedDate + docPath;
  438. if (etagValue == currentFileEtag)
  439. return GetEmptyMessage(HttpStatusCode.NotModified);
  440. byte[] bytes;
  441. var resourceName = embeddedPath + "." + docPath.Replace("/", ".");
  442. var resourceAssembly = typeof(RavenBaseApiController).Assembly;
  443. var resourceNames = resourceAssembly.GetManifestResourceNames();
  444. var lowercasedResourceName = resourceNames.FirstOrDefault(s => string.Equals(s, resourceName, StringComparison.OrdinalIgnoreCase));
  445. if (lowercasedResourceName == null)
  446. {
  447. return EmbeddedFileNotFound(docPath);
  448. }
  449. using (var resource = resourceAssembly.GetManifestResourceStream(lowercasedResourceName))
  450. {
  451. if (resource == null)
  452. return EmbeddedFileNotFound(docPath);
  453. bytes = resource.ReadData();
  454. }
  455. var msg = new HttpResponseMessage
  456. {
  457. Content = new ByteArrayContent(bytes),
  458. };
  459. WriteETag(currentFileEtag, msg);
  460. var type = GetContentType(docPath);
  461. msg.Content.Headers.ContentType = new MediaTypeHeaderValue(type);
  462. return msg;
  463. }
  464. private HttpResponseMessage EmbeddedFileNotFound(string docPath)
  465. {
  466. var message = "The following embedded file was not available: " + docPath +
  467. ". Please make sure that the Raven.Studio.Html5.zip file exist in the main directory (near to the Raven.Database.dll).";
  468. return GetMessageWithObject(new {Message = message}, HttpStatusCode.NotFound);
  469. }
  470. private static readonly string EmbeddedLastChangedDate =
  471. File.GetLastWriteTime(AssemblyHelper.GetAssemblyLocationFor(typeof(HttpExtensions))).Ticks.ToString("G");
  472. private static string GetContentType(string docPath)
  473. {
  474. switch (Path.GetExtension(docPath))
  475. {
  476. case ".html":
  477. case ".htm":
  478. return "text/html";
  479. case ".css":
  480. return "text/css";
  481. case ".js":
  482. return "text/javascript";
  483. case ".ico":
  484. return "image/vnd.microsoft.icon";
  485. case ".jpg":
  486. return "image/jpeg";
  487. case ".gif":
  488. return "image/gif";
  489. case ".png":
  490. return "image/png";
  491. case ".xap":
  492. return "application/x-silverlight-2";
  493. default:
  494. return "text/plain";
  495. }
  496. }
  497. public class Headers : HttpHeaders
  498. {
  499. }
  500. public JsonContent JsonContent(RavenJToken data = null)
  501. {
  502. return new JsonContent(data)
  503. .WithRequest(InnerRequest);
  504. }
  505. public string GetRequestUrl()
  506. {
  507. var rawUrl = InnerRequest.RequestUri.PathAndQuery;
  508. return UrlExtension.GetRequestUrlFromRawUrl(rawUrl, SystemConfiguration);
  509. }
  510. public abstract InMemoryRavenConfiguration SystemConfiguration { get; }
  511. protected void AddRavenHeader(HttpResponseMessage msg, Stopwatch sp)
  512. {
  513. AddHeader("Raven-Server-Build", DocumentDatabase.BuildVersion, msg);
  514. AddHeader("Temp-Request-Time", sp.ElapsedMilliseconds.ToString("#,#;;0", CultureInfo.InvariantCulture), msg);
  515. }
  516. public abstract bool SetupRequestToProperDatabase(RequestManager requestManager);
  517. public abstract string TenantName { get; }
  518. public List<Action<StringBuilder>> CustomRequestTraceInfo { get; private set; }
  519. public void AddRequestTraceInfo(Action<StringBuilder> info)
  520. {
  521. if (info == null)
  522. return;
  523. if (CustomRequestTraceInfo == null)
  524. CustomRequestTraceInfo = new List<Action<StringBuilder>>();
  525. CustomRequestTraceInfo.Add(info);
  526. }
  527. public abstract void MarkRequestDuration(long duration);
  528. }
  529. }