웹 브라우저는 사람들이 가장 많이 사용하는 프로그램 중에 하나이다.
브라우저의 주요 기능은 사용자가 요청한 자원을 해당 서버에 요청하고 그 데이터를 받아와 사용자에게 보여주는 것이다.
아래부터는 눈에 보이지 않는 과정을 네트워크에서 렌더링까지 흐름순으로 나열할 예정이다.
네트워크 통신을 위해서는 ip 주소가 필요하다. 인터넷 내의 모든 네트워크 장치 및 서버에는 이 ip주소가 할당되어 있다. 때문에 특정 서버로 데이터 요청을 하기 위해서는 ip주소가 필요하다.
일반적으로 ip 주소는 xxx.xxx.xxx.xxx 형식으로 이루어진 IPv4 형식을 많이 사용한다.
하지만 일반인이 이 주소를 모두 달달 외우고 있기란 쉽지 않은 일이기 때문에, 조금 더 인간에게 익숙한 언어형태를 통해 ip주소 대신 DNS(Domain Name System)을 사용하게 되었다.
DNS를 사용하여 특정 도메인 주소(www.google.com)를 브라우져 url 창에 입력하면, 브라우저는 Domain Name Server에 해당 도메인과 매핑되는 ip 주소를 요청하게 된다.
도메인 주소는 www.google.com 과 같은 형식을 일반적으로 갖게 되는데,
로 구분된다.
도메인을 이렇게 여러 레벨로 찢어놓은 이유는 세계의 방대한 Domain 내용을 한 서버에 담아둘 수 없어 분산시키기 위해서이다.
브라우저는 도메인 요청이 일어나면 가장 먼저 로컬의 캐시 영역부터 해당 IP 주소를 찾기 시작한다. 캐시에 해당 주소가 없다면 DNS 서버에 쿼리를 하고, 결과를 반환받으면 로컬 캐시 영역에 저장해준다.
DNS 서버는 요청 받은 도메인이 DNS 서버 로컬 캐시에 존재하는지 다시 확인한다.
만약 없다면, DNS 서버에서는 해당 도메인의 IP를 찾기 위해서 루트 도메인부터 찾기 시작한다. 해당 도메인을 찾기위해 DNS 서버는 root NS(name server) 로 쿼리를 보내고, 이를 통해 .com(Top-level Domain 이하 TLD)의 NS 정보를 받아온다.
이 TLD NS 정보를 통해 이 정보에 해당하는 TLD NS에 다시 google.com 에 대한 정보를 쿼리하게 된다. 이렇게 되면 google.com 의 IP주소가 있는 네임서버를 반환 받게 되고, 편의상 goo네임서버 라고 칭하겠다.
DNS 서버는 이 goo네임서버로 google.com 의 정확한 IP 주소를 쿼리하게 되고, 해당 IP주소를 응답받게 된다. DNS 서버는 이 내용을 로컬 캐시에 저장하고 클라이언트에게 IP주소를 반환해준다.
이제 DNS 서버에서 받아온 IP주소를 사용해서 내가 요청을 보낼 서버에 데이터 요청을 하는 과정을 거쳐야 한다.
먼저 브라우저는 서버로 request 할 내용을 HTTP 프로토콜을 이용해 구성한다.
대충 이렇게 생겼다.
HTTP 프로토콜을 통해 데이터를 요청하는 방식에는 대표적으로 GET 과 POST 방식이 있는데 이건 지금 글의 흐름과 맞지 않아 따로 정리해두었다.
https://velog.io/@lky9303/HTTP-메서드-GETPOST-의-차이feat.-REST-api
이 HTTP 프로토콜은 평문화된 방식이라 중간에 탈취당했을 경우 정보 노출이 쉽게 될 수 있기 때문에 최근에는 ssl 암호화 기법이 적용된 HTTPS 프로토콜을 사용한다. 공개키 암호화 기법을 통해 암호화를 해서 안전하다. 해당 내용도 글의 흐름과 맞지 않아 따로 정리했다.
https://velog.io/@lky9303/HTTP-와-HTTPS-차이-feat.-대칭키-개인키
이렇게 HTTP 프로토콜을 이용해 서버로 보낼 메시지가 만들어졌다. 그러면 브라우저는 서버로 먼저 TCP or UDP 프로토콜을 보내 서버와 통신을 시작한다. 따로 이런 번거로운 과정을 거치는 이유는 HTTP 가 도착지를 구별해내지 못하기 때문이다.
http 는 OSI 7계층에 속하는데, 해당 부분은 네트워크 연결보다는 실질적인 데이터를 담아내는 프로토콜이라고 할 수 있다. 이 HTTP 요청메시지가 생성되면 5계층(세션 계층)을 통해 서버와 연결을 시작하고, 4계층을 통해 이 연결이 올바른 연결인지 확인한다. 이 4계층에 해당되는 프로토콜이 TCP/UDP 프로토콜이다. 그리고 3계층부터는 서버까지의 데이터 플로우를 처리하게 된다.
TCP 프로토콜은 3 ways-handshake라는 기술을 통해 도착지와의 통신을 시작한다. 상대방과의 정상적인 통신이 열리지 않았는데 무턱대고 데이터를 보내버리면 해당 데이터는 폐기되기 때문에 이 과정은 반드시 필요하다.
A라는 클라이언트는 B라는 수신자에게 SYN 패킷을 먼저 보낸다. B라는 서버는 Listen 상태로 대기하고 있는데, SYN 패킷을 수신하면 SYN-received 상태로 변경된다. 동시에 SYN, ACK 패킷을 클라이언트에 보내면서 통신을 시작할 준비가 되었음을 알린다. 클라이언트는 이 응답을 받고 ACK 패킷을 보내면서 자신의 상태를 established 로 변경한다. B 서버는 ACK 패킷을 수신하고 본인의 상태를 established로 바꾼다. 이렇게 연결을 성공하게 되면 데이터 전송을 시작할 수 있다.
http 메시지가 서버로 무사히 전송되면 서버에서는 이 메시지를 해석해 이에 맞는 반환메시지를 http 프로토콜을 이용해 만들어낸다. 그리고 TCP 프로토콜이 이미 현재 연결이 정상적이라고 확인해놨으니 서버는 안심하고 클라이언트에게 반환메시지를 전송해준다.
HTTP 메시지를 반환해주고 연결을 종료해야 한다면, TCP 프로토콜은 4ways-handshake를 통해 연결을 해제한다.
현재 A와 B는 모두 연결되어 있기 때문에 established 상태이다. 이 연결을 해제하기 위해서 A 클라이언트는 ACK, FIN 패킷을 보내 연결을 끝낼 것을 B수신자에게 전달한다. B 수신자는 해당 FIN 패킷을 수신했다는 것을 확인시키기 위해 A에게 ACK 패킷을 보낸다. B는 아직 송신할 데이터가 남아있을 수 있으므로 CLOSE_WAIT 상태로 변하고, 나머지 데이터를 마저 송신한다. 모든 데이터의 송신이 완료되면 ACK, FIN 패킷을 A로 송신해, 연결 종료를 알린다. A는 해당 패킷을 수신했다는 패킷을 B로 보내면서 연결을 닫는다. B는 해당 패킷을 수신하면 연결을 닫는다.
브라우저는 통신을 통해 받아온 http메시지의 본문에 담겨있는 HTML 파일을 파싱하기 시작한다.
이 과정은 브라우저 안에 있는 렌더링 엔진이라는 곳에서 일어나는데,
아래 그림과 같이 브라우저는 구성되어 있다. 렌더링 엔진의 종류에는 webkit(크롬, 사파리), Gecko(파이어폭스)가 있다.
렌더링 엔진은 위의 그림과 같은 흐름으로 렌더링을 실행한다. 이 그림은 매우 추상적이고 큰 그림만 그린 것이니 참고만 하자.
먼저 HTML 파서를 통해 HTML 구문을 파싱하고 파싱트리를 만들어낸다.
아래 그림은 조금 더 구체적인 과정을 그려낸다. 이건 webkit 렌더링 엔진에서 수행되는 렌더링 과정을 보여준다.
아까의 파싱트리는 기계어로 컴파일되고 이것이 DOM 트리로 만들어지게 된다. 자바스크립트와 실질적으로 연동되는 것은 이 DOM 트리이다. 이 DOM 트리가 만들어지게 되면 비로소 브라우저는 이 DOM 트리와 상호작용할 수 있게 되고 자바스크립트에서 DOM 트리와 연동되어야 하는 부분이 있으면 다시 토큰화를 해서 DOM 트리를 그려내게 된다.
해당 HTML을 파싱하는데는 여러가지 알고리즘이 사용되고 자세한 것은 https://d2.naver.com/helloworld/59361
이 출처를 참고했다.
이제 CSS 를 파싱할 차례이다. CSS는 플렉스 파서 or 바이슨 파서 생성기를 통해 파싱되고, 스타일시트 객체로 파싱되고, 각 객체는 스타일 규칙을 포함하고 있다.
그림을 보면 알겠지만, DOM 트리와 CSS 스타일 규칙은 독립적으로 작동하고, 모두 생성된 후 attach 과정을 통해 합쳐지게 된다. 하지만 웹은 HTML 파싱과 스크립트 실행을 동시에 실행한다. 때문에 스크립트가 HTML 파싱 중에 스타일 정보를 요청한다면 문제가 발생할 수 있다. 이 점은 조심해야 할 부분이다. 때문에, 파이어폭스나 크롬 등 브라우저는 파싱이 끝나지 않은 스타일이 있을 경우 스크립트 실행을 중단한다.
렌더 트리는 DOM 트리가 구축되는 동시에 만들어지게 된다. 브라우저는 DOM 트리가 구축되는 즉시 해석하여 렌더트리를 만들어내는데 CSS 파서를 통해 만들어진 스타일 규칙을 적용하여 렌더트리를 만들어낸다.
HTML 마크업은 DOM 트리와 1대1로 매치되었지만, DOM 트리와 렌더트리는 1대1 로 매칭되지 않는다. 렌더 트리는 실제로 사용자에게 보여질 부분들만을 구성하기 때문에, "html" 이라던지 "head" 등의 의미론적인 구성요소는 포함하지 않는다. 또한 display:none 스타일을 가진 요소도 렌더트리에는 포함되지 않는다.
다양한 규칙에 따라 렌더트리는 화면에 배치되게 되고, 배치와 그리는 과정이 반복된다.
위의 긴 과정을 거쳐 브라우저는 사용자에게 시각화한 정보를 제공한다. 이 과정만 제대로 알고 있어도 중요한 네트워크 지식과 브라우저 지식을 알 수 있게 되니, 꼭 숙지해야 되는 과정이라고 생각한다.
출처
https://d2.naver.com/helloworld/59361
https://smartshk.tistory.com/2
https://gmlwjd9405.github.io/2018/09/19/tcp-connection.html