async Task FlushAsync(HttpContext context, CancellationToken cancellationToken) { // prepare var requestUri = context.GetRequestUri(); var pathSegments = requestUri.GetRequestPathSegments(); var attachment = new AttachmentInfo { ID = pathSegments.Length > 3 && pathSegments[3].IsValidUUID() ? pathSegments[3].ToLower() : "", ServiceName = pathSegments.Length > 1 && !pathSegments[1].IsValidUUID() ? pathSegments[1] : "", SystemID = pathSegments.Length > 1 && pathSegments[1].IsValidUUID() ? pathSegments[1].ToLower() : "", ContentType = pathSegments.Length > 2 ? pathSegments[2].Replace("=", "/") : "", Filename = pathSegments.Length > 4 && pathSegments[3].IsValidUUID() ? pathSegments[4].UrlDecode() : "", IsThumbnail = false }; if (string.IsNullOrWhiteSpace(attachment.ID) || string.IsNullOrWhiteSpace(attachment.Filename)) { throw new InvalidRequestException(); } // check "If-Modified-Since" request to reduce traffict var eTag = "file#" + attachment.ID; var noneMatch = context.GetHeaderParameter("If-None-Match"); var modifiedSince = context.GetHeaderParameter("If-Modified-Since") ?? context.GetHeaderParameter("If-Unmodified-Since"); if (eTag.IsEquals(noneMatch) && modifiedSince != null) { context.SetResponseHeaders((int)HttpStatusCode.NotModified, eTag, modifiedSince.FromHttpDateTime().ToUnixTimestamp(), "public", context.GetCorrelationID()); await context.FlushAsync(cancellationToken).ConfigureAwait(false); if (Global.IsDebugLogEnabled) { context.WriteLogs(this.Logger, "Http.Downloads", $"Response to request with status code 304 to reduce traffic ({requestUri})"); } return; } // get info & check permissions attachment = await context.GetAsync(attachment.ID, cancellationToken).ConfigureAwait(false); if (!await context.CanDownloadAsync(attachment, cancellationToken).ConfigureAwait(false)) { throw new AccessDeniedException(); } // check existed var cacheKey = attachment.ContentType.IsStartsWith("image/") && "true".IsEquals(UtilityService.GetAppSetting("Files:Cache:Images", "true")) && Global.Cache != null ? $"Image#{attachment.Filename.ToLower().GenerateUUID()}" : null; var hasCached = cacheKey != null && await Global.Cache.ExistsAsync(cacheKey, cancellationToken).ConfigureAwait(false); FileInfo fileInfo = null; if (!hasCached) { fileInfo = new FileInfo(attachment.GetFilePath()); if (!fileInfo.Exists) { if (Global.IsDebugLogEnabled) { context.WriteLogs(this.Logger, "Http.Downloads", $"Not found: {requestUri} => {fileInfo.FullName}"); } context.ShowHttpError((int)HttpStatusCode.NotFound, "Not Found", "FileNotFoundException", context.GetCorrelationID()); return; } } // flush the file to output stream, update counter & logs if (hasCached) { var lastModified = await Global.Cache.GetAsync <long>($"{cacheKey}:time", cancellationToken).ConfigureAwait(false); var bytes = await Global.Cache.GetAsync <byte[]>(cacheKey, cancellationToken).ConfigureAwait(false); using (var stream = UtilityService.CreateMemoryStream(bytes)) { await context.WriteAsync(stream, attachment.ContentType, attachment.IsReadable()?null : attachment.Filename, eTag, lastModified, "public", TimeSpan.FromDays(366), null, context.GetCorrelationID(), cancellationToken).ConfigureAwait(false); } if (Global.IsDebugLogEnabled) { context.WriteLogs(this.Logger, "Http.Downloads", $"Successfully flush a cached image ({requestUri})"); } } else { await context.WriteAsync(fileInfo, attachment.IsReadable()?null : attachment.Filename, eTag, cancellationToken).ConfigureAwait(false); if (cacheKey != null) { await Task.WhenAll ( Global.Cache.SetAsFragmentsAsync(cacheKey, await UtilityService.ReadBinaryFileAsync(fileInfo, cancellationToken).ConfigureAwait(false), 1440, cancellationToken), Global.Cache.SetAsync($"{cacheKey}:time", fileInfo.LastWriteTime.ToUnixTimestamp(), 1440, cancellationToken), Global.IsDebugLogEnabled?context.WriteLogsAsync(this.Logger, "Http.Downloads", $"Update an image file into cache successful ({requestUri})") : Task.CompletedTask ).ConfigureAwait(false); } if (Global.IsDebugLogEnabled) { context.WriteLogs(this.Logger, "Http.Downloads", $"Successfully flush a file [{requestUri} => {fileInfo.FullName}]"); } } await context.UpdateAsync(attachment, attachment.IsReadable()? "Direct" : "Download", cancellationToken).ConfigureAwait(false); }