如何在 JAVA servlet 中处理压缩 (gzip) HTTP 请求(不响应)- 简单示例?

How to handle compressed (gzip) HTTP requests (NOT response) in JAVA servlet - Simple Example?

我为这个问题苦苦挣扎了一段时间;在找到一个简单的解决方案之后......想问一个问题和答案!!

这个问题已经在堆栈溢出时以不同的方式被多次问到,accepted solutions 要么是 partially correct and complex 要么是谈论 response 压缩。

汇总关于此主题的一些旧问答:

一个简单的解决方案是使用过滤器。 (See servlet-filter tutorial)

创建 Servlet 过滤器:

  • 确保过滤器被调用 first/before 任何使用请求 body.
  • 的过滤器

我。在 web.xml:

中注册过滤器
<filter>
    <filter-name>GzipRequestFilter</filter-name>
    <filter-class>com...pkg...GzipRequestFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>GzipRequestFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

二.过滤器代码 class:

public class GzipRequestFilter implements Filter {
    // Optional but recommended.
    private static final Set<String> METHODS_TO_IGNORE = ImmutableSet.of("GET", "OPTIONS", "HEAD");

    @Override
    public void doFilter(
            final ServletRequest request,
            final ServletResponse response,
            final FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        
        String method = httpServletRequest.getMethod().toUpperCase();
        String encoding = Strings.nullToEmpty(
            httpServletRequest.getHeader(HttpHeaders.CONTENT_ENCODING));

        if (METHODS_TO_IGNORE.contains(method) || !encoding.contains("application/gzip")) {
            chain.doFilter(request, response); // pass through
            return;
        }
        
        HttpServletRequestWrapper requestInflated = new GzippedInputStreamWrapper(httpServletRequest);
        chain.doFilter(requestInflated, response);
    }

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {}
    @Override
    public void destroy() {}
}

三.后面是 GzipInputStream 包装器的代码:

// Simple Wrapper class to inflate body of a gzipped HttpServletRequest.
final class GzippedInputStreamWrapper extends HttpServletRequestWrapper {
    private GZIPInputStream inputStream;

    GzippedInputStreamWrapper(final HttpServletRequest request) throws IOException {
        super(request);
        inputStream = new GZIPInputStream(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new ServletInputStream() {
            // NOTE: Later versions of javax.servlet library may require more overrides.
            public int read() throws IOException {
                return inputStream.read();
            }
            public void close() throws IOException {
                super.close();
                inputStream.close();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(inputStream));
    }
}

现在剩下的就是如何发送压缩请求了?

Postman 尚不支持发送压缩的 HttpRequest 正文。您仍然可以使用 binary 选项使其工作,并使用包含正确编码请求的 gzip 文件 body.

一种方法是使用带有 pako compression library. For a multipart/form-data request see form-data library

的 nodejs 脚本
const pako = require('pako')
const axios = require('axios')

var params = qs.stringify({
  'num': 42,
  'str': 'A string param',
});

data = pako.gzip(Buffer.from(params));

var config = {
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Encoding': 'application/gzip';
  },
}

await axios.post(
  'http://url-for-post-api-accepting-urlencoded',
  data,
  config,
).then((res) => {
  console.log(`status: ${res.status} | data: ${res.data}`)
}).catch((error) => {
  console.error(error)
})

备注:

  • 我们使用 Content-Encoding: application/gzip header 来指定压缩请求。是的,这是标准的。
  • 不要使用 Content-Type,因为它不适用于 multipart/form-data
  • HTTP 协议运行假设 HttpRequests 的大小与 HttpResponses 相形见绌。
  • 此外,由于假定 browser/client 端的计算能力有限,所以规范是压缩响应而不是请求。浏览器无法原生压缩,但可以原生解压。
  • 但是,不幸的是,经过多年的许多开发人员推动代码;一些 HTTP API 发展到消耗大量 strings/data!!
  • 让 java servlet 可以选择处理压缩请求是小菜一碟。