관리 메뉴

HAMA 블로그

[Play2] Streaming HTTP responses (File, Chunked) (번역) 본문

PlayFramework2

[Play2] Streaming HTTP responses (File, Chunked) (번역)

[하마] 이승현 (wowlsh93@gmail.com) 2017. 3. 19. 20:04

Streaming HTTP responses

Standard responses and Content-Length header

HTTP 1.1부터 여러 HTTP 요청과 응답을 제공하기 위한 단일 연결을 유지하기 위해, 서버는 적절한 Content-Length HTTP 헤더를 응답과 함께 보내야합니다.

기본적으로 아래와 같지요. 

public Result index() {
    return ok("Hello World");
}

특별히 Content-Length 헤더를 지정하지 않았습니다. 물론, 보내는 콘텐츠가 잘 알려져 있기 때문에 Play는 콘텐츠 크기를 계산하고 적절한 헤더를 생성 할 수 있습니다.

참고 : 텍스트 기반 콘텐츠의 경우 Content-Length header가 문자를 바이트로 변환하는 데 사용되는 인코딩에 따라 계산되어야 하므로 모양이 단순하지 않습니다.

Content-Length 헤더를 올바르게 계산하려면 Play가 전체 응답 데이터를 소비하고 해당 내용을 메모리에 로드해야합니다.

Serving files

간단한 콘텐츠를 위해 전체 콘텐츠를 메모리에 로드하는 것 말고  대용량 데이터는 어떻게 할까요? 우리가 큰 파일을 웹 클라이언트로 되돌려 보내려한다고 가정 해 봅시다.

Play는 로컬 파일을 제공하는 일반적인 작업에 사용하기 쉬운 도우미를 제공합니다.

public Result index() {
    return ok(new java.io.File("/tmp/fileToServe.pdf"));
}

또한 이 헬퍼는 파일 이름에서 Content-Type 헤더를 계산합니다. 그리고 Content-Disposition 헤더를 추가하여 웹 브라우저가 이 응답을 처리하는 방법을 지정합니다. 기본값은 Content-Disposition : attachment를 사용하여 , 이 파일을 다운로드 하도록 웹 브라우저에 요청하는 것이죠. filename = fileToServe.pdf.

Chunked responses

지금까지는 스트리밍 하기 전에 콘텐츠 길이를 계산할 수 있기 때문에 스트리밍 파일 콘텐츠와 잘 작동합니다. 그러나 콘텐츠 크기가 없는 동적으로 계산된 콘텐츠는 어떻게 할까요?

이러한 종류의 응답을 위해서는 청크 분할 전송 인코딩을 사용해야합니다.

참고: 청크 분할 전송 인코딩은 웹 서버가 일련의 청크로 콘텐츠를 제공하는 HTTP 1.1 버전의 데이터 전송 메커니즘입니다. 이것은 프로토콜이 필요로 하는 Content-Length 헤더 대신 Transfer-Encoding HTTP 응답 헤더를 사용합니다. Content-Length 헤더가 사용되지 않기 때문에 서버는 클라이언트 (일반적으로 웹 브라우저)에 대한 응답 전송을 시작하기 전에 내용의 길이를 알 필요가 없습니다. 웹 서버는 동적으로 생성 된 컨텐트를 사용하여 응답을 전송하기 전에 해당 컨텐트의 전체 크기를 알 수 있습니다.

각 청크의 크기는 청크 자체 바로 전에 보내 지므로 클라이언트는 청크에 대한 데이터 수신이 완료된 시점을 알 수 있습니다. 데이터 전송은 길이가 0 인 최종 청크에 의해 종료됩니다.

https://en.wikipedia.org/wiki/Chunked_transfer_encoding

장점은 우리가 데이터를 실시간으로 제공 할 수 있다는 것입니다. 즉, 우리는 준비된 데이터를  가능한 빨리 데이터 덩어리로 보냅니다. 단점은 웹 브라우저가 콘텐츠 크기를 알지 못하기 때문에 적절한 다운로드 진행률 표시 줄을 표시 할 수 없다는 것입니다.

일부 데이터를 계산하는 동적인 InputStream을 제공하는 어딘가에 서비스가 있다고 가정 해 보겠습니다. 우리는 청크 응답을 사용하여 Play에 이 콘텐츠를 직접 스트리밍 하도록 요청할 수 있습니다.

public Result index() {
    InputStream is = getDynamicStreamSomewhere();
    return ok(is);
}

자체 청크 응답 빌더를 설정할 수도 있습니다.  (EvenSource 등) 

public Result index() {
    // Prepare a chunked text stream
    Source<ByteString, ?> source = Source.<ByteString>actorRef(256, OverflowStrategy.dropNew())
        .mapMaterializedValue(sourceActor -> {
            sourceActor.tell(ByteString.fromString("kiki"), null);
            sourceActor.tell(ByteString.fromString("foo"), null);
            sourceActor.tell(ByteString.fromString("bar"), null);
            sourceActor.tell(new Status.Success(NotUsed.getInstance()), null);
            return null;
        });
    // Serves this stream with 200 OK
    return ok().chunked(source);
}

Source.actorRef 메서드는 ActorRef에 구체화하는 Akka stream소스를 만듭니다. 그런 다음 액터로 메시지를 보내 스트림에 요소를 게시 할 수 있습니다. 다른 방법은 ActorPublisher를 확장하고 Stream.actorPublisher 메서드를 사용하여 ActorPublisher를 만드는 액터를 만드는 것입니다.

서버가 보낸 HTTP 응답을 검사 할 수 있습니다.

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked

4
kiki
3
foo
3
bar
0

우리는 응답을 닫는 세 개의 청크와 하나의 마지막 빈 청크를 얻습니다.


Comments