mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-23 04:11:26 +00:00
Raise the default per-client API limit to 600 a minute (#232)
Opening Map and Nodes on a region of a few thousand nodes could spend the whole 300-request minute and leave the map half loaded. 600 still sits under the fail2ban flood jail's 900, so the server's 429 answers first.
This commit is contained in:
1 parent
202acc6cc8
commit
127fc76092
8 files changed
+14
-14
No files matched your search
+1
-1
@@ -47,7 +47,7 @@ var version = "dev"
|
||||
// @title MeshCore Beacon API
|
||||
// @version 2.0.7
|
||||
// @description MeshCore network observation backend. Ingests LoRa packets from MQTT brokers, stores in PostgreSQL, and streams live events via WebSocket.
|
||||
// @description REST requests share a configurable per-client rate limit (default 300/minute and a 300-request one-second burst cap). Exceeded limits return HTTP 429 with error.code=rate_limited and a Retry-After header in seconds. CORS preflights and WebSocket upgrades do not consume this API budget.
|
||||
// @description REST requests share a configurable per-client rate limit (default 600/minute and a 600-request one-second burst cap). Exceeded limits return HTTP 429 with error.code=rate_limited and a Retry-After header in seconds. CORS preflights and WebSocket upgrades do not consume this API budget.
|
||||
// @description WebSocket upgrade attempts at /ws have a configurable per-client limit (default 10/minute, including failed handshakes). Rate exhaustion returns HTTP 429 with Retry-After before upgrade. An accepted socket exceeding the concurrent cap closes with code 1013 before hello; established connections remain open.
|
||||
// @termsOfService https://github.com/MeshCore-Beacon/beacon-server
|
||||
|
||||
|
||||
+2
-2
@@ -40,11 +40,11 @@ server:
|
||||
# the proxy's budget. IPv6 clients share a /64 budget to prevent address rotation.
|
||||
ratelimit:
|
||||
enabled: true
|
||||
requests_per_minute: 300
|
||||
requests_per_minute: 600
|
||||
# Maximum requests in a one-second sliding window, not token-bucket capacity.
|
||||
# Omitted/zero defaults to requests_per_minute. Negative limits prevent startup.
|
||||
# A 429 returns error.code=rate_limited and Retry-After (1 or 60 seconds).
|
||||
burst: 300
|
||||
burst: 600
|
||||
|
||||
# MeshMapper integrations (optional, require a regional or grouped-region API key).
|
||||
# Your local MeshMapper regional or grouped-region admin can generate the key.
|
||||
|
||||
+1
-1
@@ -6276,7 +6276,7 @@ var SwaggerInfo = &swag.Spec{
|
||||
BasePath: "/api/v1",
|
||||
Schemes: []string{"http", "https"},
|
||||
Title: "MeshCore Beacon API",
|
||||
Description: "MeshCore network observation backend. Ingests LoRa packets from MQTT brokers, stores in PostgreSQL, and streams live events via WebSocket.\nREST requests share a configurable per-client rate limit (default 300/minute and a 300-request one-second burst cap). Exceeded limits return HTTP 429 with error.code=rate_limited and a Retry-After header in seconds. CORS preflights and WebSocket upgrades do not consume this API budget.\nWebSocket upgrade attempts at /ws have a configurable per-client limit (default 10/minute, including failed handshakes). Rate exhaustion returns HTTP 429 with Retry-After before upgrade. An accepted socket exceeding the concurrent cap closes with code 1013 before hello; established connections remain open.",
|
||||
Description: "MeshCore network observation backend. Ingests LoRa packets from MQTT brokers, stores in PostgreSQL, and streams live events via WebSocket.\nREST requests share a configurable per-client rate limit (default 600/minute and a 600-request one-second burst cap). Exceeded limits return HTTP 429 with error.code=rate_limited and a Retry-After header in seconds. CORS preflights and WebSocket upgrades do not consume this API budget.\nWebSocket upgrade attempts at /ws have a configurable per-client limit (default 10/minute, including failed handshakes). Rate exhaustion returns HTTP 429 with Retry-After before upgrade. An accepted socket exceeding the concurrent cap closes with code 1013 before hello; established connections remain open.",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
],
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "MeshCore network observation backend. Ingests LoRa packets from MQTT brokers, stores in PostgreSQL, and streams live events via WebSocket.\nREST requests share a configurable per-client rate limit (default 300/minute and a 300-request one-second burst cap). Exceeded limits return HTTP 429 with error.code=rate_limited and a Retry-After header in seconds. CORS preflights and WebSocket upgrades do not consume this API budget.\nWebSocket upgrade attempts at /ws have a configurable per-client limit (default 10/minute, including failed handshakes). Rate exhaustion returns HTTP 429 with Retry-After before upgrade. An accepted socket exceeding the concurrent cap closes with code 1013 before hello; established connections remain open.",
|
||||
"description": "MeshCore network observation backend. Ingests LoRa packets from MQTT brokers, stores in PostgreSQL, and streams live events via WebSocket.\nREST requests share a configurable per-client rate limit (default 600/minute and a 600-request one-second burst cap). Exceeded limits return HTTP 429 with error.code=rate_limited and a Retry-After header in seconds. CORS preflights and WebSocket upgrades do not consume this API budget.\nWebSocket upgrade attempts at /ws have a configurable per-client limit (default 10/minute, including failed handshakes). Rate exhaustion returns HTTP 429 with Retry-After before upgrade. An accepted socket exceeding the concurrent cap closes with code 1013 before hello; established connections remain open.",
|
||||
"title": "MeshCore Beacon API",
|
||||
"termsOfService": "https://github.com/MeshCore-Beacon/beacon-server",
|
||||
"contact": {
|
||||
|
||||
+1
-1
@@ -1819,7 +1819,7 @@ info:
|
||||
url: https://github.com/MeshCore-Beacon/beacon-server
|
||||
description: |-
|
||||
MeshCore network observation backend. Ingests LoRa packets from MQTT brokers, stores in PostgreSQL, and streams live events via WebSocket.
|
||||
REST requests share a configurable per-client rate limit (default 300/minute and a 300-request one-second burst cap). Exceeded limits return HTTP 429 with error.code=rate_limited and a Retry-After header in seconds. CORS preflights and WebSocket upgrades do not consume this API budget.
|
||||
REST requests share a configurable per-client rate limit (default 600/minute and a 600-request one-second burst cap). Exceeded limits return HTTP 429 with error.code=rate_limited and a Retry-After header in seconds. CORS preflights and WebSocket upgrades do not consume this API budget.
|
||||
WebSocket upgrade attempts at /ws have a configurable per-client limit (default 10/minute, including failed handshakes). Rate exhaustion returns HTTP 429 with Retry-After before upgrade. An accepted socket exceeding the concurrent cap closes with code 1013 before hello; established connections remain open.
|
||||
license:
|
||||
name: AGPL-3-or-later
|
||||
|
||||
@@ -23,13 +23,13 @@ import (
|
||||
|
||||
func TestDefaultAPIRateLimit(t *testing.T) {
|
||||
handler := New(nil, nil, nil, Options{MaxConnsPerIP: 5, MaxConnectsPerMinute: 1000, RateLimit: config.Resolve(&config.Config{}).RateLimit})
|
||||
for i := 0; i <= 300; i++ {
|
||||
for i := 0; i <= 600; i++ {
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/v1/brokers", nil)
|
||||
request.RemoteAddr = "198.51.100.1:1234"
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
want := http.StatusOK
|
||||
if i == 300 {
|
||||
if i == 600 {
|
||||
want = http.StatusTooManyRequests
|
||||
}
|
||||
if response.Code != want {
|
||||
|
||||
@@ -600,7 +600,7 @@ func Resolve(cfg *Config) ResolvedConfig {
|
||||
ObserverDeleteAfter: cfg.Observers.DeleteAfter.Duration,
|
||||
}
|
||||
if r.RateLimit.RequestsPerMinute == 0 {
|
||||
r.RateLimit.RequestsPerMinute = 300
|
||||
r.RateLimit.RequestsPerMinute = 600
|
||||
}
|
||||
if r.RateLimit.Burst == 0 {
|
||||
r.RateLimit.Burst = r.RateLimit.RequestsPerMinute
|
||||
|
||||
@@ -15,11 +15,11 @@ func TestRateLimitConfig(t *testing.T) {
|
||||
want ResolvedRateLimitConfig
|
||||
wantError bool
|
||||
}{
|
||||
{"omitted", "{}", ResolvedRateLimitConfig{true, 300, 300}, false},
|
||||
{"disabled", "ratelimit: {enabled: false}", ResolvedRateLimitConfig{false, 300, 300}, false},
|
||||
{"omitted", "{}", ResolvedRateLimitConfig{true, 600, 600}, false},
|
||||
{"disabled", "ratelimit: {enabled: false}", ResolvedRateLimitConfig{false, 600, 600}, false},
|
||||
{"custom", "ratelimit: {enabled: true, requests_per_minute: 900, burst: 100}", ResolvedRateLimitConfig{true, 900, 100}, false},
|
||||
{"burst default follows budget", "ratelimit: {requests_per_minute: 600}", ResolvedRateLimitConfig{true, 600, 600}, false},
|
||||
{"zero defaults", "ratelimit: {requests_per_minute: 0, burst: 0}", ResolvedRateLimitConfig{true, 300, 300}, false},
|
||||
{"burst default follows budget", "ratelimit: {requests_per_minute: 1200}", ResolvedRateLimitConfig{true, 1200, 1200}, false},
|
||||
{"zero defaults", "ratelimit: {requests_per_minute: 0, burst: 0}", ResolvedRateLimitConfig{true, 600, 600}, false},
|
||||
{"negative minute", "ratelimit: {requests_per_minute: -1}", ResolvedRateLimitConfig{}, true},
|
||||
{"negative burst", "ratelimit: {burst: -1}", ResolvedRateLimitConfig{}, true},
|
||||
{"invalid disabled config", "ratelimit: {enabled: false, burst: -1}", ResolvedRateLimitConfig{}, true},
|
||||
@@ -39,7 +39,7 @@ func TestRateLimitConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
cfg, err := Load(filepath.Join(t.TempDir(), "missing.yaml"))
|
||||
if err != nil || Resolve(cfg).RateLimit != (ResolvedRateLimitConfig{true, 300, 300}) {
|
||||
if err != nil || Resolve(cfg).RateLimit != (ResolvedRateLimitConfig{true, 600, 600}) {
|
||||
t.Fatalf("missing config did not enable defaults: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user