6

我正在尝试使用Apache HTTPClient构建“全双工”HTTP 流式传输请求。

在我的第一次尝试中,我尝试使用以下请求代码:

URL url=new URL(/* code goes here */);

HttpPost request=new HttpPost(url.toString());

request.addHeader("Connection", "close");

PipedOutputStream requestOutput=new PipedOutputStream();
PipedInputStream requestInput=new PipedInputStream(requestOutput, DEFAULT_PIPE_SIZE);
ContentType requestContentType=getContentType();
InputStreamEntity requestEntity=new InputStreamEntity(requestInput, -1, requestContentType);
request.setEntity(requestEntity);

HttpEntity responseEntity=null;
HttpResponse response=getHttpClient().execute(request); // <-- Hanging here
try {
    if(response.getStatusLine().getStatusCode() != 200)
        throw new IOException("Unexpected status code: "+response.getStatusLine().getStatusCode());

    responseEntity = response.getEntity();
}
finally {
    if(responseEntity == null)
        request.abort();
}

InputStream responseInput=responseEntity.getContent();
ContentType responseContentType;
if(responseEntity.getContentType() != null)
    responseContentType = ContentType.parse(responseEntity.getContentType().getValue());
else
    responseContentType = DEFAULT_CONTENT_TYPE;

Reader responseStream=decode(responseInput, responseContentType);
Writer requestStream=encode(requestOutput, getContentType());

请求挂在上面指示的行。似乎代码正试图在获得响应之前发送整个请求。回想起来,这是有道理的。然而,这不是我所希望的。:)

相反,我希望使用 发送请求标头Transfer-Encoding: chunked,接收HTTP/1.1 200 OK带有Transfer-Encoding: chunked自己标头的响应标头,然后我将使用全双工流式 HTTP 连接。

令人高兴的是,我的 HTTPClient 有另一个基于 NIO 的异步客户端,有很好的使用示例(比如这个)。我的问题是:

  1. 我对同步 HTTPClient 行为的解释是否正确?或者我可以做些什么来以我描述的方式继续使用(更简单的)同步 HTTPClient ?
  2. 基于 NIO 的客户端是否在寻求响应之前等待发送整个请求?还是我能够以增量方式发送请求并同时以增量方式接收响应?

如果 HTTPClient 不支持这种方式,是否还有另一个 HTTP 客户端库可以支持?还是我应该计划编写一个(最小的)HTTP 客户端来支持这种方式?

4

2 回答 2

1

以下是我对略读代码的看法:

  1. 我不能完全同意非 200 响应意味着失败这一事实。所有 2XX 响应大多有效。检查维基了解更多详情

  2. 对于任何 TCP 请求,我建议接收整个响应以确认它是有效的。我这样说是因为,部分响应可能主要被视为错误响应,因为大多数客户端实现都无法使用它。(想象一下服务器响应 2MB 数据并且在此期间出现故障的情况)

于 2013-06-09T18:47:06.220 回答
0

必须有一个单独的线程写入 OutputStream 才能使您的代码正常工作。

  • 上面的代码为 HTTPClient 提供了一个 PipedInputStream。
  • PipedInputStream 在写入相应的 OutputStream 时使字节可用。
  • 上面的代码不会写入 OutputStream(必须由单独的线程完成。
  • 因此,代码恰好挂在您的评论所在的位置。
  • 在后台,Apache 客户端说“inputStream.read()”,在管道流的情况下,它要求之前调用 outputStream.write(bytes)(由单独的线程)。
  • 由于您没有从单独的线程将字节泵入相关的 OutputStream 中,因此 InputStream 只是坐下来等待 OutputStream 被“其他线程”写入。

来自 JavaDocs:

管道输入流应该连接到管道输出流;然后,管道输入流提供写入管道输出流的任何数据字节。

通常,数据由一个线程从 PipedInputStream 对象读取,数据由其他线程写入相应的 PipedOutputStream。

不建议尝试从单个线程中使用这两个对象,因为这可能会使线程死锁。

管道输入流包含一个缓冲区,在限制范围内将读取操作与写入操作分离。如果向连接的管道输出流提供数据字节的线程不再活动,则称管道“损坏”。

注意:在我看来,由于您的问题陈述中没有提到管道流和并发,因此没有必要。尝试先用 Entity 对象包装 ByteArrayInputStream() 以进行完整性检查……这应该可以帮助您缩小问题范围。

更新

顺便说一句,我写了一个 Apache 的 HTTP 客户端 API [PipedApacheClientOutputStream]的反转,它使用 Apache Commons HTTP Client 4.3.4 为 HTTP POST 提供了一个 OutputStream 接口。这可能接近您正在寻找的...

调用代码如下所示:

// Calling-code manages thread-pool
ExecutorService es = Executors.newCachedThreadPool(
  new ThreadFactoryBuilder()
  .setNameFormat("apache-client-executor-thread-%d")
  .build());


// Build configuration
PipedApacheClientOutputStreamConfig config = new      
  PipedApacheClientOutputStreamConfig();
config.setUrl("http://localhost:3000");
config.setPipeBufferSizeBytes(1024);
config.setThreadPool(es);
config.setHttpClient(HttpClientBuilder.create().build());

// Instantiate OutputStream
PipedApacheClientOutputStream os = new     
PipedApacheClientOutputStream(config);

// Write to OutputStream
os.write(...);

try {
  os.close();
} catch (IOException e) {
  logger.error(e.getLocalizedMessage(), e);
}

// Do stuff with HTTP response
...

// Close the HTTP response
os.getResponse().close();

// Finally, shut down thread pool
// This must occur after retrieving response (after is) if interested   
// in POST result
es.shutdown();

注意-实际上,相同的客户端、执行器服务和配置可能会在应用程序的整个生命周期中重复使用,因此上述示例中的外部准备和关闭代码可能会存在于引导/初始化和终结代码中,而不是直接内联OutputStream 实例化。

于 2016-01-27T00:48:27.720 回答