【问题标题】:HTTP request compressionHTTP 请求压缩
【发布时间】:2013-12-28 17:02:24
【问题描述】:

一般用例

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

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

特定用例

我的用例是我有一个移动应用程序,它生成大量 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

【问题讨论】:

标签: java spring http compression gzip


【解决方案1】:

因为原始代码不再可用。万一有人来这里需要它。 我使用“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 Filter 中
【解决方案2】:

看来 [Content-Encoding] 不是有效的请求标头。

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

但是,网络服务器对压缩请求主体的支持相当少。 Apache 通过mod_deflate module 在一定程度上支持它。如果nginx can handle compressed requests,我并不完全清楚。

【讨论】:

  • 有趣。因此,缺乏支持让我感到困惑。我越想越有道理。告诉服务器以您理解的方式响应是一回事,但只是开始以这种方式说话以希望服务器能够理解则完全不同。不过,现在谁没有 GZIP 实现?
  • “完全不同,只是开始这样说话,希望服务器能理解。”好吧,有Expect: 100-continue。但这是一个完全不同的故事......
  • 是的!这很有意义。我喜欢。缺乏支持仍然让我感到难过,但我可以靠自己做到这一点。谢谢!
  • 嗯,根据this post(见第一个答案),出于安全原因,一些网络服务器似乎故意选择不实现此功能。有道理,tbh。
  • 感谢您的提醒!根据73.7.2 部分,多部分请求的各个部分似乎被认为是它们自己的实体;因此,他们应该有自己的标题。因此,我觉得我应该被允许在每个部分都有一个Content-Encoding(以及Content-Type)。但是,我尝试使用的大多数库似乎即使不是不可能也使这变得非常困难。 :(
【解决方案3】:

发送时添加到您的标题中:

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 前面放置一个过滤器以启用请求解压缩。你可以找到更多关于here的信息。

【讨论】:

  • 首先,感谢您的回复!这就是我的意思。连接器将允许我的 Servlet 和我的所有代码像没有压缩一样运行,然后 Tomcat 在出门的路上会对其进行压缩。我想知道在另一个方向是否有类似的东西。上面的“服务器代码”要求我在我的代码中执行此操作。这意味着像 Spring 和 Jackson 的自动反序列化之类的东西会丢失。自己模仿不难,但既然发生在出路,那为什么进路没有类似的东西呢。
  • 有趣。这是另一个 GZIP 正在退出(而不是进入)的案例,但它给了我一个想法。我将使用一个自定义过滤器,它将使用自定义 ServletRequest(而不是响应)类继续链。在我实施解决方案之前,我想保持不检查,但一旦我实施了,我一定会检查它。
  • 此代码将无法正确处理更复杂的 Content-Encoding 版本,例如“gzip, gzip”。
  • @JulianReschke 你能帮我改进答案吗?
  • 好吧,你需要在解析方面做更多的工作。 1)使用“,”作为分隔符组合所有字段值,2)在“,”上拆分结果值,3)确保您理解所有标记并以正确的顺序处理它们。
猜你喜欢
  • 2017-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-13
  • 2016-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多