如何在 HttpResponse.BodyHandler 中消耗 InputStream?

huangapple go评论82阅读模式
英文:

How to consume an InputStream in an HttpResponse.BodyHandler?

问题

我正尝试为Java 11 HttpClient构建一个JSON处理程序。
我遇到的问题是,尝试使用BodySubscribers.ofInputStream()进行组合,导致永远不会从流中读取任何数据,并且会永远挂起。

@Override
public HttpResponse.BodySubscriber<Void> apply(HttpResponse.ResponseInfo responseInfo) {
    return HttpResponse.BodySubscribers.mapping(
            HttpResponse.BodySubscribers.ofInputStream(),
            inputStream -> {
                try {
                    System.out.print(inputStream.read());
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
                return null;
            }
    );
}

相比之下,返回流然后之后读取它确实有效。

InputStream inputStream = client.send(request, HttpResponse.BodyHandlers.ofInputStream()).body();
System.out.print(inputStream.read());  // 输出: 123

我漏掉了什么?

英文:

I'm trying to build a JSON handler for the Java 11 HttpClient.
The problem I'm having is that trying to compose with BodySubscribers.ofInputStream() results in never reading any data from the stream, and hanging forever.

@Override
public HttpResponse.BodySubscriber&lt;Void&gt; apply(HttpResponse.ResponseInfo responseInfo) {
    return HttpResponse.BodySubscribers.mapping(
            HttpResponse.BodySubscribers.ofInputStream(),
            inputStream -&gt; {
                try {
                    System.out.print(inputStream.read());
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
                return null;
            }
    );
}

In contrast, returning the stream and reading it afterwards does work.

InputStream inputStream = client.send(request, HttpResponse.BodyHandlers.ofInputStream()).body();
System.out.print(inputStream.read());  // out: 123

What am I missing?

答案1

得分: 4

问题在于在一个不应该阻塞的线程中调用了一个阻塞操作。确实存在两个问题:

  1. Java 11 API 文档暗示你可以这样做,但这真的不是一个好主意。
  2. 实现没有准备好处理映射函数中的阻塞操作。

这两个问题在 13 版本中已经被修复,见 JDK-8217264: HttpClient: 修复映射函数中阻塞操作不按文档工作的问题

如果你查看更新版本的 API 文档,你会看到现在已经更新以涵盖这种情况。
虽然从 13 版本开始这样做不再会使请求被卡住,但仍然不建议在映射函数中阻塞,因为它会阻塞 HttpClient 的执行器线程之一,直到完全接收到响应,这可能导致线程饥饿问题。

为了补充我的回答,你应该考虑遵循更新的 API 文档的建议,返回一个 Supplier<JSONObject> 而不是一个 JSONObject,这样可以延迟流的读取(以及阻塞操作),直到调用者调用 Supplier::get。这使你可以灵活地选择哪个线程会在等待响应时被阻塞。

英文:

The problem here lays in invoking a blocking operation in a thread that is not supposed to block. And really - there are two issues:

  1. The Java 11 API documentation hints that you can do this - and that's really not a good idea.
  2. The implementation wasn't prepared to handle a blocking operation in the mapper's function.

Both issues have been fixed in 13 by JDK-8217264: HttpClient: Blocking operations in mapper function do not work as documented

If you look at a more recent version of the API documentation you will see that it has now been updated to cover this case.
Although with 13 onward doing so will no longer wedge the request, blocking in the mapper's function is still discouraged, as it will block one of the HttpClient's executor thread until the response is fully received, which could lead to thread starvation.

To complement my answer - you should consider following the recommendation of the newer API documentation and return a Supplier&lt;JSONObject&gt; instead of a JSONObject, which would allow to delay the reading of the stream (and the blocking operation) until Supplier::get is called by the caller. That gives you the flexibility of choosing which thread will block waiting for the response.

huangapple
  • 本文由 发表于 2020年10月20日 22:58:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/64447837.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定