들어가며
우리가 인터넷에서 웹사이트에 접속할 때 가장 먼저 하는 일은 브라우저 주소창에 어떤 문자열을 입력하는 것입니다. 예를 들어 https://www.example.com/index.html 같은 주소를 입력하고 Enter를 누르면, 브라우저는 서버와 통신해서 해당 페이지를 받아옵니다.
이 “주소”를 흔히 URL이라 부르지만, 정확히 말하면 이것은 URI에 속하는 한 형태입니다. 이번 글에서는 URI의 개념과 구조를 깊이 있게 살펴본 다음, 웹 브라우저에서 URL(실제로는 URI)을 입력했을 때 어떤 단계들을 거쳐 서버에 요청이 전달되고 다시 응답이 도착하는지, 그 흐름을 체계적으로 살펴보겠습니다.
URI와 URL, URN의 차이
1. URI(Uniform Resource Identifier)
- URI는 인터넷에 있는 자원을 식별하기 위한 ‘통합 자원 식별자’입니다.
- URI는 크게 URL(Uniform Resource Locator)과 URN(Uniform Resource Name)으로 나눌 수 있습니다.
2. URL(Uniform Resource Locator)
- 흔히 “웹주소”라고 부르는 것이며, 자원에 어떻게 접근할 수 있는지(Location 정보)를 나타내줍니다.
- 예: https://www.example.com:443/index.html?query=hello#section1
- 흔히 사용하는 HTTP, HTTPS, FTP 등의 프로토콜 스킴을 사용하여 자원의 위치를 명시합니다.
3. URN(Uniform Resource Name)
- 자원의 위치가 아닌, 자원의 ‘이름’에 집중하여 식별자를 지정하는 방식입니다.
- 예: urn:isbn:0451450523 (ISBN으로 식별되는 특정 책을 가리키는 URN)
- 일상적으로 웹 브라우저 주소창에 입력하는 경우는 흔치 않습니다.
정리하자면, URI는 특정한 자원(Resource)을 유일하게 식별할 수 있는 모든 문자열을 의미하고, 그 중 하나의 구현체가 우리가 흔히 쓰는 “URL”입니다. 따라서 일상적으로 “URL”이라는 단어를 많이 쓰지만, 기술적으로 좀 더 포괄적인 개념은 “URI”라는 것을 기억하면 좋겠습니다.
URI의 구조
URI(특히 URL)에는 여러 가지 구성 요소가 있습니다. 다음 예시를 통해 구체적인 구조를 살펴보겠습니다.
https://www.example.com:443/index.html?search=keyword#section1
└─┬─┘ └──────┬──────┘ └─┬─┘└────┬────┘└──────┬──────┘└───┬───┘
스킴 호스트(도메인) 포트 경로(Path) 쿼리(Query) 프래그먼트(Fragment)
Bash- 스킴(Scheme): 자원을 어떻게 요청하고 접근할지를 정의하는 부분입니다. 예) http, https, ftp, mailto 등
- 호스트(Host): 자원이 위치한 서버의 도메인 이름 또는 IP 주소를 나타냅니다. 예) www.example.com
- 포트번호(Port): 서버에서 동작 중인 프로세스가 듣고 있는 포트 번호입니다. 예) :443 (일반적으로 HTTPS는 기본 포트가 443이므로, 명시하지 않아도 443으로 취급됩니다)
- 경로(Path): 서버 내부 자원의 정확한 위치(경로)를 나타냅니다. 예) /index.html
- 쿼리(Query): 자원에 추가적인 매개변수를 전달하기 위해 사용합니다. 예) ?search=keyword (Key-Value 형태로 여러 개가 연결될 수 있습니다)
- 프래그먼트(Fragment): 웹 문서 내부 특정 위치나 자원 내 특정 부분을 가리킬 때 사용합니다. 예) #section1
웹 브라우저 요청 흐름
이제 웹 브라우저의 주소창에 URI를 입력했을 때, 어떤 단계들을 통해 서버에 요청이 전달되고, 이후에 어떤 과정을 통해 클라이언트(웹 브라우저)로 응답이 돌아오는지 흐름을 살펴보겠습니다. 크게 다음 단계를 거칩니다.
- 사용자가 브라우저에 URI 입력 (또는 링크 클릭)
- DNS 조회 (도메인 -> IP로 변환)
- TCP/IP 소켓 연결(3-way handshake)
- HTTP 요청(Request) 전송
- 서버에서 요청 처리
- 서버에서 HTTP 응답(Response) 전송
- 브라우저가 응답을 해석하고 렌더링
각 단계를 좀 더 구체적으로 살펴봅시다.
1. 사용자 입력 또는 링크 클릭
- 사용자가 주소창에 https://www.example.com/index.html을 입력하거나, 웹페이지 내의 하이퍼링크를 클릭합니다.
- 브라우저는 이 URL을 분석하여 스킴(https), 호스트(www.example.com), 경로(index.html) 등의 정보를 추출합니다.
2. DNS 조회(DNS Lookup)
- 브라우저는 호스트 이름 www.example.com을 알맞은 IP 주소(예: 93.184.216.34)로 변환해야 합니다.
- 이를 위해 DNS(Domain Name System) 서버에 질의를 보내 도메인에 해당하는 IP 주소를 얻어옵니다.
- 로컬 DNS 캐시 → ISP(인터넷 서비스 제공 업체) DNS 서버 → 권한 DNS 서버까지 순차적으로 조회할 수 있습니다.
- DNS 응답이 성공하면 브라우저는 웹 서버가 동작하는 IP를 알게 됩니다.
3. TCP/IP 소켓 연결(3-way handshake)
- 브라우저와 서버 간 통신하려면 TCP 연결이 필요합니다.
- 클라이언트(브라우저)와 서버 간에는 3-way handshake 과정을 거쳐 연결을 맺습니다.
- SYN → SYN+ACK → ACK 순으로 진행
- 연결이 맺어지면 이후 HTTP 메시지를 주고받기 위한 소켓이 생성됩니다.
- HTTPS의 경우에는 TLS(SSL) 핸드셰이크도 수행되어 암호화된 통신을 설정합니다.
4. HTTP 요청(Request) 전송
- TCP/HTTPS 연결이 완료된 뒤, 브라우저는 HTTP 프로토콜 형식에 맞춰 서버에 요청 메시지를 전송합니다.
- 요청 메시지 예시(간소화된 형태)
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
Accept: text/html
...
Bash- 주요 HTTP 메서드
- GET: 리소스 가져오기 (주로 페이지, 이미지, JSON 등)
- POST: 서버에 데이터 전송 (폼 전송, JSON 전송 등)
- PUT, DELETE, PATCH 등은 RESTful API 등에서 자주 사용
5. 서버에서 요청 처리
- 서버(예: Apache, Nginx, Node.js 서버 등)는 요청 메시지를 수신한 후,
- 어떤 자원(파일, 경로, API)인지 확인
- 필요한 로직(예: DB 조회, 비즈니스 로직 수행 등)을 처리
- 결과를 담아 응답 메시지를 생성
- 만약 동적인 페이지(예: PHP, Python, Node.js, Ruby on Rails 등)라면 서버 측 스크립트가 실행되어 결과 HTML 또는 JSON 등을 생성합니다.
- 처리 과정에서 인증, 세션, 쿠키, 캐시 정책 등에 따른 로직이 포함될 수도 있습니다.
6. 서버에서 HTTP 응답(Response) 전송
- 서버는 요청을 처리한 뒤, HTTP 규격에 맞춰 응답 메시지를 브라우저에 전송합니다.
- 응답 메시지 예시(간소화된 형태)
HTTP/1.1 200 OK
Date: Mon, 01 Jan 2025 12:00:00 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 1024
...
<html>
<head><title>Example</title></head>
<body>Hello World!</body>
</html>
Bash- 응답 헤더에는 상태 코드(예: 200 OK, 404 Not Found, 500 Internal Server Error), 콘텐츠 형식(Content-Type), 압축 여부(Content-Encoding) 등이 포함됩니다.
- 바디(Body)에는 실제 콘텐츠(HTML, JSON, 이미지 등)가 담겨옵니다.
7. 브라우저가 응답을 해석하고 렌더링
- 브라우저는 서버로부터 받은 응답을 해석한 뒤, 화면에 보여줍니다.
- HTML 파싱, CSS 파싱, JavaScript 파싱 및 실행
- 외부 리소스(JS, CSS, 이미지 등) 추가 요청 발생 시 다시 네트워크 요청을 보내 받습니다.
- 최종적으로 사용자는 웹페이지를 시각적으로 확인할 수 있습니다.
요청 흐름에서 주의할 만한 요소
1. 캐싱(Caching)
- 브라우저 캐시, 프록시 캐시, CDN 캐시 등 다양한 캐싱 메커니즘이 적용될 수 있습니다.
- 정적 파일(이미지, CSS, JavaScript 등)은 캐싱이 활성화되어 있으면 재요청 없이 로컬에서 빠르게 로드될 수 있습니다.
- 캐싱 정책은 주로 HTTP 헤더(Cache-Control, Expires, ETag) 등을 통해 제어됩니다.
2. 쿠키(Cookie)와 세션(Session)
- HTTP는 기본적으로 무상태(Stateless) 프로토콜입니다.
- 사용자의 로그인 상태 유지나 쇼핑몰 장바구니 기능 등을 위해 쿠키와 세션을 활용합니다.
- 쿠키: 클라이언트(브라우저)에 key-value 형태로 저장되는 데이터
- 세션: 서버 쪽에 저장되는 사용자 상태 정보(주로 쿠키에 session ID를 담아 식별)
3. 보안(HTTPS/TLS)
- HTTPS는 일반 HTTP에 TLS/SSL로 암호화 계층을 추가한 것입니다.
- 사용자와 서버 사이에 오가는 모든 데이터를 암호화하므로, 중간에 탈취하더라도 내용을 해독하기 어렵습니다.
- SSL 인증서(공개키, 개인키)와 CA(인증 기관) 등을 통해 서버의 신뢰성이 검증됩니다.
4. 로드밸런싱(Load Balancing)
- 대규모 트래픽을 처리하기 위해 여러 서버를 분산하여 운영하는 경우가 많습니다.
- 사용자가 입력한 호스트 네임은 DNS 수준에서 여러 서버의 IP로 분산되거나, 로드밸런서가 중앙에서 트래픽을 분산해 서버 부하를 줄입니다.
정리
- URI는 웹의 자원을 식별하기 위한 표준이자, 그 구체적 실체인 URL을 통해 우리는 “어떤 프로토콜로, 어느 서버에서, 어떤 자원을 요청할 것인지”를 표현합니다.
- 브라우저가 URI를 입력받으면, DNS 조회 → TCP/HTTPS 핸드셰이크 → HTTP 요청 → 서버 처리 → HTTP 응답 → 브라우저 렌더링의 흐름을 거칩니다.
- 이 과정에서 캐싱, 쿠키/세션, 로드밸런싱, HTTPS 보안 등 다양한 요소가 개입할 수 있습니다.
- 모든 통신은 HTTP 프로토콜의 규약에 따라 이루어지며, 서버와 클라이언트가 서로 동작하는 로직에 따라 웹 사이트가 동적으로 또는 정적으로 동작합니다.
마치며
위의 흐름은 모든 웹 요청에서 근본적으로 동일하게 적용됩니다. 우리가 웹 개발을 할 때나 인터넷을 이용할 때, “도메인 → IP 변환 → TCP 연결 → HTTP 메시지 교환”과 같은 저수준 과정을 매번 의식하진 않지만, 이를 이해하고 있으면 성능 최적화(캐싱, CDN, 로드밸런싱 등)나 보안(HTTPS, 인증, 세션 관리)을 설계할 때 훨씬 유리해집니다.
이 글에서는 URI(특히 URL)의 구조를 깊이 있게 살펴보고, 웹 브라우저와 서버 간 요청 흐름을 단계별로 설명했습니다. 실제 개발·운영 환경에서는 여기에 세부적인 설정(프록시 서버, 방화벽, CDN, 서버 스케일링 등)이 더해집니다. 하지만 본질적으로는 지금까지 살펴본 과정이 웹 요청의 기본이자 핵심입니다.
이해를 탄탄히 하기 위해서는 직접 서버를 띄워보고, 로컬 DNS 설정, HTTPS 인증서 적용, 브라우저 개발자 도구에서 요청/응답 헤더 확인 등을 해보면서 실습해 보는 것을 권장합니다. 이를 통해 실제로 URI를 입력했을 때 어떤 일이 벌어지는지 체감할 수 있을 것입니다.
이상으로 URI와 웹 브라우저 요청 흐름에 대한 정리를 마칩니다. 웹 개발을 깊이 파고들거나 네트워크 지식을 쌓을 때 도움이 되길 바랍니다.