Highly available, transparent & dynamic HTTP proxy for Discord API rate limiting
Project description
nirn.py
A highly available, transparent & dynamic HTTP proxy that handles Discord rate limits and exports Prometheus metrics.
This is a Python implementation using asyncio and aiohttp for high-performance concurrent request handling.
Features
- ✅ Highly available, horizontally scalable
- ✅ Transparent ratelimit handling, per-route and global
- ✅ Works with any API version
- ✅ Small resource footprint
- ✅ Works with webhooks
- ✅ Works with Bearer tokens
- ✅ Supports an unlimited number of clients (Bots and Bearer)
- ✅ Prometheus metrics exported out of the box
- ✅ No hardcoded routes, therefore no need of updates for new routes introduced by Discord
Installation
From PyPI
pip install nirn.py
From Source
git clone https://github.com/lorenzo132/nirn.py
cd nirn.py
pip install -e .
Docker
docker build -t nirn.py .
docker run -p 8080:8080 -p 9000:9000 nirn.py
Usage
Command Line
nirn.py provides a simple CLI to manage the proxy as a background service:
# Start nirn as a background process (loads .env from current directory)
nirn start
# Stop nirn
nirn stop
# View logs (shows last 50 lines and follows new output)
nirn logs
# View last 100 lines and follow
nirn logs --lines=100
# View all logs and follow
nirn logs --lines=0
# View logs without following (exits after showing)
nirn logs --no-follow
# Check status
nirn status
| Command | Description |
|---|---|
nirn start |
Start nirn as a background process |
nirn stop |
Stop the running nirn process |
nirn logs |
View logs and follow new output (Ctrl+C to stop) |
nirn logs -n <N> |
Show last N lines before following |
nirn logs --no-follow |
Show logs without following |
nirn status |
Show nirn status and process info |
The nirn start command:
- Loads environment variables from
.envin the current directory - Runs as a background process (survives terminal close)
- Logs are saved to
%LOCALAPPDATA%\nirn\nirn.log(Windows) or~/.local/share/nirn/nirn.log(Linux/Mac)
Python API
from nirn_proxy import QueueManager, Config
config = Config(
port=8080,
log_level="info",
enable_metrics=True
)
queue_manager = QueueManager(config)
app = queue_manager.create_app()
from aiohttp import web
web.run_app(app, host=config.bind_ip, port=config.port)
Configuration
The proxy sits between the client and Discord. Instead of pointing to discord.com, you point to whatever IP and port the proxy is running on, so discord.com/api/v9/gateway becomes 10.0.0.1:8080/api/v9/gateway.
| Variable | Default | Description |
|---|---|---|
| LOG_LEVEL | info | Log level (debug, info, warning, error) |
| PORT | 8080 | Proxy port |
| METRICS_PORT | 9000 | Metrics server port |
| ENABLE_METRICS | true | Enable Prometheus metrics |
| BUFFER_SIZE | 50 | Request buffer size per bucket |
| BIND_IP | 0.0.0.0 | IP to bind to |
| REQUEST_TIMEOUT | 5000 | Request timeout in milliseconds |
| MAX_BEARER_COUNT | 1024 | Maximum bearer token queues |
| BOT_RATELIMIT_OVERRIDES | "" | Bot rate limit overrides (bot_id:limit,bot_id:limit) |
| DISABLE_GLOBAL_RATELIMIT_DETECTION | false | Disable global rate limit detection |
| CLUSTER_PORT | 7654 | Cluster communication port |
| CLUSTER_MEMBERS | "" | Comma-separated list of cluster members |
| CLUSTER_DNS | "" | DNS address for cluster discovery |
Information on each config var can be found in CONFIG.md
.env files are loaded if present
Behaviour
The proxy listens on all routes and relays them to Discord, while keeping track of ratelimit buckets and making requests wait if there are no tokens to spare. The proxy fires requests sequentially for each bucket and ordering is preserved. The proxy does not modify the requests in any way so any library compatible with Discord's API can be pointed at the proxy and it will not break the library.
When using the proxy, it is safe to remove the ratelimiting logic from clients and fire requests instantly, however, the proxy does not handle retries. If for some reason the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429.
The proxy also guards against known scenarios that might cause a Cloudflare ban, like too many webhook 404s or too many 401s.
Proxy Specific Responses
The proxy may return a 408 Request Timeout if Discord takes more than REQUEST_TIMEOUT milliseconds to respond.
High Availability
The proxy can be run in a cluster by setting either CLUSTER_MEMBERS or CLUSTER_DNS env vars. When in cluster mode, all nodes are a suitable gateway for all requests and the proxy will route requests consistently using the bucket hash.
Bearer Tokens
Bearer tokens are first class citizens. They are treated differently than bot tokens - while bot queues are long-lived and never get evicted, Bearer queues are put into an LRU and are spread out by their token hash. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.
Metrics / Health
| Key | Labels | Description |
|---|---|---|
| nirn_proxy_error | none | Counter for errors |
| nirn_proxy_requests | method, status, route, clientId | Histogram for request metrics |
| nirn_proxy_open_connections | route, method | Gauge for open connections |
The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.
Development
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Format code
black nirn_proxy/
# Lint code
flake8 nirn_proxy/
License
MIT License - see LICENSE file for details.
Acknowledgements
- nirn - used as inspiration and reference
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nirn_py-1.0.3.tar.gz.
File metadata
- Download URL: nirn_py-1.0.3.tar.gz
- Upload date:
- Size: 41.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3fb6231d78dd82da3c04be94e8ee0d4a68669320615376ba4c2312fdd037c73b
|
|
| MD5 |
427c9ebc37513e15b6eecbb1c6e454ac
|
|
| BLAKE2b-256 |
07a5b78b2a32c1997bc6db51a96c4bcf0d2391e0254e71fb9b637223ed970055
|
File details
Details for the file nirn_py-1.0.3-py3-none-any.whl.
File metadata
- Download URL: nirn_py-1.0.3-py3-none-any.whl
- Upload date:
- Size: 47.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e732e00a8f89d42abd893e909213644ad356792f0587403e78d6a314c43e31a
|
|
| MD5 |
ad74aada801e6818580290e0dd8d6056
|
|
| BLAKE2b-256 |
329a86329009bcdb5e5dd613f5acee76799d94f362cf9ebcf3cf9445d2696a47
|