【问题标题】:Does Java HTTP Client handle compressionJava HTTP 客户端是否处理压缩
【发布时间】:2023-03-21 11:47:01
【问题描述】:

我试图在新的 Java HTTP 客户端中找到有关处理压缩的任何提及,但失败了。是否有内置配置可以处理,例如gzipdeflate 压缩?

我希望有一个BodyHandler,例如像这样:

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

但我没有看到。我也没有在HttpClient 中看到任何配置。我是不是找错地方了,还是故意没有实现并推迟到支持库?

【问题讨论】:

  • 你试过查看网络日志吗?如果客户端附加标头 Accept-Encoding: gzip 它支持它。请注意,您在应用程序端和 http 客户端的网络端获得的标头通常是不同的。
  • 通过一些文档觉得这可能与问题HPACK (Header Compression for HTTP/2) implementation有关。 Indexing Tables used in compression 上的详细信息确实在 Appendix 中提到了您的示例压缩标题。
  • @patrickf 实际上我添加了这样的标题并且很惊讶我得到了未压缩的内容
  • @RomainHippeau 它是如何重复的?您链接的问题是关于 apache http 客户端的问题,而我的问题是关于 Java http 客户端(嵌入在 Java 11 中的客户端),请注意标签。

标签: java java-11 java-http-client


【解决方案1】:

我也很惊讶新的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”的了解越多,它听起来就越混乱)。但我相信您可以通过在上述开关块中添加检查并将httpResponse.body() 包装在InflaterInputStream 中来轻松支持“放气”。

【讨论】:

  • 这是一个很好的答案,但是至少有一次我遇到了一个返回 Content-Encoding 的网站:gzip,但实际上正文中没有涉及 gzip 编码。而这种代码会抛出异常。为了处理它,我使用 HttpResponse 使用 BodyHandlers.ofByteArray() 并且如果 Content-Encoding 设置为 gzip 尝试使用 new GZIPInputStream(new ByteArrayInputStream(bytes)) 并且如果我得到异常,只需使用那个 byte[ ] 作为原始数据。它的效率较低,但对我来说至关重要,因为我无法控制编码错误的网站,但需要使用它。
【解决方案2】:

您可以使用Methanol。它具有decompressingBodyHandler 实现,对gzipdeflate 提供开箱即用的支持。还有一个 module 用于brotli。

var response = client.send(request, MoreBodyHandlers.decoding(BodyHandlers.ofString()));

请注意,您可以使用任何您想要的BodyHandlerMoreBodyHandlers::decoding 让您的处理程序看起来好像响应从未被压缩!它负责Content-Encoding 标头和所有内容。

更好的是,您可以使用甲醇自己的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

【讨论】:

    【解决方案3】:

    不,默认情况下不处理 gzip/deflate 压缩。如果需要,您必须在应用程序代码中实现它 - 例如通过提供定制的BodySubscriber 来处理它。或者-您可能想看看那里的某些反应流库是否提供这样的功能,在这种情况下,您可以使用BodyHandlers.fromSubscriber​(Flow.Subscriber&lt;? super List&lt;ByteBuffer&gt;&gt; subscriber)BodyHandlers.ofPublisher() 方法之一将其导入。

    【讨论】:

    • 很遗憾,考虑到标准库中已经有一个GzipInput/OutputStream了。
    • 对。尽管在拉取请求字节时使用 Input/OutputStream 会强制您回到同步模式。也许您也可以使用 BodyPublishers.ofInputStream(..)BodySubscribers.ofInputStream() 以及 PipedInput/OutputStream 和 GzipInput/OutputStream 的某种组合 - 但是您仍然需要提取请求字节。
    • 我确实尝试了BodySubscriber 方法(请参阅this question),但它导致了完全挂起。因此,我采用了我在对@KrzysztofKrasoń 的回答中描述的不那么迷人的方法,并且效果很好。不过,令人沮丧。
    • 在编写自定义 BodyHandler 时是否有一个示例可以参考使用这种方法?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 2021-10-26
    相关资源
    最近更新 更多