PageRenderTime 60ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

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

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