모던 브라우저 내부 들여다보기(part. 2)

Navigation, Service Worker

#Web#Browser

본 글은 https://developer.chrome.com/blog/inside-browser-part2을 번역한 글입니다.


이번 글은 크롬 내부 동작에 대한 4개의 연재물 중 2번째 글입니다. 이전 글에서는 브라우저의 여러 프로세스와 쓰레드가 어떻게 각자의 기능을 처리하는지 살펴보았습니다. 이번 글에서는 웹사이트 화면을 출력하기 위해서 어떻게 각 프로세스와 쓰레드가 정보를 교환하는지 더 깊게 알아보도록 하겠습니다.

사용자가 브라우저에 URL을 입력하면 브라우저는 인터넷에서 데이터를 가져오고 화면을 출력합니다. 이번 글에서는 앞선 예시처럼 사용자가 브라우저에 사이트를 요청하고, 브라우저가 해당 페이지 렌더링을 준비하는 과정(내비게이션)에 대해 중점적으로 알아보겠습니다.

브라우저 프로세스로 출발합니다.

그림 1. 브라우저 UI(위), UI, 네트워크, 스토리지 쓰레드를 포함한 브라우저 프로세스(아래)

그림 1. 브라우저 UI(위), UI, 네트워크, 스토리지 쓰레드를 포함한 브라우저 프로세스(아래)

part1에서 다루었듯이 각 탭을 제외한 바깥 브라우저 영역은 브라우저 프로세스가 담당합니다. 브라우저 프로세스는 버튼, 입력창 등을 그리는 UI 쓰레드, 인터넷으로부터 데이터를 수신하기 위한 네트워크 스택을 담당하는 네트워크 쓰레드, 파일에 대한 접근 제어를 담당하는 스토리지 쓰레드와 같은 쓰레드 등을 포함하고 있습니다. 사용자가 주소창에 URL을 입력하면, 그 입력은 브라우저 프로세스의 UI 쓰레드가 처리하게 됩니다.

간단한 내비게이션

1단계: 입력 처리

사용자가 주소창에 뭔가 입력했을 때 UI 쓰레드는 가장 먼저 입력이 ‘검색어인지 URL인지’를 확인합니다. 크롬의 주소창은 동시에 검색창이기도 하기에 UI 쓰레드는 입력값의 형태을 확인하여 사용자를 검색 엔진으로 이동시킬지, 아니면 요청한 웹사이트로 이동시킬지를 결정해야 합니다.

그림 1. 입력값이 검색어인지 URL인지 확인하는 UI 쓰레드

그림 1. 입력값이 검색어인지 URL인지 확인하는 UI 쓰레드

2단계: 내비게이션 시작

사용자가 enter 키를 누르면, UI 쓰레드는 사이트 정보를 얻기 위한 네트워크 호출을 시작합니다. 탭 구석에 로딩 이미지가 돌아가고, 네트워크 쓰레드는 DNS 조회 및 해당 요청을 위한 TLS 연결 설정과 같은 프로토콜 단계를 거치게 됩니다.

그림 2. mysite.com으로 이동 시키기 위해 네트워크 쓰레드와 대화하는 UI 쓰레드

그림 2. mysite.com으로 이동 시키기 위해 네트워크 쓰레드와 대화하는 UI 쓰레드

3단계: 응답 읽기

그림 3. Content-Type 정보를 담고 있는 응답 header와 실제 데이터인 payload

그림 3. Content-Type 정보를 담고 있는 응답 header와 실제 데이터인 payload

응답 body(payload)가 들어오면, 네트워크 쓰레드는 필요에 따라 payload stream의 처음 몇 byte를 확인합니다. 응답 header의 Content-Type에는 payload에 어떤 타입의 데이터가 쓰여있는지 명시되어 있어야 하지만, 만약 이 값이 없거나 잘못되었을 때는 MIME Type sniffing이 실행됩니다. MIME Type sniffing은 링크된 소스 코드에 쓰인 주석 내용과 같이 무척 번거로운 작업입니다. 주석을 보면 브라우저마다 content-type/payload 정보 쌍을 얼마나 다르게 처리하고 있는지 알 수 있을 것입니다.

만약 응답이 HTML 파일이라면, 다음 단계는 데이터를 렌더러 프로세스에 전달하는 것이 될 것입니다. 하지만 만약 응답이 zip 파일이나 다른 파일이라면 다음 단계는 다운로드 매니저에게 데이터를 전달할 수 있도록 다운로드를 요청하는 작업이 될 것입니다.

그림 4. 안전한 사이트에서 응답 받은 HTML 데이터인지 확인하는 네트워크 쓰레드

그림 4. 안전한 사이트에서 응답 받은 HTML 데이터인지 확인하는 네트워크 쓰레드

이 단계에서는 SafeBrowsing 확인 작업도 함께 진행됩니다. 만약 도메인과 응답 데이터가 악성 사이트에 해당한다면, 네트워크 쓰레드는 경고 페이지를 출력하기 위한 경고 메시지를 전달합니다. 추가로 다른 사이트로(cross-site)부터 전달받은 데이터가 렌더러 프로세스에 전달될 수 없도록 **C**ross Origin Read Blocking (CORB) 확인 작업도 진행됩니다.

4단계: 렌더러 프로세스 찾기

모든 확인 작업이 완료되고 네트워크 쓰레드가 요청한 사이트로 브라우저를 이동시킬 준비가 되었다고 확인하면, 네트워크 쓰레드는 UI 쓰레드에게 데이터가 준비되었음을 알립니다. UI 쓰레드는 웹 페이지 렌더링을 계속해서 진행할 수 있도록 렌더러 프로세스를 찾습니다.

그림 5. UI 쓰레드에게 렌더러 프로세스를 찾으라고 알리는 네트워크 쓰레드

그림 5. UI 쓰레드에게 렌더러 프로세스를 찾으라고 알리는 네트워크 쓰레드

네트워크 요청은 응답받기까지 수백 밀리초가 걸릴 수 있으므로, 속도를 높이기 위한 최적화 작업이 적용됩니다. 2단계에서 UI 쓰레드가 URL 요청을 네트워크 쓰레드에게 보낼 때 UI 쓰레드는 이동할 사이트 주소를 이미 알고 있으므로, 네트워크 요청과 동시에 미리 렌더러 프로세스를 찾아놓거나 렌더러 프로세스를 실행시킵니다. 만약 이 방식을 적용하여 모든 작업이 예상한대로 진행된다면 네트워크 응답을 받았을 때 렌더러 프로세스는 이미 작업을 실행할 준비를 마친 상태일 것입니다. 하지만 만약 다른 사이트로 리다이렉트해야 할 경우에는 다른 프로세스가 실행되어야 할 수도 있으므로 준비된 프로세스는 사용되지 않을 수도 있습니다.

5단계: 내비게이션 커밋

이제 데이터와 렌더러 프로세스가 준비되었으니, 브라우저 프로세스에서 렌더러 프로세스로 내비게이션 커밋을 위한 IPC(Inter Process Communication)가 전달됩니다. 이때 데이터 스트림이 함께 전달되고 렌더러 프로세스는 HTML 데이터를 지속해서 수신할 수 있게 됩니다. 브라우저 프로세스가 렌더러 프로세서에서 커밋이 확정되었다는 확인 내용을 전달받으면 내비게이션은 완료되고 문서 로딩 단계로 넘어가게 됩니다.

이때 주소창이 변경되고 보안 표시와 사이트 설정 UI가 새 사이트 정보로 업데이트됩니다. 해당 탭의 세션 기록이 업데이트 되어 이전페이지/다음페이지 버튼으로 이전/다음 페이지로 이동할 수 있게 됩니다. 닫은 탭/세션 복원 기능을 수월하게 하기 위해서 세션 기록은 기기에 저장됩니다.

그림 6. 브라우저 프로세스와 렌더러 프로세스 사이의 렌더링 요청 IPC

그림 6. 브라우저 프로세스와 렌더러 프로세스 사이의 렌더링 요청 IPC

추가단계: 초기 로딩 완료

내비게이션이 커밋되면, 렌더러 프로세스는 계속해서 데이터를 로드하고 웹페이지를 화면에 출력합니다. 다음 글에서는 이 단계의 세부 내용에 대해 살펴볼 예정입니다. 렌더러 프로세스가 렌더링을 마치면 브라우저 프로세스로 IPC를 회신합니다(페이지 모든 영역의 onload 이벤트가 호출되고 실행 완료된 이후입니다). 이때 UI 쓰레드는 탭의 로딩바가 돌아가는 것을 멈춥니다. 이후 JavaScript 코드에 따라 추가 데이터를 로드하고 새 화면을 렌더링할 수도 있습니다.

그림 7. 렌더러 프로세스에서 브라우저 프로세스로 보내는 페이지 로딩 완료 IPC

그림 7. 렌더러 프로세스에서 브라우저 프로세스로 보내는 페이지 로딩 완료 IPC

다른 사이트로 이동

간단한 사이트 이동은 이로써 완료되었습니다. 하지만 만약 사용자가 다른 URL을 다시 주소창에 입력하면 어떻게 될까요? 브라우저 프로세스는 앞선 일련의 과정을 다시 밟습니다. 하지만 그 전에 현재 사이트에 다뤄야할 beforeunload 이벤트가 있는지를 확인합니다.

beforeunload 이벤트는 페이지를 나가거나 탭을 닫기 전에 실행되는 이벤트로 “정말 해당 페이지를 나가시겠습니까?”가 적힌 Alert 창 등을 띄울 수 있습니다. JavaScript 코드를 포함한 하나의 탭 안의 모든 것은 렌더러 프로세스에 의해 처리되므로 새 내비게이션 요청이 들어왔을 때 브라우저 프로세스는 현재 활성화된 탭의 렌더러 프로세스와 함께 이를 확인해야 합니다.

그림 8. 렌더러 프로세스에게 다른 사이트로 이동할 예정이라는 IPC를 전달하는 브라우저 프로세스

그림 8. 렌더러 프로세스에게 다른 사이트로 이동할 예정이라는 IPC를 전달하는 브라우저 프로세스

만약 사용자의 링크 클릭, window.location = "https://newsite.com"을 실행하는 것과 같이 렌더러 프로세스에 의해 내비게이션이 시작되었을 때, 렌더러 프로세스는 먼저 beforeunload 핸들러를 확인합니다. 그다음에는 앞에서 설명한 브라우저 프로세스의 네이게이션 처리 절차와 동일한 절차를 거칩니다. 유일하게 다른 점은 내비게이션 요청의 방향이 렌더러 프로세스에서 브라우저 프로세스로 바뀌었다는 것입니다.

만약 현재 렌더링 된 사이트와 다른 사이트로 내비게이션이 발생하면 새 내비게이션을 처리할 다른 렌더러 프로세스가 호출되고 현재 렌더러 프로세스는 unload와 같은 이벤트를 처리하는 동안 유지됩니다. 이에 대한 자세한 내용은 an overview of page lifecycle statesthe Page Lifecycle API를 참고 부탁드립니다.

그림 9. 브라우저 프로세스의 두 IPC - 새 렌더러 프로세스에게 새 페이지 렌더링을 요청하는 IPC와 기존 렌더러 프로세스에게 기존 페이지의 ‘unload’ 이벤트 처리를 요청하는 IPC

그림 9. 브라우저 프로세스의 두 IPC - 새 렌더러 프로세스에게 새 페이지 렌더링을 요청하는 IPC와 기존 렌더러 프로세스에게 기존 페이지의 ‘unload’ 이벤트 처리를 요청하는 IPC

Service Worker

최근 내비게이션 처리 과정에서 달라진 부분 중 하나는 바로 service worker 입니다. Service worker는 애플리케이션 코드에서 네트워크 proxy를 작성하는 방법입니다. 개발자는 이를 통해 로컬에 캐시 할 데이터 항목과 네트워크로부터 새 데이터를 가져올 시기 등을 더 세부적으로 제어할 수 있습니다. 만약 service worker에 어떤 페이지 데이터를 캐시로부터 불러오도록 설정되어 있다면, 해당 데이터에 대한 네트워크 요청은 발생하지 않습니다.

여기서 중요한 점은 바로 service worker가 렌더러 프로세스에서 실행되는 JavaScript 코드라는 점입니다. 내비게이션 요청이 들어왔을 때 브라우저 프로세스는 요청한 사이트에 service worker가 있다는 것을 어떻게 알 수 있을까요?

그림 10. service worker 스코프가 존재하는지 확인하는 브라우저 프로세스의 네트워크 쓰레드

그림 10. service worker 스코프가 존재하는지 확인하는 브라우저 프로세스의 네트워크 쓰레드

Service worker가 등록되어 있으면, service worker 스코프가 참조 형태로 유지됩니다(스코프에 관한 자세한 내용은 The Service Worker Lifecycle에서 확인할 수 있습니다). 내비게이션이 발생하면 네트워크 쓰레드는 등록된 service worker 스코프와 도메인을 비교하여 확인합니다. 만약 해당 URL에 대한 service worker가 등록되어 있다면, UI 쓰레드는 service worker 코드를 실행할 수 있도록 렌더러 프로세스를 찾습니다. 실행한 service woker는 캐시를 로드하고, 일부 데이터에 대한 네트워크 요청을 막거나 별도의 데이터를 위한 네트워크 요청을 생성하는 작업 등을 할 것입니다.

그림 11. service worker를 실행하기 위해 렌더러 프로세스를 동작시키는 브라우저 프로세스의 UI 쓰레드. 이후 네트워크 쓰레드에게 네트워크 요청을 하는 렌더러 프로세스의 워커 쓰레드

그림 11. service worker를 실행하기 위해 렌더러 프로세스를 동작시키는 브라우저 프로세스의 UI 쓰레드. 이후 네트워크 쓰레드에게 네트워크 요청을 하는 렌더러 프로세스의 워커 쓰레드

Preload

앞서 살펴본 브라우저 프로세스와 렌더러 프로세스 사이에서 일어나는 과정에서 service worker가 네트워크 요청을 하는 경우 지연이 발생할 수 있다는 것을 보았습니다. Navigation Preload는 자원 로딩과 service worker를 동시에 실행하여 위 과정을 더 빠르게 처리하는 메커니즘입니다. 이 메커니즘은 각 요청을 header로 구분하여 서버가 요청에 따라 다른 콘텐츠로 응답할 수 있도록 합니다(예컨대 전체 문서 대신 업데이트된 데이터를 응답).

그림 12. 네트워크 요청을 보내며 동시에 service worker를 실행할 렌더러 프로세스를 동작시키는 브라우저 프로세스의 UI 쓰레드

그림 12. 네트워크 요청을 보내며 동시에 service worker를 실행할 렌더러 프로세스를 동작시키는 브라우저 프로세스의 UI 쓰레드

요약

이번 글에서는 내비게이션이 일어나는 과정과 어떻게 웹 애플리케이션 코드(예컨대 응답 header, client 측 JavaScript)가 브라우저와 상호작용하는지 알아보았습니다. 브라우저가 네트워크로부터 데이터를 수신하는 과정을 아는 것은 왜 내비게이션 preload와 같은 API가 개발되었는지 이해하는 데 도움이 될 것입니다. 다음 글에서는 어떻게 브라우저가 페이지를 출력하기 위해 HTML/CSS/JavaScript를 다루는지 알아보겠습니다.


Profile picture

Jinsol Kim

Developer. Busan, South Korea.

#React #ReactNative #NextJS #iOS #Swift