http.server는 간단한 HTTP 서버를 제공합니다. 정적 파일 서빙, 개발 테스트, 로컬 파일 공유 등에 유용합니다. (프로덕션용 아님)
언제 이 치트시트를 보나?
- 로컬에서 파일을 HTTP로 서빙하고 싶을 때
- 간단한 테스트 서버가 필요할 때
- 다른 기기에서 파일 접근이 필요할 때
한 줄로 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 현재 디렉토리를 8000 포트로 서빙
python -m http.server
# 포트 지정
python -m http.server 8080
# 특정 디렉토리
python -m http.server -d /path/to/directory
# 특정 IP에 바인딩
python -m http.server --bind 0.0.0.0
# 모든 옵션
python -m http.server 8080 --bind 0.0.0.0 -d ./public
|
최소 예제
1. 코드에서 서버 실행
1
2
3
4
5
6
7
8
9
| import http.server
import socketserver
PORT = 8000
# 간단한 파일 서버
with socketserver.TCPServer(("", PORT), http.server.SimpleHTTPRequestHandler) as httpd:
print(f"Serving at http://localhost:{PORT}")
httpd.serve_forever()
|
2. 커스텀 핸들러
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| from http.server import HTTPServer, BaseHTTPRequestHandler
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
# 응답 상태
self.send_response(200)
# 헤더
self.send_header('Content-type', 'text/html')
self.end_headers()
# 본문
response = f"<h1>Hello! Path: {self.path}</h1>"
self.wfile.write(response.encode())
def do_POST(self):
# POST 데이터 읽기
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = f'{{"received": {len(post_data)} bytes}}'
self.wfile.write(response.encode())
# 서버 실행
server = HTTPServer(('localhost', 8000), MyHandler)
print("Server running on http://localhost:8000")
server.serve_forever()
|
3. JSON API 서버
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| from http.server import HTTPServer, BaseHTTPRequestHandler
import json
# 간단한 데이터 저장소
data = {"items": []}
class APIHandler(BaseHTTPRequestHandler):
def _send_json(self, data, status=200):
self.send_response(status)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*') # CORS
self.end_headers()
self.wfile.write(json.dumps(data).encode())
def do_GET(self):
if self.path == '/api/items':
self._send_json(data)
else:
self._send_json({"error": "Not found"}, 404)
def do_POST(self):
if self.path == '/api/items':
content_length = int(self.headers['Content-Length'])
body = json.loads(self.rfile.read(content_length))
data["items"].append(body)
self._send_json({"status": "created"}, 201)
else:
self._send_json({"error": "Not found"}, 404)
server = HTTPServer(('localhost', 8000), APIHandler)
server.serve_forever()
|
4. 특정 디렉토리 서빙
1
2
3
4
5
6
7
8
9
10
| from http.server import SimpleHTTPRequestHandler
import socketserver
import os
# 서빙할 디렉토리
os.chdir('/path/to/directory')
PORT = 8000
with socketserver.TCPServer(("", PORT), SimpleHTTPRequestHandler) as httpd:
httpd.serve_forever()
|
5. 스레드 서버 (동시 요청)
1
2
3
4
5
6
7
8
9
10
11
12
| from http.server import SimpleHTTPRequestHandler
import socketserver
PORT = 8000
# ThreadingTCPServer: 각 요청을 별도 스레드에서 처리
class ThreadedHTTPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
with ThreadedHTTPServer(("", PORT), SimpleHTTPRequestHandler) as httpd:
print(f"Threaded server at port {PORT}")
httpd.serve_forever()
|
6. 요청 정보 출력
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| from http.server import BaseHTTPRequestHandler, HTTPServer
class DebugHandler(BaseHTTPRequestHandler):
def do_GET(self):
print(f"Path: {self.path}")
print(f"Headers: {dict(self.headers)}")
print(f"Client: {self.client_address}")
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(b"OK")
# 로그 출력 비활성화
def log_message(self, format, *args):
pass # 또는 커스텀 로깅
HTTPServer(('', 8000), DebugHandler).serve_forever()
|
7. 파일 업로드 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| from http.server import BaseHTTPRequestHandler, HTTPServer
import cgi
class UploadHandler(BaseHTTPRequestHandler):
def do_GET(self):
# 업로드 폼 제공
html = '''
<html>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
</body>
</html>
'''
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html.encode())
def do_POST(self):
if self.path == '/upload':
# multipart 파싱
content_type = self.headers['Content-Type']
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': content_type}
)
file_item = form['file']
if file_item.filename:
# 파일 저장
with open(file_item.filename, 'wb') as f:
f.write(file_item.file.read())
self.send_response(200)
self.end_headers()
self.wfile.write(f"Uploaded: {file_item.filename}".encode())
return
self.send_response(404)
self.end_headers()
HTTPServer(('', 8000), UploadHandler).serve_forever()
|
8. HTTPS (SSL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| from http.server import HTTPServer, SimpleHTTPRequestHandler
import ssl
# 인증서 필요 (openssl로 생성)
# openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
server = HTTPServer(('localhost', 4443), SimpleHTTPRequestHandler)
server.socket = ssl.wrap_socket(
server.socket,
keyfile='key.pem',
certfile='cert.pem',
server_side=True
)
print("HTTPS server on https://localhost:4443")
server.serve_forever()
|
자주 하는 실수
1. 프로덕션에 사용
1
2
3
4
5
6
7
| # 경고: http.server는 개발/테스트용
# 프로덕션에서는 nginx, gunicorn 등 사용
# 보안 문제:
# - 디렉토리 트래버설 공격에 취약할 수 있음
# - 성능이 낮음
# - 인증/권한 기능 없음
|
2. 0.0.0.0 바인딩 주의
1
2
3
4
5
6
| # 로컬 전용
python -m http.server # localhost만
# 네트워크 공개 (주의!)
python -m http.server --bind 0.0.0.0
# 같은 네트워크의 모든 기기에서 접근 가능
|
한눈에 정리
| 용도 | 명령/코드 |
|---|
| 빠른 파일 서버 | python -m http.server |
| 포트 지정 | python -m http.server 8080 |
| 커스텀 핸들러 | BaseHTTPRequestHandler 상속 |
| 동시 처리 | ThreadingMixIn 사용 |
참고