프록시
Proxy
클라이언트와 서버 사이의 중개 서버. 포워드/리버스 프록시. 캐싱, 보안, 로드밸런싱에 활용.
Proxy
클라이언트와 서버 사이의 중개 서버. 포워드/리버스 프록시. 캐싱, 보안, 로드밸런싱에 활용.
프록시(Proxy)는 "대리인"이라는 뜻으로, 네트워크에서 클라이언트와 서버 사이에 위치하여 요청과 응답을 중계하는 중간 서버입니다. 프록시는 단순히 트래픽을 전달하는 것을 넘어 캐싱, 보안 필터링, 로드밸런싱, 익명화, SSL 종료(SSL Termination) 등 다양한 기능을 수행합니다. 현대 웹 아키텍처에서 프록시는 성능 최적화와 보안 강화의 핵심 구성 요소입니다.
포워드 프록시(Forward Proxy)는 클라이언트 측에 위치하여 클라이언트를 대신해 외부 서버에 요청을 보냅니다. 기업 네트워크에서 인터넷 접근 통제, 콘텐츠 필터링, 사용자 활동 로깅에 사용됩니다. 클라이언트의 실제 IP를 숨겨 익명성을 제공하며, VPN과 유사한 역할을 합니다. 대표적으로 Squid, Privoxy 등이 있습니다.
리버스 프록시(Reverse Proxy)는 서버 측에 위치하여 클라이언트의 요청을 받아 뒤에 있는 실제 서버(Origin Server)로 전달합니다. 클라이언트는 리버스 프록시만 보고 실제 서버의 존재를 알지 못합니다. 로드밸런싱(여러 서버에 트래픽 분산), SSL 종료(프록시에서 HTTPS 처리), 캐싱(자주 요청되는 콘텐츠 저장), 압축, 보안(WAF 통합, DDoS 방어) 등 다양한 기능을 제공합니다. Nginx, HAProxy, Apache Traffic Server, Envoy 등이 대표적입니다.
API Gateway는 리버스 프록시의 특수한 형태로, 마이크로서비스 아키텍처에서 모든 API 요청의 단일 진입점 역할을 합니다. 인증/인가, 속도 제한(Rate Limiting), 요청 변환, API 버전 관리, 모니터링 등 API 특화 기능을 제공합니다. Kong, AWS API Gateway, Traefik 등이 있습니다. CDN(Content Delivery Network)도 글로벌 규모의 리버스 프록시로, 전 세계 엣지 서버에서 콘텐츠를 캐싱하여 사용자에게 가장 가까운 위치에서 응답합니다.
# /etc/nginx/nginx.conf 또는 /etc/nginx/conf.d/app.conf
# 업스트림 서버 그룹 정의 (로드밸런싱)
upstream backend_servers {
# 라운드 로빈 (기본)
server 10.0.1.10:3000 weight=5; # 가중치 높음
server 10.0.1.11:3000 weight=3;
server 10.0.1.12:3000 backup; # 백업 서버
# 연결 유지 설정 (Keep-alive)
keepalive 32;
}
# API 서버 그룹 (최소 연결 방식)
upstream api_servers {
least_conn; # 연결 수가 가장 적은 서버로 라우팅
server 10.0.2.10:8080;
server 10.0.2.11:8080;
}
# HTTP 서버 블록
server {
listen 80;
server_name example.com www.example.com;
# HTTPS 리다이렉트
return 301 https://$server_name$request_uri;
}
# HTTPS 서버 블록
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL 인증서
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# SSL 보안 설정
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
# 보안 헤더
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip 압축
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
# 정적 파일 캐싱
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API 요청 프록시
location /api/ {
proxy_pass http://api_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 타임아웃 설정
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 버퍼링 설정
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# WebSocket 프록시
location /ws/ {
proxy_pass http://backend_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400; # 24시간
}
# 기본 요청 프록시
location / {
proxy_pass http://backend_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
# 캐싱 설정
proxy_cache my_cache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502;
add_header X-Cache-Status $upstream_cache_status;
}
# 헬스체크 엔드포인트
location /health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
}
# 캐시 영역 정의 (http 블록에 추가)
# proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m inactive=60m max_size=1g;
import http from 'http';
import httpProxy from 'http-proxy';
import { URL } from 'url';
// 프록시 서버 생성
const proxy = httpProxy.createProxyServer({
changeOrigin: true, // 호스트 헤더 변경
ws: true, // WebSocket 지원
xfwd: true, // X-Forwarded-* 헤더 추가
proxyTimeout: 30000, // 30초 타임아웃
timeout: 30000,
});
// 에러 핸들링
proxy.on('error', (err, req, res) => {
console.error('Proxy error:', err);
if (res.writeHead) {
res.writeHead(502, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Bad Gateway', message: err.message }));
}
});
// 프록시 요청 로깅
proxy.on('proxyReq', (proxyReq, req, res, options) => {
console.log(`[PROXY] ${req.method} ${req.url} -> ${options.target}`);
// 커스텀 헤더 추가
proxyReq.setHeader('X-Proxy-Timestamp', Date.now().toString());
});
// 프록시 응답 수정
proxy.on('proxyRes', (proxyRes, req, res) => {
// 응답 헤더 추가
res.setHeader('X-Proxied-By', 'my-proxy-server');
console.log(`[RESPONSE] ${req.url} - ${proxyRes.statusCode}`);
});
// 라우팅 규칙 정의
interface RouteConfig {
prefix: string;
target: string;
rewrite?: (path: string) => string;
}
const routes: RouteConfig[] = [
{
prefix: '/api/users',
target: 'http://user-service:3001',
rewrite: (path) => path.replace('/api/users', '/users'),
},
{
prefix: '/api/orders',
target: 'http://order-service:3002',
rewrite: (path) => path.replace('/api/orders', '/orders'),
},
{
prefix: '/api/products',
target: 'http://product-service:3003',
},
{
prefix: '/',
target: 'http://frontend:3000',
},
];
// 경로에 맞는 라우트 찾기
function findRoute(path: string): RouteConfig | undefined {
return routes.find(route => path.startsWith(route.prefix));
}
// HTTP 서버 생성
const server = http.createServer((req, res) => {
const route = findRoute(req.url || '/');
if (!route) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Route not found' }));
return;
}
// 경로 재작성
if (route.rewrite && req.url) {
req.url = route.rewrite(req.url);
}
// 프록시 실행
proxy.web(req, res, { target: route.target });
});
// WebSocket 업그레이드 처리
server.on('upgrade', (req, socket, head) => {
const route = findRoute(req.url || '/');
if (route) {
proxy.ws(req, socket, head, { target: route.target });
} else {
socket.destroy();
}
});
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`Proxy server running on port ${PORT}`);
});
import requests
from requests.auth import HTTPProxyAuth
# 1. 기본 HTTP/HTTPS 프록시 설정
proxies = {
'http': 'http://proxy.example.com:8080',
'https': 'http://proxy.example.com:8080',
}
response = requests.get(
'https://api.example.com/data',
proxies=proxies,
timeout=30
)
print(response.json())
# 2. 인증이 필요한 프록시
proxies_with_auth = {
'http': 'http://username:password@proxy.example.com:8080',
'https': 'http://username:password@proxy.example.com:8080',
}
# 또는 HTTPProxyAuth 사용
auth = HTTPProxyAuth('username', 'password')
response = requests.get(
'https://api.example.com/data',
proxies={'http': 'http://proxy.example.com:8080'},
auth=auth
)
# 3. SOCKS 프록시 (pip install requests[socks] 필요)
socks_proxies = {
'http': 'socks5://localhost:1080',
'https': 'socks5://localhost:1080',
}
response = requests.get(
'https://api.example.com/data',
proxies=socks_proxies
)
# 4. Session을 사용한 프록시 설정 (연결 재사용)
session = requests.Session()
session.proxies.update({
'http': 'http://proxy.example.com:8080',
'https': 'http://proxy.example.com:8080',
})
# 여러 요청에 동일한 프록시 사용
response1 = session.get('https://api.example.com/users')
response2 = session.get('https://api.example.com/orders')
# 5. 환경 변수로 프록시 설정
# export HTTP_PROXY=http://proxy.example.com:8080
# export HTTPS_PROXY=http://proxy.example.com:8080
# export NO_PROXY=localhost,127.0.0.1,.internal.com
import os
os.environ['HTTP_PROXY'] = 'http://proxy.example.com:8080'
os.environ['HTTPS_PROXY'] = 'http://proxy.example.com:8080'
# requests는 자동으로 환경 변수 사용
response = requests.get('https://api.example.com/data')
# 6. 특정 요청에서 프록시 비활성화
response = requests.get(
'https://internal.example.com/data',
proxies={'http': None, 'https': None} # 프록시 무시
)