public async Task Invoke(HttpContext context) { var path = context.Request.Path; // Delegate to the diagnostics page if it is requested if (diagnosticsPage.MatchesPath(path.Value)) { await diagnosticsPage.Invoke(context); return; } // We only handle requests with an image extension, period. if (!PathHelpers.IsImagePath(path)) { await next.Invoke(context); return; } var imageJobInfo = new ImageJobInfo(context, options, blobProvider); if (!imageJobInfo.Authorized) { await NotAuthorized(context); return; } // If the file is definitely missing hand to the next middleware // Remote providers will fail late rather than make 2 requests if (!imageJobInfo.PrimaryBlobMayExist()) { await next.Invoke(context); return; } var memoryCacheEnabled = memoryCache != null && options.AllowMemoryCaching && imageJobInfo.NeedsCaching(); var diskCacheEnabled = diskCache != null && options.AllowDiskCaching && imageJobInfo.NeedsCaching(); var distributedCacheEnabled = distributedCache != null && options.AllowDistributedCaching && imageJobInfo.NeedsCaching(); var sqliteCacheEnabled = sqliteCache != null && options.AllowSqliteCaching && imageJobInfo.NeedsCaching(); string cacheKey = null; if (memoryCacheEnabled || diskCacheEnabled || distributedCacheEnabled | sqliteCacheEnabled) { cacheKey = await imageJobInfo.GetFastCacheKey(); if (context.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var etag) && cacheKey == etag) { context.Response.StatusCode = StatusCodes.Status304NotModified; context.Response.ContentLength = 0; context.Response.ContentType = null; return; } } try { if (sqliteCacheEnabled) { await ProcessWithSqliteCache(context, cacheKey, imageJobInfo); } else if (diskCacheEnabled) { await ProcessWithDiskCache(context, cacheKey, imageJobInfo); } else if (memoryCacheEnabled) { await ProcessWithMemoryCache(context, cacheKey, imageJobInfo); // ReSharper disable once ConditionIsAlwaysTrueOrFalse } else if (distributedCacheEnabled) { await ProcessWithDistributedCache(context, cacheKey, imageJobInfo); } else { await ProcessWithNoCache(context, imageJobInfo); } } catch (BlobMissingException e) { await NotFound(context, e); } }
private bool ProcessRewritesAndAuthorization(HttpContext context, ImageflowMiddlewareOptions options) { var path = context.Request.Path.Value; var args = new UrlEventArgs(context, context.Request.Path.Value, PathHelpers.ToQueryDictionary(context.Request.Query)); foreach (var handler in options.PreRewriteAuthorization) { var matches = string.IsNullOrEmpty(handler.PathPrefix) || path.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase); if (matches && !handler.Handler(args)) { return(false); } } if (options.UsePresetsExclusively) { var firstKey = args.Query.FirstOrDefault().Key; if (args.Query.Count > 1 || (firstKey != null && firstKey != "preset")) { return(false); } } // Parse and apply presets before rewriting if (args.Query.TryGetValue("preset", out var presetNames)) { var presetNamesList = presetNames .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (var presetName in presetNamesList) { if (options.Presets.TryGetValue(presetName, out var presetOptions)) { foreach (var pair in presetOptions.pairs) { if (presetOptions.Priority == PresetPriority.OverrideQuery || !args.Query.ContainsKey(pair.Key)) { args.Query[pair.Key] = pair.Value; } } } else { throw new InvalidOperationException($"The image preset {presetName} was referenced from the querystring but is not registered."); } } } // Apply rewrite handlers foreach (var handler in options.Rewrite) { var matches = string.IsNullOrEmpty(handler.PathPrefix) || path.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase); if (matches) { handler.Handler(args); path = args.VirtualPath; } } // Set defaults if keys are missing, but at least 1 supported key is present if (PathHelpers.SupportedQuerystringKeys.Any(args.Query.ContainsKey)) { foreach (var pair in options.CommandDefaults) { if (!args.Query.ContainsKey(pair.Key)) { args.Query[pair.Key] = pair.Value; } } } // Run post-rewrite authorization foreach (var handler in options.PostRewriteAuthorization) { var matches = string.IsNullOrEmpty(handler.PathPrefix) || path.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase); if (matches && !handler.Handler(args)) { return(false); } } FinalVirtualPath = args.VirtualPath; FinalQuery = args.Query; return(true); }
private string HashStrings(IEnumerable <string> strings) { return(PathHelpers.Base64Hash(string.Join('|', strings))); }
public ImageJobInfo(HttpContext context, ImageflowMiddlewareOptions options, BlobProvider blobProvider) { this.options = options; Authorized = ProcessRewritesAndAuthorization(context, options); if (!Authorized) { return; } HasParams = PathHelpers.SupportedQuerystringKeys.Any(FinalQuery.ContainsKey); var extension = Path.GetExtension(FinalVirtualPath); if (FinalQuery.TryGetValue("format", out var newExtension)) { extension = newExtension; } EstimatedFileExtension = PathHelpers.SanitizeImageExtension(extension) ?? "jpg"; primaryBlob = new BlobFetchCache(FinalVirtualPath, blobProvider); allBlobs = new List <BlobFetchCache>(1) { primaryBlob }; appliedWatermarks = new List <NamedWatermark>(); if (HasParams) { CommandString = PathHelpers.SerializeCommandString(FinalQuery); // Look up watermark names if (FinalQuery.TryGetValue("watermark", out var watermarkValues)) { var watermarkNames = watermarkValues.Split(",").Select(s => s.Trim(' ')); foreach (var name in watermarkNames) { var watermark = options.NamedWatermarks.FirstOrDefault(w => w.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (watermark == null) { throw new InvalidOperationException( $"watermark {name} was referenced from the querystring but no watermark by that name is registered with the middleware"); } appliedWatermarks.Add(watermark); } } } // After we've populated the defaults, run the event handlers for custom watermarking logic var args = new WatermarkingEventArgs(context, FinalVirtualPath, FinalQuery, appliedWatermarks); foreach (var handler in options.Watermarking) { var matches = string.IsNullOrEmpty(handler.PathPrefix) || FinalVirtualPath.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase); if (matches) { handler.Handler(args); } } appliedWatermarks = args.AppliedWatermarks; // Add the watermark source files foreach (var w in appliedWatermarks) { allBlobs.Add(new BlobFetchCache(w.VirtualPath, blobProvider)); } provider = blobProvider; }
// ReSharper disable once UnusedMember.Global public async Task Invoke(HttpContext context) { // For instrumentation globalInfoProvider.CopyHttpContextInfo(context); var path = context.Request.Path; // Delegate to the diagnostics page if it is requested if (DiagnosticsPage.MatchesPath(path.Value)) { await diagnosticsPage.Invoke(context); return; } // Delegate to licenses page if requested if (licensePage.MatchesPath(path.Value)) { await licensePage.Invoke(context); return; } // Respond to /imageflow.ready if ("/imageflow.ready".Equals(path.Value, StringComparison.Ordinal)) { options.Licensing.FireHeartbeat(); using (new JobContext()) { await StringResponseNoCache(context, 200, "Imageflow.Server is ready to accept requests."); } return; } // Respond to /imageflow.health if ("/imageflow.health".Equals(path.Value, StringComparison.Ordinal)) { options.Licensing.FireHeartbeat(); await StringResponseNoCache(context, 200, "Imageflow.Server is healthy."); return; } // We only handle requests with an image extension or if we configured a path prefix for which to handle // extensionless requests if (!ImageJobInfo.ShouldHandleRequest(context, options)) { await next.Invoke(context); return; } options.Licensing.FireHeartbeat(); var imageJobInfo = new ImageJobInfo(context, options, blobProvider); if (!imageJobInfo.Authorized) { await NotAuthorized(context, imageJobInfo.AuthorizedMessage); return; } if (imageJobInfo.LicenseError) { if (options.EnforcementMethod == EnforceLicenseWith.Http422Error) { await StringResponseNoCache(context, 422, options.Licensing.InvalidLicenseMessage); return; } if (options.EnforcementMethod == EnforceLicenseWith.Http402Error) { await StringResponseNoCache(context, 402, options.Licensing.InvalidLicenseMessage); return; } } // If the file is definitely missing hand to the next middleware // Remote providers will fail late rather than make 2 requests if (!imageJobInfo.PrimaryBlobMayExist()) { await next.Invoke(context); return; } string cacheKey = null; var cachingPath = imageJobInfo.NeedsCaching() ? options.ActiveCacheBackend : CacheBackend.NoCache; if (cachingPath != CacheBackend.NoCache) { cacheKey = await imageJobInfo.GetFastCacheKey(); if (context.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var etag) && cacheKey == etag) { GlobalPerf.Singleton.IncrementCounter("etag_hit"); context.Response.StatusCode = StatusCodes.Status304NotModified; context.Response.ContentLength = 0; context.Response.ContentType = null; return; } GlobalPerf.Singleton.IncrementCounter("etag_miss"); } try { switch (cachingPath) { case CacheBackend.ClassicDiskCache: await ProcessWithDiskCache(context, cacheKey, imageJobInfo); break; case CacheBackend.NoCache: await ProcessWithNoCache(context, imageJobInfo); break; case CacheBackend.StreamCache: await ProcessWithStreamCache(context, cacheKey, imageJobInfo); break; default: throw new ArgumentOutOfRangeException(); } GlobalPerf.Singleton.IncrementCounter("middleware_ok"); } catch (BlobMissingException e) { await NotFound(context, e); } catch (Exception e) { var errorName = e.GetType().Name; var errorCounter = "middleware_" + errorName; GlobalPerf.Singleton.IncrementCounter(errorCounter); GlobalPerf.Singleton.IncrementCounter("middleware_errors"); throw; } finally { // Increment counter for type of file served var imageExtension = PathHelpers.GetImageExtensionFromContentType(context.Response.ContentType); if (imageExtension != null) { GlobalPerf.Singleton.IncrementCounter("module_response_ext_" + imageExtension); } } }
public ImageJobInfo(HttpContext context, ImageflowMiddlewareOptions options, BlobProvider blobProvider) { this.options = options; Authorized = ProcessRewritesAndAuthorization(context, options); if (!Authorized) { return; } HasParams = PathHelpers.SupportedQuerystringKeys.Any(FinalQuery.ContainsKey); // Get the image and page domains ImageDomain = context.Request.Host.Host; var referer = context.Request.Headers["Referer"].ToString(); if (!string.IsNullOrEmpty(referer) && Uri.TryCreate(referer, UriKind.Absolute, out var result)) { PageDomain = result.DnsSafeHost; } var extension = Path.GetExtension(FinalVirtualPath); if (FinalQuery.TryGetValue("format", out var newExtension)) { extension = newExtension; } EstimatedFileExtension = PathHelpers.SanitizeImageExtension(extension) ?? "jpg"; primaryBlob = new BlobFetchCache(FinalVirtualPath, blobProvider); allBlobs = new List <BlobFetchCache>(1) { primaryBlob }; appliedWatermarks = new List <NamedWatermark>(); if (HasParams) { if (options.Licensing.RequestNeedsEnforcementAction(context.Request)) { if (options.EnforcementMethod == EnforceLicenseWith.RedDotWatermark) { FinalQuery["watermark_red_dot"] = "true"; } LicenseError = true; } CommandString = PathHelpers.SerializeCommandString(FinalQuery); // Look up watermark names if (FinalQuery.TryGetValue("watermark", out var watermarkValues)) { var watermarkNames = watermarkValues.Split(",").Select(s => s.Trim(' ')); foreach (var name in watermarkNames) { var watermark = options.NamedWatermarks.FirstOrDefault(w => w.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (watermark == null) { throw new InvalidOperationException( $"watermark {name} was referenced from the querystring but no watermark by that name is registered with the middleware"); } appliedWatermarks.Add(watermark); } } } // After we've populated the defaults, run the event handlers for custom watermarking logic var args = new WatermarkingEventArgs(context, FinalVirtualPath, FinalQuery, appliedWatermarks); foreach (var handler in options.Watermarking) { var matches = string.IsNullOrEmpty(handler.PathPrefix) || (FinalVirtualPath != null && FinalVirtualPath.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase)); if (matches) { handler.Handler(args); } } appliedWatermarks = args.AppliedWatermarks; if (appliedWatermarks.Count > 0) { HasParams = true; } // Add the watermark source files foreach (var w in appliedWatermarks) { allBlobs.Add(new BlobFetchCache(w.VirtualPath, blobProvider)); } }