웹 서버
고친 사람 github-actions[bot]
웹 서버는 브라우저가 보낸 요청을 받아 웹 페이지와 파일을 돌려주는 프로그램입니다. 주소 하나를 받으면 그 주소가 가리키는 내용을 찾아 응답으로 내보냅니다. 늘 켜진 채 기다리다가 요청이 올 때마다 이 일을 되풀이합니다.
쉽고 빠른 이해
웹 서버는 밖에서 온 요청을 받아 답을 돌려주는 창구 프로그램입니다. 주소창에 주소를 넣으면 그 주소가 가리키는 기계에서 이 프로그램이 요청을 받습니다.
이게 없으면 파일을 가진 기계와 그 파일을 보려는 사람을 이어 줄 것이 없습니다. 기계마다 내주는 방식이 제각각이면 브라우저가 상대마다 다른 말을 배워야 합니다.
이렇게 돕니다:
- 정해진 번호의 문을 열어 두고 요청이 오기를 기다립니다
- 요청이 오면 어느 주소를 달라는 것인지 읽습니다
- 그 주소에 맞는 내용을 찾거나 만들어서 응답으로 돌려줍니다
대가는 늘 켜 두어야 한다는 것입니다. 아무도 안 찾는 동안에도 기다려야 합니다. 한꺼번에 몰리면 받아 낼 수 있는 만큼만 받습니다.
상세
도서관 대출 창구를 떠올리면 됩니다. 창구 직원은 책 제목을 들으면 서가에서 그 책을 찾아 건네줍니다. 누가 오든 같은 절차로 받아 줍니다.
웹 서버는 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)라는 약속을 지켜 요청을 받고 응답을 돌려주는 서버 프로그램입니다. HTTP 는 무엇을 달라고 말하는 방법과 무엇을 돌려준다고 말하는 방법을 미리 정해 둔 규칙입니다. 브라우저 주소창에 주소를 넣었을 때 화면에 글과 그림이 뜨는 것은, 그 주소가 가리키는 기계에서 웹 서버가 요청을 받아 답을 내보냈기 때문입니다.
파일이 담긴 기계와 그 파일을 보려는 사람은 대개 멀리 떨어져 있습니다. 누가 언제 무엇을 달라 할지도 미리 알 수 없습니다. 그래서 늘 켜진 채 기다리다가 요청이 올 때마다 답하는 프로그램이 하나 있어야 합니다.
규칙을 맞추는 일도 있습니다. 브라우저 하나가 세상의 모든 기계를 상대하려면 내주는 말이 어디서나 같아야 합니다. 그 말을 미리 정해 둔 것이 HTTP 입니다.
웹 서버는 이 둘을 함께 맡습니다. 늘 켜진 채 기다리는 일과, 모두가 같은 규칙으로 말하게 하는 일입니다.
요청 한 번이 지나가는 길
웹 서버는 켜지면 먼저 정해진 번호의 문 앞에 앉습니다. 그 번호가 포트입니다. 웹은 80번을 씁니다. 번호를 미리 정해 두었기 때문에 브라우저가 주소만 알아도 어느 문을 두드릴지 압니다.
두 프로그램이 네트워크 너머로 글자를 주고받을 때 쓰는 통로를 소켓이라고 부릅니다. 통로 하나가 연결 하나입니다. 아래에서는 연결이라고 부릅니다.
웹 서버는 이 연결을 열어 두고 요청이 오기를 기다립니다. 요청 하나가 오가는 모습은 아래와 같습니다.
sequenceDiagram
participant B as 브라우저
participant W as 웹 서버
participant D as 디스크
B->>W: 연결을 맺고 요청을 보낸다
W->>D: 주소에 맞는 파일을 찾는다
D-->>W: 파일 내용
W-->>B: 응답을 돌려준다
그림에서 볼 것은 방향입니다. 말을 먼저 거는 쪽은 언제나 브라우저이고, 웹 서버는 먼저 말을 걸지 않습니다. 이렇게 먼저 묻는 쪽을 클라이언트라고 부릅니다. 가운데에 그려진 디스크는 웹 서버가 도는 기계에 붙은 저장 장치입니다.
브라우저가 보내는 요청은 사람이 읽을 수 있는 글자입니다. 첫 줄에 무엇을 달라는지 적습니다. 다음 줄부터는 딸린 정보를 적습니다.
GET /index.html HTTP/1.1
Host: example.com
첫 줄은 /index.html 이라는 주소의 내용을 달라는 말입니다. 둘째 줄은 어느 사이트 이름으로 찾아왔는지를 알립니다. 한 대가 사이트 여럿을 맡을 때 이 줄로 갈라집니다.
웹 서버가 돌려주는 응답도 같은 꼴입니다. 첫 줄에 결과를 적습니다. 다음 줄은 본문이 어떤 것인지 설명합니다. 빈 줄 뒤에 본문이 옵니다.
HTTP/1.1 200 OK
Content-Type: text/html
<!doctype html>
첫 줄의 숫자가 상태 코드입니다. 요청이 어떻게 됐는지를 세 자리 숫자로 알리는 값입니다. 200 은 요청대로 됐다는 뜻입니다.
둘째 줄은 본문이 어떤 종류인지 알려 줍니다. 글로 된 웹 문서인지 그림인지를 브라우저가 이 줄로 구별합니다.
주소를 무엇으로 바꾸나
요청의 주소는 그 자체로는 글자일 뿐입니다. 웹 서버는 이 글자를 무엇으로 바꿔 응답을 만들지 정해 두어야 합니다.
가장 단순한 규칙은 디스크의 한 폴더를 정해 두고 주소를 그 아래 경로로 읽는 것입니다. 그 폴더를 문서 루트라고 부릅니다. 문서 루트가 /var/www 이면 /index.html 요청은 /var/www/index.html 파일이 됩니다. 이렇게 미리 만들어 두고 찾아서 그대로 내보내는 것이 정적 파일입니다.
주소에 맞는 파일이 없는 요청도 옵니다. 요청마다 답이 달라지는 내용은 미리 파일로 만들어 둘 수 없기 때문입니다. 그런 요청은 웹 서버가 직접 답하지 못하므로, 답을 계산해 줄 프로그램에 넘깁니다.
flowchart TD
A["요청 주소"] --> B{"그 주소의 파일이 있나"}
B -->|있다| C["파일을 찾아 그대로 응답"]
B -->|없다| D{"넘길 프로그램을 지정했나"}
D -->|지정했다| E["뒤로 넘기고 받은 답을 응답"]
D -->|안 했다| F["없다는 상태 코드로 응답"]
그림의 갈림 둘이 웹 서버 설정의 대부분을 차지합니다. 어느 주소를 파일로 볼지, 어느 주소를 뒤로 넘길지를 적는 일입니다. 넘겨받아 답을 계산하는 프로그램이 애플리케이션 서버입니다.
한 대가 여러 요청을 한꺼번에 받는 법
요청 하나를 끝낼 때까지 다음 요청을 못 받는다면, 앞사람이 끝날 때까지 뒷사람이 전부 기다리게 됩니다. 그래서 웹 서버는 요청 여럿을 겹쳐서 처리합니다. 겹치는 단위를 무엇으로 잡느냐에 따라 방식이 갈립니다.
| 겹치는 단위 | 어떻게 겹치나 | 아픈 대목 |
|---|---|---|
| 프로세스 | 요청마다 새 프로세스를 띄운다 | 하나가 차지하는 메모리가 크다 |
| 스레드 | 요청마다 새 스레드를 띄운다 | 수가 늘면 갈아타는 비용이 붙는다 |
| 이벤트 루프 | 한 스레드가 여러 연결을 번갈아 본다 | 한 요청이 오래 붙들면 뒤가 밀린다 |
표의 셋은 한 대 안에서 무엇을 여러 벌 만드느냐가 다릅니다. 앞의 둘은 운영체제가 주는 실행 단위를 요청 수만큼 만듭니다. 이벤트 루프는 실행 단위를 늘리지 않습니다. 기다리는 동안 다른 연결을 번갈아 들여다봅니다.
연결 셋이 들어왔을 때 실행 단위가 몇 벌 생기는지로 갈라 보면 이렇습니다.
flowchart TD
subgraph P["프로세스 · 스레드 — 요청 수만큼 만든다"]
C1["연결 1"] --> U1["실행 단위 1"]
C2["연결 2"] --> U2["실행 단위 2"]
C3["연결 3"] --> U3["실행 단위 3"]
end
subgraph E["이벤트 루프 — 늘리지 않고 번갈아 본다"]
C4["연결 1"] --> L["실행 단위 하나"]
C5["연결 2"] --> L
C6["연결 3"] --> L
end
연결을 아끼는 방법도 있습니다. 한 페이지를 여는 데 그림과 스타일 파일까지 수십 개가 필요한데, 그때마다 연결을 새로 맺으면 맺는 절차가 요청보다 비싸집니다. 그래서 한 번 맺은 연결을 잠시 열어 두고 요청 여럿을 실어 보냅니다. 이 방식을 keep-alive라고 부릅니다.
sequenceDiagram
participant 브라우저
participant 웹 서버
브라우저->>웹 서버: 연결을 맺는다 (한 번)
loop 그림 · 스타일 파일마다
브라우저->>웹 서버: 요청
웹 서버-->>브라우저: 응답
end
Note over 브라우저,웹 서버: 연결은 그대로 두었다가 나중에 닫는다
파일을 내주는 것 말고 맡는 일
웹 서버는 요청이 가장 먼저 닿는 프로그램입니다. 모든 요청이 이곳을 지나므로, 요청마다 한 번씩 해야 하는 일을 여기 모아 두면 뒤쪽 코드가 그 일을 안 해도 됩니다.
| 맡는 일 | 무엇을 하나 |
|---|---|
| TLS(Transport Layer Security, 전송 계층 보안) 종료 | 암호로 싸여 온 요청을 풀어 뒤로는 평문으로 넘긴다. 이런 요청은 80번이 아니라 443번 포트로 온다 |
| 가상 호스트 | 한 대에서 사이트 여럿을 이름으로 갈라 맡는다 |
| 압축 | 본문을 줄여 보내고 브라우저가 풀게 한다 |
| 접근 로그 | 누가 언제 무엇을 요청했고 결과가 무엇이었는지 적는다 |
| 속도 제한 | 한 쪽이 너무 자주 부르면 받는 수를 줄인다 |
표의 일들은 응답의 내용과 상관이 없습니다. 무엇을 돌려주든 똑같이 해야 하는 일이라 애플리케이션 코드에서 걷어 내 앞으로 모아 둔 것입니다.
이웃한 서버들과 갈리는 선
「서버」가 붙은 이름이 여럿이라 헷갈리기 쉽습니다. 무엇을 기준으로 갈리는지를 먼저 정해 두면 선이 또렷해집니다.
| 이름 | 무엇으로 갈리나 |
|---|---|
| 웹 서버 | HTTP 로 요청을 받아 응답한다. 미리 있는 것을 찾아 내주는 일이 본래 몫이다 |
| 애플리케이션 서버 | 요청이 올 때마다 코드를 실행해 응답을 만든다 |
| 리버스 프록시 | 자기가 답을 만들지 않고 뒤에 있는 쪽에 넘기고 받아 전한다 |
| 오리진 서버 | 답을 복사해 두는 [[HTTP 캐시 |
넷이 어떻게 늘어서는지를 보면 선이 더 또렷해집니다.
flowchart TD
B["브라우저"] -->|요청| W
subgraph O["오리진 서버 · 원본을 쥔 뒷단에 붙는 역할 이름"]
W["웹 서버"] -->|미리 있는 파일| D["디스크"]
W -->|뒤로 넘긴다 · 리버스 프록시| A["애플리케이션 서버"]
end
W -->|응답| B
웹 서버·애플리케이션 서버·리버스 프록시는 그 프로그램이 무엇을 하느냐로 나뉩니다. 오리진 서버는 그 프로그램이 다른 쪽과 어떻게 서 있느냐를 가리키는 이름입니다. 그래서 웹 서버가 곧 오리진 서버일 수 있습니다.
한 프로그램이 여러 일을 겸하는 것도 흔합니다. 파일을 내주면서 뒤로 넘기는 일을 함께 하거나, 요청을 여러 대에 나눠 보내는 로드 밸런서 일까지 맡기도 합니다. 겸한다고 해서 선이 흐려지지는 않습니다. 파일을 찾아 내보내는 부분과 뒤로 넘기는 부분은 그 안에서도 다른 일입니다.
언제 신경 쓰나
글과 그림만 있는 사이트라면 웹 서버 하나로 끝납니다. 미리 만들어 둔 파일을 내주는 것이 전부이기 때문입니다. 이런 사이트를 정적 사이트라고 부릅니다.
요청마다 답이 달라지는 서비스를 짜면 웹 서버를 앞단에 두고 뒤에 애플리케이션 서버를 둡니다. 이때 웹 서버가 맡는 일은 「파일을 내주는 것 말고 맡는 일」에 적은 것들입니다.
직접 굴리지 않는 길도 있습니다. 웹 서버를 남이 대신 굴려 주는 관리형 호스팅 서비스를 쓰면 설정할 일이 없습니다. 이때도 요청은 여전히 어딘가의 웹 서버를 지납니다. 내가 안 만질 뿐입니다.
관련 항목
웹 서버가 지키는 규칙과 형식 표준
HTTP · HTTPS · TLS · MIME · URL · 상태 코드 · 콘텐츠 협상
웹 서버 노릇을 맡는 제품
nginx · Apache HTTP Server · Caddy · IIS · lighttpd
웹 서버 뒤에서 응답을 만드는 프로그램
애플리케이션 서버 · CGI · FastCGI · WSGI · 서블릿 컨테이너 · 서버 사이드 렌더링
웹 서버 앞에 서서 요청을 넘기는 장치
리버스 프록시 · 로드 밸런서 · 게이트웨이 · 프록시 · CDN
웹 서버가 내주는 콘텐츠의 갈래
정적 파일 · 정적 사이트 · 동적 페이지 · HTML · 문서 루트
한 대가 여러 요청을 한꺼번에 받게 하는 구조
소켓 · 포트 · 커넥션 · 이벤트 루프 · 스레드 · 프로세스 · keep-alive · 동시성
응답을 대신 보관해 두는 캐시
HTTP 캐시 · 브라우저 캐시 · 콘텐츠 캐시 · 오리진 서버
웹 서버를 굴리며 보는 기록과 지표
접근 로그 · 처리량 · 지연 · 타임아웃 · 속도 제한
한 대에서 사이트 여럿을 갈라 맡는 설정
가상 호스트 · 도메인 · DNS · TLS 인증서 · 서버 이름 표시
다른 이름: web server · HTTP 서버 · httpd