Last active
October 17, 2016 04:35
-
-
Save fxbeckers/593c2427447bc0d1fe60 to your computer and use it in GitHub Desktop.
Pretty naive version of an OWIN/Katana middleware gzipping response stream whose length meets fixed requirements.
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
public sealed class GZipMiddleware { | |
private readonly Func<IDictionary<string, object>, Task> next; | |
public GZipMiddleware(Func<IDictionary<string, object>, Task> next) { | |
this.next = next; | |
} | |
public async Task Invoke(IDictionary<string, object> environment) { | |
var context = new OwinContext(environment); | |
// Verifies that the calling client supports gzip encoding. | |
if (!(from encoding in context.Request.Headers.GetCommaSeparatedValues("Accept-Encoding") ?? Enumerable.Empty<string>() | |
where String.Equals(encoding, "gzip", StringComparison.Ordinal) | |
select encoding).Any() ||context.Environment.ContainsKey("websocket.Accept")) { | |
await next(environment); | |
return; | |
} | |
// Replaces the response stream by a memory stream | |
// and keeps track of the real response stream. | |
var body = 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) { | |
throw new InvalidOperationException("The response stream has been replaced by an unreadable or unseekable stream."); | |
} | |
// 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. | |
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(body, 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(body, 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(body, 81920, context.Request.CallCancelled); | |
} | |
finally { | |
// Restores the real stream in the environment dictionary. | |
context.Response.Body = body; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This implementation doesn't require buffering the entire response: https://gist.github.com/PinpointTownes/ac7059733afcf91ec319