브라우저 주소창에 www.naver.com을 치면 일어나는 일

September 05, 2022

What happens when…을 바탕으로 네트워크와 OS, 브라우저에서 일어나는 일을 정리했습니다.

URL을 파싱합니다.

  • protocol: http
  • hostname: www.naver.com
  • resource: /

브라우저는 호스트네임에서 a-z, A-Z, 0-9, -, 혹은 .이 아닌 문자들을 확인합니다. (지금의 호스트네임은 www.naver.com 이기때문에 유니코드가 없지만, 국제화 도메인 네임과 같이 유니코드가 있을 때에는 브라우저가 URL에서 호스트네임 부분에 퓨니코드 (Punycode) 인코딩을 하기도 합니다. 도메인 주소 뒤 / 부터는 특정 문자를 UTF-8로 변환하는 URL escape code를 사용합니다.)

🤔 잠깐! 주소인가요, 검색어인가요?

프로토콜이나 유효한 도메인 이름이 주어지지 않으면, 브라우저는 주소창에 놓인 텍스트를 브라우저의 기본 웹 검색엔진에 넘겨줍니다.

HTTPS로 보내야 하나요?

HSTS 셋은 HTTPS로만 연결되도록 요청한 웹사이트들의 목록입니다. 브라우저는 이 목록을 조회하여 www.naver.com이 목록에 있다면 HTTPS로, 그렇지 않다면 HTTP로 요청을 보냅니다.

크롬에 저장된 HSTS 셋
크롬에 저장된 HSTS 셋

IP 주소를 찾습니다.

  1. 이제 브라우저는 캐시를 조회하여 해당 웹사이트의 IP 주소를 가지고 있는지 확인합니다.
  2. 요청된 도메인 네임의 IP 주소를 찾을 수 없으면 gethostbyname 시스템 라이브러리 함수를 호출해 OS에 IP 주소를 찾도록 요청합니다. OS가 해당 도메인의 IP 주소를 확인하는 첫 번째 위치는 로컬에 위치한 hosts 파일입니다. URL이 hosts 파일 내에서 발견되지 않으면 OS는 웹 페이지의 IP 주소를 찾기 위해 DNS 요청을 합니다.
  3. gethostbyname의 캐시 혹은 hosts 파일에서도 IP 주소를 찾지 못하면 OS는 DNS 서버에 요청을 보냅니다. 만약 DNS 서버가 같은 서브넷에 있다면 네트워크 라이브러리는 DNS 서버에 대해 ARP 프로세스를 따릅니다. DNS 서버가 다른 서브넷에 있다면 네트워크 라이브러리는 기본 게이트 IP에 대해 ARP 프로세스를 따릅니다.

ARP 프로세스

ARP(Address Resolution Protocol)는 LAN에서 끊임없이 변화하는 IP 주소를 MAC 주소라고도 하는 고정된 물리적 컴퓨터 주소에 연결하는 프로토콜입니다. MAC 주소는 link 레이어이며 물리적으로 연결된 두 장치 간의 연결을 설정하고 종료하여 데이터 전송이 이루어질 수 있도록 합니다. IP 주소는 network 레이어입니다. ARP는 이러한 OSI 레이어 사이에서 작동합니다.

먼저 ARP 캐시에 목적지 IP의 ARP 항목이 있는지 확인합니다. 만약 캐시에 있다면 시스템 라이브러리 함수는 해당 MAC 주소를 반환합니다.

항목이 캐시에 없다면 라우트 테이블을 검색해 목적지 IP 주소가 로컬 라우트 테이블의 서브넷에 존재하는지 확인합니다. 존재한다면 그 서브넷에 속하는 인터페이스를 활용하고 없다면 기본 게이트웨이의 서브넷에 속하는 인터페이스를 활용합니다. 선택된 네트워크 인터페이스의 MAC 주소를 검색해 네트워크 라이브러리는 Link 레이어를 통해 ARP 요청을 보냅니다.

ARP Request

Sender MAC: interface:mac:address:here
Sender IP: interface.ip.goes.here
Target MAC: FF:FF:FF:FF:FF:FF (Broadcast)
Target IP: target.ip.goes.here

ARP Reply

Sender MAC: target:mac:address:here
Sender IP: target.ip.goes.here
Target MAC: interface:mac:address:here
Target IP: interface.ip.goes.here

DNS 서버에 요청합니다.

이제 네트워크 라이브러리는 우리 DNS 서버나 DNS 프로세스를 재개할 수 있는 기본 게이트웨이 중 하나의 IP 주소를 갖고 있습니다.

첫 번째 단계는 DNS Resolver가 로컬/ISP 서버에 캐시에 IP 주소를 알고 있는지 확인하도록 요청하는 것입니다. 해당 서버가 정보를 갖고 있지 않다면 재귀적인 탐색이 수행되어 IP 주소가 되돌아올 때까지 DNS 서버 리스트를 타고 올라갑니다.

DNS Resolver는 루트 네임 서버, .COM_ TLD(Top Level Domain) 네임 서버 순으로 요청합니다. TLD 서버는 요청된 IP 주소가 있는지 확인하기 위해 캐시를 다시 확인합니다. 없다면 해당 URL과 연결된 authoritative DNS 서버로 이동한 후 IP 주소를 반환합니다. 이 과정에서 DNS Resolver는 authoritative 네임 서버에서 받은 정보를 캐시합니다. 클라이언트가 다른 클라이언트가 최근에 요청한 도메인 이름의 IP 주소를 요청하면 Resolver는 네임 서버와의 통신 프로세스를 우회하고 캐시에서 요청한 레코드를 클라이언트에 전달합니다. 이 모든 과정은 밀리초 사이에 이루어집니다.

소켓을 엽니다.

브라우저가 목적지 서버의 IP 주소를 얻게 되면, 주소와 포트 번호(HTTP에서는 기본값 80, HTTPS에서는 기본값 443)를 이용해 시스템 라이브러리 함수를 호출하여 TCP 소켓 스트림을 요청합니다.

Transport 레이어에서 출발지 포트를 커널의 동적 포트 범위에서 선택하고 목적지 포트를 헤더에 추가합니다. Network 레이어는 출발지와 목적지 서버의 IP 주소 등의 추가적인 IP 헤더를 덧씌워 패킷을 만듭니다. Link 레이어에서는 패킷에 MAC 주소를 포함하여 프레임 헤더를 추가합니다.

완성된 패킷은 이더넷, 와이파이, 무선 통신 네트워크를 통해 전송됩니다. 출발한 패킷은 모뎀으로 보내져 아날로그 신호로 변환되거나 광케이블 및 다이렉트 이더넷 연결을 통해 디지털 신호로 다음 네트워크 노드까지 전송됩니다.

로컬 서브넷을 관리하는 라우터에 도착한 패킷은 여러 라우터를 경유하며 목적지 서버까지 여행합니다. 지나치는 각각의 라우터는 IP 헤더로부터 목적지 주소를 추출해 적절한 다음 단계로 이어줍니다. IP 헤더 내의 TTL은 라우터를 하나씩 지날 때마다 감소되어 TTL이 0이 되거나 도달한 라우터의 큐에 자리가 없을 때 패킷은 드랍됩니다.

이러한 송수신은 TCP를 따라 연결 시 3-way handshake, 종료 시 4-way handshake를 따르며 여러 차례 발생합니다.

TLS handshake

drake handshake

클라이언트 컴퓨터가 자신의 Transport Layer Security (TLS) 버전, 암호 알고리즘 목록 그리고 사용 가능한 압축 방식을 ClientHello 메시지에 담아 서버로 보냅니다. 서버는 클라이언트에게 TLS 버전, 선택한 암호 알고리즘, 선택한 압축 방식 그리고 CA (Certificate Authority) 가 사인한 서버의 공개 인증서를 ServerHello 메시지에 담아 답장합니다. 이 인증서는 대칭키가 생성되기 전까지 클라이언트가 나머지 handshake 과정을 암호화하는 데에 쓸 공개키를 담고 있습니다.

클라이언트는 서버측 디지털 인증서가 유효한지를, 신뢰할 수 있는 CA 목록을 통해 확인합니다. 만약 CA를 통해 신뢰성이 확보되면, 클라이언트는 의사 난수 (pseudo-random) 바이트를 생성해 서버의 공개키로 암호화합니다. 이 난수 바이트는 대칭키를 정하는 데에 사용됩니다. 서버는 난수 바이트를 자기 개인키로 복호화해 대칭 마스터키 생성에 활용합니다.

클라이언트는 Finished 메시지를 서버에 보내면서, 지금까지의 교환 내역을 해시한 값을 대칭키로 암호화하여 담습니다. 서버는 스스로도 해시를 생성해 클라이언트에서 도착한 값과 일치하는지 봅니다. 일치하면, 서버도 마찬가지로 대칭키를 통해 암호화한 Finished 메시지를 클라이언트에 보냅니다. 이제부터는 TLS 세션이 대칭키로 암호화된 어플리케이션 (HTTP) 데이터를 전송합니다.

이제 GET 요청을 보냅니다.

구글이 만든 웹 브라우저라면, 페이지를 가져오기 위해 HTTP 요청을 보내는 대신, 서버에게 HTTP에서 SPDY업그레이드할 것을 협상해봅니다.

만약 클라이언트가 SPDY를 지원하지 않고 HTTP만 쓴다면, 서버에 다음과 같은 GET 요청을 보냅니다:

GET / HTTP/1.1
Host: naver.com
Connection: close
[other headers]

HTTP/1.1은 송신자측에서 응답을 받은 직후에 연결이 끊어질 것이라는 신호를 보내기 위해 “close”라는 연결 옵션을 정의합니다. [other headers] 부분은 HTTP 사양에 따라 콜론으로 구분되고 각각 새 줄로 나뉘는 일련의 키-값 쌍을 나타냅니다.

요청과 헤더를 보낸 후에, 웹 브라우저는 하나의 빈 줄을 서버에 보내 요청 내용이 모두 보내졌음을 알립니다. 서버는 요청의 상태를 나타내는 코드와 다음과 같은 형태의 답신으로 응답합니다:

200 OK
content-encoding: gzip
content-type: text/html; charset=UTF-8
date: Sun, 04 Sep 2022 21:31:43 GMT
p3p: CP="CAO DSP CURa ADMa TAIa PSAa OUR LAW STP PHY ONL UNI PUR FIN COM NAV INT DEM STA PRE"
pragma: no-cache
referrer-policy: unsafe-url
server: NWS
strict-transport-security: max-age=63072000; includeSubdomains
x-frame-options: DENY
x-xss-protection: 1; mode=block

빈 줄을 하나 붙인 뒤, www.naver.com의 HTML 본문을 페이로드에 담아 보냅니다. 서버는 곧 연결을 끊거나, 클라이언트가 보낸 헤더에 요청이 있었을 시, 추가적인 요청을 위해 재사용될 수 있도록 연결을 유지해둡니다.

🤔 잠깐! 브라우저에 파일이 캐시되어 있나요?

웹 브라우저에서 보낸 HTTP 헤더에, 마지막으로 보냈던 파일이 브라우저에 캐시되어 있고 그 뒤로 변하지 않았다는 판단을 내릴 만큼 충분한 정보 (예를 들어, 웹 브라우저가 ETag 헤더를 포함시켰다든지) 가 담겨 있었다면, 304 Not Modified로 응답할 수도 있습니다.

HTML을 파싱한 후에는, 브라우저 (그리고 서버) 가 이 과정을 HTML 페이지에서 참조되는 모든 자원 (이미지, CSS, favicon.ico, 기타 등등) 에 대해 반복합니다. HTML이 www.naver.com 이 아닌 도메인의 자원을 참조할 땐, 브라우저가 다른 도메인을 확정하는 단계로 되돌아가 해당 도메인에 대해 여기까지의 과정들을 밟습니다.

웹 서버의 요청 처리

naver web server
네이버 메인 페이지의 분산 처리 모델

HTTPD (HTTP 데몬) 서버는 서버측에서 요청/응답을 처리하는 친구입니다. 가장 흔한 HTTPD 서버는 리눅스용인 Apache나 nginx 그리고 윈도우용인 IIS가 있습니다. 2018년 글에 의하면 네이버는 Apache를 사용 중이라고 합니다.

  • HTTPD가 요청을 수신합니다.
  • 서버는 요청을 메소드, 도메인, 요청된 경로로 파싱합니다.

    • method: GET
    • domain: naver.com.
    • path: /
  • 서버는 naver.com에 해당하는 서버에 구성된 가상 호스트가 있는지 확인합니다.
  • 서버는 naver.com이 GET 요청을 수락할 수 있는지 확인합니다.
  • 서버는 클라이언트가 이 방법을 사용할 수 있는지 확인합니다(IP, 인증 등을 통해).
  • 서버가 index.html과 CSS 및 Javascript 파일을 보냅니다.

브라우저가 문서를 렌더링합니다.

렌더링 엔진은 Network 레이어에서 요청된 문서의 내용을 가져오기 시작합니다. 일반적으로 TCP/IP 통신의 경우 8kb chunk로 수행됩니다. 그 후, 렌더링 엔진의 기본 흐름은 다음과 같습니다:

  • HTML을 파싱하여 DOM 트리 생성
  • 스타일 정보와 합쳐 렌더 트리 생성
  • 렌더 트리 배치
  • 렌더 트리 그리기
Webkit 렌더링 파이프라인
Webkit 렌더링 파이프라인
Gecko 렌더링 파이프라인
Gecko 렌더링 파이프라인

HTML 파싱

파서는 문자 스트림을 트리 구조로 변환하는 일을 합니다. HTML 문서는 HTML5 스펙의 파싱 알고리즘을 구현한 HTML 파서에 의해 파싱됩니다. 알고리즘은 토큰화 및 트리 구성의 두 단계로 구성됩니다.

Overview of the parsing model
Overview of the parsing model

토큰화를 통해 입력은 구문 분석 후 토큰으로 파싱됩니다. Lexer라고도 부르는 Tokenizer는 입력이 끝날 때까지 토큰을 생성해 트리 생성기에 전달합니다.

구문 분석이 완료되면 브라우저는 페이지에 연결된 외부 리소스를 가져오기 시작합니다. 이때 브라우저는 해당 문서가 상호작용 중이라는 표시를 해두고 deferred 모드에 있는 스크립트를 파싱하기 시작합니다. 그 후 문서의 상태는 complete로 표시되고 load 이벤트가 트리거됩니다.

CSS 해석

<style> 태그 내용과 style 속성값으로 되어있는 외부 CSS 파일들을 “CSS lexical and syntax grammar”를 활용해 파싱합니다. 각각의 CSS 파일은 Stylesheet object로 파싱되는데, 각 객체는 selector 및 CSS 문법에 해당하는 객체들과 함께 CSS 규칙들을 담고 있습니다.

페이지 렌더링

  • DOM 노드를 순회하며 각 노드의 CSS 스타일 값을 계산하여 프레임 트리(혹은 렌더 트리)를 생성합니다.
  • 각 노드의 width, height, margin, padding, border, 좌표를 계산합니다.
  • 레이어를 만들고 각 프레임/렌더 객체를 레이어에 할당합니다.
  • 페이지의 각 레이어에 텍스쳐가 할당됩니다.
  • 각 레이어의 프레임/렌더 객체를 순회하며 해당 레이어의 drawing 명령을 실행합니다.
  • 가장 최근에 웹 페이지가 렌더링될 때 계산된 값은 기억되어 이후의 변화를 계산할 때 재사용됩니다.
  • 페이지 레이어는 합성 과정으로 넘어가 크롬 브라우저나 iframe 그리고 애드온과 같은 다른 시각 요소들과 합쳐집니다.
  • 마지막 레이어 위치가 계산되면 합성 명령이 Direct3D/OpenGL 등을 통해 실행됩니다. GPU 명령 버퍼는 비동기적 렌더링을 위해 비워지고 프레임은 윈도우 서버로 전송됩니다.

GPU 렌더링

렌더링 과정에서 그래픽 처리 연산 레이어는 GPU가 담당하기도 합니다. 그래픽 렌더링 연산을 위해 GPU를 사용할 때는 그래픽 담당 소프트웨어 레이어가 작업을 여러 조각으로 쪼개어 GPU의 부동 소수점 연산 병렬처리를 통해 렌더링을 돕습니다.

생각해보기

  • 도메인 네임, 호스트 네임, url의 차이는 무엇일까요?
  • 응답의 상태코드가 40X나 50X라면 어떤 일이 일어날까요?

참고


우정민

웹 개발, 프론트엔드