스트리밍으로 즉각적으로 반응하기

제프 포스닉
제프 포스닉

서비스 워커를 사용해 본 사람이라면 모두 비동기 방식이라고 생각할 수 있습니다. FetchEvent와 같은 이벤트 기반 인터페이스에만 의존하며 프로미스를 사용하여 비동기 작업이 완료되면 신호를 보냅니다.

비동기성도 서비스 워커의 가져오기 이벤트 핸들러에서 제공하는 응답의 경우 개발자에게 덜 표시되더라도 똑같이 중요합니다. 스트리밍 응답은 가장 좋은 표준입니다. 스트리밍 응답을 사용하면 원래 요청을 한 페이지에서 첫 번째 데이터 청크가 제공되는 즉시 응답 작업을 시작하고, 스트리밍에 최적화된 파서를 사용하여 점진적으로 콘텐츠를 표시할 수 있습니다.

자체 fetch 이벤트 핸들러를 작성할 때는 일반적으로 respondWith() 메서드에 fetch() 또는 caches.match()를 통해 얻은 Response(또는 Response의 프로미스)를 전달하고 하루를 호출하는 것이 일반적입니다. 다행히 이 두 가지 메서드로 만든 Response는 이미 스트리밍이 가능합니다. 안타까운 점은 '수동으로' 구성된 Response는 적어도 지금까지는 스트리밍할 수 없다는 것입니다. 이때 Streams API가 필요합니다.

스트림,

스트림은 점진적으로 생성 및 조작할 수 있는 데이터 소스로, 비동기 데이터 청크의 읽기 또는 쓰기를 위한 인터페이스를 제공합니다. 데이터의 일부분만 특정 시점에 메모리에서 사용할 수 있습니다. 지금은 fetchEvent.respondWith()에 전달되는 Response 객체를 구성하는 데 사용할 수 있는 ReadableStream를 살펴보겠습니다.

self.addEventListener('fetch', event => {
    var stream = new ReadableStream({
    start(controller) {
        if (/* there's more data */) {
        controller.enqueue(/* your data here */);
        } else {
        controller.close();
        }
    });
    });

    var response = new Response(stream, {
    headers: {'content-type': /* your content-type here */}
    });

    event.respondWith(response);
});

요청이 fetch 이벤트를 트리거한 페이지는 event.respondWith()가 호출되는 즉시 다시 스트리밍 응답을 받으며 서비스 워커가 추가 데이터를 계속 enqueue()하는 한 이 스트림에서 계속 읽습니다. 서비스 워커에서 페이지로 흐르는 응답은 진정으로 비동기적이며 Google에서 스트림을 채우는 것을 완전히 제어할 수 있습니다.

실제 사용 사례

이전 예에 자리표시자 /* your data here */ 주석이 포함되어 있으며 실제 구현 세부정보를 간략하게 확인했을 수 있습니다. 그렇다면 실제 사례는 어떻게 될까요?

제이크 아치볼드에게는 fetch()를 통해 스트리밍되는 '실시간' 데이터 (이 경우 블로그 콘텐츠)와 함께 스트림을 사용하여 캐시된 여러 HTML 스니펫의 HTML 응답을 결합하는 좋은 예가 있습니다.

Jake에서 설명한 것처럼 스트리밍 응답을 사용할 때의 이점은 전체 블로그 콘텐츠 가져오기가 완료될 때까지 기다릴 필요 없이 캐시에서 빠르게 로드되는 초기 비트를 포함하여 스트림이 들어올 때 HTML을 파싱하고 렌더링할 수 있다는 것입니다. 이렇게 하면 브라우저의 프로그레시브 HTML 렌더링 기능을 최대한 활용할 수 있습니다. 일부 이미지 및 동영상 형식과 같이 점진적으로 렌더링될 수 있는 다른 리소스도 이 접근 방식을 활용할 수 있습니다.

스트림, 아니면 앱 셸인가요?

서비스 워커를 사용하여 웹 앱을 구동하는 것과 관련된 기존 권장사항은 앱 셸 + 동적 콘텐츠 모델에 중점을 둡니다. 이러한 접근 방식은 웹 애플리케이션의 '셸'(구조와 레이아웃을 표시하는 데 필요한 최소한의 HTML, 자바스크립트, CSS)을 적극적으로 캐시한 다음 클라이언트 측 요청을 통해 각 특정 페이지에 필요한 동적 콘텐츠를 로드하는 방식에 의존합니다.

스트림은 앱 셸 모델의 대안을 제공합니다. 이 모델에서는 사용자가 새 페이지로 이동할 때 브라우저에 더 완전한 HTML 응답이 스트리밍됩니다. 스트리밍된 응답은 캐시된 리소스를 활용할 수 있으므로 오프라인 상태에서도 여전히 HTML의 초기 청크를 빠르게 제공할 수 있지만 결국에는 기존의 서버에서 렌더링된 응답 본문과 비슷한 모습입니다. 예를 들어 웹 앱이 부분 템플릿을 결합하여 서버에서 HTML을 렌더링하는 콘텐츠 관리 시스템을 기반으로 하는 경우, 이 모델은 서버 대신 서비스 워커에 복제된 템플릿 로직을 사용하여 스트리밍 응답을 사용하도록 직접 변환됩니다. 다음 동영상에서 확인할 수 있듯이 이 사용 사례에서는 스트리밍된 응답이 제공하는 속도상의 이점이 크게 향상됩니다.

전체 HTML 응답 스트리밍의 중요한 이점은 동영상에서 가장 빠른 대안인 이유를 설명하며, 초기 탐색 요청 중에 렌더링된 HTML이 브라우저의 스트리밍 HTML 파서를 최대한 활용할 수 있다는 것입니다. 앱 셸 모델에서 일반적인 경우와 같이 페이지가 로드된 후 문서에 삽입되는 HTML 덩어리는 이 최적화를 활용할 수 없습니다.

서비스 워커 구현의 계획 단계에 있다면 점진적으로 렌더링되는 스트리밍 응답 모델 또는 동적 콘텐츠에 대한 클라이언트 측 요청과 결합된 경량 셸 모델 중 어떤 모델을 채택해야 할까요? CMS와 부분 템플릿(장점: 스트림)을 사용하는 기존 구현이 있는지, 점진적 렌더링(장점: 스트림), 웹 앱이 단일 페이지 애플리케이션으로 가장 효과적으로 모델링되는지(장점: 앱 셸의 장점: 현재 여러 브라우저에서 지원되는 앱 셸의 장점)에 따라 다릅니다.

아직 서비스 워커 기반 스트리밍 응답의 초기 단계에 있으며, 다양한 모델이 성숙 단계에 이르기를 기대하며, 특히 일반적인 사용 사례를 자동화하기 위해 더 많은 도구가 개발되기를 기대합니다.

스트림 심층 분석하기

읽을 수 있는 자체 스트림을 구성하는 경우 controller.enqueue()를 무분별하게 호출하는 것만으로는 충분하지 않거나 효율적이지 않을 수 있습니다. 제이크가 start(), pull(), cancel() 메서드를 함께 사용하여 사용 사례에 맞는 데이터 스트림을 만드는 방법을 자세히 설명합니다.

더 자세한 내용은 스트림 사양을 참조하세요.

호환성

ReadableStream를 소스로 사용하여 서비스 워커 내에 Response 객체를 생성할 수 있는 지원이 Chrome 52에 추가되었습니다.

Firefox의 서비스 워커 구현은 아직 ReadableStream에서 지원하는 응답을 지원하지 않지만, Streams API 지원에 관한 관련 추적 버그를 팔로우할 수 있습니다.

전체 서비스 워커 지원과 함께 Edge의 프리픽스가 없는 Streams API 지원 진행 상황은 Microsoft 플랫폼 상태 페이지에서 확인할 수 있습니다.