-
Notifications
You must be signed in to change notification settings - Fork 0
KR_Bottle
somaz edited this page Apr 22, 2025
·
5 revisions
Bottle은 단일 파일로 구성된 간단하고 가벼운 WSGI 웹 프레임워크이다.
from bottle import Bottle, run, response, request
app = Bottle()
@app.route('/')
def hello():
return 'Hello World!'
@app.route('/api/users/<id:int>')
def get_user(id):
response.content_type = 'application/json'
return {
'id': id,
'name': f'User {id}',
'email': f'user{id}@example.com'
}
@app.route('/api/users', method='POST')
def create_user():
data = request.json
# 사용자 생성 로직
return {'status': 'created', 'data': data}
if __name__ == '__main__':
run(app, host='localhost', port=8080, debug=True)✅ 특징:
- 단일 파일 구성
- 간단한 라우팅 시스템
- JSON 응답 지원
- 의존성 없는 독립적 실행
- WSGI 호환성
Bottle의 라우팅 시스템을 활용하여 다양한 HTTP 메서드를 처리하는 방법이다.
from bottle import route, get, post, put, delete, request
@route('/hello/<name>')
def hello(name):
return f'Hello {name}!'
@get('/api/items')
def get_items():
return {'items': ['item1', 'item2', 'item3']}
@post('/api/items')
def create_item():
data = request.json
# 아이템 생성 로직
return {'status': 'created'}
@put('/api/items/<id:int>')
def update_item(id):
data = request.json
# 아이템 업데이트 로직
return {'status': 'updated'}
@delete('/api/items/<id:int>')
def delete_item(id):
# 아이템 삭제 로직
return {'status': 'deleted'}✅ 특징:
- 다양한 HTTP 메서드 지원
- URL 패턴 매칭
- 데코레이터 기반 라우팅
- 타입 변환 지원
- 경로 매개변수 추출
Bottle에서 템플릿 엔진을 사용하고 정적 파일을 제공하는 방법이다.
from bottle import route, template, static_file
@route('/static/<filename:path>')
def serve_static(filename):
return static_file(filename, root='./static')
@route('/hello/<name>')
def hello_template(name):
return template('hello_template', name=name)
# views/hello_template.tpl
"""
<!DOCTYPE html>
<html>
<head>
<title>Hello {{name}}</title>
</head>
<body>
<h1>Hello {{name}}!</h1>
</body>
</html>
"""✅ 특징:
- 템플릿 엔진 내장
- 정적 파일 서빙
- 간단한 템플릿 문법
- MIME 타입 자동 감지
- 조건부 HTTP 지원
Bottle과 데이터베이스를 연동하여 데이터를 저장하고 조회하는 방법이다.
import sqlite3
from bottle import route, post, request, response
def db_connection():
return sqlite3.connect('database.db')
@route('/users')
def get_users():
conn = db_connection()
c = conn.cursor()
try:
c.execute('SELECT * FROM users')
users = [
{'id': row[0], 'name': row[1], 'email': row[2]}
for row in c.fetchall()
]
return {'users': users}
finally:
conn.close()
@post('/users')
def create_user():
data = request.json
conn = db_connection()
c = conn.cursor()
try:
c.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
(data['name'], data['email'])
)
conn.commit()
return {'id': c.lastrowid}
finally:
conn.close()✅ 특징:
- 다양한 DB 지원
- 커넥션 관리
- 트랜잭션 처리
- SQL 인젝션 방지
- ORM 라이브러리 통합 가능
요청/응답 처리 전후에 추가 로직을 실행할 수 있는 미들웨어와 플러그인 시스템이다.
from bottle import hook, request, response, install
@hook('before_request')
def setup_request():
request.db = db_connection()
@hook('after_request')
def cleanup_request():
if hasattr(request, 'db'):
request.db.close()
class AuthPlugin:
def apply(self, callback, route):
def wrapper(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
response.status = 401
return {'error': '인증이 필요합니다'}
# 토큰 검증 로직
return callback(*args, **kwargs)
return wrapper
app = Bottle()
app.install(AuthPlugin())✅ 특징:
- 미들웨어 시스템
- 플러그인 아키텍처
- 인증 처리 지원
- 요청/응답 수정 가능
- 기능 모듈화
Bottle을 사용한 실제 애플리케이션 구현 예제를 살펴보자.
from bottle import Bottle, route, request, response
app = Bottle()
class UserAPI:
def __init__(self):
self.users = {}
@app.route('/api/users', method='GET')
def get_users(self):
return {'users': list(self.users.values())}
@app.route('/api/users/<id:int>', method='GET')
def get_user(self, id):
user = self.users.get(id)
if not user:
response.status = 404
return {'error': '사용자를 찾을 수 없습니다'}
return user
@app.route('/api/users', method='POST')
def create_user(self):
data = request.json
user_id = len(self.users) + 1
user = {
'id': user_id,
'name': data['name'],
'email': data['email']
}
self.users[user_id] = user
response.status = 201
return user
api = UserAPI()import os
from bottle import route, request, static_file
@route('/upload', method='POST')
def upload_file():
upload = request.files.get('upload')
if not upload:
return {'error': '파일이 없습니다'}
name, ext = os.path.splitext(upload.filename)
if ext not in ('.png', '.jpg', '.jpeg'):
return {'error': '지원하지 않는 파일 형식입니다'}
save_path = f"uploads/{upload.filename}"
upload.save(save_path)
return {'filename': upload.filename}
@route('/uploads/<filename:path>')
def serve_upload(filename):
return static_file(filename, root='./uploads')from bottle import route, request, response
import uuid
from datetime import datetime
class Session:
def __init__(self):
self.sessions = {}
def create_session(self, user_id):
session_id = str(uuid.uuid4())
self.sessions[session_id] = {
'user_id': user_id,
'created_at': datetime.now()
}
return session_id
def get_session(self, session_id):
return self.sessions.get(session_id)
def delete_session(self, session_id):
self.sessions.pop(session_id, None)
session_manager = Session()
@route('/login', method='POST')
def login():
data = request.json
# 사용자 인증 로직
def authenticate_user(data):
# 실제 구현에서는 DB 확인 등의 로직
if data.get('username') == 'admin' and data.get('password') == 'password':
return 1
return None
user_id = authenticate_user(data)
if user_id:
session_id = session_manager.create_session(user_id)
response.set_cookie('session_id', session_id)
return {'status': 'logged in'}
response.status = 401
return {'error': '인증 실패'}from bottle import error, response
@error(404)
def error404(error):
return {
'error': '페이지를 찾을 수 없습니다',
'status': 404
}
@error(500)
def error500(error):
return {
'error': '서버 오류가 발생했습니다',
'status': 500
}
def handle_errors(fn):
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception as e:
response.status = 500
return {'error': str(e)}
return wrapper
@route('/api/protected')
@handle_errors
def protected_route():
# 예외가 발생할 수 있는 코드
result = 1 / 0 # 의도적인 예외
return {'data': result}import asyncio
import threading
from bottle import route, run
# 백그라운드 작업을 위한 이벤트 루프
background_loop = asyncio.new_event_loop()
def run_async_task(coroutine):
"""비동기 작업을 백그라운드에서 실행"""
asyncio.set_event_loop(background_loop)
background_loop.run_until_complete(coroutine)
async def long_running_task(task_id):
"""시간이 오래 걸리는 작업 시뮬레이션"""
print(f"작업 {task_id} 시작")
await asyncio.sleep(5) # I/O 작업 시뮬레이션
print(f"작업 {task_id} 완료")
return f"작업 {task_id} 결과"
@route('/async-task/<task_id>')
def start_async_task(task_id):
# 백그라운드에서 비동기 작업 실행
thread = threading.Thread(
target=run_async_task,
args=(long_running_task(task_id),)
)
thread.daemon = True
thread.start()
return {'status': 'task_started', 'task_id': task_id}✅ 특징:
- RESTful API 설계
- 파일 업로드 처리
- 세션 기반 인증
- 종합적인 에러 처리
- 비동기 작업 처리
- 미들웨어 활용
- 데이터 유효성 검증
- 보안 고려사항
Bottle 애플리케이션을 프로덕션 환경에 배포하고 확장하는 방법이다.
# wsgi.py (gunicorn, uWSGI 등과 함께 사용)
from bottle import Bottle
app = Bottle()
@app.route('/')
def index():
return "Hello from Production!"
# 추가 라우트 정의
# ...
# WSGI 호환성을 위한 애플리케이션 객체
application = app
# 로컬 테스트용
if __name__ == '__main__':
from bottle import run
run(app, host='localhost', port=8080)# bottle_app/
# ├── app.py
# ├── routes/
# │ ├── __init__.py
# │ ├── user_routes.py
# │ └── product_routes.py
# ├── models/
# │ ├── __init__.py
# │ ├── user.py
# │ └── product.py
# ├── views/
# │ ├── base.tpl
# │ └── index.tpl
# └── static/
# ├── css/
# └── js/
# routes/user_routes.py
from bottle import Bottle, request, response
user_app = Bottle()
@user_app.route('/users')
def get_users():
# 사용자 목록 반환
pass
@user_app.route('/users/<id:int>')
def get_user(id):
# 특정 사용자 반환
pass
# app.py
from bottle import Bottle
from routes.user_routes import user_app
from routes.product_routes import product_app
app = Bottle()
# 하위 애플리케이션 마운트
app.mount('/api', user_app)
app.mount('/api', product_app)
# 기본 라우트
@app.route('/')
def index():
return "Welcome to the main application"from bottle import route, response
import time
import gzip
import functools
# 응답 캐싱
cache = {}
def cached(timeout=5 * 60):
"""간단한 메모리 캐싱 데코레이터"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
# 캐시 항목 확인
if key in cache:
timestamp, value = cache[key]
if time.time() - timestamp < timeout:
return value
# 캐시 미스 또는 만료
result = func(*args, **kwargs)
cache[key] = (time.time(), result)
return result
return wrapper
return decorator
# gzip 압축
def enable_gzip(func):
"""응답을 gzip으로 압축하는 데코레이터"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
# 문자열 응답만 처리
if isinstance(result, str):
response.headers['Content-Encoding'] = 'gzip'
return gzip.compress(result.encode('utf-8'))
return result
return wrapper
@route('/heavy-operation')
@cached(timeout=60)
@enable_gzip
def heavy_operation():
# 시간이 오래 걸리는 작업 시뮬레이션
time.sleep(2)
return "대량의 데이터 " * 1000 # 큰 응답✅ 특징:
- WSGI 서버 통합
- 모듈화된 애플리케이션 구조
- 캐싱 전략
- 응답 압축
- 로드 밸런싱 고려
- 마이크로서비스 아키텍처
- 데이터베이스 연결 풀링
- 보안 강화
Bottle 애플리케이션의 보안을 강화하는 방법이다.
import uuid
from bottle import request, response, abort
class CSRFProtection:
def __init__(self, app):
self.app = app
self.app.add_hook('before_request', self.before_request)
self.tokens = {} # 사용자 세션별 토큰 저장
def before_request(self):
# 읽기 전용 메서드는 검사 제외
if request.method in ('GET', 'HEAD', 'OPTIONS'):
return
# CSRF 토큰 검사
session_id = request.cookies.get('session_id')
if not session_id:
abort(403, "세션이 없습니다")
token = request.headers.get('X-CSRF-Token')
if not token or token != self.tokens.get(session_id):
abort(403, "CSRF 토큰이 유효하지 않습니다")
def generate_token(self, session_id):
"""새 CSRF 토큰 생성 및 저장"""
token = str(uuid.uuid4())
self.tokens[session_id] = token
return token
# 애플리케이션에 CSRF 보호 추가
app = Bottle()
csrf = CSRFProtection(app)
@app.route('/get-csrf-token')
def get_csrf_token():
session_id = request.cookies.get('session_id')
if not session_id:
# 세션이 없는 경우 생성
session_id = str(uuid.uuid4())
response.set_cookie('session_id', session_id)
token = csrf.generate_token(session_id)
return {'csrf_token': token}from bottle import route, request, abort
import re
import html
def validate_email(email):
"""이메일 형식 검증"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
def sanitize_input(text):
"""HTML 이스케이프 처리"""
return html.escape(text)
@route('/register', method='POST')
def register():
data = request.json
# 필수 필드 확인
if not all(k in data for k in ('username', 'email', 'password')):
abort(400, "필수 필드가 누락되었습니다")
# 이메일 유효성 검사
if not validate_email(data['email']):
abort(400, "유효하지 않은 이메일 형식입니다")
# 비밀번호 강도 검사
if len(data['password']) < 8:
abort(400, "비밀번호는 최소 8자 이상이어야 합니다")
# HTML 이스케이프 처리
username = sanitize_input(data['username'])
# 사용자 등록 로직
# ...
return {'status': 'success'}from bottle import hook, response
@hook('after_request')
def add_security_headers():
"""보안 관련 HTTP 헤더 추가"""
headers = response.headers
# XSS 방지
headers['X-XSS-Protection'] = '1; mode=block'
# 클릭재킹 방지
headers['X-Frame-Options'] = 'DENY'
# MIME 스니핑 방지
headers['X-Content-Type-Options'] = 'nosniff'
# HTTPS 강제
headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
# 콘텐츠 보안 정책
headers['Content-Security-Policy'] = "default-src 'self'"✅ 특징:
- CSRF 방어 매커니즘
- 입력 데이터 검증
- 보안 헤더 설정
- XSS 방지
- SQL 인젝션 방지
- 세션 보안
- 암호화 통신
- 인증 및 권한 제어
✅ 모범 사례:
- 모듈화 구조: 큰 애플리케이션은 기능별로 분리하여 구성
-
컨텍스트 관리자: 리소스 관리에
with문 활용 - 적절한 HTTP 상태 코드: 응답에 올바른 상태 코드 사용
- 설정 분리: 개발/테스트/프로덕션 환경별 설정 관리
- 라우트 그룹화: 관련 기능은 하위 애플리케이션으로 구성
- 보안 우선: 입력 검증, CSRF 보호, 세션 관리 철저히
- 응답 최적화: 압축, 캐싱으로 성능 향상
- 데이터베이스 연결 관리: 커넥션 풀 또는 컨텍스트 관리자 사용
- 템플릿 상속: 공통 레이아웃은 베이스 템플릿 활용
- 정적 파일 캐싱: 적절한 캐시 헤더 설정
- WSGI 서버 활용: 프로덕션에서는 gunicorn, uWSGI 등 사용
- 에러 처리: 사용자 친화적인 에러 메시지 제공
- 로깅 구현: 디버깅 및 모니터링을 위한 로깅 설정
- 성능 측정: 애플리케이션 지연 요소 모니터링
- API 설계: 일관된 엔드포인트와 응답 형식 유지