public async Task Invoke(HttpContext context) { bool useMinification = _options.IsMinificationEnabled() && _minificationManagers.Count > 0; bool useCompression = _options.IsCompressionEnabled() && _compressionManager != null; if (!useMinification && !useCompression) { await _next.Invoke(context); return; } HttpRequest request = context.Request; HttpResponse response = context.Response; using (var cachedStream = new MemoryStream()) { Stream originalStream = response.Body; response.Body = cachedStream; try { await _next.Invoke(context); } catch (Exception) { response.Body = originalStream; cachedStream.Clear(); throw; } byte[] cachedBytes = cachedStream.ToArray(); int cachedByteCount = cachedBytes.Length; bool isProcessed = false; response.Body = originalStream; cachedStream.Clear(); if (request.Method == "GET" && response.StatusCode == 200 && _options.IsAllowableResponseSize(cachedByteCount)) { string contentType = response.ContentType; string mediaType = null; Encoding encoding = null; if (contentType != null) { MediaTypeHeaderValue mediaTypeHeader; if (MediaTypeHeaderValue.TryParse(contentType, out mediaTypeHeader)) { mediaType = mediaTypeHeader.MediaType.ToLowerInvariant(); encoding = mediaTypeHeader.Encoding; } } encoding = encoding ?? Encoding.GetEncoding(0); string currentUrl = request.Path.Value; QueryString queryString = request.QueryString; if (queryString.HasValue) { currentUrl += queryString.Value; } string content = encoding.GetString(cachedBytes); string processedContent = content; IHeaderDictionary responseHeaders = response.Headers; bool isEncodedContent = responseHeaders.IsEncodedContent(); Action<string, string> appendHttpHeader = (key, value) => { responseHeaders.Append(key, new StringValues(value)); }; if (useMinification) { foreach (IMarkupMinificationManager minificationManager in _minificationManagers) { if (mediaType != null && minificationManager.IsSupportedMediaType(mediaType) && minificationManager.IsProcessablePage(currentUrl)) { if (isEncodedContent) { throw new InvalidOperationException( string.Format( AspNetCommonStrings.MarkupMinificationIsNotApplicableToEncodedContent, responseHeaders["Content-Encoding"] ) ); } IMarkupMinifier minifier = minificationManager.CreateMinifier(); MarkupMinificationResult minificationResult = minifier.Minify(processedContent, currentUrl, encoding, false); if (minificationResult.Errors.Count == 0) { processedContent = minificationResult.MinifiedContent; if (_options.IsPoweredByHttpHeadersEnabled()) { minificationManager.AppendPoweredByHttpHeader(appendHttpHeader); } isProcessed = true; } } if (isProcessed) { break; } } } if (useCompression && !isEncodedContent && _compressionManager.IsSupportedMediaType(mediaType)) { byte[] processedBytes = encoding.GetBytes(processedContent); using (var inputStream = new MemoryStream(processedBytes)) using (var outputStream = new MemoryStream()) { string acceptEncoding = request.Headers["Accept-Encoding"]; ICompressor compressor = _compressionManager.CreateCompressor(acceptEncoding); using (Stream compressedStream = compressor.Compress(outputStream)) { await inputStream.CopyToAsync(compressedStream); } byte[] compressedBytes = outputStream.ToArray(); int compressedByteCount = compressedBytes.Length; outputStream.Clear(); inputStream.Clear(); responseHeaders["Content-Length"] = compressedByteCount.ToString(); compressor.AppendHttpHeaders(appendHttpHeader); await originalStream.WriteAsync(compressedBytes, 0, compressedByteCount); } isProcessed = true; } else { if (isProcessed) { byte[] processedBytes = encoding.GetBytes(processedContent); int processedByteCount = processedBytes.Length; responseHeaders["Content-Length"] = processedByteCount.ToString(); await originalStream.WriteAsync(processedBytes, 0, processedByteCount); } } } if (!isProcessed) { await originalStream.WriteAsync(cachedBytes, 0, cachedByteCount); } } }
/// <summary> /// Calculatea a size of gzipped code /// </summary> /// <param name="bytes">Array of bytes</param> /// <returns>Size of gzipped code in bytes</returns> private static long CalculateGzipSize(byte[] bytes) { using (var memoryStream = new MemoryStream()) { // the third parameter tells the GZIP stream to leave the base stream open so it doesn't // dispose of it when it gets disposed. This is needed because we need to dispose the // GZIP stream before it will write ANY of its data. using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) { gzipStream.Write(bytes, 0, bytes.Length); } long compressedByteCount = memoryStream.Position; memoryStream.Clear(); return compressedByteCount; } }