Java HTTP请求压缩

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20507007/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 02:24:12  来源:igfitidea点击:

HTTP request compression

javaspringhttpcompressiongzip

提问by jojenki

General Use-Case

一般用例

Imagine a client that is uploading large amounts of JSON. The Content-Type should remain application/jsonbecause that describes the actual data. Accept-Encoding and Transfer-Encoding seem to be for telling the server how it should format the response. It appears that responsesuse the Content-Encoding header explicitly for this purpose, but it is not a valid requestheader.

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

Is there something I am missing? Has anyone found an elegant solution?

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

Specific Use-Case

特定用例

My use-case is that I have a mobile app that is generating large amounts of JSON (and some binary data in some cases but to a lesser extent) and compressing the requests saves a largeamount of bandwidth. I am using Tomcat as my Servlet container. I am using Spring for it's MVC annotations primarily just to abstract away some of the JEE stuff into a much cleaner, annotation-based interface. I also use Hymanson for auto (de)serialization.

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

I also use nginx, but I am not sure if thats where I want the decompression to take place. The nginx nodes simply balance the requests which are then distributed through the data center. It would be just as nice to keep it compressed until it actually got to the node that was going to process it.

我也使用 nginx,但我不确定这是否是我想要解压的地方。nginx 节点简单地平衡然后通过数据中心分发的请求。保持压缩直到它真正到达要处理它的节点也一样好。

Thanks in advance,

提前致谢,

John

约翰

EDIT:

编辑:

The discussion between myself and @DaSourcerer was really helpful for those that are curious about the state of things at the time of writing this.

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

I ended up implementing a solution of my own. Note that this specifies the branch "ohmage-3.0", but it will soon be merged into the master branch. You might want to check there to see if I have made any updates/fixes.

我最终实现了自己的解决方案。注意这里指定了分支“ohmage-3.0”,但很快就会合并到master分支。您可能想在那里查看我是否进行了任何更新/修复。

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

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

采纳答案by DaSourcerer

It appears [Content-Encoding] is not a valid request header.

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

That is actually not quite true. As per RFC 2616, sec 14.11, Content-Encodingis an entityheader which means it can be applied on the entities of both, http responses and requests. Through the powers of multipart MIME messages, even selected partsof a request (or response) can be compressed.

这实际上并不完全正确。按照RFC 2616,仲14.11Content-Encoding是一实体报头,这意味着它可以在两者的实体被应用,HTTP响应和请求。通过多部分 MIME 消息的强大功能,甚至可以压缩请求(或响应)的选定部分

However, webserver support for compressed request bodies is rather slim. Apache supports it to a degree via the mod_deflatemodule. It's not entirely clear to me if nginx can handle compressed requests.

然而,网络服务器对压缩请求正文的支持相当有限。Apache 通过mod_deflate模块在一定程度上支持它。我并不完全清楚nginx 是否可以处理压缩请求

回答by user987339

Add to your header when you are sending:

发送时添加到您的标题:

JSON : "Accept-Encoding" : "gzip, deflate"

Client code :

客户端代码:

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

@JulianReschke pointed out that there can be a case of:

@JulianReschke 指出可能存在以下情况:

"Content-Encoding" : "gzip, gzip"

so extended server code will be:

所以扩展的服务器代码将是:

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);
      }
    }
}

I suppose that nginx is used as load balancer or proxy, so you need to set tomcat to do decompression.

我猜想nginx是作为负载均衡器或者代理使用的,所以需要设置tomcat来做解压。

Add following attributes to the Connector in server.xml on Tomcat,

将以下属性添加到 Tomcat 上 server.xml 中的连接器,

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

Accepting gziped requests in tomcat is a different story. You'll have to put a filter in front of your servlets to enable request decompression. You can find more about that here.

在 tomcat 中接受 gzip 请求是另一回事。您必须在 servlet 前面放置一个过滤器以启用请求解压缩。您可以在此处找到更多相关信息。

回答by Happier

Because the original code is not available any more. In case someone come here need it. I use "Content-Encoding: gzip" to identify the filter need to decompression or not.

因为原来的代码已经没有了。万一有人来这里需要它。我使用“Content-Encoding: gzip”来确定过滤器是否需要解压。

Here's the codes.

这是代码。

 @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);
}

Simple ServletInputStream wrapper class

简单的 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();
    }

}

Decompression stream code

解压码流

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;
    }
}