如何在 HTTP/1.1 中接收分块响应,同时在 Java/Android 中向服务器发送数据

How to received chunked responses in HTTP/1.1 while sending data at the same time to server in Java/Android

我们正在构建一个应用程序,它可以实时记录用户的语音,并通过 HTTP 请求将记录的数据发送到服务器。当服务器实时处理数据时,它也以分块的形式发回响应。简单的说,app就是一条条的向服务器发送数据,同时也在接收一条条的服务器响应。

请不要告诉我这是不可能的,因为我在 iOS 中有一个工作示例,它使用 URLSessionuploadTask 使用流对发送真实数据服务器时间,然后从此回调 urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data).

逐块接收响应

下面是我在 Java 中的代码。我的发送工作正常,但只有在发送完成后才会收到响应。

RequestBody body = new RequestBody() 
{
    @Override 
    public MediaType contentType() 
    {
        return MediaType.get("application/octet-stream");
    }

    @Override 
    public void writeTo(BufferedSink sink) throws IOException 
    {
        String filename = "/path/spoken.pcm";

        try 
        {
            InputStream inputStream = new DataInputStream(new FileInputStream(new File(filename)));

            byte[] cacheBytes = new byte[320];

            int length;

            while((length = inputStream.read(cacheBytes, 0, cacheBytes.length)) != -1) 
            {
                System.out.println("write thread name: " + Thread.currentThread());

                sink.write(cacheBytes, 0, length);

                Thread.sleep(10);
            }

            inputStream.close();
        } 

        catch(IOException | InterruptedException e) 
        {
            e.printStackTrace();
        }
    }
};

Request request = new Request.Builder()
            .url("www.server.com")
            .post(body)
            .build();

Interceptor interceptor = new Interceptor()
{
    @Override
    public Response intercept(Chain chain) throws IOException 
    {
        System.out.println("intercept!!!");

        CountDownLatch latch = new CountDownLatch(1);

        Response response = chain.proceed(chain.request());
        BufferedSource source = response.body().source();

        System.out.println("got response body !!!!");

        new Thread(new Runnable()
        {
            @Override
            public void run() 
            {
                byte[] cachedBytes = new byte[512];

                try 
                {
                    while(!source.exhausted())
                    {
                        int length = source.read(cachedBytes);
                        byte[] partialBytes = new byte[length];

                        System.arraycopy(cachedBytes, 0, partialBytes, 0, length);

                        System.out.println("partial response received: " + getHexString(partialBytes));
                    }
                } 

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

                latch.countDown();
            }
        }).start();

        try 
        {
            latch.await();
        } 

        catch (InterruptedException e) 
        {
            e.printStackTrace();
        }

        return response;
    }
};

httpClient = new OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .build();

httpClient.newCall(request).enqueue(new Callback() 
{
    @Override
    public void onFailure(Call call, IOException e) 
    {
        e.printStackTrace();
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException 
    {
        try(ResponseBody responseBody = response.body()) 
        {
            if(!response.isSuccessful()) throw new IOException("Unexpected code " + response);

            System.out.println("all responses received!");
        }
    }
});

此日志:System.out.println("got response body !!!!"); 仅在我将所有数据发送到服务器后才会出现。这意味着,当 writeTo(BufferedSink sink) returns 时,我在拦截器回调中以块的形式获得响应,然后回调 onResponse(Call call, Response response) 被调用。

我需要的是,当我发送数据时,我希望能够同时获得分块响应。

对于HTTP/1.1,OkHttp不会return response body,直到request body发送完成。对于 HTTP/2,您可以将 isDuplex() 覆盖为 return true。

您可以尝试实现一个将保持打开状态并一直监听直到应用程序关闭的套接字。通过这种方式,您的服务器的响应可以涌入,您只需解析它并获取您需要的内容。 Java Socket 可以让你这样做 implementation.Java socket 可以让你独立处理输入流和输出流。在您的情况下,输入流将永远保持打开状态。

您上述解决方案的问题在于您正在向服务器发送请求。一旦服务器回复(200 OK,401 ...等),请求通过回调完成。请记住,后面的机制是异步和无状态的。

好吧,到处查,这个实现并不是其他人所说的HTTP标准方式。虽然 HTTP 不限制您在单个请求中同时进行发送和接收,但我已经使用 iOS 应用程序作为客户端并使用 Java HTTP Servlet 作为服务器来实现这一点。问题是,大多数库不支持这种行为。您可以在客户端为此编写自己的 SDK,但如果您打算为您的服务器使用负载平衡器和反向代理,您还应该考虑将来的风险,它们可能也不支持这种行为。

因此,我实现相同效果的最快解决方案是使用 HTTP 发送请求,但响应将通过 MQTT 发送到客户端。