Apache HTTPClient流式传输HTTP POST请求



我正在尝试使用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发送请求头,用自己的Transfer-Encoding: chunked头接收HTTP/1.1 200 OK的响应头,然后我将有一个全双工流HTTP连接来工作。

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

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

如果HTTPClient不支持这种方式,还有其他HTTP客户端库吗?或者我应该计划编写一个(最小的)HTTP客户端来支持这种模式?

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

  1. 我不能完全同意非200响应意味着失败的事实。所有2XX响应大多是有效的。查看wiki获取更多详细信息

  2. 对于任何TCP请求,我建议接收整个响应以确认它是有效的。我之所以这样说,是因为部分响应可能被视为糟糕的响应,因为大多数客户端实现都无法使用它。(想象一下,服务器响应2MB的数据,在此期间宕机)

必须有一个单独的线程写入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();

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

最新更新