HTTP请求压缩

时间:2013-12-10 22:49:18

标签: java spring http compression gzip

一般用例

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

我有什么遗失的吗?有没有人找到一个优雅的解决方案?

特定用例

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

我也使用nginx,但我不确定那些我想要进行解压缩的地方。 nginx节点只是平衡请求,然后通过数据中心分发这些请求。保持压缩直到实际到达将要处理它的节点为止一样好。

提前致谢,

约翰

编辑:

我和@DaSourcerer之间的讨论对那些在撰写本文时对事物状况感到好奇的人非常有帮助。

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

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

3 个答案:

答案 0 :(得分:4)

  

似乎[Content-Encoding]不是有效的请求标头。

实际上并非如此。根据{{​​3}},Content-Encoding实体标头,这意味着它可以应用于http响应和请求的实体。通过多部分MIME消息的功能,甚至可以压缩请求(或响应)的选定部分

但是,对压缩请求主体的Web服务器支持相当渺茫。 Apache通过RFC 2616, sec 14.11在一定程度上支持它。我不完全清楚mod_deflate module

答案 1 :(得分:4)

因为原始代码不再可用。如果有人来这里需要它。 我使用"内容编码: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;
    }
}

答案 2 :(得分: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中接受gziped请求是另一回事。您必须在servlet前放置一个过滤器以启用请求解压缩。您可以找到有关here的更多信息。