Skip to content

Instantly share code, notes, and snippets.

@stefanolsen
Last active October 22, 2020 14:38
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 stefanolsen/07c48824f7062115313a92e9313e0958 to your computer and use it in GitHub Desktop.
Save stefanolsen/07c48824f7062115313a92e9313e0958 to your computer and use it in GitHub Desktop.
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Brotli; // Install the Brotli.NET NuGet package.
namespace WebApiDemo.WebApi
{
public class BrotliCompressor : ICompressor
{
public string EncodingType => "br";
public async Task Compress(Stream source, Stream destination)
{
using (Stream compressionStream = new BrotliStream(destination, CompressionMode.Compress))
{
await source.CopyToAsync(compressionStream);
}
}
}
}
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace WebApiDemo.WebApi
{
public class CompressedContent : HttpContent
{
private readonly HttpContent _content;
private readonly ICompressor _compressor;
public CompressedContent(HttpContent content, ICompressor compressor)
{
_content = content;
_compressor = compressor ?? throw new ArgumentNullException(nameof(compressor));
foreach (var header in content.Headers)
{
Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
if (_content == null)
{
return;
}
using (Stream contentStream = await _content.ReadAsStreamAsync())
{
await _compressor.Compress(contentStream, stream);
}
}
protected override void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
_content?.Dispose();
}
}
}
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace WebApiDemo.WebApi
{
public class CompressionHandler : DelegatingHandler
{
private readonly IReadOnlyDictionary<string, ICompressor> _compressors;
public CompressionHandler()
{
_compressors = new Dictionary<string, ICompressor>
{
{"br", new BrotliCompressor()},
{"gzip", new GZipCompressor()},
{"deflate", new DeflateCompressor()},
{"identity", null}
};
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!ShouldCompressResponse(response.Content))
{
return response;
}
var encodings = request.Headers.AcceptEncoding;
if (encodings == null ||
encodings.Count == 0)
{
return response;
}
foreach (var encoding in encodings)
{
if (!_compressors.TryGetValue(encoding.Value, out var compressor))
{
continue;
}
if (compressor == null)
{
break;
}
response.Content = new CompressedContent(response.Content, compressor);
response.Content.Headers.ContentMD5 = null;
response.Content.Headers.ContentLength = null;
response.Content.Headers.ContentEncoding.Add(compressor.EncodingType);
response.Headers.Vary.ParseAdd("Accept-Encoding");
break;
}
return response;
}
private static bool ShouldCompressResponse(HttpContent content)
{
if (content.Headers.ContentRange != null ||
content.Headers.ContentEncoding.Count > 0)
{
return false;
}
switch (content.Headers.ContentType.MediaType)
{
case "application/json":
case "application/xml":
case "text/html":
case "text/json":
case "text/plain":
case "text/xml":
return true;
default:
return false;
}
}
}
}
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace WebApiDemo.WebApi
{
public class DeflateCompressor : ICompressor
{
public string EncodingType => "deflate";
public async Task Compress(Stream source, Stream destination)
{
using (Stream compressionStream = new DeflateStream(destination, CompressionMode.Compress))
{
await source.CopyToAsync(compressionStream);
}
}
}
}
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace WebApiDemo.WebApi
{
public class GZipCompressor : ICompressor
{
public string EncodingType => "gzip";
public async Task Compress(Stream source, Stream destination)
{
using (Stream compressionStream = new GZipStream(destination, CompressionMode.Compress))
{
await source.CopyToAsync(compressionStream);
}
}
}
}
using System.IO;
using System.Threading.Tasks;
namespace WebApiDemo.WebApi
{
public interface ICompressor
{
string EncodingType { get; }
Task Compress(Stream source, Stream destination);
}
}
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using System.Web.Http;
namespace WebApiDemo.WebApi
{
public class InitializeSite : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new CompressionHandler());
}
public void Uninitialize(InitializationEngine context)
{
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment