콘텐츠로 이동

Streaming

목차 | 이전: 인증·TLS·Proxy | 다음: Client와 요청의 생애

이 장을 읽고 나면

응답을 chunk로 처리하고 요청 body를 나누어 보낼 수 있다. 이 장의 코드는 각 언어의 HttpClient tutorial에서 가져왔다.

Streaming은 큰 body를 호출자 쪽 callback 또는 provider로 옮기는 경로다. JSON body와 달리 body 전체를 직렬화하거나 누적하지 않는다. status와 압축 규칙은 응답 처리 규칙에서 확인한다.

↗ 크게 보기

provider와 sink는 body가 이동하는 동안 한 번씩만 소비된다. 이 특성 때문에 streaming 경로에는 재전송과 투명 압축 해제가 적용되지 않는다.

1. Download sink로 응답을 받는다

download sink는 수신한 body chunk마다 호출되는 callback이다. raw 응답은 status와 header, JSON으로 읽기 전 body를 담는 응답이다. download가 돌려주는 최종 raw 응답은 status와 header를 보존하며, redirect 중간 응답 body는 sink로 전달하지 않는다.

AtomicInteger chunks = new AtomicInteger();
AtomicInteger bytes = new AtomicInteger();
client.get("/rooms/" + roomId + "/export")
        .download(
                chunk -> {
                    chunks.incrementAndGet();
                    bytes.addAndGet(chunk.length);
                })
        .toCompletableFuture()
        .join();
System.out.println("download stream: chunks " + chunks.get() + " bytes " + bytes.get());

예제는 export body를 chunk마다 세어 전체 chunk 수와 byte 수를 누적한다.

2. Body stream provider로 요청을 보낸다

body stream provider는 전송할 다음 chunk를 반환하고, 언어의 빈 값으로 끝을 알린다. content type은 provider와 함께 지정한다.

byte[][] lines = {
    "{\"playerId\":\"p2\",\"text\":\"import-1\"}\n".getBytes(StandardCharsets.UTF_8),
    "{\"playerId\":\"p2\",\"text\":\"import-2\"}\n".getBytes(StandardCharsets.UTF_8),
    "{\"playerId\":\"p2\",\"text\":\"import-3\"}\n".getBytes(StandardCharsets.UTF_8),
};
AtomicInteger nextLine = new AtomicInteger();
HttpResponse<Imported> imported =
        client.post("/rooms/" + roomId + "/import")
                .bodyStream(
                        () -> {
                            int index = nextLine.getAndIncrement();
                            return index < lines.length ? lines[index] : null;
                        },
                        "application/x-ndjson")
                .submit(Imported.class)
                .toCompletableFuture()
                .join();
System.out.println("upload stream: imported " + imported.body().imported());

예제는 NDJSON 세 줄을 차례로 공급하고 서버가 세 항목을 가져온 결과를 확인한다.

3. 재시도와 압축을 기대하지 않는다

download sink와 body stream provider는 한 번 소비하면 같은 body를 다시 만들 수 없다. 따라서 streaming 요청은 retry 대상이 아니며 redirect 재전송에도 사용하지 않는다. download sink에는 압축 해제가 적용되지 않아 압축된 응답의 원시 바이트를 받는다.

4. 다음 장

client 재사용, close 시점, 응답 완료 방식과 timeout 경계는 Client와 요청의 생애에서 다룬다.