簡體   English   中英

如何使用 apache HttpClient 流式傳輸響應正文

[英]How to stream response body with 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 放棄整個練習,因為基本上谷歌說 HttpUrlConnection 將支持流式響應,並且確實如此工作!)

檢測所有細節(如轉儲標頭)可能並不容易。 但是對於普通的流式響應對象,我認為它應該可以正常工作......請參閱 HttpsUrlConnection 代碼示例的編輯 3

結束編輯2

從問題中不清楚正在使用什么“流”協議(漸進式下載或 HTTP 流)或您如何實際管理客戶端上的流響應。

建議從連接中轉儲標頭以查看客戶端和服務器到底同意什么?

我假設您已關閉 UI 線程(在 AsyncTask 中或在 Handler 的回調部分); 如果這不准確,您可能需要稍微重構一下。

假設 HTTP 流與 Apache HttpClient 4.3.5+ 一起使用

如果響應的標頭中沒有長度,那么您正在 HTTP 1.1 上執行“分塊”響應,您必須讀取緩沖區,直到獲得“最后一個塊”或決定關閉流或連接:

服務器剛剛開始發送(流式傳輸),客戶端應該根據 Apache生成實體內容的詳細說明使用緩沖區來處理它從 HTTP 響應中獲取的“輸入流”

我不記得 30 秒的套接字超時是否會搶占活動流? 請記住,在 Apache 中,構建器中存在單獨的套接字超時設置和讀取超時設置。 不希望套接字在您身上關閉,也不希望在服務器提供響應時超時等待可讀流的可用字節。

無論如何,客戶端處理程序只需要通過檢查讀入緩沖區的內容來了解​​流如何結束......

如果適當的協議是“繼續”和“分塊”,那么客戶端上的響應處理程序應該處於流處理程序循環中,直到它看到來自http 規范的 LAST-CHUNK。

 response.getEntity().getContent() 

應該為您提供處理響應流所需的參考,直到“最后一個塊”...

我認為您應該在這里閱讀有關如何使用緩沖實體的信息,其中需要不止一次讀取才能在響應中的“最后一個塊”處結束。 這是 HttpURLConnection 可能更容易的另一個原因......

執行一個循環處理緩沖讀取,直到匹配“last-chunk”的字節發出 END 信號。

然后根據有關使用實體和可重用連接的詳細 Apache 說明關閉流或連接。

Apache HttpClient 中流式響應的編輯代碼

在“處理程序的回調”中或在 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();}

}

嘗試 RxSON: https : //github.com/rxson/rxson它利用 JsonPath 和 RxJava 從響應中讀取 JSON 流數據塊,並在響應完成之前將它們解析為 Java 對象。

例子:

String serviceURL = "https://think.cs.vt.edu/corgis/datasets/json/airlines/airlines.json";
   HttpRequest req = HttpRequest.newBuilder(URI.create(serviceURL)).GET().build();
   RxSON rxson = new RxSON.Builder().build();

   String jsonPath = "$[*].Airport.Name";
   Flowable<String> airportStream = rxson.create(String.class, req, jsonPath);
   airportStream
       .doOnNext(it -> System.out.println("Received new item: " + it))
       //Just for test
       .toList()
       .blockingGet();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM