개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

기초개념 › 네트워크

프록시 서버

프록시 서버는 무엇이고, 사이에 끼면 무엇이 달라지는가

프록시 서버는 클라이언트(요청을 보내는 프로그램)와 진짜 서버 사이에 서서 요청을 대신 전달해 주는 서버입니다. 코드 칸은 세 가지를 적은 것입니다. 첫째는 클라이언트가 접속하는 주소(http://localhost:8080/hello)이고, 주소 중간의 8080은 포트 번호, 곧 한 컴퓨터에서 프로그램마다 나뉜 문의 번호입니다. 둘째는 8080번에서 기다리는 프록시 서버로, 받은 요청을 9000번의 서버에 넘깁니다. 셋째는 9000번에서 기다리는 진짜 서버로, "안녕하세요"를 돌려줍니다. 그래서 클라이언트는 프록시 서버에 연결하고, 프록시 서버가 진짜 서버에 다시 연결해 요청을 넘기고 응답을 돌려줍니다. 이 실습의 프록시 서버는 서버 앞에 서 있고 클라이언트는 그것을 서버로 알고 접속하는 모양(리버스 프록시)입니다. 이 실습은 접속하는 주소를 바꾸거나, 프록시 서버나 진짜 서버의 줄을 지워(그 서버를 켜지 않은 것) 같은 요청을 보내 가며, 클라이언트가 무엇을 받는지와 진짜 서버가 누구를 상대로 보는지를 봅니다. 코드 칸은 실행한 프로그램의 소스가 아니라 무엇이 어느 번호에서 기다리는지를 적은 것이고, 8080과 9000은 예시로 적은 번호입니다. 로드 밸런서, CDN, API 게이트웨이도 클라이언트와 서버 사이에 서는 서버입니다.

실행으로 대조 · Node.js 24.18.0의 http 서버 둘(프록시 서버와 진짜 서버)과 http 클라이언트로 여섯 가지 조합을 실행해 대조

이 실습에 나오는 개념

프록시 서버
프록시 서버는 클라이언트와 진짜 서버 사이에 서서, 받은 요청을 다른 서버에 대신 전달하고 그 응답을 클라이언트에 돌려주는 중간 서버다. 클라이언트는 프록시 서버에 연결하고, 프록시 서버가 진짜 서버에 따로 연결을 맺는다. 그래서 진짜 서버가 보는 연결 상대는 클라이언트가 아니라 프록시 서버다. RFC 9110 3.7절은 이런 중간 서버를 클라이언트가 설정으로 골라 거치는 proxy와, 서버처럼 보이면서 요청을 안쪽 서버로 넘기는 gateway(리버스 프록시)로 나누어 부른다. 프록시 서버가 꺼지면 그 주소로 접속하는 클라이언트는 뒤의 서버가 살아 있어도 응답을 받지 못한다.
진짜 서버(원 서버)
진짜 서버는 요청받은 자원에 대해 최종적인 응답을 직접 만들어 낼 수 있는 프로그램으로, RFC 9110 3.6절은 origin server라고 부른다. 프록시 서버는 요청을 전달할 뿐이어서, 응답의 내용을 만드는 쪽은 이 서버다. 프록시 서버가 있으면 클라이언트는 이 서버에 직접 연결하지 않을 수 있고, 이 서버는 연결해 온 상대로 프록시 서버를 보게 된다.
502 Bad Gateway
502는 서버가 게이트웨이나 프록시로 일하다가, 요청을 이루려고 접속한 안쪽 서버에게서 올바르지 않은 응답을 받았을 때 쓰는 상태 코드다(RFC 9110 15.6.3절). 이 응답을 만들어 보내는 것은 클라이언트가 연결한 프록시 서버이고, 그 뒤의 진짜 서버가 아니다. 그래서 클라이언트가 502를 받았다는 것은 연결한 상대인 프록시 서버까지는 닿았고, 문제가 프록시 서버와 그 뒤쪽 서버 사이에 있다는 뜻이다.
리버스 프록시
클라이언트의 요청을 대신 받아 내부의 서버로 전달하고 그 응답을 클라이언트에 돌려주는 서버다. 클라이언트는 실제 서버를 직접 알지 못한다. 여러 서버로의 요청 분산, TLS 처리, 캐싱, 내부 서버 보호 등의 역할을 한다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 클라이언트(요청을 보내는 프로그램)가 코드 칸의 주소로 접속해 GET /hello 요청을 보냅니다. 주소 중간의 8080은 포트 번호로, 한 컴퓨터에서 프로그램마다 나뉜 문의 번호입니다. 클라이언트는 8080번에서 기다리는 프로그램에 연결하려고 합니다. 이 번호에서 기다리는 것으로 적힌 것은 프록시 서버입니다. 클라이언트가 쓰는 주소에는 진짜 서버의 번호 9000이 없습니다.
  2. 프록시 서버가 요청을 받았습니다. 프록시 서버는 받은 요청을 9000번에서 기다리는 진짜 서버에 넘기도록 되어 있습니다(코드 칸의 프록시 서버 줄). 그래서 프록시 서버가 진짜 서버에 새로 연결해 같은 GET /hello 요청을 보냅니다. 이 연결은 클라이언트가 맺은 연결과는 따로입니다. 클라이언트는 이 연결을 맺지 않았고 9000이라는 번호도 쓰지 않았습니다.
  3. 진짜 서버가 요청을 받고, 코드 칸에 적힌 대로 "안녕하세요"를 응답으로 돌려줍니다. 이때 서버가 보는 연결 상대는 프록시 서버입니다. 서버가 본 상대의 포트가 클라이언트가 쓴 포트와는 달랐고, 프록시 서버가 서버에 연결할 때 쓴 포트와 같았습니다(이 실행에서는 세 프로그램이 한 컴퓨터에 있어 주소는 모두 127.0.0.1로 같아서 포트로 구별했습니다). 진짜 서버에 연결해 온 상대는 프록시 서버 하나뿐이었습니다.
  4. 프록시 서버가 진짜 서버의 응답을 받아 그대로 클라이언트에 돌려줍니다. 클라이언트가 받은 것은 200 OK와 본문 "안녕하세요"입니다. 클라이언트는 자기가 연결한 프록시 서버하고만 주고받았습니다.

실행 결과

코드

# 클라이언트가 접속하는 주소
http://localhost:8080/hello
# 8080에 있는 것: 프록시 서버 (받은 요청을 9000의 서버에 넘긴다)
# 9000에 있는 것: 진짜 서버 ("안녕하세요"를 돌려준다)

이 모형의 전제

여섯 가지 조합을 모두 이 PC의 127.0.0.1 안에서만 실제로 실행해 대조했습니다. 외부 네트워크에는 연결하지 않았습니다. 진짜 서버와 프록시 서버는 Node.js 24.18.0의 http 서버이고, 클라이언트는 같은 Node.js의 http.request(연결을 재사용하지 않음)입니다. 진짜 서버는 GET /hello에 200과 코드 칸에 적힌 글 "안녕하세요"를 돌려주고, 연결해 온 상대의 포트를 기록했습니다. 프록시 서버는 받은 요청을 코드 칸에 적힌 번호의 서버에 새 연결을 맺어 그대로 넘기고 응답을 클라이언트에 돌려주며, 그 연결에서 쓴 자기 포트를 기록했습니다. 실행 프로그램은 화면의 코드와 같은 글을 만들어 읽고(어느 번호에 무엇이 적혀 있는지) 그대로 띄웠습니다. 줄이 지워진 서버는 띄우지 않았고, 그 번호로 접속하면 기다리는 프로그램이 없는 빈 포트(방금 닫은 포트)로 연결을 시도했습니다. 화면의 8080과 9000은 예시 번호이고 실제로는 빈 포트를 썼으며, 코드 칸은 실행한 프로그램의 소스가 아니라 무엇이 어느 번호에서 기다리는지를 적은 것입니다. 대조한 것은 클라이언트가 받은 것(상태 코드와 본문 또는 오류 종류), 진짜 서버가 본 상대, 요청이 지나간 길이고, 진짜 서버가 본 상대는 서버가 기록한 상대 포트가 클라이언트의 포트와 같은지 프록시 서버가 서버에 연결할 때 쓴 포트와 같은지를 견주어 정했습니다. 세 프로그램이 한 컴퓨터에 있어서 서버가 본 상대 주소는 어느 조합에서나 127.0.0.1이었고, 그래서 포트로 구별했습니다. 프록시 서버를 거친 조합에서 진짜 서버에 연결해 온 상대는 하나(프록시 서버)뿐이었고, 직접 접속한 조합에서는 프록시 서버가 받은 요청이 0개였습니다. 8080(또는 9000)에서 기다리는 것이 없을 때 클라이언트는 연결 거부 오류(ECONNREFUSED)를 받았고, 진짜 서버가 꺼진 채 프록시 서버를 거치면 프록시 서버가 같은 연결 거부 오류를 받은 뒤 502를 돌려주었습니다. 이 502는 이 실습의 프록시 서버를 그렇게 만들어서 나온 동작이고, 실제 프록시 제품이 연결하지 못했을 때 무엇을 돌려주는지는 확인하지 않았습니다. 문서로 확인한 것은 RFC 9110의 3.6절(원 서버는 대상 자원에 대한 최종적인 응답을 만들어 낼 수 있는 프로그램), 3.7절(중간 서버 가운데 proxy는 클라이언트가 설정으로 고르는 것, gateway는 리버스 프록시라고도 하며 바깥 연결에서는 원 서버처럼 행동하고 받은 요청을 안쪽 서버로 넘기는 것), 15.6.3절(502는 게이트웨이나 프록시로 일하는 서버가 안쪽 서버에게서 올바르지 않은 응답을 받았을 때 쓰는 코드)입니다. 15.6.3절은 첫 문장을 원문 그대로 확인했고 나머지는 요약으로 확인했습니다. 이 실습의 프록시 서버는 클라이언트가 서버로 알고 접속하는 쪽이라 RFC 용어로는 gateway(리버스 프록시)에 가깝고, 클라이언트가 일부러 고르는 포워드 프록시와의 차이는 다루지 않았습니다. 연결 자체가 거부된 경우에 502를 쓰는 것이 RFC 문장에 정확히 들어맞는지는 따지지 않았습니다. 로드 밸런서, CDN, API 게이트웨이도 클라이언트와 서버 사이에 서는 서버라는 문장은 일반적인 설명이며 이번 실행이나 문서로 확인한 것이 아닙니다. 이 실습은 다루지 않은 것이 많습니다. 원래 클라이언트의 주소를 서버에 전하는 X-Forwarded-For 같은 헤더, HTTPS를 프록시 서버에서 푸는 구성, 응답이 오래 걸릴 때의 타임아웃과 504, 요청을 여러 서버로 나누기와 응답 보관은 보지 않았고, 앞의 둘은 "세부 동작 실습: 프록시와 리버스 프록시", 서버를 나누는 것은 "세부 동작 실습: 로드밸런서와 스케일 아웃", 응답 보관은 "세부 동작 실습: CDN"에서 다룹니다. 브라우저는 쓰지 않았고 요청은 GET /hello 하나만 보냈습니다.

앞 실습: HTTPS · 다음 실습: 인증과 인가

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요