【问题标题】:Apache HTTPClient Streaming HTTP POST Request?Apache HTTPClient 流式传输 HTTP POST 请求?
【发布时间】:2013-06-09 17:34:24
【问题描述】:

我正在尝试使用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 的异步客户端,具有很好的使用示例(例如 this one)。我的问题是:

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

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

【问题讨论】:

    标签: java http apache-httpclient-4.x


    【解决方案1】:

    这是我对略读代码的看法:

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

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

    【讨论】:

    • 这些都是好点。我只接受 200,因为我处于测试模式;不过,你说得对,我应该接受 2XX 来表示成功。但是,对于后一点,实现的全部目的是随着时间的推移接收和处理响应。
    • 这些不是程序挂起的原因。这是由于管道输入流造成的。
    【解决方案2】:

    必须有一个单独的线程写入 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 和终结代码中,而不是直接与 OutputStream 实例化内联。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-02
      • 1970-01-01
      • 1970-01-01
      • 2019-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-22
      相关资源
      最近更新 更多