Python

Python WSGI & ASGI: Panduan Lengkap

Tutorial mendalam tentang WSGI dan ASGI di Python β€” Gunicorn, Uvicorn, Starlette, protokol web server, dan deployment aplikasi web production-ready

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
KepanjanganWeb Server Gateway InterfaceAsynchronous Server Gateway Interface
PEPPEP 3333Proprietary (Django project)
Sync/AsyncSynchronousAsynchronous
WebSocket❌ Tidakβœ… Ya
HTTP/2Terbatasβœ… Ya
FrameworkFlask, Django (legacy)FastAPI, Starlette, Django 3.0+
ServerGunicorn, uWSGIUvicorn, Daphne, Hypercorn
Diagram: WSGI vs ASGI Architecture
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    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:

Python β€” WSGI App Dasar
# 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_METHODHTTP methodGET, POST, PUT, DELETE
PATH_INFOURL path/api/users
QUERY_STRINGQuery parameterspage=1&limit=10
CONTENT_TYPEContent-Type headerapplication/json
CONTENT_LENGTHBody length1024
HTTP_HOSTHost headerlocalhost:8000
wsgi.inputRequest body streamFile-like object
wsgi.errorsError output streamFile-like object

3. Membuat Aplikasi WSGI

Mini Framework WSGI dengan Routing

Python β€” Mini WSGI Framework
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

Terminal β€” Gunicorn
# 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

Python β€” app.py (Flask)
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

Python β€” gunicorn.conf.py
# 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"
Terminal β€” Jalankan dengan config
# 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
syncSynchronous, blockingApp sederhana, CPU-bound
gthreadThreaded (GIL-aware)I/O-bound apps
geventGreen threads (coroutine)High concurrency I/O
eventletGreen threadsSimilar to gevent
uvicorn.workers.UvicornWorkerASGI via GunicornFastAPI, 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

Python β€” WSGI vs 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

πŸ” Komponen ASGI
  • scope β€” dictionary berisi info koneksi (mirip WSGI environ)
  • receive β€” async callable untuk menerima data dari client
  • send β€” async callable untuk mengirim data ke client

6. Membuat Aplikasi ASGI

Aplikasi ASGI Dasar

Python β€” ASGI App 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

Python β€” ASGI Router
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

Terminal β€” Uvicorn
# 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

Python β€” 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.

Python β€” Starlette App
# 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

Python β€” Flask WSGI
# 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+)

Python β€” Django ASGI
# 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

Diagram: Production Architecture
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               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

Nginx β€” Reverse Proxy
# /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

Systemd β€” Service File
# /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

πŸ’‘ Tips Penting
  • Development β†’ Gunakan uvicorn --reload atau flask run --debug
  • Production WSGI β†’ Gunicorn dengan sync atau gthread workers
  • 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?

πŸ” Zoom
100%
🎨 Tema