Skip to content

Commit bc9f366

Browse files
committed
Add classes for GZip compression
CS-16240
1 parent ca1900c commit bc9f366

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

src/GZipCompressPostHandler.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.IO;
4+
using System.IO.Compression;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Archon.Http
11+
{
12+
/// <summary>
13+
/// GZip-compresses requests to save bytes over the wire
14+
/// </summary>
15+
public sealed class GZipCompressingHandler : DelegatingHandler
16+
{
17+
private readonly ImmutableHashSet<HttpMethod> verbs;
18+
19+
/// <inheritdoc cref="DelegatingHandler"/>
20+
/// <param name="verbsToCompress">A list of HTTP verbs to compress. Generally, only <see cref="HttpMethod.Post"/> and <see cref="HttpMethod.Put"/> are particularly useful.</param>
21+
public GZipCompressingHandler(HttpMessageHandler innerHandler, params HttpMethod[] verbsToCompress)
22+
: base(innerHandler)
23+
{
24+
if (verbsToCompress.Length == 0)
25+
throw new ArgumentException("Must specify at least one HTTP verb to compress", nameof(verbsToCompress));
26+
27+
verbs = verbsToCompress.ToImmutableHashSet();
28+
}
29+
30+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
31+
CancellationToken cancellationToken)
32+
{
33+
if (verbs.Contains(request.Method))
34+
request.Content = new GZipHttpContent(request.Content);
35+
36+
return base.SendAsync(request, cancellationToken);
37+
}
38+
}
39+
40+
public sealed class GZipHttpContent : HttpContent
41+
{
42+
private readonly HttpContent originalContent;
43+
44+
public GZipHttpContent(HttpContent content)
45+
{
46+
originalContent = content;
47+
48+
foreach (var header in originalContent.Headers)
49+
{
50+
Headers.TryAddWithoutValidation(header.Key, header.Value);
51+
}
52+
53+
Headers.ContentEncoding.Add("gzip");
54+
}
55+
56+
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
57+
{
58+
using (var gzip = new GZipStream(stream, CompressionLevel.Fastest, true))
59+
{
60+
await originalContent.CopyToAsync(gzip);
61+
}
62+
}
63+
64+
protected override bool TryComputeLength(out long length)
65+
{
66+
length = -1;
67+
68+
return false;
69+
}
70+
}
71+
}

src/GZipResourceFilter.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.IO.Compression;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNetCore.Mvc.Filters;
4+
using Microsoft.Extensions.Primitives;
5+
6+
namespace Archon.Http
7+
{
8+
// TODO: Separate the server and client helpers in this project so that our web clients don't need to import frickin' ASP.NET
9+
10+
/// <summary>
11+
/// A filter that decompresses GZip content from HTTP requests specified as <c>Content-Encoding: gzip</c>
12+
/// </summary>
13+
public class GZipResourceFilter : IAsyncResourceFilter
14+
{
15+
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
16+
{
17+
if (context.HttpContext.Request.Headers.TryGetValue("Content-Encoding", out StringValues values)
18+
&& values.Count == 1
19+
&& (values[0] == "gzip" || values[0] == "x-gzip"))
20+
context.HttpContext.Request.Body = new GZipStream(context.HttpContext.Request.Body, CompressionMode.Decompress, false);
21+
22+
await next();
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)