PageRenderTime 61ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Database/Server/RavenFS/Controllers/RavenFsApiController.cs

https://github.com/nwendel/ravendb
C# | 525 lines | 439 code | 73 blank | 13 comment | 38 complexity | 7c6a4bab08fb629e0f17ece95b832b5d 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.Specialized;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.Http;
  8. using System.Net.Http.Headers;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using System.Web;
  12. using System.Web.Http;
  13. using System.Web.Http.Controllers;
  14. using System.Web.Http.Routing;
  15. using Raven.Abstractions;
  16. using Raven.Abstractions.Extensions;
  17. using Raven.Abstractions.Exceptions;
  18. using Raven.Abstractions.Logging;
  19. using Raven.Abstractions.Util.Streams;
  20. using Raven.Database.Config;
  21. using Raven.Database.Server.Controllers;
  22. using Raven.Database.Server.RavenFS.Infrastructure;
  23. using Raven.Database.Server.RavenFS.Notifications;
  24. using Raven.Database.Server.RavenFS.Search;
  25. using Raven.Database.Server.RavenFS.Storage;
  26. using Raven.Database.Server.RavenFS.Synchronization;
  27. using Raven.Database.Server.RavenFS.Synchronization.Conflictuality;
  28. using Raven.Database.Server.RavenFS.Synchronization.Rdc.Wrapper;
  29. using Raven.Database.Server.Security;
  30. using Raven.Database.Server.Tenancy;
  31. using Raven.Database.Server.WebApi;
  32. using Raven.Json.Linq;
  33. using System.Collections.Generic;
  34. using Raven.Abstractions.FileSystem;
  35. using Raven.Abstractions.Data;
  36. namespace Raven.Database.Server.RavenFS.Controllers
  37. {
  38. public abstract class RavenFsApiController : RavenBaseApiController
  39. {
  40. private static readonly ILog Logger = LogManager.GetCurrentClassLogger();
  41. private PagingInfo paging;
  42. private NameValueCollection queryString;
  43. private FileSystemsLandlord landlord;
  44. private RequestManager requestManager;
  45. public override LogTenantType TenantType
  46. {
  47. get { return LogTenantType.Filesystem; }
  48. }
  49. public RequestManager RequestManager
  50. {
  51. get
  52. {
  53. if (Configuration == null)
  54. return requestManager;
  55. return (RequestManager)Configuration.Properties[typeof(RequestManager)];
  56. }
  57. }
  58. public RavenFileSystem FileSystem
  59. {
  60. get
  61. {
  62. var fs = FileSystemsLandlord.GetFileSystemInternal(FileSystemName);
  63. if (fs == null)
  64. {
  65. throw new InvalidOperationException("Could not find a file system named: " + FileSystemName);
  66. }
  67. return fs.Result;
  68. }
  69. }
  70. public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
  71. {
  72. InnerInitialization(controllerContext);
  73. var authorizer = (MixedModeRequestAuthorizer)controllerContext.Configuration.Properties[typeof(MixedModeRequestAuthorizer)];
  74. var result = new HttpResponseMessage();
  75. if (InnerRequest.Method.Method != "OPTIONS")
  76. {
  77. result = await RequestManager.HandleActualRequest(this, async () =>
  78. {
  79. RequestManager.SetThreadLocalState(InnerHeaders, FileSystemName);
  80. return await ExecuteActualRequest(controllerContext, cancellationToken, authorizer);
  81. }, httpException => GetMessageWithObject(new { Error = httpException.Message }, HttpStatusCode.ServiceUnavailable));
  82. }
  83. RequestManager.AddAccessControlHeaders(this, result);
  84. RequestManager.ResetThreadLocalState();
  85. return result;
  86. }
  87. private async Task<HttpResponseMessage> ExecuteActualRequest(HttpControllerContext controllerContext, CancellationToken cancellationToken,
  88. MixedModeRequestAuthorizer authorizer)
  89. {
  90. HttpResponseMessage authMsg;
  91. if (authorizer.TryAuthorize(this, out authMsg) == false)
  92. return authMsg;
  93. var internalHeader = GetHeader("Raven-internal-request");
  94. if (internalHeader == null || internalHeader != "true")
  95. RequestManager.IncrementRequestCount();
  96. var fileSystemInternal = await FileSystemsLandlord.GetFileSystemInternal(FileSystemName);
  97. if (fileSystemInternal == null)
  98. {
  99. var msg = "Could not find a file system named: " + FileSystemName;
  100. return GetMessageWithObject(new { Error = msg }, HttpStatusCode.ServiceUnavailable);
  101. }
  102. var sp = Stopwatch.StartNew();
  103. var result = await base.ExecuteAsync(controllerContext, cancellationToken);
  104. sp.Stop();
  105. AddRavenHeader(result, sp);
  106. return result;
  107. }
  108. protected override void InnerInitialization(HttpControllerContext controllerContext)
  109. {
  110. base.InnerInitialization(controllerContext);
  111. landlord = (FileSystemsLandlord)controllerContext.Configuration.Properties[typeof(FileSystemsLandlord)];
  112. requestManager = (RequestManager)controllerContext.Configuration.Properties[typeof(RequestManager)];
  113. var values = controllerContext.Request.GetRouteData().Values;
  114. if (values.ContainsKey("MS_SubRoutes"))
  115. {
  116. var routeDatas = (IHttpRouteData[])controllerContext.Request.GetRouteData().Values["MS_SubRoutes"];
  117. var selectedData = routeDatas.FirstOrDefault(data => data.Values.ContainsKey("fileSystemName"));
  118. if (selectedData != null)
  119. FileSystemName = selectedData.Values["fileSystemName"] as string;
  120. }
  121. else
  122. {
  123. if (values.ContainsKey("fil"))
  124. FileSystemName = values["fileSystemName"] as string;
  125. }
  126. if (FileSystemName == null)
  127. throw new InvalidOperationException("Could not find file system name for this request");
  128. }
  129. public string FileSystemName { get; private set; }
  130. public FileSystemsLandlord FileSystemsLandlord
  131. {
  132. get
  133. {
  134. if (Configuration == null)
  135. return landlord;
  136. return (FileSystemsLandlord)Configuration.Properties[typeof(FileSystemsLandlord)];
  137. }
  138. }
  139. public NotificationPublisher Publisher
  140. {
  141. get { return FileSystem.Publisher; }
  142. }
  143. public BufferPool BufferPool
  144. {
  145. get { return FileSystem.BufferPool; }
  146. }
  147. public SigGenerator SigGenerator
  148. {
  149. get { return FileSystem.SigGenerator; }
  150. }
  151. public Historian Historian
  152. {
  153. get { return FileSystem.Historian; }
  154. }
  155. public override InMemoryRavenConfiguration SystemConfiguration
  156. {
  157. get { return FileSystemsLandlord.SystemConfiguration; }
  158. }
  159. private NameValueCollection QueryString
  160. {
  161. get { return queryString ?? (queryString = HttpUtility.ParseQueryString(Request.RequestUri.Query)); }
  162. }
  163. protected ITransactionalStorage Storage
  164. {
  165. get { return FileSystem.Storage; }
  166. }
  167. protected IndexStorage Search
  168. {
  169. get { return FileSystem.Search; }
  170. }
  171. protected FileLockManager FileLockManager
  172. {
  173. get { return FileSystem.FileLockManager; }
  174. }
  175. protected ConflictArtifactManager ConflictArtifactManager
  176. {
  177. get { return FileSystem.ConflictArtifactManager; }
  178. }
  179. protected ConflictDetector ConflictDetector
  180. {
  181. get { return FileSystem.ConflictDetector; }
  182. }
  183. protected ConflictResolver ConflictResolver
  184. {
  185. get { return FileSystem.ConflictResolver; }
  186. }
  187. protected SynchronizationTask SynchronizationTask
  188. {
  189. get { return FileSystem.SynchronizationTask; }
  190. }
  191. protected StorageOperationsTask StorageOperationsTask
  192. {
  193. get { return FileSystem.StorageOperationsTask; }
  194. }
  195. protected PagingInfo Paging
  196. {
  197. get
  198. {
  199. if (paging != null)
  200. return paging;
  201. int start;
  202. int.TryParse(QueryString["start"], out start);
  203. int pageSize;
  204. if (int.TryParse(QueryString["pageSize"], out pageSize) == false)
  205. pageSize = 25;
  206. paging = new PagingInfo
  207. {
  208. PageSize = Math.Min(1024, Math.Max(1, pageSize)),
  209. Start = Math.Max(start, 0)
  210. };
  211. return paging;
  212. }
  213. }
  214. protected Task<T> Result<T>(T result)
  215. {
  216. var tcs = new TaskCompletionSource<T>();
  217. tcs.SetResult(result);
  218. return tcs.Task;
  219. }
  220. protected HttpResponseMessage StreamResult(string filename, Stream resultContent)
  221. {
  222. var response = new HttpResponseMessage
  223. {
  224. Headers =
  225. {
  226. TransferEncodingChunked = false
  227. }
  228. };
  229. long length;
  230. ContentRangeHeaderValue contentRange = null;
  231. if (Request.Headers.Range != null)
  232. {
  233. if (Request.Headers.Range.Ranges.Count != 1)
  234. {
  235. throw new InvalidOperationException("Can't handle multiple range values");
  236. }
  237. var range = Request.Headers.Range.Ranges.First();
  238. var from = range.From ?? 0;
  239. var to = range.To ?? resultContent.Length;
  240. length = (to - from);
  241. // "to" in Content-Range points on the last byte. In other words the set is: <from..to> not <from..to)
  242. if (from < to)
  243. {
  244. contentRange = new ContentRangeHeaderValue(from, to - 1, resultContent.Length);
  245. resultContent = new LimitedStream(resultContent, from, to);
  246. }
  247. else
  248. {
  249. contentRange = new ContentRangeHeaderValue(0);
  250. resultContent = Stream.Null;
  251. }
  252. }
  253. else
  254. {
  255. length = resultContent.Length;
  256. }
  257. response.Content = new StreamContent(resultContent)
  258. {
  259. Headers =
  260. {
  261. ContentDisposition = new ContentDispositionHeaderValue("attachment")
  262. {
  263. FileName = filename
  264. },
  265. // ContentLength = length,
  266. ContentRange = contentRange,
  267. }
  268. };
  269. return response;
  270. }
  271. protected void AssertFileIsNotBeingSynced(string fileName, IStorageActionsAccessor accessor,
  272. bool wrapByResponseException = false)
  273. {
  274. if (FileLockManager.TimeoutExceeded(fileName, accessor))
  275. {
  276. FileLockManager.UnlockByDeletingSyncConfiguration(fileName, accessor);
  277. }
  278. else
  279. {
  280. Log.Debug("Cannot execute operation because file '{0}' is being synced", fileName);
  281. var beingSyncedException = new SynchronizationException(string.Format("File {0} is being synced", fileName));
  282. if (wrapByResponseException)
  283. {
  284. throw new HttpResponseException(Request.CreateResponse((HttpStatusCode)420, beingSyncedException));
  285. }
  286. throw beingSyncedException;
  287. }
  288. }
  289. protected HttpResponseException BadRequestException(string message)
  290. {
  291. return
  292. new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest) {Content = new StringContent(message)});
  293. }
  294. protected HttpResponseException ConcurrencyResponseException(ConcurrencyException concurrencyException)
  295. {
  296. return new HttpResponseException(Request.CreateResponse(HttpStatusCode.MethodNotAllowed, concurrencyException));
  297. }
  298. protected class PagingInfo
  299. {
  300. public int PageSize;
  301. public int Start;
  302. }
  303. public override bool SetupRequestToProperDatabase(RequestManager rm)
  304. {
  305. var tenantId = FileSystemName;
  306. if (string.IsNullOrWhiteSpace(tenantId))
  307. {
  308. throw new HttpException(503, "Could not find a file system with no name");
  309. }
  310. Task<RavenFileSystem> resourceStoreTask;
  311. bool hasDb;
  312. try
  313. {
  314. hasDb = landlord.TryGetOrCreateResourceStore(tenantId, out resourceStoreTask);
  315. }
  316. catch (Exception e)
  317. {
  318. var msg = "Could not open file system named: " + tenantId;
  319. Logger.WarnException(msg, e);
  320. throw new HttpException(503, msg, e);
  321. }
  322. if (hasDb)
  323. {
  324. try
  325. {
  326. if (resourceStoreTask.Wait(TimeSpan.FromSeconds(30)) == false)
  327. {
  328. var msg = "The filesystem " + tenantId +
  329. " is currently being loaded, but after 30 seconds, this request has been aborted. Please try again later, file system loading continues.";
  330. Logger.Warn(msg);
  331. throw new HttpException(503, msg);
  332. }
  333. var args = new BeforeRequestWebApiEventArgs()
  334. {
  335. Controller = this,
  336. IgnoreRequest = false,
  337. TenantId = tenantId,
  338. FileSystem = resourceStoreTask.Result
  339. };
  340. rm.OnBeforeRequest(args);
  341. if (args.IgnoreRequest)
  342. return false;
  343. }
  344. catch (Exception e)
  345. {
  346. var msg = "Could not open file system named: " + tenantId;
  347. Logger.WarnException(msg, e);
  348. throw new HttpException(503, msg, e);
  349. }
  350. landlord.LastRecentlyUsed.AddOrUpdate(tenantId, SystemTime.UtcNow, (s, time) => SystemTime.UtcNow);
  351. }
  352. else
  353. {
  354. var msg = "Could not find a file system named: " + tenantId;
  355. Logger.Warn(msg);
  356. throw new HttpException(503, msg);
  357. }
  358. return true;
  359. }
  360. public override string TenantName
  361. {
  362. get { return "fs/" + FileSystemName; }
  363. }
  364. public override void MarkRequestDuration(long duration)
  365. {
  366. FileSystem.MetricsCounters.RequestDuationMetric.Update(duration);
  367. }
  368. #region Metadata Headers Handling
  369. private static readonly HashSet<string> HeadersToIgnoreClient = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
  370. {
  371. // Raven internal headers
  372. "Raven-Server-Build",
  373. "Non-Authoritive-Information",
  374. "Raven-Timer-Request",
  375. //proxy
  376. "Reverse-Via",
  377. "Allow",
  378. "Content-Disposition",
  379. "Content-Encoding",
  380. "Content-Language",
  381. "Content-Location",
  382. "Content-MD5",
  383. "Content-Range",
  384. "Content-Type",
  385. "Expires",
  386. // ignoring this header, we handle this internally
  387. Constants.LastModified,
  388. // Ignoring this header, since it may
  389. // very well change due to things like encoding,
  390. // adding metadata, etc
  391. "Content-Length",
  392. // Special things to ignore
  393. "Keep-Alive",
  394. "X-Powered-By",
  395. "X-AspNet-Version",
  396. "X-Requested-With",
  397. "X-SourceFiles",
  398. // Request headers
  399. "Accept-Charset",
  400. "Accept-Encoding",
  401. "Accept",
  402. "Accept-Language",
  403. "Authorization",
  404. "Cookie",
  405. "Expect",
  406. "From",
  407. "Host",
  408. "If-Match",
  409. "If-Modified-Since",
  410. "If-None-Match",
  411. "If-Range",
  412. "If-Unmodified-Since",
  413. "Max-Forwards",
  414. "Referer",
  415. "TE",
  416. "User-Agent",
  417. //Response headers
  418. "Accept-Ranges",
  419. "Age",
  420. "Allow",
  421. Constants.MetadataEtagField,
  422. "Location",
  423. "Origin",
  424. "Retry-After",
  425. "Server",
  426. "Set-Cookie2",
  427. "Set-Cookie",
  428. "Vary",
  429. "Www-Authenticate",
  430. // General
  431. "Cache-Control",
  432. "Connection",
  433. "Date",
  434. "Pragma",
  435. "Trailer",
  436. "Transfer-Encoding",
  437. "Upgrade",
  438. "Via",
  439. "Warning",
  440. // Azure specific
  441. "X-LiveUpgrade",
  442. "DISGUISED-HOST",
  443. "X-SITE-DEPLOYMENT-ID",
  444. };
  445. protected static readonly IList<string> ReadOnlyHeaders = new List<string> { Constants.LastModified, Constants.MetadataEtagField }.AsReadOnly();
  446. protected virtual RavenJObject GetFilteredMetadataFromHeaders(HttpHeaders headers)
  447. {
  448. return headers.FilterHeadersToObject();
  449. }
  450. #endregion Metadata Headers Handling
  451. }
  452. }