DelegatingHandler to decompress incoming requests in WebApi
In my app we are sending decent size packets up from a client and
receiving a decent sized response, so I want to implement some compression
on the way up and the way back.
On the way back it is fine because I can lean on IIS's dynamic compression
to do that for me, but on the way up I am finding the following issue.
I have a delegating handler that sits there to decompress the incoming
requests: (Most of this code is based on parts of Fabrik.Common
(https://github.com/benfoster/Fabrik.Common))
public class DecompressionHandler : DelegatingHandler
{
public Collection<ICompressor> Compressors;
public DecompressionHandler()
{
Compressors = new Collection<ICompressor> {new GZipCompressor(),
new DeflateCompressor()};
}
protected async override Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request,
System.Threading.CancellationToken cancellationToken)
{
if (request.Content.Headers.ContentEncoding.IsntNullOrEmpty() &&
request.Content != null)
{
var encoding = request.Content.Headers.ContentEncoding.First();
var compressor = Compressors.FirstOrDefault(c =>
c.EncodingType.Equals(encoding,
StringComparison.InvariantCultureIgnoreCase));
if (compressor != null)
{
request.Content = await
DecompressContentAsync(request.Content,
compressor).ConfigureAwait(true);
}
}
var response = await base.SendAsync(request,
cancellationToken).ConfigureAwait(true);
return response;
}
private static async Task<HttpContent>
DecompressContentAsync(HttpContent compressedContent, ICompressor
compressor)
{
using (compressedContent)
{
var decompressed = new MemoryStream();
await compressor.Decompress(await
compressedContent.ReadAsStreamAsync(),
decompressed).ConfigureAwait(true);
// set position back to 0 so it can be read again
decompressed.Position = 0;
var newContent = new StreamContent(decompressed);
// copy content type so we know how to load correct formatter
newContent.Headers.ContentType =
compressedContent.Headers.ContentType;
return newContent;
}
}
}
public class DeflateCompressor : Compressor
{
private const string DeflateEncoding = "deflate";
public override string EncodingType
{
get { return DeflateEncoding; }
}
public override Stream CreateCompressionStream(Stream output)
{
return new DeflateStream(output, CompressionMode.Compress,
leaveOpen: true);
}
public override Stream CreateDecompressionStream(Stream input)
{
return new DeflateStream(input, CompressionMode.Decompress,
leaveOpen: true);
}
}
public abstract class Compressor : ICompressor
{
public abstract string EncodingType { get; }
public abstract Stream CreateCompressionStream(Stream output);
public abstract Stream CreateDecompressionStream(Stream input);
public virtual Task Compress(Stream source, Stream destination)
{
var compressed = CreateCompressionStream(destination);
return Pump(source, compressed)
.ContinueWith(task => compressed.Dispose());
}
public virtual Task Decompress(Stream source, Stream destination)
{
var decompressed = CreateDecompressionStream(source);
return Pump(decompressed, destination)
.ContinueWith(task => decompressed.Dispose());
}
protected virtual Task Pump(Stream input, Stream output)
{
return input.CopyToAsync(output);
}
}
public interface ICompressor
{
string EncodingType { get; }
Task Compress(Stream source, Stream destination);
Task Decompress(Stream source, Stream destination);
}
public class GZipCompressor : Compressor
{
private const string GZipEncoding = "gzip";
public override string EncodingType
{
get { return GZipEncoding; }
}
public override Stream CreateCompressionStream(Stream output)
{
return new GZipStream(output, CompressionMode.Compress, leaveOpen:
true);
}
public override Stream CreateDecompressionStream(Stream input)
{
return new GZipStream(input, CompressionMode.Decompress,
leaveOpen: true);
}
}
The decompression works fine and I have my request.Content populated with
a result that is my decompressed JSON.
When I pass that off to base.SendAsync and it hits my controller method,
the model is null, whereas before I implemented compression it was all
working great.
I have read that when you Read the content stream coming in that it is a
once off thing, but I thought setting the request.content to the
decompressed result should let it be read again?
Cheers,
James
No comments:
Post a Comment