본문 바로가기
Web

크롬 브라우저는 어떻게 작동 할까? - 02

by Vintz 2021. 7. 25.
반응형
Google Developers 사이트에서 읽은 내용을 정리한 글입니다.

탐색할 때 일어나는 일

저번 글에서, 개별 프로세스 및 스레드가 브라우저에서 여러 부분들을 어떻게 처리하는지 그리고 브라우저 아키텍처에 대해 알아봤다. 이번 글에선 웹사이트를 디스플레이하기 위해 각 프로세스와 스레드들이 어떻게 통신하는 지 깊게 알아볼 것이다.

 

간단히 웹 서핑하는 경우를 생각해보자. 브라우저에 주소를 치면 브라우저는 서버에서 데이터를 받아 페이지를 표시한다. 여기서 탐색(Navigation)¹ 에 대해 집중적으로 알아보자.

탐색(Navigation)¹ - 사용자의 요청을 받아 브라우저가 페이지를 렌더링하는 과정

시작은 브라우저 프로세스에서

저번 글에서 브라우저 프로세스는 탭 밖의 보이지 않는 부분들을 담당하는 것을 알게 되었다. 브라우저 프로세스는 버튼이나 입력창을 그리는 UI 스레드, 인터넷에서 데이터를 수신하기 위해 통신 스택을 건드리는 네트워크 스레드, 파일 같은 것들에 접근하기 위한 스토리지 스레드 등을 가지고 있다.

 

주소창에서 URL을 입력하는 순간 브라우저 프로세스의 UI 스레드가 나타난다.

위엔 브라우저 UI, 아래엔 브라우저 프로세스 안에 있는 UI, 네트워크, 스토리지 스레드 다이어그램 - Google Developers

탐색 단계별로 알아보기

1단계 : 입력 처리

사용자가 주소창에 입력을 하게되면 UI 스레드는 제일 먼저 '검색어인지 URL인지'부터 판단을 한다. 크롬에서는 주소창이 검색창도 겸하기 때문에 UI 스레드가 입력된 문구를 파싱해서 검색 엔진에 보낼 지, 요청한 페이지로 연결할 지 결정한다.

입력이 검색어인지 URL인지 확인하는 UI 스레드 - Google Developers

2단계 : 탐색 시작

사용자가 엔터를 치면 UI 스레드가 사이트의 컨텐츠를 받기 위해 네트워크 요청을 초기화²한다.

mysite.com 사이트로 이동하기 위해 네트워크 스레드에게 알리는 UI 스레드 - Google Developers

이 시점에서 네트워크 스레드는 HTTP 301같은 서버의 리다이렉션³ 헤더를 수신할 수도 있다. 그럴 경우 네트워크 스레드는 UI 스레드에게 서버가 리다이렉션을 요청했음을 알린다. 그러면 UI 스레드는 새로운 URL 요청을 초기화한다.

초기화(Initialization)² - 데이터 오브젝트나 변수의 초기 값 할당을 의미한다. 여기선 "네트워크 요청을 시작한다"와 같은 맥락 같다.

HTTP 리다이렉트(HTTP Redirect)³ - re(다시) + 지시하다(direct). 즉 다시 지시하는 것을 뜻한다. 서버는 HTTP 응답 메시지를 통해 브라우저에게 다른 URL을 지시할 수 있다.

3단계 : 응답 읽기

Content-Type을 포함한 응답 헤더와 실질적 데이터인 페이로드 - Google Developers

응답 바디(payload)가 들어오기 시작할 때 필요하면 네트워크 스레드가 스트림⁴의 처음 몇 바이트를 확인한다. 응답의 Content-Type 헤더는 데이터 타입이 무엇인지 알려주지만, 빠지거나 틀릴 수가 있다. 따라서 MIME Type 스니핑을 수행한다.

스트림(Stream)⁴ - 스트림은 흐르는 시냇물을 뜻한다. 컴퓨터 공학에선 데이터를 주고 받을 수 있게 하는 흐름을 의미하며 프로세스 관점에서 스트림은 프로세스 상호간의 통신에서 가상의 연결 통로를 의미한다. 

응답이 HTML 파일이면 다음으로 렌더러 프로세스에 데이터를 전달한다. 그 외 zip 또는 다른 형식의 파일일 경우 다운로드 요청이라는 뜻이므로 다운로드 매니저에 데이터를 넘긴다.

응답 데이터가 안전한 사이트에서 전송된 HTML인지 확인하는 네트워크 스레드 - Google Developers

이 시점에서 안전 브라우징 체크도 수행한다. 만약 도메인과 응답 데이터가 이미 알려진 악성 사이트와 일치 한다면 네트워크 스레드는 warning 페이지를 보여주어 경고를 한다. 추가적으로, Cross Origin Read Blocking (CORB) 체크를 해서 반드시 민감한 cross-site 데이터가 렌더러 프로세스에 도달하지 못하게 한다.

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

모든 확인 작업이 끝난 후 네트워크 스레드는 브라우저가 요청된 사이트로 이동해야 함을 확신하면 UI 스레드에게 데이터가 준비되었음을 알려준다. 그럼 UI 스레드는 웹 페이지의 렌더링을 담당 할 렌더러 프로세스를 찾는다.

렌더러 프로세스를 찾기 위해 UI 스레드에게 알리는 네트워크 스레드 - Google Developers

네트워크 요청이 응답을 받는데 수 백 밀리초 정도 소요될 수 있으므로 이 과정을 빠르게 하는 최적화가 적용된다. 2단계에서 UI 스레드는 네트워크 스레드에게 URL 요청을 보내면 어느 사이트로 가야 할 지 이미 알고있다. 그래서 UI 스레드는 네트워크 요청과 병행해서 적극적으로 렌더러 프로세스를 찾거나 시작하려 한다. 

 

이 경우 모든 게 계획대로라면 네트워크 스레드가 데이터를 수신했을 때 렌더러 프로세스는 이미 대기하고 있다. 만약 탐색 도중 cross-site로 리다이렉트한다면 준비된 프로세스는 사용되지 않고, 다른 렌더러 프로세스가 필요하게 된다.

5단계 : 탐색 수행(commit)

이제 데이터와 렌더러 프로세스가 준비 되면 다음과 같은 과정을 실행한다.

  1. 탐색을 커밋하기 위해 브라우저 프로세스에서 렌더러 프로세스로 IPC(프로세스간 통신)가 전송됨.
  2. 데이터 스트림을 전달하여 렌더러 프로세스가 HTML 데이터를 계속 받을 수 있게 한다.
  3. 렌더러 프로세스에서 커밋을 확인한다.
  4. 브라우저 프로세스가 탐색을 완료하고 문서 로딩 단계를 시작한다.

이 시점(문서 로딩 단계)에서 

  1. 주소창 갱신
  2. 보안 알리미(security indicator)와 사이트 설정 UI가 새 페이지의 사이트 정보를 반영
  3. 탭의 세션 이력이 갱신되어 뒤로/앞으로 가기 버튼에 방금 방문한 사이트를 추가
  4. 탭/세션 복구 기능을 위해 탭이나 윈도우를 닫을 때 세션 이력을 디스크에 저장

과 같은 일을 수행한다.

페이지 렌더를 요청하는 브라우저 프로세스와 렌더러 프로세스 간의 IPC - Google Developers

추가 단계 : 초기 로딩 완료

탐색이 수행되고 나면 렌더러 프로세스는 리소스 로딩과 페이지 렌더를 지속한다.(다음 글에서 이 단계에 대해 좀 더 자세히 알아보자.)

 

렌더러 프로세스가 렌더링을 '마무리(finishes)'하면 브라우저 프로세스에 IPC를 반환한다.(onload 이벤트가 페이지의 모든 프레임에서 발생하고 실행까지 완료가 된 후를 말한다.) 이 시점에서 UI 스레드는 탭의 로딩 스피너(loading spinner)를 정지한다.

 

'마무리'라고 말한 이유는, 클라이언트 사이드 자바스크립트는 이 시점 이후에도 계속 추가적인 리소스를 로드하거나 새로운 뷰를 렌더할 수 있기 때문이다.

렌더러 프로세스에서 브라우저 프로세스까지 페이지가 '로드 되었음'을 알리는 IPC - Google Developers

다른 사이트를 탐색할 때는?

이렇게 탐색을 알아보았다. 그런데 사용자가 주소창에 다른 URL을 다시 입력하면 어떻게 될까? 물론, 브라우저가 동일한 방식으로 다른 사이트를 탐색할 것이다. 하지만 그 전에 현재 렌더링된 사이트가 beforeunload 이벤트를 처리하는 지 확인할 필요가 있다.

 

beforeunload 이벤트는 다른 사이트를 방문하거나 탭을 닫을 때 "이 사이트에서 나가시겠습니까?"와 같은 팝업을 띄울 수 있다. 자바스크립트 코드를 포함한 탭 안의 모든 것들은 렌더러 프로세스가 처리하기 때문에, 브라우저 프로세스는 새 탐색 요청이 들어올 때마다 렌더러 프로세스를 체크할 필요가 있다.

브라우저 프로세스로부터 렌더러 프로세스까지 다른 사이트로 이동하여 탐색한다는 정보를 알리는 IPC - Google Developers

⚠️ 무조건적으로 beforeunload 이벤트 핸들러를 추가할 경우 탐색을 시작하기도 전에 해당 이벤트를 실행시켜야 하기 때문에 대기 시간이 더 길어지게 된다. 페이지에 작성한 데이터가 소실될 수 있음을 경고하는 등, 반드시 필요한 경우에만 추가한다. (예를들어 지금 쓰고 있는 글을 실수로 닫아서 데이터가 소실되면..비극이다.)

새로운 탐색이 현재 렌더링된 사이트와는 다른 곳으로 새로 탐색하게 되면, 현재 레더러 프로세스가 unload 같은 이벤트를 처리하는 동안 별개의 렌더러 프로세스가 새 탐색을 위해 호출된다.

브라우저 프로세스가 새 렌더러 프로세스에겐 페이지 렌더링을 요청하고, 이전 렌더러 프로세스에게는 페이지를 'unload'하도록 요청하는 IPC - Google Developers

서비스 워커(Service Worker)의 도입

서비스 워커는 웹 응용 프로그램, 브라우저, 그리고 (사용 가능한 경우) 네트워크 사이의 프록시 서버 역할을 합니다. 서비스 워커의 개발 의도는 여러가지가 있지만, 그 중에서도 효과적인 오프라인 경험을 생성하고, 네트워크 요청을 가로채서 네트워크 사용 가능 여부에 따라 적절한 행동을 취하고, 서버의 자산을 업데이트할 수 있습니다. 또한 푸시 알림과 백그라운드 동기화 API로의 접근도 제공합니다. - MDN

중요한 점은 서비스 워커가 렌더러 프로세스에서 돌아가는 자바스크립트 코드라는 것이다. 그런데 탐색 요청이 들어오자마자 사이트에 서비스 워커가 있다는 걸 브라우저는 어떻게 알 수 있을까?

서비스 워커 범위를 검색하는 브라우저 프로세스의 네트워크 스레드 - Google Developers

서비스 워커는 웹 페이지와 완전히 별개이기 때문에 요청하지 않는 이상, 없는 것이나 다름없다. 서비스 워커가 등록되면 서비스 워커 스코프가 레퍼런스로 취급된다.(스코프에 대한 자세한 내용은 The Service Worker Lifecycle 글 참조)

 

탐색을 시작할 때 네트워크 스레드는 등록된 서비스 워커 스코프와 도메인을 비교한다. 그 후 동일한 URL에 서비스 워커가 등록되어 있으면 UI 스레드가 해당 서비스 워커 코드를 실행하기 위해 렌더러 프로세스를 찾는다. 서비스 워커는 데이터를 캐시에서 로드하기 할 것이다. 따라서 네트워크 데이터 요청을 다 없애거나, 새로운 리소스를 요청할 것이다.

브라우저 프로세스의 UI 스레드가 서비스 워커를 처리하도록 렌더러 프로세스를 시작하는 모습. 렌더러 프로세스의 워커 스레드가 네트워크 스레드에 데이터 요청 - Google Developers

선제 탐색(Navigation Preload)

서비스 워커가 네트워크에 데이터를 요청하기로 결정하면 브라우저 프로세스와 렌더러 프로세스간의 이런 반복 행위는 딜레이가 발생할 요인으로 보인다. 선제 탐색은 서비스 워커의 시작과 동시에 리소스들을 병행 로딩해서 이 과정을 빠르게 하는 메커니즘이다.

 

이런 요청들에 헤더를 표기해서 서버가 다른 콘텐츠를 보낼 지 결정하게끔 한다. 예를 들어 전체 문서 대신에 갱신된 내용만 보내는 것이다.

브라우저 프로세스의 UI 스레드가 서비스 워커를 처리하도록 렌더러 프로세스를 시작하는 동시에 네트워크 요청을 병행하는 모습 - Google Developers

마무리

사용자는 체감상 몇초도 안되는 시간에 탐색하는 과정만 해도 이렇게 많은 일들이 일어난다. 크롬 브라우저는 정말 거대한 프로그램이 맞는 것 같다. 여기서 브라우저가 네트워크를 통해 데이터를 가져오는 단계를 알게 되었고 그런 과정 중에 속도를 조금이라도 향상 시키기 위해 선제 탐색 같은 API들을 개발한 것 같다. 다시금 개발자가 얼마나 대단한지 알게 되었다. 다음은 브라우저가 페이지를 렌더링하기 위해 HTML/CSS/JavaScript를 어떻게 다루는 지 알아보자.

반응형