Java HTTP客户端是否处理压缩?

13

我试图查找新的Java HTTP客户端中有关处理压缩的任何提及,但未能成功。是否有内置配置来处理例如gzipdeflate压缩?

我期望拥有一个BodyHandler,用于例如以下内容:

HttpResponse.BodyHandlers.ofGzipped(HttpResponse.BodyHandlers.ofString())

但我没看到。我在HttpClient中也没有看到任何配置。我是在错误的位置查找还是这个功能故意没有实现并推迟到支持库中?

但我没有看到任何配置。HttpClient 中也没有任何配置。我是在错误的地方查找还是这个功能故意没有实现并且推迟到支持库中了?


你尝试查看网络日志了吗?如果客户端附加头部 Accept-Encoding: gzip,则支持它。请注意,在 HTTP 客户端的应用程序侧和网络侧通常存在不同的头部。 - Patrick
1
阅读一些文档后,我觉得这可能与问题HPACK(HTTP/2的头部压缩)实现有关。在压缩中使用的索引表的详细信息中确实提到了附录中的示例压缩头部和静态表定义 - Naman
@patrickf 实际上我添加了这样的头部,但惊讶地发现我得到了未压缩的内容。 - Krzysztof Krasoń
1
@RomainHippeau 这怎么是重复的问题呢?你提供的链接是关于 apache http client 的,而我提出的是关于 Java http client 的(Java 11 中内建的那个),请注意标签。 - Krzysztof Krasoń
3个回答

17

我也很惊讶新的java.net.http框架不能自动处理这个问题,但是下面的代码可以处理作为InputStream收到的HTTP响应,无论它们是未压缩的还是使用gzip压缩的:

public static InputStream getDecodedInputStream(
        HttpResponse<InputStream> httpResponse) {
    String encoding = determineContentEncoding(httpResponse);
    try {
        switch (encoding) {
            case "":
                return httpResponse.body();
            case "gzip":
                return new GZIPInputStream(httpResponse.body());
            default:
                throw new UnsupportedOperationException(
                        "Unexpected Content-Encoding: " + encoding);
        }
    } catch (IOException ioe) {
        throw new UncheckedIOException(ioe);
    }
}

public static String determineContentEncoding(
        HttpResponse<?> httpResponse) {
    return httpResponse.headers().firstValue("Content-Encoding").orElse("");
}
请注意,我没有为“deflate”类型添加支持(因为我目前不需要它,而且我阅读有关“deflate”的内容越多,它似乎越来越混乱)。但我相信您可以通过向上面的 switch 块添加检查并将 httpResponse.body() 包装在 InflaterInputStream 中轻松支持“deflate”。

2
这是一个很好的答案,但至少有一次我遇到了一个返回Content-Encoding: gzip的网站,但实际上在正文中并没有gzip编码。这种代码会抛出异常。为了处理它,我使用了HttpResponse<byte[]>,使用BodyHandlers.ofByteArray(),如果Content-Encoding设置为gzip,则尝试使用new GZIPInputStream(new ByteArrayInputStream(bytes)),如果我得到异常,就简单地将该byte[]作为原始数据使用。这样做效率较低,但对我来说非常关键,因为我无法控制具有错误编码的网站,但需要使用它。 - Kivan
@Kivan 我之前也遇到过这个问题,我记得我开发了一个解决方法,使用 java.io.PushbackInputStream 来发现实际的有效载荷内容,因此很容易检查 Gzip有效载荷头 (1F 8B 08),推回并且可以选择是否包装在 GZIPINputStream 中。 - bric3

8
你可以使用Methanol。它具有 解压缩 BodyHandler 实现,支持开箱即用的 gzipdeflate。还有一个用于 brotli 的 模块
var response = client.send(request, MoreBodyHandlers.decoding(BodyHandlers.ofString()));

请注意,您可以使用任何想要的BodyHandlerMoreBodyHandlers :: decoding 使您的处理程序看起来好像响应从未被压缩过!它会处理Content-Encoding标头和所有内容。
更好的是,您可以使用Methanol自己的HttpClient,在添加适当的Accept-Encoding到您的请求后进行透明解压缩。
var client = Methanol.create();
var request = MutableRequest.GET("https://example.com");
var response = client.send(request, BodyHandlers.ofString()); // The response is transparently decompressed

1
甲醇看起来非常有趣!感谢您的推荐。 - ivant

4
不,gzip/deflate压缩不是默认处理的。如果需要,您需要在应用程序代码中实现它 - 例如,通过提供自定义的BodySubscriber来处理它。或者,您可以查看一下是否有一些响应式流库提供了这样的功能,如果有的话,您可能可以使用其中一个BodyHandlers.fromSubscriber(Flow.Subscriber> subscriber)BodyHandlers.ofPublisher()方法将其导入。

很遗憾,考虑到标准库中已经有GzipInput/OutputStream。 - Krzysztof Krasoń
1
没错。虽然使用Input/OutputStream会在拉取请求字节时将您强制返回同步模式。也许您可以使用BodyPublishers.ofInputStream(..)BodySubscribers.ofInputStream(),并结合PipedInput/OutputStream和GzipInput/OutputStream的某些组合 - 但是您仍然需要拉取请求字节。 - daniel
我确实尝试了BodySubscriber方法(请参见此问题),但它导致完全挂起。因此,我选择了不太引人注目的方法,这也是我在回答@KrzysztofKrasoń时描述的方法,而且它运行良好。虽然有些令人沮丧。 - Bobulous
在编写自定义BodyHandler时,有没有使用此方法的示例可供参考? - hemu

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