Skip to content

Instantly share code, notes, and snippets.

@centur
Created September 21, 2015 05:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save centur/eee30685d2d52a5f2f20 to your computer and use it in GitHub Desktop.
Save centur/eee30685d2d52a5f2f20 to your computer and use it in GitHub Desktop.
Gzip Middleware to compress Swashbuckle SwaggerUI assets
/// <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