如何使用Apache HttpClient流式传输响应主体


问题内容

我需要执行一个没有长度的八位字节流的api。它只是实时数据流。我遇到的问题是,当我发出请求时,似乎试图在将信息读入输入流之前先等待内容的结尾,但是它没有看到内容的结尾和NoHttpResponse异常的超时。以下是我的代码的简化版本:

private static HttpPost getPostRequest() {
    // Build uri
    URI uri = new URIBuilder()
            .setScheme("https")
            .setHost(entity.getStreamUrl())
            .setPath("/")
            .build();

    // Create http http
    HttpPost httpPost = new HttpPost(uri);

    String nvpsStr = "";
    Object myArray[] = nvps.toArray();
    for(int i = 0; i < myArray.length; i ++) {
        nvpsStr += myArray[i].toString();
        if(i < myArray.length - 1) {
            nvpsStr += "&";
        }
    }

    // Build http payload
    String request = nvpsStr + scv + streamRequest + "\n\n";
    // Attach http data
    httpPost.setEntity(new StringEntity(URLEncoder.encode(request,"UTF-8")));

    return httpPost;
}

// Where client is simply
// private static final CloseableHttpClient client = HttpClients.createDefault();
private static runPostRequest (HttpPost request) {
    CloseableHttpResponse response = client.execute(request);
    try {
        HttpEntity ent = response.getEntity();
        InputStream is = ent.getContent();
        DataInputStream dis = new DataInputStream(is);
        // Only stream the first 200 bytes
        for(int i = 0; i < 200; i++) {
            System.out.println(( (char)dis.readByte()));
        }

    } finally {
        response.close();
    }
}

问题答案:

编辑2

因此,如果您对线程/可运行程序/处理程序不满意,而对Android
AsyncTask不满意,我将直接转到HttpUrlConnection(使用Apache
HttpClient删除整个练习,因为基本上Google表示HttpUrlConnection将支持流式响应,并且它确实工作!)

检测所有细节(例如转储标头)可能并不容易。但是,对于正常的流式响应对象,我认为它应该可以正常工作....参见HttpsUrlConnection代码示例的编辑3

EndEdit2

从问题中不清楚是使用什么“流”协议(渐进式下载或HTTP流),还是您实际上如何在客户端上管理流响应。

建议将标头从连接中转储以查看客户端和服务器的确切协议??

我假设您关闭了UI线程(在AsyncTask或Handler的回调部分中);如果这不准确,则可能需要重构一点。

假设HTTP流与Apache HttpClient 4.3.5+一起使用

如果响应的标题中没有长度,则您正在HTTP 1.1上执行“分块”响应,您必须在其中读取缓冲区,直到获得“最后一个块”或决定关闭流或连接为止:

服务器只是开始发送(流式传输),并且客户端应按照Apache关于 生成实体内容 的详细说明,使用缓冲区来处理它从HTTP响应中获得的“输入流”

我不记得30秒钟的套接字超时是否会抢占活动流吗?请记住,在Apache中,构建器中存在用于套接字超时和 读取
超时的单独设置。当服务器提供响应时,不想让套接字关闭,也不想等待可读流的可用字节超时。

无论如何,客户端处理程序只需要通过检查读入缓冲区的内容来知道流是如何结束的…

如果使用的协议是“ continue”和“ chunked”,则客户端上的响应处理程序应处于流处理程序循环中,直到从http
spec中
看到LAST-CHUNK为止。

 response.getEntity().getContent()

应该为您提供处理响应流直到’last-chunk’所需的参考…

我认为您应该在这里阅读如何使用缓冲的实体,而在响应中的“最后一个块”结束时,将需要不止一次读取。这是HttpURLConnection可能更容易的另一个原因…

进行一个循环处理缓存的读取,直到由匹配“ last-chunk”的字节发出的END信号为止。

然后根据有关使用实体和可重用连接的详细Apache注释,关闭流或连接。

*用于Apache HttpClient中流式响应的 *EDIT 代码

在“处理程序”的回调或asyncTask中

 request.execute();
...

 processStreamingEntity(response.getEntity());
 response.close();

//implement your own wrapper as mentioned in apache docs

    private void processStreamingEntity(HttpEntity entity) throws IOException {
        InputStreamHttpEntityHC4 bufHttpEntity = new InputStreamHttpEntityHC4(entity);
        while not bufHttpEntity.LAST_CHUNK {
            handleResponse(bufHttpEntity.readLine())
}

编辑3

如果您使用HttpURLConnection版本。(使用MessageHandler,但您可以在原处使用字节,因为这是来自流式传输示例,此处来自文本的单词被发送回UI)

private void openHttpsConnection(String urlStr, Handler mhandler) throws IOException {
    HttpsURLConnection httpConn = null;
    String line = null;
    try {
        URL url = new URL(urlStr);
        URLConnection urlConn = url.openConnection();               
        if (!(urlConn instanceof HttpsURLConnection)) {
            throw new IOException ("URL is not an Https URL");
        }               
        httpConn = (HttpsURLConnection)urlConn;
        httpConn.setAllowUserInteraction(false);
        httpConn.setInstanceFollowRedirects(true);
        httpConn.setRequestMethod("GET");
        httpConn.setReadTimeout(50 * 1000);
        BufferedReader is =
                new BufferedReader(new InputStreamReader(httpConn.getInputStream()));

        while ((line = is.readLine( )) != null) {

                Message msg = Message.obtain();
                msg.what=1;  
                msg.obj=line;                       
                mhandler.sendMessage(msg);

        }               
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch( SocketTimeoutException e){
        e.printStackTrace();
    } catch (IOException e) {

        e.printStackTrace();
        Message msg = Message.obtain();
            msg.what=2;
            BufferedInputStream in = new BufferedInputStream(httpConn.getErrorStream());

            line =new String(readStream(in));
            msg.obj=line;
            mhandler.sendMessage(msg);

    }
    finally {httpConn.disconnect();}

}