1. Pengenalan WSGI & ASGI
Ketika Anda menjalankan aplikasi web Python (Flask, Django, FastAPI), Anda memerlukan web server yang menerima HTTP request dari browser dan meneruskannya ke aplikasi Anda. WSGI dan ASGI adalah standar protokol yang mendefinisikan bagaimana web server berkomunikasi dengan aplikasi Python.
Apa Itu WSGI dan ASGI?
| Aspek | WSGI | ASGI |
|---|---|---|
| Kepanjangan | Web Server Gateway Interface | Asynchronous Server Gateway Interface |
| PEP | PEP 3333 | Proprietary (Django project) |
| Sync/Async | Synchronous | Asynchronous |
| WebSocket | β Tidak | β Ya |
| HTTP/2 | Terbatas | β Ya |
| Framework | Flask, Django (legacy) | FastAPI, Starlette, Django 3.0+ |
| Server | Gunicorn, uWSGI | Uvicorn, Daphne, Hypercorn |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β WSGI ARCHITECTURE β β β β Browser ββHTTPβββ Gunicorn ββWSGIβββ Flask/Django App β β (sync) (sync) (synchronous) β β β β Setiap request β 1 thread/blocking β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ASGI ARCHITECTURE β β β β Browser ββHTTPβββ Uvicorn ββASGIβββ FastAPI/Starlette App β β Browser ββWSβββββ (async) (async) (asynchronous) β β β β Bisa handle: HTTP, WebSocket, HTTP/2, SSE β β Event loop: request bisa concurrent tanpa thread β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. WSGI: Konsep Dasar
WSGI (PEP 3333) adalah standar antarmuka antara web server dan aplikasi web Python. WSGI memastikan kompatibilitas antara berbagai server dan framework.
Bagaimana WSGI Bekerja
Aplikasi WSGI adalah sebuah callable (fungsi atau objek dengan __call__) yang menerima dua argumen:
environβ dictionary berisi informasi request (method, path, headers, dll)start_responseβ callback untuk mengirim status dan headers
# Aplikasi WSGI paling sederhana
def aplikasi_wsgi(environ, start_response):
"""Hello World WSGI application."""
# environ berisi info request
method = environ['REQUEST_METHOD'] # GET, POST, dll
path = environ['PATH_INFO'] # URL path
query = environ.get('QUERY_STRING', '') # Query parameters
# Tentukan response
status = '200 OK'
headers = [('Content-Type', 'text/html; charset=utf-8')]
start_response(status, headers)
# Return body dalam bentuk bytes
html = f"""
<html>
<body>
<h1>Halo dari WSGI!</h1>
<p>Method: {method}</p>
<p>Path: {path}</p>
<p>Query: {query}</p>
</body>
</html>
"""
return [html.encode('utf-8')]
# Jalankan dengan server built-in (untuk development saja!)
if __name__ == '__main__':
from wsgiref.simple_server import make_server
server = make_server('localhost', 8000, aplikasi_wsgi)
print("Server berjalan di http://localhost:8000")
server.serve_forever()
WSGI Environ Variables
| Key | Deskripsi | Contoh |
|---|---|---|
REQUEST_METHOD | HTTP method | GET, POST, PUT, DELETE |
PATH_INFO | URL path | /api/users |
QUERY_STRING | Query parameters | page=1&limit=10 |
CONTENT_TYPE | Content-Type header | application/json |
CONTENT_LENGTH | Body length | 1024 |
HTTP_HOST | Host header | localhost:8000 |
wsgi.input | Request body stream | File-like object |
wsgi.errors | Error output stream | File-like object |
3. Membuat Aplikasi WSGI
Mini Framework WSGI dengan Routing
import json
import re
from urllib.parse import parse_qs
class MiniWSGIFramework:
"""Framework WSGI minimalis dengan routing."""
def __init__(self):
self.routes = []
def route(self, path, methods=None):
"""Decorator untuk mendaftarkan route."""
if methods is None:
methods = ['GET']
def decorator(func):
pattern = re.sub(r'<(\w+)>', r'(?P<\1>[^/]+)', path)
self.routes.append({
'pattern': re.compile(f'^{pattern}$'),
'methods': methods,
'handler': func,
})
return func
return decorator
def __call__(self, environ, start_response):
"""WSGI callable."""
method = environ['REQUEST_METHOD']
path = environ['PATH_INFO']
# Cari route yang cocok
for route in self.routes:
match = route['pattern'].match(path)
if match and method in route['methods']:
kwargs = match.groupdict()
# Parse query string
query = parse_qs(environ.get('QUERY_STRING', ''))
# Parse body untuk POST/PUT
body = None
if method in ('POST', 'PUT', 'PATCH'):
content_length = int(environ.get('CONTENT_LENGTH', 0))
if content_length:
body = environ['wsgi.input'].read(content_length)
# Panggil handler
try:
result = route['handler'](query=query, body=body, **kwargs)
status = '200 OK'
headers = [('Content-Type', 'application/json; charset=utf-8')]
start_response(status, headers)
return [json.dumps(result).encode('utf-8')]
except Exception as e:
status = '500 Internal Server Error'
headers = [('Content-Type', 'application/json')]
start_response(status, headers)
error = {'error': str(e)}
return [json.dumps(error).encode('utf-8')]
# 404 Not Found
start_response('404 Not Found', [('Content-Type', 'application/json')])
return [json.dumps({'error': 'Not found'}).encode('utf-8')]
# Gunakan framework
app = MiniWSGIFramework()
@app.route('/', methods=['GET'])
def beranda(query=None, body=None):
return {'message': 'Selamat datang!', 'status': 'ok'}
@app.route('/api/users', methods=['GET'])
def get_users(query=None, body=None):
users = [
{'id': 1, 'nama': 'Budi'},
{'id': 2, 'nama': 'Siti'},
]
return {'users': users, 'total': len(users)}
@app.route('/api/users/<user_id>', methods=['GET'])
def get_user(query=None, body=None, user_id=None):
return {'id': user_id, 'nama': f'User {user_id}'}
if __name__ == '__main__':
from wsgiref.simple_server import make_server
server = make_server('localhost', 8000, app)
print("Mini Framework berjalan di http://localhost:8000")
server.serve_forever()
4. Gunicorn: Production WSGI Server
Gunicorn (Green Unicorn) adalah WSGI HTTP server yang populer untuk production. Gunicorn menggunakan model pre-fork β master process yang mengelola beberapa worker processes.
Instalasi dan Penggunaan Dasar
# Instalasi pip install gunicorn # Jalankan aplikasi Flask gunicorn app:application --bind 0.0.0.0:8000 # Jalankan dengan workers gunicorn app:application -w 4 -b 0.0.0.0:8000 # Penjelasan flags: # app:application = module:callable # -w 4 = 4 worker processes # -b 0.0.0.0:8000 = bind ke semua interface, port 8000 # --timeout 120 = timeout per request (detik) # --access-logfile - = log ke stdout # --error-logfile - = error log ke stdout
Flask dengan Gunicorn
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def beranda():
return jsonify({'message': 'Hello dari Flask + Gunicorn!'})
@app.route('/api/health')
def health():
return jsonify({'status': 'healthy', 'server': 'gunicorn'})
# Jalankan development server
if __name__ == '__main__':
app.run(debug=True, port=5000)
# Production: gunicorn app:app -w 4 -b 0.0.0.0:8000
Gunicorn Configuration
# gunicorn.conf.py import multiprocessing # Server socket bind = "0.0.0.0:8000" backlog = 2048 # Worker processes workers = multiprocessing.cpu_count() * 2 + 1 # Rekomendasi Gunicorn worker_class = "sync" # sync, gthread, gevent, eventlet threads = 4 # Untuk gthread worker worker_connections = 1000 # Untuk async workers # Timeout timeout = 30 graceful_timeout = 30 keepalive = 5 # Logging accesslog = "-" # stdout errorlog = "-" # stdout loglevel = "info" # Process naming proc_name = "myapp" # Server mechanics preload_app = True # Load app sebelum fork (hemat memory) max_requests = 1000 # Restart worker setelah N request (prevent memory leak) max_requests_jitter = 50 # Random jitter untuk prevent all restart bersamaan # SSL (opsional) # certfile = "/path/to/cert.pem" # keyfile = "/path/to/key.pem"
# Jalankan dengan config file gunicorn app:application -c gunicorn.conf.py # Atau dari command line gunicorn app:app \ --workers 4 \ --bind 0.0.0.0:8000 \ --timeout 30 \ --access-logfile - \ --error-logfile - \ --log-level info \ --preload
Tipe Worker Gunicorn
| Worker Class | Karakteristik | Cocok Untuk |
|---|---|---|
sync | Synchronous, blocking | App sederhana, CPU-bound |
gthread | Threaded (GIL-aware) | I/O-bound apps |
gevent | Green threads (coroutine) | High concurrency I/O |
eventlet | Green threads | Similar to gevent |
uvicorn.workers.UvicornWorker | ASGI via Gunicorn | FastAPI, async apps |
5. ASGI: Konsep Dasar
ASGI (Asynchronous Server Gateway Interface) adalah evolusi dari WSGI yang mendukung asynchronous processing, WebSocket, dan HTTP/2.
Perbedaan WSGI dan ASGI
# WSGI β synchronous
def wsgi_app(environ, start_response):
# Blocking: satu request menguasai satu thread
result = database_query() # Thread blocked di sini
start_response('200 OK', [('Content-Type', 'text/html')])
return [result.encode()]
# ASGI β asynchronous
async def asgi_app(scope, receive, send):
# Non-blocking: event loop bisa handle request lain
result = await database_query() # Thread TIDAK blocked
await send({
'type': 'http.response.start',
'status': 200,
'headers': [[b'content-type', b'text/html']],
})
await send({
'type': 'http.response.body',
'body': result.encode(),
})
ASGI Scope, Receive, Send
scopeβ dictionary berisi info koneksi (mirip WSGI environ)receiveβ async callable untuk menerima data dari clientsendβ async callable untuk mengirim data ke client
6. Membuat Aplikasi ASGI
Aplikasi ASGI Dasar
import json
async def aplikasi_asgi(scope, receive, send):
"""Aplikasi ASGI paling sederhana."""
assert scope['type'] == 'http'
method = scope['method']
path = scope['path']
# Baca request body
body = b''
while True:
message = await receive()
body += message.get('body', b'')
if not message.get('more_body', False):
break
# Buat response
response_body = json.dumps({
'message': 'Hello dari ASGI!',
'method': method,
'path': path,
'body_length': len(body),
}).encode('utf-8')
# Kirim response headers
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'application/json'],
[b'content-length', str(len(response_body)).encode()],
],
})
# Kirim response body
await send({
'type': 'http.response.body',
'body': response_body,
})
# Jalankan dengan Uvicorn
if __name__ == '__main__':
import uvicorn
uvicorn.run(aplikasi_asgi, host='0.0.0.0', port=8000)
ASGI Router Sederhana
import json
from urllib.parse import parse_qs
class ASGIRouter:
"""Router ASGI sederhana."""
def __init__(self):
self.routes = {}
def route(self, path):
def decorator(func):
self.routes[path] = func
return func
return decorator
async def __call__(self, scope, receive, send):
path = scope['path']
handler = self.routes.get(path, self.not_found)
await handler(scope, receive, send)
async def not_found(self, scope, receive, send):
response = json.dumps({'error': 'Not Found'}).encode()
await send({'type': 'http.response.start', 'status': 404,
'headers': [[b'content-type', b'application/json']]})
await send({'type': 'http.response.body', 'body': response})
router = ASGIRouter()
@router.route('/')
async def beranda(scope, receive, send):
response = json.dumps({'message': 'Halo dari ASGI Router!'}).encode()
await send({'type': 'http.response.start', 'status': 200,
'headers': [[b'content-type', b'application/json']]})
await send({'type': 'http.response.body', 'body': response})
@router.route('/api/users')
async def users(scope, receive, send):
data = {'users': [{'id': 1, 'nama': 'Budi'}, {'id': 2, 'nama': 'Siti'}]}
response = json.dumps(data).encode()
await send({'type': 'http.response.start', 'status': 200,
'headers': [[b'content-type', b'application/json']]})
await send({'type': 'http.response.body', 'body': response})
if __name__ == '__main__':
import uvicorn
uvicorn.run(router, host='0.0.0.0', port=8000)
7. Uvicorn: ASGI Server
Uvicorn adalah ASGI server berbasis uvloop dan httptools yang sangat cepat. Uvicorn adalah server default untuk FastAPI.
Instalasi dan Penggunaan
# Instalasi pip install uvicorn[standard] # Jalankan aplikasi ASGI uvicorn app:application --host 0.0.0.0 --port 8000 # Jalankan dengan hot-reload (development) uvicorn app:app --reload --host 0.0.0.0 --port 8000 # Jalankan dengan multiple workers (production) uvicorn app:app --workers 4 --host 0.0.0.0 --port 8000 # Flags penting: # --reload = auto-reload saat file berubah (dev only) # --workers 4 = jumlah worker processes # --host 0.0.0.0 = bind ke semua interface # --port 8000 = port # --log-level info = log level (debug, info, warning, error) # --ssl-keyfile = SSL key file # --ssl-certfile = SSL cert file # --uds /tmp/uv.sock = Unix domain socket
Uvicorn Programmatic
import uvicorn
# Dari dalam kode
if __name__ == '__main__':
uvicorn.run(
"myapp:app",
host="0.0.0.0",
port=8000,
reload=True,
log_level="info",
workers=1,
access_log=True,
)
# Atau dengan config
config = uvicorn.Config(
app="myapp:app",
host="0.0.0.0",
port=8000,
log_level="info",
reload=True,
)
server = uvicorn.Server(config)
server.run()
8. Starlette: Framework ASGI
Starlette adalah framework ASGI ringan yang menjadi fondasi FastAPI. Starlette mendukung HTTP, WebSocket, GraphQL, dan fitur modern lainnya.
# pip install starlette uvicorn
from starlette.applications import Starlette
from starlette.responses import JSONResponse, HTMLResponse
from starlette.routing import Route, WebSocketRoute
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
import json
async def beranda(request):
"""Halaman utama."""
return HTMLResponse("""
<html>
<body>
<h1>Starlette ASGI App</h1>
<p><a href="/api/users">API Users</a></p>
<p><a href="/ws">WebSocket Test</a></p>
</body>
</html>
""")
async def get_users(request):
"""API endpoint: ambil daftar users."""
users = [
{'id': 1, 'nama': 'Budi Santoso', 'email': 'budi@email.com'},
{'id': 2, 'nama': 'Siti Rahayu', 'email': 'siti@email.com'},
{'id': 3, 'nama': 'Ahmad Hidayat', 'email': 'ahmad@email.com'},
]
return JSONResponse({'users': users, 'total': len(users)})
async def get_user(request):
"""API endpoint: ambil user berdasarkan ID."""
user_id = request.path_params['user_id']
users = {'1': 'Budi', '2': 'Siti', '3': 'Ahmad'}
nama = users.get(user_id)
if nama:
return JSONResponse({'id': user_id, 'nama': nama})
return JSONResponse({'error': 'User not found'}, status_code=404)
async def websocket_endpoint(webchat):
"""WebSocket endpoint: echo server."""
await websocket.accept()
try:
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Echo: {data}")
except Exception:
await websocket.close()
# Routing
routes = [
Route('/', beranda),
Route('/api/users', get_users),
Route('/api/users/{user_id}', get_user),
WebSocketRoute('/ws', websocket_endpoint),
]
# Middleware
middleware = [
Middleware(CORSMiddleware, allow_origins=['*'], allow_methods=['*']),
]
# App
app = Starlette(routes=routes, middleware=middleware)
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=8000)
9. Flask (WSGI) & Django (ASGI)
Flask: WSGI Framework
# pip install flask
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/')
def beranda():
return jsonify({'message': 'Flask WSGI App'})
@app.route('/api/users', methods=['GET', 'POST'])
def users():
if request.method == 'POST':
data = request.get_json()
return jsonify({'created': data}), 201
return jsonify({'users': [
{'id': 1, 'nama': 'Budi'},
{'id': 2, 'nama': 'Siti'},
]})
# Development
if __name__ == '__main__':
app.run(debug=True)
# Production: gunicorn app:app -w 4
Django: ASGI (Django 3.0+)
# Django 3.0+ mendukung ASGI
# asgi.py di root project Django
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_asgi_application()
# Jalankan dengan Uvicorn:
# uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000
# Atau dengan Daphne (Django's ASGI server):
# daphne myproject.asgi:application
10. Deployment Production
Deployment Stack
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β PRODUCTION ARCHITECTURE β β β β Internet β β β β β βΌ β β βββββββββββ β β β Nginx β β reverse proxy, SSL termination β β β (port β static files, rate limiting β β β 80/443)β β β ββββββ¬βββββ β β β β β βΌ β β βββββββββββββββββββββββββββββββββββββββββββββββ β β β Gunicorn (WSGI) atau Uvicorn (ASGI) β β β β ββββββββββ ββββββββββ ββββββββββ β β β β βWorker 1β βWorker 2β βWorker 3β β β β β β Flask β β Flask β β Flask β β β β β ββββββββββ ββββββββββ ββββββββββ β β β βββββββββββββββββββββββββββββββββββββββββββββββ β β β β β βΌ β β βββββββββββ ββββββββββββ β β βPostgreSQLβ β Redis β β β βββββββββββ ββββββββββββ β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Nginx Reverse Proxy Config
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.example.com;
# Redirect HTTP β HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name myapp.example.com;
ssl_certificate /etc/ssl/certs/myapp.crt;
ssl_certificate_key /etc/ssl/private/myapp.key;
# Static files (langsung serve oleh Nginx)
location /static/ {
alias /var/www/myapp/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Dynamic requests β Gunicorn/Uvicorn
location / {
proxy_pass http://127.0.0.1:8000;
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;
# Timeouts
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}
# WebSocket support (untuk ASGI)
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
Systemd Service
# /etc/systemd/system/myapp.service [Unit] Description=My Python Web Application After=network.target [Service] User=www-data Group=www-data WorkingDirectory=/var/www/myapp ExecStart=/var/www/myapp/venv/bin/gunicorn app:app -c gunicorn.conf.py ExecReload=/bin/kill -s HUP $MAINPID Restart=always RestartSec=5 KillMode=mixed TimeoutStopSec=10 [Install] WantedBy=multi-user.target
11. Best Practices
- Development β Gunakan
uvicorn --reloadatauflask run --debug - Production WSGI β Gunicorn dengan
syncataugthreadworkers - Production ASGI β Uvicorn dengan multiple workers atau Gunicorn + UvicornWorker
- Selalu gunakan reverse proxy (Nginx/Caddy) di production
- Workers formula β
2 Γ CPU cores + 1 - Jangan gunakan development server Flask/Django untuk production
12. Quiz Pemahaman
π§ Quiz: WSGI & ASGI
1. Apa kepanjangan WSGI?
2. Apa keunggulan utama ASGI dibanding WSGI?
3. Server apa yang direkomendasikan untuk FastAPI di production?
4. Berapa rekomendasi jumlah Gunicorn workers?
5. Mengapa tidak boleh pakai Flask/Django dev server di production?