HTTP请求压缩

10

一般用例

想象一个客户端正在上传大量的JSON数据。Content-Type应该保持为application/json,因为这描述了实际的数据。 Accept-Encoding和Transfer-Encoding似乎是用于告诉服务器如何格式化响应的。看起来,响应使用Content-Encoding头显式地进行此操作,但它不是有效的请求头。

我是否忽略了什么?有人找到了优雅的解决方案吗?

特定用例

我的用例是,我有一个移动应用程序生成大量的JSON(在某些情况下还包括一些二进制数据,但规模较小),压缩请求可节省大量带宽。我使用Tomcat作为Servlet容器。我主要使用Spring的MVC注释,只是为了将一些JEE内容抽象成更清晰、基于注释的接口。我也使用Jackson进行自动(反)序列化。

我还使用nginx,但我不确定是否要在那里进行解压缩。nginx节点只是简单平衡请求,然后通过数据中心分发请求。将其保持压缩状态,直到它实际到达要处理它的节点,这样做也可以。

提前感谢您的回复,

John

编辑:

我和@DaSourcerer之间的讨论对于那些对写作时事态度感到好奇的人来说非常有帮助。

最终,我实现了自己的解决方案。请注意,这指定了分支“ohmage-3.0”,但它很快将合并到主分支中。您可能想检查那里是否有任何更新/修复。

https://github.com/ohmage/server/blob/ohmage-3.0/src/org/ohmage/servlet/filter/DecompressionFilter.java


1
Github的链接已经失效了! - Aryan Venkat
3
看起来它被重命名了:https://github.com/ohmage/server/blob/master/src/org/ohmage/jee/filter/GzipFilter.java - Eric Pabst
3个回答

12

因为原始代码不再可用。以防有人需要它,请注意。

我使用 "Content-Encoding: gzip" 来确定是否需要解压缩过滤器。

这是代码。

 @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    String contentEncoding = httpServletRequest.getHeader("Content-Encoding");
    if (contentEncoding != null && contentEncoding.indexOf("gzip") > -1)
    {
        try
        {
            final InputStream decompressStream = StreamHelper.decompressStream(httpServletRequest.getInputStream());

            httpServletRequest = new HttpServletRequestWrapper(httpServletRequest)
            {

                @Override
                public ServletInputStream getInputStream() throws IOException
                {
                    return new DecompressServletInputStream(decompressStream);
                }

                @Override
                public BufferedReader getReader() throws IOException
                {
                    return new BufferedReader(new InputStreamReader(decompressStream));
                }
            };
        }
        catch (IOException e)
        {
            mLogger.error("error while handling the request", e);
        }
    }

    chain.doFilter(httpServletRequest, response);
}

简单的ServletInputStream包装类

public static class DecompressServletInputStream extends ServletInputStream
{
    private InputStream inputStream;

    public DecompressServletInputStream(InputStream input)
    {
        inputStream = input;

    }

    @Override
    public int read() throws IOException
    {
        return inputStream.read();
    }

}

解压缩流代码

public class StreamHelper
{

    /**
     * Gzip magic number, fixed values in the beginning to identify the gzip
     * format <br>
     * http://www.gzip.org/zlib/rfc-gzip.html#file-format
     */
    private static final byte GZIP_ID1 = 0x1f;
    /**
     * Gzip magic number, fixed values in the beginning to identify the gzip
     * format <br>
     * http://www.gzip.org/zlib/rfc-gzip.html#file-format
     */
    private static final byte GZIP_ID2 = (byte) 0x8b;

    /**
     * Return decompression input stream if needed.
     * 
     * @param input
     *            original stream
     * @return decompression stream
     * @throws IOException
     *             exception while reading the input
     */
    public static InputStream decompressStream(InputStream input) throws IOException
    {
        PushbackInputStream pushbackInput = new PushbackInputStream(input, 2);

        byte[] signature = new byte[2];
        pushbackInput.read(signature);
        pushbackInput.unread(signature);

        if (signature[0] == GZIP_ID1 && signature[1] == GZIP_ID2)
        {
            return new GZIPInputStream(pushbackInput);
        }
        return pushbackInput;
    }
}

非常好。在Spring Boot中,这被放置在一个简单的Bean过滤器中。 - phil294

12

看起来[Content-Encoding]并不是一个有效的请求头。

实际上这并不完全正确。根据RFC 2616, sec 14.11Content-Encoding是一个实体头,这意味着它可以应用于HTTP响应和请求的实体。通过多部分MIME消息的功能,甚至可以压缩请求(或响应)的选定部分

然而,Web服务器对压缩请求正文的支持相当有限。Apache通过mod_deflate模块在一定程度上支持。我并不确定nginx是否能够处理压缩请求


有趣。所以,缺乏支持是让我感到困惑的原因。随着我思考得越多,这种做法似乎更有道理了。告诉服务器以你理解的方式响应是一回事,但仅仅开始以这种方式说话,希望服务器能够理解则完全不同。尽管如此,现在谁没有GZIP实现呢? - jojenki
是的!那很有道理。我喜欢它。缺乏支持仍然让我感到悲伤,但我可以自己解决。谢谢! - jojenki
谢谢提醒!根据73.7.2节的规定,多部分请求的各个部分被视为独立实体,因此它们应该有自己的头部。因此,我认为每个部分都应该允许有Content-Encoding(以及Content-Type)。然而,我正在尝试使用的大多数库似乎很难甚至不可能做到这一点。 :( - jojenki
压缩请求的部分甚至比整个压缩请求更为罕见。离开Java世界,我认为Guzzle支持压缩多部分请求的选定部分。然而,Web服务器对此的支持几乎为零。 - DaSourcerer
这很有道理。压缩部分是/曾经是我的原始问题,但当我发布这个问题时,我决定将其简化并使其更加通用。感谢您提供的所有信息!我实施了一个看起来现在正在工作的解决方案,并将更新我的问题。 - jojenki
显示剩余4条评论

2
在发送邮件时,请将以下代码添加到您的标题中:
JSON : "Accept-Encoding" : "gzip, deflate"

客户端代码:

HttpUriRequest request = new HttpGet(url);
request.addHeader("Accept-Encoding", "gzip");

@JulianReschke指出,可能存在以下情况:
"Content-Encoding" : "gzip, gzip"

因此,扩展后的服务器代码将为:

InputStream in = response.getEntity().getContent();
Header encodingHeader = response.getFirstHeader("Content-Encoding");

String gzip = "gzip";
if (encodingHeader != null) {
    String encoding = encodingHeader.getValue().toLowerCase();
    int firstGzip = encoding.indexOf(gzip);
    if (firstGzip > -1) {
      in = new GZIPInputStream(in);
      int secondGzip = encoding.indexOf(gzip, firstGzip + gzip.length());
      if (secondGzip > -1) {
        in = new GZIPInputStream(in);
      }
    }
}

我猜nginx被用作负载均衡器或代理,所以你需要设置tomcat进行解压缩。

在Tomcat的server.xml文件中,为Connector添加以下属性:

<Connector 
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,application/json"
... />

在Tomcat中接受gzip请求有所不同。您需要在servlet前面放置一个过滤器以启用请求解压缩。您可以在这里找到更多相关信息。


首先,感谢您的回复!这就是我的问题所在。连接器将允许我的Servlet和所有代码表现得好像没有压缩,然后Tomcat在出门时会对其进行压缩。我想知道是否有类似于另一个方向的东西。上面的“服务器代码”要求我在我的代码中执行此操作。这意味着诸如Spring和Jackson的自动反序列化之类的东西会丢失。模拟它并不难,但考虑到它是在出门时发生的,为什么在进门时没有类似的东西呢? - jojenki
有趣。这是另一个在输出时(而不是输入时)使用GZIP的案例,但这给了我一个想法。我将使用自定义过滤器,该过滤器将使用自定义ServletRequest(而不是响应)类继续链。我希望在实施解决方案之前保持此选项未选中,但一旦完成后我一定会检查它。 - jojenki
此代码无法正确处理更复杂的 Content-Encoding 版本,例如 "gzip, gzip"。 - Julian Reschke
你只检查一个标题字段实例,并且你已经固定了支持的gzip标记数。 - Julian Reschke
@JulianReschke 我真的不知道关于头实例的事情。你能给我一个例子,说明这个片段不起作用吗? - user987339
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接