
오늘날의 웹은 복잡해 보이지만, 그 복잡함은 한 번에 생긴 것이 아니다. 정적인 문서 한 장을 보여주던 초창기 웹에서 출발해, 필요할 때마다 부품이 하나씩 더해지며 지금의 구조가 되었다. 그래서 이 글은 웹의 발전 과정을 시간 순서로 따라간다. 각 단계에서 어떤 한계에 부딪혔고, 그 한계를 넘기 위해 무엇이 등장했는지를 보면, 복잡한 전체 구조가 자연스러운 이야기로 읽힌다.
출발점 — TCP 연결 위의 HTTP
웹 클라이언트, 곧 브라우저와 웹 서버 사이에는 먼저 TCP 연결이 전제된다. 그 연결 위에서 HTTP 통신이 이루어진다. HTTP 트래픽은 소켓 수준에서 만들어지는 스트림 데이터다. 끝을 곧바로 알기 어려운 데이터가 쭉 이어지는 형태다.

이 스트림을 네트워크로 보낼 때는 잘게 잘린다. IP 네트워크의 MTU가 1500밖에 안 되므로, TCP가 일정 단위로 잘라낸다. 이 조각 하나를 세그먼트라고 하고, 세그먼트를 패킷으로 캡슐화해 네트워크로 전송한다. 스트림 데이터가 이렇게 분해되어 나가고, 수신 측에서 다시 조립된다. 웹을 이야기할 때 보통 패킷이나 세그먼트 같은 말이 나오지 않는 것은, 웹이 그보다 위, 소켓과 스트림 수준의 이야기이기 때문이다.
여기에 HTTP의 중요한 성질이 하나 있다. HTTP는 상태가 없다. 연결이라는 개념은 TCP에 있는 것이고, HTTP 자체에는 상태 개념이 존재하지 않는다.
HTTP는 stateless
HTTP는 이전 요청과 다음 요청 사이에 아무 상태도 기억하지 않는다. 요청과 응답이 끝나면 그것으로 끝이다. 이 성질은 뒤에서 여러 문제를 낳지만, 동시에 더 좋은 구조를 만들기 쉽게 하는 장점이기도 하다. 이 글의 상당 부분이 결국 이 stateless를 어떻게 다루느냐에 관한 이야기다.
초창기 웹 — 정적 문서와 문서 뷰어
웹이 처음 만들어졌을 때, HTML은 문서였고 브라우저는 그 문서를 보여주는 뷰어였다. 기존의 문서 뷰어와 달랐던 점은, 당대의 신기술인 인터넷을 적용해 멀리 떨어진 웹 서버에 저장된 문서를 가져와 보여주었다는 것이다.
동작은 단순했다. 브라우저 주소창에 주소를 입력하면, DNS로 IP 주소를 알아내고, 그 주소로 TCP 연결을 맺은 뒤 HTTP 통신을 한다. 이때 요청은 GET 메서드로 나간다. 한마디로 "문서 주세요"다. 서버는 이 요청을 받고 HTML 문서를 응답한다. 브라우저는 문서 뷰어이므로 받아온 HTML 문서를 화면에 표시한다. 그리고 문서 속 링크를 클릭하면, 마치 처음 주소를 입력한 것처럼 같은 과정이 되풀이된다. 이것이 초창기 웹의 모습이다.
여기서 짚어둘 것은, 이 문서가 정적인 문서였다는 점이다. 신문이나 책 같은 것이다. 내용이 정해져 있고 움직이지 않는다.
꾸미기와 움직임 — CSS, JavaScript, 그리고 브라우저의 세 엔진
문서를 잘 가져오게 되자, 다음 욕심이 생긴다. 문서를 예쁘게 꾸미고 싶어진 것이다. 그런데 여기서 소프트웨어 설계의 한 가지 원칙이 작동한다. 무언가를 분리하라는 것이다.
화면에 보이는 UI, 자료에 해당하는 데이터, 그리고 제어 로직, 이 세 가지는 가급적 섞지 말고 분리하는 것이 좋다. 섞어 두면 유지보수가 어려워지기 때문이다. 데이터를 바꾸려는데 화면까지 고쳐야 하거나, 화면을 손봤더니 데이터 구조가 틀어지는 식의 문제가 생긴다. 그래서 문서 자체와 그 문서를 꾸미는 요소를 분리하게 되는데, 그렇게 떨어져 나온 것이 CSS, 곧 스타일 시트다.
관심사를 분리하라
UI와 데이터와 제어를 분리하는 것은 좋은 설계의 핵심 원칙이다. 화면을 예쁘게 꾸미겠다고 문서 자체를 뜯어고치는 일이 없도록, 꾸미는 일은 CSS로 떼어낸다. 이 분리의 원칙은 이 글 뒤에서 MVC 구조로 다시 등장한다.
그다음은 움직임이다. 정적인 문서에 동적인 변화를 주려는 시도가 이어졌고, 그 변화를 기술하는 것이 스크립트 형태로 등장했다. 이 스크립트는 처음 Mocha Script라는 이름이었다가, Live Script로 바뀌었고, 마지막에 JavaScript가 되었다. 당시 유행하던 Java의 유명세를 빌린 이름이다.
스크립트가 더해지면서 브라우저 자체가 복잡해졌다. 예전에는 단순 텍스트만 화면에 찍으면 끝이었지만, 이제는 받아온 HTML을 해석해 화면에 그려야 한다.

브라우저가 HTML을 처리하는 과정을 보면, 먼저 텍스트 내용과 태그를 분리해 해석하는 구문 분석기가 있고, 그 해석에 맞게 화면에 그리는 렌더링 엔진이 있다. 여기에 스크립트가 들어오면서 JavaScript 엔진까지 더해진다. 구문 분석기, 렌더링 엔진, JavaScript 엔진, 이 셋이 오늘날 브라우저의 가장 기본적인 구성 요소다.
이렇게 해서 서버에서 날아오는 것이 HTML 문서 하나에서, CSS와 JavaScript, 그리고 JPG 같은 이미지까지로 늘어난다. 모두 HTTP 요청에 대한 응답으로 받아오는 리소스다.
단방향에서 양방향으로 — POST와 상호작용
여기까지의 웹은 한쪽 방향이었다. 클라이언트가 문서를 달라고 하면 서버가 보내준다. 도서관에서 책을 빌리거나 TV 채널을 보는 것처럼, 요청한 대로 받아올 뿐이다. 클라이언트가 콘텐츠에 개입할 방법이 없으니 상호작용을 기대하기 어려웠다.
이 한계를 넘기 위해 다른 메서드가 등장한다. 앞서 GET이 다운로드 성격이었다면, 이번에는 POST다. 로그인 화면을 떠올려 보자. 아이디와 비밀번호를 입력하는 칸이 있고, 로그인 버튼이 있다. 버튼을 누르면 입력한 내용이 서버로 전달된다. GET이 서버에서 내려받는 방식이었다면, POST가 더해지면서 클라이언트가 서버로 무언가를 올려보내는 양방향 상호작용이 가능해진다.
원격지 사용자 입력은 신뢰하지 않는다
로그인 화면에 입력한 아이디와 비밀번호는, 서버 관점에서 보면 원격지에 있는 사용자가 보낸 입력이다. 이것은 절대 그대로 신뢰해서는 안 되고 반드시 검증해야 하는 대상이다. 이 원칙은 시큐어 코딩의 핵심이며, 이 글 뒤의 보안 절에서 다시 짚는다.
기억해야 하는 문제 — 쿠키와 세션
양방향 상호작용이 가능해지자 새로운 문제가 따라온다. 상호작용에는 필연적으로 문맥이 생긴다. 어제 나눈 대화가 오늘로 이어지듯, 작용의 절차를 따르는 상태가 전이한다. 그런데 앞서 보았듯 HTTP는 stateless다. 상태를 기억하지 못한다.
그래서 상태 전이 과정에서 필요한 무언가를, 어딘가에 기억시켜야 할 필요가 생긴다. 이 기억은 서버와 클라이언트 양쪽 모두 해야 한다. 양방향으로 상호작용하기 때문이다.

클라이언트 쪽에서 이 기억을 구현하는 것이 쿠키다. 쿠키는 기술적인 문법으로 설명할 수도 있지만, 개념적으로는 상호작용을 위한 기억의 부산물이라고 이해하면 된다. 키와 값의 쌍으로 되어 있다.
서버 쪽은 기억해야 할 손님이 많다. 클라이언트는 자기 것만 기억하면 되지만, 서버는 수많은 사용자와의 상호작용을 기억해야 한다. 그 양이 감당하기 어려워지면서, 결국 이 기억을 데이터베이스로 만들게 된다. 웹 서버 외에 DB 서버가 생겨나는 지점이다.
3계층 구조 — 웹서버, WAS, DB
로그인 과정을 따라가 보면 웹 서비스의 전형적인 구조가 드러난다. 클라이언트가 아이디와 비밀번호를 POST로 보내면, 서버는 데이터베이스에 그런 계정이 있는지 조회해야 한다. 그런데 웹 서버가 데이터베이스에 직접 접근하는 것은 아니다. 그 중간에서 처리를 담당하는 서버가 하나 더 들어간다.

이렇게 해서 웹 서비스는 보통 세 개의 서버로 연결된다. 각자의 역할을 보면, 웹 서버는 리소스를 보내고 받는 송수신을 담당하고, 데이터베이스는 자료를 기억하는 일을 담당한다. 그리고 그 중간에 있는 서버는 어떤 처리, 곧 연산을 담당한다. 검증해야 할 사용자 입력을 받아 데이터베이스에 질의를 던지고, 그 결과로 화면을 만들어내는 일이 여기서 이루어진다.
이 중간의 처리를 담당하는 서버를 WAS, 웹 애플리케이션 서버라고 부른다. WAS는 눈에 보이는 화면을 담당하는 부분, 자료를 담당하는 부분, 그리고 네트워크 요청에 대한 제어 체계를 담은 부분으로 나뉜다. 이를 각각 뷰, 모델, 컨트롤이라 하고, 약자를 따면 MVC 구조가 된다. 앞에서 본 관심사 분리의 원칙이 서버 구조에서 다시 나타나는 것이다.
세 서버를 각각 하나의 계층으로 보면, 웹 서버가 한 계층, WAS가 한 계층, 데이터베이스가 한 계층이 되어 3티어 구조가 된다. 웹 서버와 WAS를 합치면 2티어가 되기도 한다.
로그인 한 번에 일어나는 일
아이디가 tester, 비밀번호가 1234로 POST되면, WAS는 이 입력을 받아 데이터베이스에 계정을 조회하는 질의를 보낸다. 데이터베이스 연동에는 ODBC, JDBC 같은 인터페이스가 쓰인다. 계정을 찾으면 그에 맞는 환영 화면을, 못 찾으면 다른 화면을 동적으로 생성해 응답한다. 같은 주소라도 그때그때 다른 HTML이 만들어지는 것이 동적 웹이다.
WAS의 정체 — 미들웨어, 서블릿 컨테이너, JVM

WAS가 무엇인지 더 깊이 들어가려면, 그 토대가 되는 JVM부터 보아야 한다.
하드웨어 계층에 있는 CPU는 실제 기계다. 그 위로 올라가면 모두 소프트웨어인데, 소프트웨어의 본질은 논리적이고 가상적이라는 데 있다. 국내 웹 개발은 대부분 Java로 이루어지는데, 여기서 CPU에 해당하는 것을 소프트웨어로 구현한 것이 있다. 그것이 JVM, 자바 가상 머신이다. JVM은 Java를 위해 소프트웨어로 구현된 CPU라고 생각하면 된다. 실제 CPU가 기계어를 처리하듯, JVM은 Java 바이트코드라는 명령 체계로 작동한다.
이 바이트코드 위에서 애플리케이션이 잘 돌아가도록 도와주는 소프트웨어를 미들웨어라고 한다. 미들웨어는 자기 자신을 위한 것이라기보다, 다른 소프트웨어가 잘 작동하도록 돕는 소프트웨어다. 어떤 프로그램이든 공통으로 필요한 기능들, 가령 TCP/IP 통신, 데이터베이스 입출력, 파일 입출력 같은 것을 미리 만들어 두고, 개발자는 핵심 로직만 작성하면 나머지는 미들웨어가 처리해 주는 식이다.
Java로 웹을 개발할 때 JSP 같은 것으로 작성한 코드는 서블릿이라는 형태로 변환되어 들어간다. 메인 함수까지 일일이 짤 필요 없이 핵심 기능만 작성하면, 그것을 감싸 안아 연동해 주는 것이다. 이 알맹이를 서블릿이라 하고, 그 전체를 서블릿 컨테이너라고 부른다. 이 미들웨어를 다른 말로 WAS라고 하며, 가장 대표적인 것이 Tomcat이다. 여기서 한 걸음 더 나아간 컨테이너 기술이 프레임워크 개념으로 등장하는데, 국내에서 많이 쓰이는 Spring이 그것이다. Spring 컨테이너는 객체의 생성과 소멸, 의존성 같은 것을 알아서 관리해 준다.
APM으로 들여다본다
웹 서비스가 잘 작동하는지는 늘 모니터링한다. 네트워크가 빨라도 처리가 늦거나 DB 응답이 느리면 전체가 느려지기 때문이다. DB 응답 시간과 WAS의 상태, JVM 영역을 모니터링하는 시스템을 APM이라 하며, 오픈소스 중에는 스카우터 같은 것이 있다. 장애가 나면 결국 이런 도구로 원인을 추적한다.
데이터만 보내기 — JSON과 RESTful API
여기까지의 구조에서 서버는 동적인 HTML을 만들어 통째로 보내주었다. 그런데 또 다른 문제가 생긴다. 클라이언트가 다양해진 것이다. 누구는 아이폰을, 누구는 큰 화면의 폴더블을, 누구는 태블릿이나 PC를 쓴다. 사용자 환경이 너무 다양해지면서, UI를 환경마다 따로 만들고 거기에 맞는 코드를 다 넣는 것이 머리 아픈 일이 되었다.

그래서 발상을 바꾼다. 응답할 때 완성된 HTML 대신, 순수하게 자료만 보내는 것이다. 이 자료는 주로 XML이나 JSON 형태로 온다. 그러면 클라이언트 쪽에서 JavaScript 기반의 소프트웨어가 그 데이터를 받아, 디바이스에 맞게 HTML을 그 자리에서 생성한다. 이 JavaScript 프레임워크로 널리 쓰이는 것이 React.js, Vue.js 같은 것이다. HTML을 직접 전송하던 시대에서, 자료를 받아 그 자리에서 생성하는 구조로 패러다임이 바뀐 것이다.
여기서 요청의 본질을 따져보면, 결국 무언가를 읽어오거나, 새로 생성하거나, 수정하거나, 삭제하라는 것으로 정리된다. 생성, 읽기, 수정, 삭제, 곧 CRUD다. 이런 기능들을 함수 형태로 만들고, 그 함수 자체를 URI로 기술해 호출할 수 있게 만든다. 이렇게 웹에서 함수를 호출하는 형태로 구현한 것을 RESTful API라고 부른다. API들을 호출해 데이터를 받고 HTML을 생성하는 구조로 웹이 발전한 것이다.
지켜야 하는 것들 — 보안 계층과 검증
마지막으로 보안이다. 백엔드를 다루면 보안도 함께 신경 써야 한다.

가장 앞단에는 침입 방지 시스템인 IPS가 1차 방어 체계로 들어간다. 그다음 웹 서버를 보호하기 위한 웹 애플리케이션 방화벽, 곧 WAF가 2차 방어 체계가 된다. 또 SSL 처리를 담당하는 부분이 있는데, 이것이 중간에 끼어들면 그 앞 구간까지는 HTTPS로 암호화된 통신이고, 그 뒤 구간부터는 평문인 HTTP로 바뀐다.
보안에서 한 가지 더 중요한 원칙은 검증이다. 앞서 원격지 사용자 입력은 신뢰하지 말라고 했다. 사용자가 입력한 값 속에 SQL문이 들어 있는지 검사하는 것이 그 예인데, 입력에 악의적인 SQL을 끼워 넣는 공격이 SQL 인젝션이다. 이름을 넣으라는 칸에 SQL문이나 비정상적으로 긴 값이 들어오면 걸러내야 한다.
검증은 반드시 서버에서
JavaScript로 하는 검증은 실행 자체가 클라이언트에서 이루어진다. 코드는 서버에 있지만 다운로드되어 클라이언트에서 돌기 때문에, 사용자가 조작할 수 있다. JavaScript의 입력 검증은 잘못된 입력을 막아주는 편의 기능일 뿐, 보안 대응이 되지 못한다. 진짜 검증은 반드시 서버에서 해야 한다.
'Network' 카테고리의 다른 글
| 인터넷 공유기와 NAT — 하나의 주소로 여럿이 인터넷을 쓰는 법 (0) | 2026.06.16 |
|---|---|
| 세 가지 구조: Inline·Out of path·Proxy (0) | 2026.06.16 |
| 웹 — URL·URI와 HTTP (0) | 2026.06.09 |
| 웹 — 이름을 주소로 바꾸는 관문, DNS 그리고 웹의 탄생 (0) | 2026.06.09 |
| L4 — TCP와 UDP: 연결의 실체와 착각 (0) | 2026.06.09 |
