/// <summary> /// Adds the compression headers /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { //get the model from the items if (!context.HttpContext.Items.ContainsKey(nameof(AddCompressionHeaderAttribute))) { return; } var file = context.HttpContext.Items[nameof(AddCompressionHeaderAttribute)] as RequestModel; if (file == null) { return; } var enableCompression = true; //check if it's a bundle (not composite file) var bundleRequest = file as BundleRequestModel; if (bundleRequest != null) { Bundle b; if (_bundleManager.TryGetValue(bundleRequest.FileKey, out b)) { var bundleOptions = b.GetBundleOptions(_bundleManager, bundleRequest.Debug); enableCompression = bundleOptions.CompressResult; } } if (enableCompression) { context.HttpContext.Response.AddCompressionResponseHeader( _requestHelper.GetClientCompression(context.HttpContext.Request.Headers)); } }
public BundleRequestModel(IUrlManager urlManager, IActionContextAccessor accessor, IRequestHelper requestHelper, IBundleManager bundleManager, CacheBusterResolver cacheBusterResolver) : base("bundle", urlManager, accessor, requestHelper) { //TODO: Pretty sure if we want to control the caching of the file, we'll have to retrieve the bundle definition here // In reality we'll need to do that anyways if we want to support load balancing! // https://github.com/Shazwazza/Smidge/issues/17 if (!ParsedPath.Names.Any()) { throw new InvalidOperationException("The bundle route value does not contain a bundle name"); } FileKey = ParsedPath.Names.Single(); Bundle bundle; if (!bundleManager.TryGetValue(FileKey, out bundle)) { throw new InvalidOperationException("No bundle found with key " + FileKey); } Bundle = bundle; CacheBuster = cacheBusterResolver.GetCacheBuster(bundle.GetBundleOptions(bundleManager, Debug).GetCacheBusterType()); }
/// <summary> /// Adds the expiry headers /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { if (context.Exception != null) { return; } //get the model from the items if (!context.HttpContext.Items.ContainsKey(nameof(AddExpiryHeadersAttribute))) { return; } var file = context.HttpContext.Items[nameof(AddExpiryHeadersAttribute)] as RequestModel; if (file == null) { return; } var enableETag = true; var cacheControlMaxAge = 10 * 24; //10 days //check if it's a bundle (not composite file) var bundleRequest = file as BundleRequestModel; if (bundleRequest != null) { Bundle b; if (_bundleManager.TryGetValue(bundleRequest.FileKey, out b)) { var bundleOptions = b.GetBundleOptions(_bundleManager, bundleRequest.Debug); enableETag = bundleOptions.CacheControlOptions.EnableETag; cacheControlMaxAge = bundleOptions.CacheControlOptions.CacheControlMaxAge; } } if (enableETag) { var etag = _hasher.Hash(file.FileKey + file.Compression + file.Mime); context.HttpContext.Response.AddETagResponseHeader(etag); } if (cacheControlMaxAge > 0) { context.HttpContext.Response.AddCacheControlResponseHeader(cacheControlMaxAge); context.HttpContext.Response.AddLastModifiedResponseHeader(file); context.HttpContext.Response.AddExpiresResponseHeader(cacheControlMaxAge); } }
public FileResult SourceMap([FromServices] BundleRequestModel bundle) { Bundle foundBundle; if (!_bundleManager.TryGetValue(bundle.FileKey, out foundBundle)) { //TODO: Throw an exception, this will result in an exception anyways return(null); } //now we need to determine if this bundle has already been created var compositeFilePath = new FileInfo(_fileSystemHelper.GetCurrentCompositeFilePath(bundle.CacheBuster, bundle.Compression, bundle.FileKey)); //we need to go one level above the composite path into the non-compression named folder since the map request will always be 'none' compression var mapPath = new FileInfo(Path.Combine(compositeFilePath.Directory.Parent.FullName, compositeFilePath.Name + ".map")); if (mapPath.Exists) { //this should already be processed if this is being requested! return(File(mapPath.OpenRead(), "application/json")); } //TODO: Throw an exception, this will result in an exception anyways return(null); }
/// <summary> /// Handles requests for named bundles /// </summary> /// <param name="bundle">The bundle model</param> /// <returns></returns> public async Task <IActionResult> Bundle( [FromServices] BundleRequestModel bundle) { if (!_bundleManager.TryGetValue(bundle.FileKey, out Bundle foundBundle)) { return(NotFound()); } var bundleOptions = foundBundle.GetBundleOptions(_bundleManager, bundle.Debug); //now we need to determine if this bundle has already been created var compositeFilePath = new FileInfo(_fileSystemHelper.GetCurrentCompositeFilePath(bundle.CacheBuster, bundle.Compression, bundle.FileKey)); if (compositeFilePath.Exists) { _logger.LogDebug($"Returning bundle '{bundle.FileKey}' from cache"); //this is already processed, return it return(File(compositeFilePath.OpenRead(), bundle.Mime)); } //the bundle doesn't exist so we'll go get the files, process them and create the bundle //TODO: We should probably lock here right?! we don't want multiple threads trying to do this at the same time, we'll need a dictionary of locks to do this effectively //get the files for the bundle var files = _fileSetGenerator.GetOrderedFileSet(foundBundle, _processorFactory.CreateDefault( //the file type in the bundle will always be the same foundBundle.Files[0].DependencyType)) .ToArray(); if (files.Length == 0) { return(NotFound()); } using (var bundleContext = new BundleContext(bundle, compositeFilePath)) { var watch = new Stopwatch(); watch.Start(); _logger.LogDebug($"Processing bundle '{bundle.FileKey}', debug? {bundle.Debug} ..."); //we need to do the minify on the original files foreach (var file in files) { await _preProcessManager.ProcessAndCacheFileAsync(file, bundleOptions, bundleContext); } //Get each file path to it's hashed location since that is what the pre-processed file will be saved as Lazy <IFileInfo> fi; var filePaths = files.Select( x => _fileSystemHelper.GetCacheFilePath(x, bundleOptions.FileWatchOptions.Enabled, Path.GetExtension(x.FilePath), bundle.CacheBuster, out fi)); using (var resultStream = await GetCombinedStreamAsync(filePaths, bundleContext)) { //compress the response (if enabled) var compressedStream = await Compressor.CompressAsync( //do not compress anything if it's not enabled in the bundle options bundleOptions.CompressResult?bundle.Compression : CompressionType.none, resultStream); //save the resulting compressed file, if compression is not enabled it will just save the non compressed format // this persisted file will be used in the CheckNotModifiedAttribute which will short circuit the request and return // the raw file if it exists for further requests to this path await CacheCompositeFileAsync(compositeFilePath, compressedStream); _logger.LogDebug($"Processed bundle '{bundle.FileKey}' in {watch.ElapsedMilliseconds}ms"); //return the stream return(File(compressedStream, bundle.Mime)); } } }