Created
September 21, 2015 05:55
-
-
Save centur/eee30685d2d52a5f2f20 to your computer and use it in GitHub Desktop.
Gzip Middleware to compress Swashbuckle SwaggerUI assets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Gzip Middleware to compress Swashbuckle SwaggerUI assets | |
/// </summary> | |
public class GzipMiddleware | |
{ | |
private readonly Func<IDictionary<string, object>, Task> _next; | |
/// <summary> | |
/// Ctor | |
/// </summary> | |
/// <param name="next"></param> | |
public GzipMiddleware(Func<IDictionary<string, object>, Task> next) | |
{ | |
_next = next; | |
} | |
/// <summary> | |
/// Invoke middleware method | |
/// </summary> | |
/// <param name="environment"> Owin environment dictionary</param> | |
/// <returns></returns> | |
public async Task Invoke(IDictionary<string, object> environment) | |
{ | |
var context = new OwinContext(environment); | |
// Verifies that the calling client supports gzip encoding and it calls swagger subpath | |
if ( DoesntNeedGzip(context.Request.Path, context.Request.Headers) ) | |
{ | |
await _next(environment); | |
return; | |
} | |
// Replaces the response stream by a memory stream | |
// and keeps track of the real response stream. | |
var realBody = context.Response.Body; | |
context.Response.Body = new MemoryStream(); | |
try | |
{ | |
await _next(environment); | |
// Verifies that the response stream is still a readable and seekable stream. | |
if ( !context.Response.Body.CanSeek || !context.Response.Body.CanRead ) | |
{ | |
return; | |
} | |
// Determines if the response stream meets the length requirements to be gzipped. | |
if ( context.Response.Body.Length >= 4096 ) | |
{ | |
context.Response.Headers["Content-Encoding"] = "gzip"; | |
// Determines if chunking can be safely used (most of the time this is true | |
if ( string.Equals(context.Request.Protocol, "HTTP/1.1", StringComparison.Ordinal) ) | |
{ | |
context.Response.Headers["Transfer-Encoding"] = "chunked"; | |
// Opens a new GZip stream pointing directly to the real response stream. | |
using ( var gzip = new GZipStream(realBody, CompressionMode.Compress, leaveOpen: true) ) | |
{ | |
// Rewinds the memory stream and copies it to the GZip stream. | |
context.Response.Body.Seek(0, SeekOrigin.Begin); | |
await context.Response.Body.CopyToAsync(gzip, 81920, context.Request.CallCancelled); | |
} | |
return; | |
} | |
// Opens a new buffer to determine the gzipped response stream length. | |
using ( var buffer = new MemoryStream() ) | |
{ | |
// Opens a new GZip stream pointing to the buffer stream. | |
using ( var gzip = new GZipStream(buffer, CompressionMode.Compress, leaveOpen: true) ) | |
{ | |
// Rewinds the memory stream and copies it to the GZip stream. | |
context.Response.Body.Seek(0, SeekOrigin.Begin); | |
await context.Response.Body.CopyToAsync(gzip, 81920, context.Request.CallCancelled); | |
} | |
// Rewinds the buffer stream and copies it to the real stream. | |
// See http://blogs.msdn.com/b/bclteam/archive/2006/05/10/592551.aspx | |
// to see why the buffer is only read after the GZip stream has been disposed. | |
buffer.Seek(0, SeekOrigin.Begin); | |
context.Response.ContentLength = buffer.Length; | |
await buffer.CopyToAsync(realBody, 81920, context.Request.CallCancelled); | |
} | |
return; | |
} | |
// Rewinds the memory stream and copies it to the real response stream. | |
context.Response.Body.Seek(0, SeekOrigin.Begin); | |
context.Response.ContentLength = context.Response.Body.Length; | |
await context.Response.Body.CopyToAsync(realBody, 81920, context.Request.CallCancelled); | |
} | |
finally | |
{ | |
// Restores the real stream in the environment dictionary. | |
context.Response.Body = realBody; | |
} | |
} | |
private static bool DoesntNeedGzip(PathString requestPath, IHeaderDictionary requestHeaders) | |
{ | |
return | |
!requestPath.StartsWithSegments(new PathString("/swagger")) //not on /swagger path | |
|| !(requestPath.Value.EndsWith("-js") || requestPath.Value.EndsWith("-css")) //Not a JS or CSS | |
|| !requestHeaders.ContainsKey("Accept-Encoding") || !requestHeaders["Accept-encoding"].Contains("gzip"); // Doesn't support Gzip | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment