콘텐츠로 이동

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로 전달하지 않는다.

var downloadChunks = 0;
var downloadBytes = 0;
var downloaded = await client
    .Get($"/rooms/{room.Body}/export")
    .DownloadAsync(chunk =>
    {
        downloadChunks++;
        downloadBytes += chunk.Length;
    });
Console.WriteLine($"download stream: chunks {downloadChunks} bytes {downloadBytes}");

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

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

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

var uploadChunks = new Queue<byte[]>(
    new[]
    {
        Encoding.UTF8.GetBytes("{\"playerId\":\"p2\",\"text\":\"one\"}\n"),
        Encoding.UTF8.GetBytes("{\"playerId\":\"p2\",\"text\":\"two\"}\n"),
        Encoding.UTF8.GetBytes("{\"playerId\":\"p2\",\"text\":\"three\"}\n"),
    }
);
var imported = await client
    .Post($"/rooms/{room.Body}/import")
    .BodyStream(
        () => uploadChunks.Count > 0 ? uploadChunks.Dequeue() : null,
        "application/x-ndjson"
    )
    .Async<ImportResponse>();
Console.WriteLine($"upload stream: imported {imported.Body.Imported}");

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

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

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

4. 다음 장

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