使用 ServiceStack 支持压缩请求 body

Supporting compressed request body with ServiceStack

我需要实现一个端点,它可以接受带有 gzip-compressed 请求的 POST 消息 body(不是,不是压缩响应body).

我找到了一种很容易处理这个问题的方法,方法是将请求 DTO 标记为 IRequiresRequestStream,并在服务 class 中使用 GZipStream 来解压缩原始请求 body 并构造一个表示解压缩数据的字符串。这是一个纯文本请求 body,因此在这种情况下,跳过自动请求 DTO 反序列化不会造成任何损失。

这很好,我可以就此打住,但是 我在考虑我是否可以用通用的方式编写它,以便在正确设置 Content-Encoding header 的情况下,可以解压缩发布到 ServiceStack 的每个请求。我写了一个看起来不错的 PreRequestFilter,直到我 运行 遇到了一个问题:我找不到修改原始请求输入流或以其他方式确保后面的反序列化和其他代码的方法管道可以访问解压缩的字节而不是压缩的数据:

public static void Decompress(IRequest request, IResponse response)
{
    if (!request.Headers[HttpHeaders.ContentEncoding].EqualsIgnoreCase(CompressionTypes.GZip))
        return;

    request.UseBufferedStream = true;
    using (var decompressor = new GZipStream(request.InputStream, CompressionMode.Decompress))
    {
        var decompressedBytes = decompressor.ReadFully();

        // Fails because the MemoryStream, used when UseBufferedStream
        // is true, is not expandable.
        // Or, if we set request.UseBufferedStream = false first, then 
        // it fails because the original HttpInputStream is not writeable.
        request.InputStream.SetLength(decompressedBytes.Length);
        request.InputStream.Write(decompressedBytes, 0, decompressedBytes.Length);
   }
}
PreRequestFilters.Add(Decompress);

有什么方法可以修改 PreRequestFilter 或 RawHttpHandler 中的请求 body?或者,是否有一种完全不同的方法来实现对任何内容 type/DTO 的通用请求 body 解压缩?我现在使用的 per-request 实现很好,但找到通用解决方案会很有趣。

您可以覆盖 AppHost.OnPreExecuteServiceFilter() 以更改用于执行服务的请求 DTO,例如:

class AppHost : AppHostBase
{
    public virtual object OnPreExecuteServiceFilter(IService service,
        object request, IRequest httpReq, IResponse httpRes)
    {
        if (httpReq.Headers[HttpHeaders.ContentEncoding]
            .EqualsIgnoreCase(CompressionTypes.GZip))
        {
            //...
            return customRequest;
        }

        return request;
    }
}

使用通用 ASP.NET HTTP 模块

因为这是可以在外部 HTTP 协议层通用化的东西,更好的选择是使用 ,例如:

public class GZipRequestDecompressingModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, e) =>
        {
            var request = (sender as HttpApplication).Request;

            string contentEncoding = request.Headers["Content-Encoding"];

            if (string.Equals(contentEncoding, "gzip",
                StringComparison.OrdinalIgnoreCase))
            {
                request.Filter = new GZipStream(request.Filter,
                    CompressionMode.Decompress);
                request.Headers.Remove("Content-Encoding");
            }
        };
    }
    public void Dispose()
    {
    }
}

可以在您的 Web.Config 中配置:

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
        <add name="AnyUniqueName"
            type="YourNamespace.GZipRequestDecompressingModule, YourAssembly"
            preCondition="integratedMode" />
    </modules>
</system.webServer>