This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Repository files navigation

Note: This is a heavily customized version trimmed to the needs of EazyAutodelete. It will NOT meet your expectations nor your usecase.

Start

 docker run -d --name nirn --restart always --network rabbit-rest --network monitor_monitoring -p 8480:8080 -p 9490:9000 ea-rest:2

Nirn-proxy

Nirn-proxy is a highly available, transparent & dynamic HTTP proxy that handles Discord ratelimits for you and exports meaningful prometheus metrics. This project is at the heart of Dyno, handling several hundreds of requests per sec across hundreds of bots all while keeping 429s at ~100 per hour.

It is designed to be minimally invasive and exploits common library patterns to make the adoption as simple as a URL change.

Features

  • Highly available, horizontally scalable
  • Transparent ratelimit handling, per-route and global
  • Works with any API version (Also supports using two or more versions for the same bot)
  • 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

Usage

Binaries can be found here. Docker images can be found here

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. This can be achieved in many ways, some suggestions are host remapping on the OS level, DNS overrides or changes to the library code. Please note that the proxy currently does not support SSL.

Configuration options are

VariableValueDefault
LOG_LEVELpanic, fatal, error, warn, info, debug, traceinfo
PORTnumber8080
METRICS_PORTnumber9000
ENABLE_METRICSbooleantrue
ENABLE_PPROFbooleanfalse
BUFFER_SIZEnumber50
OUTBOUND_IPstring""
BIND_IPstring0.0.0.0
REQUEST_TIMEOUTnumber (milliseconds)5000
CLUSTER_PORTnumber7946
CLUSTER_MEMBERSstring list (comma separated)""
CLUSTER_DNSstring""
MAX_BEARER_COUNTnumber1024
DISABLE_HTTP_2booltrue
BOT_RATELIMIT_OVERRIDESstring list (comma separated)""
DISABLE_GLOBAL_RATELIMIT_DETECTIONbooleanfalse

Information on each config var can be found here

.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 Discords API can be pointed at the proxy and it will not break the library, even with the libraries own ratelimiting intact.

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 (i.e shared ratelimits, internal discord ratelimits, etc) the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429 or even setup retry logic elsewhere (like in a load balancer or service mesh).

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. This allows you to identify and react to routes that have issues.

Requests may also return a 408 status code in the event that they were aborted because of ratelimits, as documented above.

Limitations

The ratelimiting only works with X-RateLimit-Precision set to seconds. If you are using Discord API v8+, that is the only possible behaviour. For users on v6 or v7, please refer to your library docs for information on which precision it uses and how to change it to seconds.

The proxy tries its best to detect your REST global limits, but Discord does not expose this information. Be sure to set BOT_RATELIMIT_OVERRIDES for any clients with elevated limits.

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.

It's recommended that all nodes are reachable through LAN. Please reach out if a WAN cluster is desired for your use case.

If a node fails, there is a brief period where it will be unhealthy but requests will still be routed to it. When these requests fail, the proxy will mock a 429 to send back to the user. The 429 will signal the client to wait 1s and will have a custom header generated-by-proxy. This is done in order to allow seamless retries when a member fails. If you want to backoff, use the custom header to override your lib retry logic.

The cluster uses SWIM, which is an AP protocol and is powered by hashicorps excellent memberlist implementation.

Being an AP system means that the cluster will tolerate a network partition and needs no quorum to function. In case a network partition occurs, you'll have two clusters running independently, which may or may not be desirable. Configure your network accordingly.

In case you want to specifically target a node (i.e, for troubleshooting), set the nirn-routed-to header on the request. The value doesn't matter. This will prevent the node from routing the request to another node.

During recovery periods or when nodes join/leave the cluster, you might notice increased 429s. This is expected since the hashing table is changing as members change. Once the cluster settles into a stable state, it'll go back to normal.

Global ratelimits are handled by a single node on the cluster, however this affinity is soft. There is no concept of leader or elections and if this node leaves, the cluster will simply pick a new one. This is a bottleneck and might increase tail latency, but the other options were either too complex, required an external storage, or would require quorum for the proxy to function. Webhooks and other requests with no token bypass this mechanism completely.

The best deployment strategy for the cluster is to kill nodes one at a time, preferably with the replacement node already up.

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 instead of by the path hash. This provides a more even spread of bearer queues across nodes in the cluster. In addition, Bearer globals are always handled locally. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.

Why?

As projects grow, it's desirable to break them into multiple pieces, each responsible for its own domain. Discord provides gateway sharding on their end but REST can get tricky once you start moving logic out of the shards themselves and lose the guild affinity that shards inherently have, thus a centralized place for handling ratelimits is a must to prevent cloudflare bans and prevent avoidable 429s. At the time this project was created, there was no alternative that fully satisfied our requirements like multi-bot support. We are also early adopters of Discord features, so we need a proxy that supports new routes without us having to manually update it. Thus, this project was born.

Resource usage

This will vary depending on your usage, how many unique routes you see, etc. For reference, for Dynos use case, doing 150req/s, the usage is ~0.3 CPU and ~550MB of RAM. The proxy can comfortably run on a cheap VPS or an ARM based system.

Metrics / Health

KeyLabelsDescription
nirn_proxy_errornoneCounter for errors
nirn_proxy_requestsmethod, status, route, clientIdHistogram that keeps track of all request metrics
nirn_proxy_open_connectionsroute, methodGauge for open client connections with the proxy
nirn_proxy_requests_routed_sentnoneCounter for requests routed to other nodes
nirn_proxy_requests_routed_receivednoneCounter for requests received from other nodes
nirn_proxy_requests_routed_errornoneCounter for requests routed that failed

Note: 429s can produce two status: 429 Too Many Requests or 429 Shared. The latter is only produced for requests that return with the x-ratelimit-scope header set to "shared", which means they don't count towards the cloudflare firewall limit and thus should not be used for alerts, etc.

The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.

Profiling

The proxy can be profiled at runtime by enabling the ENABLE_PPROF flag and browsing to http://ip:7654/debug/pprof/

Related projects

nirn-probe - Checks and alerts if a server is cloudflare banned

Acknowledgements
  • Eris - used as reference throughout this project
  • Twilight - used as inspiration and reference
  • @bsian & @bean - for listening to my rants and providing assistance

About

πŸ”— Nirn Rest Proxy but with RabbitMQ. Used by the EazyAutodelete Discord Bot.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Repository files navigation

Note: This is a heavily customized version trimmed to the needs of EazyAutodelete. It will NOT meet your expectations nor your usecase.

Start

 docker run -d --name nirn --restart always --network rabbit-rest --network monitor_monitoring -p 8480:8080 -p 9490:9000 ea-rest:2

Nirn-proxy

Nirn-proxy is a highly available, transparent & dynamic HTTP proxy that handles Discord ratelimits for you and exports meaningful prometheus metrics. This project is at the heart of Dyno, handling several hundreds of requests per sec across hundreds of bots all while keeping 429s at ~100 per hour.

It is designed to be minimally invasive and exploits common library patterns to make the adoption as simple as a URL change.

Features

  • Highly available, horizontally scalable
  • Transparent ratelimit handling, per-route and global
  • Works with any API version (Also supports using two or more versions for the same bot)
  • 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

Usage

Binaries can be found here. Docker images can be found here

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. This can be achieved in many ways, some suggestions are host remapping on the OS level, DNS overrides or changes to the library code. Please note that the proxy currently does not support SSL.

Configuration options are

VariableValueDefault
LOG_LEVELpanic, fatal, error, warn, info, debug, traceinfo
PORTnumber8080
METRICS_PORTnumber9000
ENABLE_METRICSbooleantrue
ENABLE_PPROFbooleanfalse
BUFFER_SIZEnumber50
OUTBOUND_IPstring""
BIND_IPstring0.0.0.0
REQUEST_TIMEOUTnumber (milliseconds)5000
CLUSTER_PORTnumber7946
CLUSTER_MEMBERSstring list (comma separated)""
CLUSTER_DNSstring""
MAX_BEARER_COUNTnumber1024
DISABLE_HTTP_2booltrue
BOT_RATELIMIT_OVERRIDESstring list (comma separated)""
DISABLE_GLOBAL_RATELIMIT_DETECTIONbooleanfalse

Information on each config var can be found here

.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 Discords API can be pointed at the proxy and it will not break the library, even with the libraries own ratelimiting intact.

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 (i.e shared ratelimits, internal discord ratelimits, etc) the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429 or even setup retry logic elsewhere (like in a load balancer or service mesh).

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. This allows you to identify and react to routes that have issues.

Requests may also return a 408 status code in the event that they were aborted because of ratelimits, as documented above.

Limitations

The ratelimiting only works with X-RateLimit-Precision set to seconds. If you are using Discord API v8+, that is the only possible behaviour. For users on v6 or v7, please refer to your library docs for information on which precision it uses and how to change it to seconds.

The proxy tries its best to detect your REST global limits, but Discord does not expose this information. Be sure to set BOT_RATELIMIT_OVERRIDES for any clients with elevated limits.

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.

It's recommended that all nodes are reachable through LAN. Please reach out if a WAN cluster is desired for your use case.

If a node fails, there is a brief period where it will be unhealthy but requests will still be routed to it. When these requests fail, the proxy will mock a 429 to send back to the user. The 429 will signal the client to wait 1s and will have a custom header generated-by-proxy. This is done in order to allow seamless retries when a member fails. If you want to backoff, use the custom header to override your lib retry logic.

The cluster uses SWIM, which is an AP protocol and is powered by hashicorps excellent memberlist implementation.

Being an AP system means that the cluster will tolerate a network partition and needs no quorum to function. In case a network partition occurs, you'll have two clusters running independently, which may or may not be desirable. Configure your network accordingly.

In case you want to specifically target a node (i.e, for troubleshooting), set the nirn-routed-to header on the request. The value doesn't matter. This will prevent the node from routing the request to another node.

During recovery periods or when nodes join/leave the cluster, you might notice increased 429s. This is expected since the hashing table is changing as members change. Once the cluster settles into a stable state, it'll go back to normal.

Global ratelimits are handled by a single node on the cluster, however this affinity is soft. There is no concept of leader or elections and if this node leaves, the cluster will simply pick a new one. This is a bottleneck and might increase tail latency, but the other options were either too complex, required an external storage, or would require quorum for the proxy to function. Webhooks and other requests with no token bypass this mechanism completely.

The best deployment strategy for the cluster is to kill nodes one at a time, preferably with the replacement node already up.

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 instead of by the path hash. This provides a more even spread of bearer queues across nodes in the cluster. In addition, Bearer globals are always handled locally. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.

Why?

As projects grow, it's desirable to break them into multiple pieces, each responsible for its own domain. Discord provides gateway sharding on their end but REST can get tricky once you start moving logic out of the shards themselves and lose the guild affinity that shards inherently have, thus a centralized place for handling ratelimits is a must to prevent cloudflare bans and prevent avoidable 429s. At the time this project was created, there was no alternative that fully satisfied our requirements like multi-bot support. We are also early adopters of Discord features, so we need a proxy that supports new routes without us having to manually update it. Thus, this project was born.

Resource usage

This will vary depending on your usage, how many unique routes you see, etc. For reference, for Dynos use case, doing 150req/s, the usage is ~0.3 CPU and ~550MB of RAM. The proxy can comfortably run on a cheap VPS or an ARM based system.

Metrics / Health

KeyLabelsDescription
nirn_proxy_errornoneCounter for errors
nirn_proxy_requestsmethod, status, route, clientIdHistogram that keeps track of all request metrics
nirn_proxy_open_connectionsroute, methodGauge for open client connections with the proxy
nirn_proxy_requests_routed_sentnoneCounter for requests routed to other nodes
nirn_proxy_requests_routed_receivednoneCounter for requests received from other nodes
nirn_proxy_requests_routed_errornoneCounter for requests routed that failed

Note: 429s can produce two status: 429 Too Many Requests or 429 Shared. The latter is only produced for requests that return with the x-ratelimit-scope header set to "shared", which means they don't count towards the cloudflare firewall limit and thus should not be used for alerts, etc.

The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.

Profiling

The proxy can be profiled at runtime by enabling the ENABLE_PPROF flag and browsing to http://ip:7654/debug/pprof/

Related projects

nirn-probe - Checks and alerts if a server is cloudflare banned

Acknowledgements
  • Eris - used as reference throughout this project
  • Twilight - used as inspiration and reference
  • @bsian & @bean - for listening to my rants and providing assistance

About

πŸ”— Nirn Rest Proxy but with RabbitMQ. Used by the EazyAutodelete Discord Bot.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Repository files navigation

Note: This is a heavily customized version trimmed to the needs of EazyAutodelete. It will NOT meet your expectations nor your usecase.

Start

 docker run -d --name nirn --restart always --network rabbit-rest --network monitor_monitoring -p 8480:8080 -p 9490:9000 ea-rest:2

Nirn-proxy

Nirn-proxy is a highly available, transparent & dynamic HTTP proxy that handles Discord ratelimits for you and exports meaningful prometheus metrics. This project is at the heart of Dyno, handling several hundreds of requests per sec across hundreds of bots all while keeping 429s at ~100 per hour.

It is designed to be minimally invasive and exploits common library patterns to make the adoption as simple as a URL change.

Features

  • Highly available, horizontally scalable
  • Transparent ratelimit handling, per-route and global
  • Works with any API version (Also supports using two or more versions for the same bot)
  • 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

Usage

Binaries can be found here. Docker images can be found here

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. This can be achieved in many ways, some suggestions are host remapping on the OS level, DNS overrides or changes to the library code. Please note that the proxy currently does not support SSL.

Configuration options are

VariableValueDefault
LOG_LEVELpanic, fatal, error, warn, info, debug, traceinfo
PORTnumber8080
METRICS_PORTnumber9000
ENABLE_METRICSbooleantrue
ENABLE_PPROFbooleanfalse
BUFFER_SIZEnumber50
OUTBOUND_IPstring""
BIND_IPstring0.0.0.0
REQUEST_TIMEOUTnumber (milliseconds)5000
CLUSTER_PORTnumber7946
CLUSTER_MEMBERSstring list (comma separated)""
CLUSTER_DNSstring""
MAX_BEARER_COUNTnumber1024
DISABLE_HTTP_2booltrue
BOT_RATELIMIT_OVERRIDESstring list (comma separated)""
DISABLE_GLOBAL_RATELIMIT_DETECTIONbooleanfalse

Information on each config var can be found here

.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 Discords API can be pointed at the proxy and it will not break the library, even with the libraries own ratelimiting intact.

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 (i.e shared ratelimits, internal discord ratelimits, etc) the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429 or even setup retry logic elsewhere (like in a load balancer or service mesh).

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. This allows you to identify and react to routes that have issues.

Requests may also return a 408 status code in the event that they were aborted because of ratelimits, as documented above.

Limitations

The ratelimiting only works with X-RateLimit-Precision set to seconds. If you are using Discord API v8+, that is the only possible behaviour. For users on v6 or v7, please refer to your library docs for information on which precision it uses and how to change it to seconds.

The proxy tries its best to detect your REST global limits, but Discord does not expose this information. Be sure to set BOT_RATELIMIT_OVERRIDES for any clients with elevated limits.

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.

It's recommended that all nodes are reachable through LAN. Please reach out if a WAN cluster is desired for your use case.

If a node fails, there is a brief period where it will be unhealthy but requests will still be routed to it. When these requests fail, the proxy will mock a 429 to send back to the user. The 429 will signal the client to wait 1s and will have a custom header generated-by-proxy. This is done in order to allow seamless retries when a member fails. If you want to backoff, use the custom header to override your lib retry logic.

The cluster uses SWIM, which is an AP protocol and is powered by hashicorps excellent memberlist implementation.

Being an AP system means that the cluster will tolerate a network partition and needs no quorum to function. In case a network partition occurs, you'll have two clusters running independently, which may or may not be desirable. Configure your network accordingly.

In case you want to specifically target a node (i.e, for troubleshooting), set the nirn-routed-to header on the request. The value doesn't matter. This will prevent the node from routing the request to another node.

During recovery periods or when nodes join/leave the cluster, you might notice increased 429s. This is expected since the hashing table is changing as members change. Once the cluster settles into a stable state, it'll go back to normal.

Global ratelimits are handled by a single node on the cluster, however this affinity is soft. There is no concept of leader or elections and if this node leaves, the cluster will simply pick a new one. This is a bottleneck and might increase tail latency, but the other options were either too complex, required an external storage, or would require quorum for the proxy to function. Webhooks and other requests with no token bypass this mechanism completely.

The best deployment strategy for the cluster is to kill nodes one at a time, preferably with the replacement node already up.

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 instead of by the path hash. This provides a more even spread of bearer queues across nodes in the cluster. In addition, Bearer globals are always handled locally. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.

Why?

As projects grow, it's desirable to break them into multiple pieces, each responsible for its own domain. Discord provides gateway sharding on their end but REST can get tricky once you start moving logic out of the shards themselves and lose the guild affinity that shards inherently have, thus a centralized place for handling ratelimits is a must to prevent cloudflare bans and prevent avoidable 429s. At the time this project was created, there was no alternative that fully satisfied our requirements like multi-bot support. We are also early adopters of Discord features, so we need a proxy that supports new routes without us having to manually update it. Thus, this project was born.

Resource usage

This will vary depending on your usage, how many unique routes you see, etc. For reference, for Dynos use case, doing 150req/s, the usage is ~0.3 CPU and ~550MB of RAM. The proxy can comfortably run on a cheap VPS or an ARM based system.

Metrics / Health

KeyLabelsDescription
nirn_proxy_errornoneCounter for errors
nirn_proxy_requestsmethod, status, route, clientIdHistogram that keeps track of all request metrics
nirn_proxy_open_connectionsroute, methodGauge for open client connections with the proxy
nirn_proxy_requests_routed_sentnoneCounter for requests routed to other nodes
nirn_proxy_requests_routed_receivednoneCounter for requests received from other nodes
nirn_proxy_requests_routed_errornoneCounter for requests routed that failed

Note: 429s can produce two status: 429 Too Many Requests or 429 Shared. The latter is only produced for requests that return with the x-ratelimit-scope header set to "shared", which means they don't count towards the cloudflare firewall limit and thus should not be used for alerts, etc.

The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.

Profiling

The proxy can be profiled at runtime by enabling the ENABLE_PPROF flag and browsing to http://ip:7654/debug/pprof/

Related projects

nirn-probe - Checks and alerts if a server is cloudflare banned

Acknowledgements
  • Eris - used as reference throughout this project
  • Twilight - used as inspiration and reference
  • @bsian & @bean - for listening to my rants and providing assistance

About

πŸ”— Nirn Rest Proxy but with RabbitMQ. Used by the EazyAutodelete Discord Bot.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Repository files navigation

Note: This is a heavily customized version trimmed to the needs of EazyAutodelete. It will NOT meet your expectations nor your usecase.

Start

 docker run -d --name nirn --restart always --network rabbit-rest --network monitor_monitoring -p 8480:8080 -p 9490:9000 ea-rest:2

Nirn-proxy

Nirn-proxy is a highly available, transparent & dynamic HTTP proxy that handles Discord ratelimits for you and exports meaningful prometheus metrics. This project is at the heart of Dyno, handling several hundreds of requests per sec across hundreds of bots all while keeping 429s at ~100 per hour.

It is designed to be minimally invasive and exploits common library patterns to make the adoption as simple as a URL change.

Features

  • Highly available, horizontally scalable
  • Transparent ratelimit handling, per-route and global
  • Works with any API version (Also supports using two or more versions for the same bot)
  • 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

Usage

Binaries can be found here. Docker images can be found here

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. This can be achieved in many ways, some suggestions are host remapping on the OS level, DNS overrides or changes to the library code. Please note that the proxy currently does not support SSL.

Configuration options are

VariableValueDefault
LOG_LEVELpanic, fatal, error, warn, info, debug, traceinfo
PORTnumber8080
METRICS_PORTnumber9000
ENABLE_METRICSbooleantrue
ENABLE_PPROFbooleanfalse
BUFFER_SIZEnumber50
OUTBOUND_IPstring""
BIND_IPstring0.0.0.0
REQUEST_TIMEOUTnumber (milliseconds)5000
CLUSTER_PORTnumber7946
CLUSTER_MEMBERSstring list (comma separated)""
CLUSTER_DNSstring""
MAX_BEARER_COUNTnumber1024
DISABLE_HTTP_2booltrue
BOT_RATELIMIT_OVERRIDESstring list (comma separated)""
DISABLE_GLOBAL_RATELIMIT_DETECTIONbooleanfalse

Information on each config var can be found here

.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 Discords API can be pointed at the proxy and it will not break the library, even with the libraries own ratelimiting intact.

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 (i.e shared ratelimits, internal discord ratelimits, etc) the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429 or even setup retry logic elsewhere (like in a load balancer or service mesh).

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. This allows you to identify and react to routes that have issues.

Requests may also return a 408 status code in the event that they were aborted because of ratelimits, as documented above.

Limitations

The ratelimiting only works with X-RateLimit-Precision set to seconds. If you are using Discord API v8+, that is the only possible behaviour. For users on v6 or v7, please refer to your library docs for information on which precision it uses and how to change it to seconds.

The proxy tries its best to detect your REST global limits, but Discord does not expose this information. Be sure to set BOT_RATELIMIT_OVERRIDES for any clients with elevated limits.

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.

It's recommended that all nodes are reachable through LAN. Please reach out if a WAN cluster is desired for your use case.

If a node fails, there is a brief period where it will be unhealthy but requests will still be routed to it. When these requests fail, the proxy will mock a 429 to send back to the user. The 429 will signal the client to wait 1s and will have a custom header generated-by-proxy. This is done in order to allow seamless retries when a member fails. If you want to backoff, use the custom header to override your lib retry logic.

The cluster uses SWIM, which is an AP protocol and is powered by hashicorps excellent memberlist implementation.

Being an AP system means that the cluster will tolerate a network partition and needs no quorum to function. In case a network partition occurs, you'll have two clusters running independently, which may or may not be desirable. Configure your network accordingly.

In case you want to specifically target a node (i.e, for troubleshooting), set the nirn-routed-to header on the request. The value doesn't matter. This will prevent the node from routing the request to another node.

During recovery periods or when nodes join/leave the cluster, you might notice increased 429s. This is expected since the hashing table is changing as members change. Once the cluster settles into a stable state, it'll go back to normal.

Global ratelimits are handled by a single node on the cluster, however this affinity is soft. There is no concept of leader or elections and if this node leaves, the cluster will simply pick a new one. This is a bottleneck and might increase tail latency, but the other options were either too complex, required an external storage, or would require quorum for the proxy to function. Webhooks and other requests with no token bypass this mechanism completely.

The best deployment strategy for the cluster is to kill nodes one at a time, preferably with the replacement node already up.

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 instead of by the path hash. This provides a more even spread of bearer queues across nodes in the cluster. In addition, Bearer globals are always handled locally. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.

Why?

As projects grow, it's desirable to break them into multiple pieces, each responsible for its own domain. Discord provides gateway sharding on their end but REST can get tricky once you start moving logic out of the shards themselves and lose the guild affinity that shards inherently have, thus a centralized place for handling ratelimits is a must to prevent cloudflare bans and prevent avoidable 429s. At the time this project was created, there was no alternative that fully satisfied our requirements like multi-bot support. We are also early adopters of Discord features, so we need a proxy that supports new routes without us having to manually update it. Thus, this project was born.

Resource usage

This will vary depending on your usage, how many unique routes you see, etc. For reference, for Dynos use case, doing 150req/s, the usage is ~0.3 CPU and ~550MB of RAM. The proxy can comfortably run on a cheap VPS or an ARM based system.

Metrics / Health

KeyLabelsDescription
nirn_proxy_errornoneCounter for errors
nirn_proxy_requestsmethod, status, route, clientIdHistogram that keeps track of all request metrics
nirn_proxy_open_connectionsroute, methodGauge for open client connections with the proxy
nirn_proxy_requests_routed_sentnoneCounter for requests routed to other nodes
nirn_proxy_requests_routed_receivednoneCounter for requests received from other nodes
nirn_proxy_requests_routed_errornoneCounter for requests routed that failed

Note: 429s can produce two status: 429 Too Many Requests or 429 Shared. The latter is only produced for requests that return with the x-ratelimit-scope header set to "shared", which means they don't count towards the cloudflare firewall limit and thus should not be used for alerts, etc.

The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.

Profiling

The proxy can be profiled at runtime by enabling the ENABLE_PPROF flag and browsing to http://ip:7654/debug/pprof/

Related projects

nirn-probe - Checks and alerts if a server is cloudflare banned

Acknowledgements
  • Eris - used as reference throughout this project
  • Twilight - used as inspiration and reference
  • @bsian & @bean - for listening to my rants and providing assistance

About

πŸ”— Nirn Rest Proxy but with RabbitMQ. Used by the EazyAutodelete Discord Bot.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Repository files navigation

Note: This is a heavily customized version trimmed to the needs of EazyAutodelete. It will NOT meet your expectations nor your usecase.

Start

 docker run -d --name nirn --restart always --network rabbit-rest --network monitor_monitoring -p 8480:8080 -p 9490:9000 ea-rest:2

Nirn-proxy

Nirn-proxy is a highly available, transparent & dynamic HTTP proxy that handles Discord ratelimits for you and exports meaningful prometheus metrics. This project is at the heart of Dyno, handling several hundreds of requests per sec across hundreds of bots all while keeping 429s at ~100 per hour.

It is designed to be minimally invasive and exploits common library patterns to make the adoption as simple as a URL change.

Features

  • Highly available, horizontally scalable
  • Transparent ratelimit handling, per-route and global
  • Works with any API version (Also supports using two or more versions for the same bot)
  • 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

Usage

Binaries can be found here. Docker images can be found here

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. This can be achieved in many ways, some suggestions are host remapping on the OS level, DNS overrides or changes to the library code. Please note that the proxy currently does not support SSL.

Configuration options are

VariableValueDefault
LOG_LEVELpanic, fatal, error, warn, info, debug, traceinfo
PORTnumber8080
METRICS_PORTnumber9000
ENABLE_METRICSbooleantrue
ENABLE_PPROFbooleanfalse
BUFFER_SIZEnumber50
OUTBOUND_IPstring""
BIND_IPstring0.0.0.0
REQUEST_TIMEOUTnumber (milliseconds)5000
CLUSTER_PORTnumber7946
CLUSTER_MEMBERSstring list (comma separated)""
CLUSTER_DNSstring""
MAX_BEARER_COUNTnumber1024
DISABLE_HTTP_2booltrue
BOT_RATELIMIT_OVERRIDESstring list (comma separated)""
DISABLE_GLOBAL_RATELIMIT_DETECTIONbooleanfalse

Information on each config var can be found here

.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 Discords API can be pointed at the proxy and it will not break the library, even with the libraries own ratelimiting intact.

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 (i.e shared ratelimits, internal discord ratelimits, etc) the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429 or even setup retry logic elsewhere (like in a load balancer or service mesh).

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. This allows you to identify and react to routes that have issues.

Requests may also return a 408 status code in the event that they were aborted because of ratelimits, as documented above.

Limitations

The ratelimiting only works with X-RateLimit-Precision set to seconds. If you are using Discord API v8+, that is the only possible behaviour. For users on v6 or v7, please refer to your library docs for information on which precision it uses and how to change it to seconds.

The proxy tries its best to detect your REST global limits, but Discord does not expose this information. Be sure to set BOT_RATELIMIT_OVERRIDES for any clients with elevated limits.

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.

It's recommended that all nodes are reachable through LAN. Please reach out if a WAN cluster is desired for your use case.

If a node fails, there is a brief period where it will be unhealthy but requests will still be routed to it. When these requests fail, the proxy will mock a 429 to send back to the user. The 429 will signal the client to wait 1s and will have a custom header generated-by-proxy. This is done in order to allow seamless retries when a member fails. If you want to backoff, use the custom header to override your lib retry logic.

The cluster uses SWIM, which is an AP protocol and is powered by hashicorps excellent memberlist implementation.

Being an AP system means that the cluster will tolerate a network partition and needs no quorum to function. In case a network partition occurs, you'll have two clusters running independently, which may or may not be desirable. Configure your network accordingly.

In case you want to specifically target a node (i.e, for troubleshooting), set the nirn-routed-to header on the request. The value doesn't matter. This will prevent the node from routing the request to another node.

During recovery periods or when nodes join/leave the cluster, you might notice increased 429s. This is expected since the hashing table is changing as members change. Once the cluster settles into a stable state, it'll go back to normal.

Global ratelimits are handled by a single node on the cluster, however this affinity is soft. There is no concept of leader or elections and if this node leaves, the cluster will simply pick a new one. This is a bottleneck and might increase tail latency, but the other options were either too complex, required an external storage, or would require quorum for the proxy to function. Webhooks and other requests with no token bypass this mechanism completely.

The best deployment strategy for the cluster is to kill nodes one at a time, preferably with the replacement node already up.

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 instead of by the path hash. This provides a more even spread of bearer queues across nodes in the cluster. In addition, Bearer globals are always handled locally. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.

Why?

As projects grow, it's desirable to break them into multiple pieces, each responsible for its own domain. Discord provides gateway sharding on their end but REST can get tricky once you start moving logic out of the shards themselves and lose the guild affinity that shards inherently have, thus a centralized place for handling ratelimits is a must to prevent cloudflare bans and prevent avoidable 429s. At the time this project was created, there was no alternative that fully satisfied our requirements like multi-bot support. We are also early adopters of Discord features, so we need a proxy that supports new routes without us having to manually update it. Thus, this project was born.

Resource usage

This will vary depending on your usage, how many unique routes you see, etc. For reference, for Dynos use case, doing 150req/s, the usage is ~0.3 CPU and ~550MB of RAM. The proxy can comfortably run on a cheap VPS or an ARM based system.

Metrics / Health

KeyLabelsDescription
nirn_proxy_errornoneCounter for errors
nirn_proxy_requestsmethod, status, route, clientIdHistogram that keeps track of all request metrics
nirn_proxy_open_connectionsroute, methodGauge for open client connections with the proxy
nirn_proxy_requests_routed_sentnoneCounter for requests routed to other nodes
nirn_proxy_requests_routed_receivednoneCounter for requests received from other nodes
nirn_proxy_requests_routed_errornoneCounter for requests routed that failed

Note: 429s can produce two status: 429 Too Many Requests or 429 Shared. The latter is only produced for requests that return with the x-ratelimit-scope header set to "shared", which means they don't count towards the cloudflare firewall limit and thus should not be used for alerts, etc.

The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.

Profiling

The proxy can be profiled at runtime by enabling the ENABLE_PPROF flag and browsing to http://ip:7654/debug/pprof/

Related projects

nirn-probe - Checks and alerts if a server is cloudflare banned

Acknowledgements
  • Eris - used as reference throughout this project
  • Twilight - used as inspiration and reference
  • @bsian & @bean - for listening to my rants and providing assistance

About

πŸ”— Nirn Rest Proxy but with RabbitMQ. Used by the EazyAutodelete Discord Bot.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Repository files navigation

Note: This is a heavily customized version trimmed to the needs of EazyAutodelete. It will NOT meet your expectations nor your usecase.

Start

 docker run -d --name nirn --restart always --network rabbit-rest --network monitor_monitoring -p 8480:8080 -p 9490:9000 ea-rest:2

Nirn-proxy

Nirn-proxy is a highly available, transparent & dynamic HTTP proxy that handles Discord ratelimits for you and exports meaningful prometheus metrics. This project is at the heart of Dyno, handling several hundreds of requests per sec across hundreds of bots all while keeping 429s at ~100 per hour.

It is designed to be minimally invasive and exploits common library patterns to make the adoption as simple as a URL change.

Features

  • Highly available, horizontally scalable
  • Transparent ratelimit handling, per-route and global
  • Works with any API version (Also supports using two or more versions for the same bot)
  • 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

Usage

Binaries can be found here. Docker images can be found here

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. This can be achieved in many ways, some suggestions are host remapping on the OS level, DNS overrides or changes to the library code. Please note that the proxy currently does not support SSL.

Configuration options are

VariableValueDefault
LOG_LEVELpanic, fatal, error, warn, info, debug, traceinfo
PORTnumber8080
METRICS_PORTnumber9000
ENABLE_METRICSbooleantrue
ENABLE_PPROFbooleanfalse
BUFFER_SIZEnumber50
OUTBOUND_IPstring""
BIND_IPstring0.0.0.0
REQUEST_TIMEOUTnumber (milliseconds)5000
CLUSTER_PORTnumber7946
CLUSTER_MEMBERSstring list (comma separated)""
CLUSTER_DNSstring""
MAX_BEARER_COUNTnumber1024
DISABLE_HTTP_2booltrue
BOT_RATELIMIT_OVERRIDESstring list (comma separated)""
DISABLE_GLOBAL_RATELIMIT_DETECTIONbooleanfalse

Information on each config var can be found here

.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 Discords API can be pointed at the proxy and it will not break the library, even with the libraries own ratelimiting intact.

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 (i.e shared ratelimits, internal discord ratelimits, etc) the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429 or even setup retry logic elsewhere (like in a load balancer or service mesh).

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. This allows you to identify and react to routes that have issues.

Requests may also return a 408 status code in the event that they were aborted because of ratelimits, as documented above.

Limitations

The ratelimiting only works with X-RateLimit-Precision set to seconds. If you are using Discord API v8+, that is the only possible behaviour. For users on v6 or v7, please refer to your library docs for information on which precision it uses and how to change it to seconds.

The proxy tries its best to detect your REST global limits, but Discord does not expose this information. Be sure to set BOT_RATELIMIT_OVERRIDES for any clients with elevated limits.

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.

It's recommended that all nodes are reachable through LAN. Please reach out if a WAN cluster is desired for your use case.

If a node fails, there is a brief period where it will be unhealthy but requests will still be routed to it. When these requests fail, the proxy will mock a 429 to send back to the user. The 429 will signal the client to wait 1s and will have a custom header generated-by-proxy. This is done in order to allow seamless retries when a member fails. If you want to backoff, use the custom header to override your lib retry logic.

The cluster uses SWIM, which is an AP protocol and is powered by hashicorps excellent memberlist implementation.

Being an AP system means that the cluster will tolerate a network partition and needs no quorum to function. In case a network partition occurs, you'll have two clusters running independently, which may or may not be desirable. Configure your network accordingly.

In case you want to specifically target a node (i.e, for troubleshooting), set the nirn-routed-to header on the request. The value doesn't matter. This will prevent the node from routing the request to another node.

During recovery periods or when nodes join/leave the cluster, you might notice increased 429s. This is expected since the hashing table is changing as members change. Once the cluster settles into a stable state, it'll go back to normal.

Global ratelimits are handled by a single node on the cluster, however this affinity is soft. There is no concept of leader or elections and if this node leaves, the cluster will simply pick a new one. This is a bottleneck and might increase tail latency, but the other options were either too complex, required an external storage, or would require quorum for the proxy to function. Webhooks and other requests with no token bypass this mechanism completely.

The best deployment strategy for the cluster is to kill nodes one at a time, preferably with the replacement node already up.

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 instead of by the path hash. This provides a more even spread of bearer queues across nodes in the cluster. In addition, Bearer globals are always handled locally. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.

Why?

As projects grow, it's desirable to break them into multiple pieces, each responsible for its own domain. Discord provides gateway sharding on their end but REST can get tricky once you start moving logic out of the shards themselves and lose the guild affinity that shards inherently have, thus a centralized place for handling ratelimits is a must to prevent cloudflare bans and prevent avoidable 429s. At the time this project was created, there was no alternative that fully satisfied our requirements like multi-bot support. We are also early adopters of Discord features, so we need a proxy that supports new routes without us having to manually update it. Thus, this project was born.

Resource usage

This will vary depending on your usage, how many unique routes you see, etc. For reference, for Dynos use case, doing 150req/s, the usage is ~0.3 CPU and ~550MB of RAM. The proxy can comfortably run on a cheap VPS or an ARM based system.

Metrics / Health

KeyLabelsDescription
nirn_proxy_errornoneCounter for errors
nirn_proxy_requestsmethod, status, route, clientIdHistogram that keeps track of all request metrics
nirn_proxy_open_connectionsroute, methodGauge for open client connections with the proxy
nirn_proxy_requests_routed_sentnoneCounter for requests routed to other nodes
nirn_proxy_requests_routed_receivednoneCounter for requests received from other nodes
nirn_proxy_requests_routed_errornoneCounter for requests routed that failed

Note: 429s can produce two status: 429 Too Many Requests or 429 Shared. The latter is only produced for requests that return with the x-ratelimit-scope header set to "shared", which means they don't count towards the cloudflare firewall limit and thus should not be used for alerts, etc.

The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.

Profiling

The proxy can be profiled at runtime by enabling the ENABLE_PPROF flag and browsing to http://ip:7654/debug/pprof/

Related projects

nirn-probe - Checks and alerts if a server is cloudflare banned

Acknowledgements
  • Eris - used as reference throughout this project
  • Twilight - used as inspiration and reference
  • @bsian & @bean - for listening to my rants and providing assistance

About

πŸ”— Nirn Rest Proxy but with RabbitMQ. Used by the EazyAutodelete Discord Bot.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Repository files navigation

Note: This is a heavily customized version trimmed to the needs of EazyAutodelete. It will NOT meet your expectations nor your usecase.

Start

 docker run -d --name nirn --restart always --network rabbit-rest --network monitor_monitoring -p 8480:8080 -p 9490:9000 ea-rest:2

Nirn-proxy

Nirn-proxy is a highly available, transparent & dynamic HTTP proxy that handles Discord ratelimits for you and exports meaningful prometheus metrics. This project is at the heart of Dyno, handling several hundreds of requests per sec across hundreds of bots all while keeping 429s at ~100 per hour.

It is designed to be minimally invasive and exploits common library patterns to make the adoption as simple as a URL change.

Features

  • Highly available, horizontally scalable
  • Transparent ratelimit handling, per-route and global
  • Works with any API version (Also supports using two or more versions for the same bot)
  • 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

Usage

Binaries can be found here. Docker images can be found here

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. This can be achieved in many ways, some suggestions are host remapping on the OS level, DNS overrides or changes to the library code. Please note that the proxy currently does not support SSL.

Configuration options are

VariableValueDefault
LOG_LEVELpanic, fatal, error, warn, info, debug, traceinfo
PORTnumber8080
METRICS_PORTnumber9000
ENABLE_METRICSbooleantrue
ENABLE_PPROFbooleanfalse
BUFFER_SIZEnumber50
OUTBOUND_IPstring""
BIND_IPstring0.0.0.0
REQUEST_TIMEOUTnumber (milliseconds)5000
CLUSTER_PORTnumber7946
CLUSTER_MEMBERSstring list (comma separated)""
CLUSTER_DNSstring""
MAX_BEARER_COUNTnumber1024
DISABLE_HTTP_2booltrue
BOT_RATELIMIT_OVERRIDESstring list (comma separated)""
DISABLE_GLOBAL_RATELIMIT_DETECTIONbooleanfalse

Information on each config var can be found here

.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 Discords API can be pointed at the proxy and it will not break the library, even with the libraries own ratelimiting intact.

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 (i.e shared ratelimits, internal discord ratelimits, etc) the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429 or even setup retry logic elsewhere (like in a load balancer or service mesh).

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. This allows you to identify and react to routes that have issues.

Requests may also return a 408 status code in the event that they were aborted because of ratelimits, as documented above.

Limitations

The ratelimiting only works with X-RateLimit-Precision set to seconds. If you are using Discord API v8+, that is the only possible behaviour. For users on v6 or v7, please refer to your library docs for information on which precision it uses and how to change it to seconds.

The proxy tries its best to detect your REST global limits, but Discord does not expose this information. Be sure to set BOT_RATELIMIT_OVERRIDES for any clients with elevated limits.

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.

It's recommended that all nodes are reachable through LAN. Please reach out if a WAN cluster is desired for your use case.

If a node fails, there is a brief period where it will be unhealthy but requests will still be routed to it. When these requests fail, the proxy will mock a 429 to send back to the user. The 429 will signal the client to wait 1s and will have a custom header generated-by-proxy. This is done in order to allow seamless retries when a member fails. If you want to backoff, use the custom header to override your lib retry logic.

The cluster uses SWIM, which is an AP protocol and is powered by hashicorps excellent memberlist implementation.

Being an AP system means that the cluster will tolerate a network partition and needs no quorum to function. In case a network partition occurs, you'll have two clusters running independently, which may or may not be desirable. Configure your network accordingly.

In case you want to specifically target a node (i.e, for troubleshooting), set the nirn-routed-to header on the request. The value doesn't matter. This will prevent the node from routing the request to another node.

During recovery periods or when nodes join/leave the cluster, you might notice increased 429s. This is expected since the hashing table is changing as members change. Once the cluster settles into a stable state, it'll go back to normal.

Global ratelimits are handled by a single node on the cluster, however this affinity is soft. There is no concept of leader or elections and if this node leaves, the cluster will simply pick a new one. This is a bottleneck and might increase tail latency, but the other options were either too complex, required an external storage, or would require quorum for the proxy to function. Webhooks and other requests with no token bypass this mechanism completely.

The best deployment strategy for the cluster is to kill nodes one at a time, preferably with the replacement node already up.

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 instead of by the path hash. This provides a more even spread of bearer queues across nodes in the cluster. In addition, Bearer globals are always handled locally. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.

Why?

As projects grow, it's desirable to break them into multiple pieces, each responsible for its own domain. Discord provides gateway sharding on their end but REST can get tricky once you start moving logic out of the shards themselves and lose the guild affinity that shards inherently have, thus a centralized place for handling ratelimits is a must to prevent cloudflare bans and prevent avoidable 429s. At the time this project was created, there was no alternative that fully satisfied our requirements like multi-bot support. We are also early adopters of Discord features, so we need a proxy that supports new routes without us having to manually update it. Thus, this project was born.

Resource usage

This will vary depending on your usage, how many unique routes you see, etc. For reference, for Dynos use case, doing 150req/s, the usage is ~0.3 CPU and ~550MB of RAM. The proxy can comfortably run on a cheap VPS or an ARM based system.

Metrics / Health

KeyLabelsDescription
nirn_proxy_errornoneCounter for errors
nirn_proxy_requestsmethod, status, route, clientIdHistogram that keeps track of all request metrics
nirn_proxy_open_connectionsroute, methodGauge for open client connections with the proxy
nirn_proxy_requests_routed_sentnoneCounter for requests routed to other nodes
nirn_proxy_requests_routed_receivednoneCounter for requests received from other nodes
nirn_proxy_requests_routed_errornoneCounter for requests routed that failed

Note: 429s can produce two status: 429 Too Many Requests or 429 Shared. The latter is only produced for requests that return with the x-ratelimit-scope header set to "shared", which means they don't count towards the cloudflare firewall limit and thus should not be used for alerts, etc.

The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.

Profiling

The proxy can be profiled at runtime by enabling the ENABLE_PPROF flag and browsing to http://ip:7654/debug/pprof/

Related projects

nirn-probe - Checks and alerts if a server is cloudflare banned

Acknowledgements
  • Eris - used as reference throughout this project
  • Twilight - used as inspiration and reference
  • @bsian & @bean - for listening to my rants and providing assistance

About

πŸ”— Nirn Rest Proxy but with RabbitMQ. Used by the EazyAutodelete Discord Bot.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Nov 1, 2025. It is now read-only.

Repository files navigation

Note: This is a heavily customized version trimmed to the needs of EazyAutodelete. It will NOT meet your expectations nor your usecase.

Start

 docker run -d --name nirn --restart always --network rabbit-rest --network monitor_monitoring -p 8480:8080 -p 9490:9000 ea-rest:2

Nirn-proxy

Nirn-proxy is a highly available, transparent & dynamic HTTP proxy that handles Discord ratelimits for you and exports meaningful prometheus metrics. This project is at the heart of Dyno, handling several hundreds of requests per sec across hundreds of bots all while keeping 429s at ~100 per hour.

It is designed to be minimally invasive and exploits common library patterns to make the adoption as simple as a URL change.

Features

  • Highly available, horizontally scalable
  • Transparent ratelimit handling, per-route and global
  • Works with any API version (Also supports using two or more versions for the same bot)
  • 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

Usage

Binaries can be found here. Docker images can be found here

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. This can be achieved in many ways, some suggestions are host remapping on the OS level, DNS overrides or changes to the library code. Please note that the proxy currently does not support SSL.

Configuration options are

VariableValueDefault
LOG_LEVELpanic, fatal, error, warn, info, debug, traceinfo
PORTnumber8080
METRICS_PORTnumber9000
ENABLE_METRICSbooleantrue
ENABLE_PPROFbooleanfalse
BUFFER_SIZEnumber50
OUTBOUND_IPstring""
BIND_IPstring0.0.0.0
REQUEST_TIMEOUTnumber (milliseconds)5000
CLUSTER_PORTnumber7946
CLUSTER_MEMBERSstring list (comma separated)""
CLUSTER_DNSstring""
MAX_BEARER_COUNTnumber1024
DISABLE_HTTP_2booltrue
BOT_RATELIMIT_OVERRIDESstring list (comma separated)""
DISABLE_GLOBAL_RATELIMIT_DETECTIONbooleanfalse

Information on each config var can be found here

.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 Discords API can be pointed at the proxy and it will not break the library, even with the libraries own ratelimiting intact.

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 (i.e shared ratelimits, internal discord ratelimits, etc) the proxy encounters a 429, it will return that to the client. It is safe to immediately retry requests that return 429 or even setup retry logic elsewhere (like in a load balancer or service mesh).

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. This allows you to identify and react to routes that have issues.

Requests may also return a 408 status code in the event that they were aborted because of ratelimits, as documented above.

Limitations

The ratelimiting only works with X-RateLimit-Precision set to seconds. If you are using Discord API v8+, that is the only possible behaviour. For users on v6 or v7, please refer to your library docs for information on which precision it uses and how to change it to seconds.

The proxy tries its best to detect your REST global limits, but Discord does not expose this information. Be sure to set BOT_RATELIMIT_OVERRIDES for any clients with elevated limits.

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.

It's recommended that all nodes are reachable through LAN. Please reach out if a WAN cluster is desired for your use case.

If a node fails, there is a brief period where it will be unhealthy but requests will still be routed to it. When these requests fail, the proxy will mock a 429 to send back to the user. The 429 will signal the client to wait 1s and will have a custom header generated-by-proxy. This is done in order to allow seamless retries when a member fails. If you want to backoff, use the custom header to override your lib retry logic.

The cluster uses SWIM, which is an AP protocol and is powered by hashicorps excellent memberlist implementation.

Being an AP system means that the cluster will tolerate a network partition and needs no quorum to function. In case a network partition occurs, you'll have two clusters running independently, which may or may not be desirable. Configure your network accordingly.

In case you want to specifically target a node (i.e, for troubleshooting), set the nirn-routed-to header on the request. The value doesn't matter. This will prevent the node from routing the request to another node.

During recovery periods or when nodes join/leave the cluster, you might notice increased 429s. This is expected since the hashing table is changing as members change. Once the cluster settles into a stable state, it'll go back to normal.

Global ratelimits are handled by a single node on the cluster, however this affinity is soft. There is no concept of leader or elections and if this node leaves, the cluster will simply pick a new one. This is a bottleneck and might increase tail latency, but the other options were either too complex, required an external storage, or would require quorum for the proxy to function. Webhooks and other requests with no token bypass this mechanism completely.

The best deployment strategy for the cluster is to kill nodes one at a time, preferably with the replacement node already up.

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 instead of by the path hash. This provides a more even spread of bearer queues across nodes in the cluster. In addition, Bearer globals are always handled locally. You can control how many bearer queues to keep at any time with the MAX_BEARER_COUNT env var.

Why?

As projects grow, it's desirable to break them into multiple pieces, each responsible for its own domain. Discord provides gateway sharding on their end but REST can get tricky once you start moving logic out of the shards themselves and lose the guild affinity that shards inherently have, thus a centralized place for handling ratelimits is a must to prevent cloudflare bans and prevent avoidable 429s. At the time this project was created, there was no alternative that fully satisfied our requirements like multi-bot support. We are also early adopters of Discord features, so we need a proxy that supports new routes without us having to manually update it. Thus, this project was born.

Resource usage

This will vary depending on your usage, how many unique routes you see, etc. For reference, for Dynos use case, doing 150req/s, the usage is ~0.3 CPU and ~550MB of RAM. The proxy can comfortably run on a cheap VPS or an ARM based system.

Metrics / Health

KeyLabelsDescription
nirn_proxy_errornoneCounter for errors
nirn_proxy_requestsmethod, status, route, clientIdHistogram that keeps track of all request metrics
nirn_proxy_open_connectionsroute, methodGauge for open client connections with the proxy
nirn_proxy_requests_routed_sentnoneCounter for requests routed to other nodes
nirn_proxy_requests_routed_receivednoneCounter for requests received from other nodes
nirn_proxy_requests_routed_errornoneCounter for requests routed that failed

Note: 429s can produce two status: 429 Too Many Requests or 429 Shared. The latter is only produced for requests that return with the x-ratelimit-scope header set to "shared", which means they don't count towards the cloudflare firewall limit and thus should not be used for alerts, etc.

The proxy has an internal endpoint located at /nirn/healthz for liveliness and readiness probes.

Profiling

The proxy can be profiled at runtime by enabling the ENABLE_PPROF flag and browsing to http://ip:7654/debug/pprof/

Related projects

nirn-probe - Checks and alerts if a server is cloudflare banned

Acknowledgements
  • Eris - used as reference throughout this project
  • Twilight - used as inspiration and reference
  • @bsian & @bean - for listening to my rants and providing assistance

About

πŸ”— Nirn Rest Proxy but with RabbitMQ. Used by the EazyAutodelete Discord Bot.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages