How to Implement Request Debouncing Middleware in Gorig: A Complete Guide
Gorig provides a built-in Debounce middleware in httpx/mid.debounce.go that uses a sharded request map with automatic cleanup to throttle duplicate HTTP requests within a configurable time window, returning HTTP 429 when limits are exceeded.
The jom-io/gorig framework includes a production-ready request debouncing middleware designed for high-concurrency Gin applications. When you implement request debouncing middleware in Gorig, you leverage a sharded map architecture that minimizes lock contention while automatically managing memory through background cleanup routines.
Understanding Gorig's Debounce Middleware Architecture
Sharded Request Map for High Concurrency
At the core of the implementation is the ShardedRequestMap structure defined in httpx/mid.debounce.go. This component splits the request tracking storage into 32 independent shards, each protected by its own RWMutex. When a request arrives, Gorig hashes the request key using FNV-1a to determine which shard owns the timestamp, dramatically reducing lock granularity under heavy load compared to a single global map.
Automatic Memory Cleanup
To prevent unbounded memory growth, the middleware spawns a background goroutine that executes every minute. As implemented in httpx/mid.debounce.go, this routine scans each shard and clears the map entirely if the shard's estimated memory consumption exceeds 2 MiB. This ensures that stale request keys from high-traffic endpoints do not accumulate indefinitely.
Request Key Generation
The middleware constructs a unique fingerprint for each request using the following logic from httpx/mid.debounce.go:
- Path and Query:
path+?+RawQueryfor GET requests - User Identification: If a JWT token is present, append
:id:+userID; otherwise append:ip:+ClientIP
This dual strategy ensures authenticated users are throttled independently from anonymous traffic while preventing IP-based collision attacks.
How to Implement Request Debouncing Middleware in Gorig
Basic Implementation with Default Settings
Gorig automatically applies debouncing to all routes when you bootstrap the server. In httpx/serv.go, the default Gin engine is configured with a 200 millisecond debounce window:
gEngine.Use(Debounce(200 * time.Millisecond))
This single line at line 82 of httpx/serv.go protects your entire API from duplicate submissions without additional configuration.
Custom Debounce Durations for Specific Routes
For endpoints requiring different throttling behavior, apply the middleware to specific route groups. This overrides the global default for that subtree:
router := gEngine.Group("/api")
router.Use(Debounce(500 * time.Millisecond))
router.GET("/items", itemsHandler)
This configuration extends the debounce window to 500ms for all /api/* endpoints, accommodating slower client-side polling patterns while maintaining protection.
Disabling Debounce for Testing
When running integration tests that issue rapid sequential requests, disable the middleware globally to avoid HTTP 429 errors. Call DebounceDisable() before initializing test servers:
func TestFastCalls(t *testing.T) {
httpx.DebounceDisable()
// ... initialize server and run test code
}
This function sets the internal enable flag to false, causing the Debounce middleware to skip all checks while maintaining handler chain compatibility.
Whitelisting Specific Endpoints
For health checks, metrics scraping, or other high-frequency legitimate traffic, exclude specific paths from debouncing using DebouceAw() (note the spelling as implemented in the source):
func init() {
httpx.DebouceAw("/health", "/metrics")
}
This registers the specified paths in a global whitelist map. When the middleware processes a request, it checks this map before generating the request key, allowing unlimited requests to these endpoints regardless of the debounce window.
Advanced: Manual Request Key Construction
For custom rate-limiting logic outside the standard middleware, Gorig exposes the key generation algorithm. Replicate the fingerprinting logic to interact with the sharded map directly:
func myHandler(c *gin.Context) {
token := httpx.GetTokenByCtx(c, false)
userID := httpx.GetUserIDByToken(token)
key := c.Request.URL.Path
if c.Request.Method == "GET" {
key += "?" + c.Request.URL.RawQuery
}
if userID != "" {
key += ":id:" + userID
} else {
key += ":ip:" + c.ClientIP()
}
// Access the sharded map for custom logic
shard := httpx.NewShardedRequestMap()
// ... custom implementation
}
This pattern allows you to extend the debounce concept to WebSocket connections, gRPC streams, or other non-HTTP contexts while maintaining consistency with Gorig's core architecture.
Summary
- Gorig's debounce middleware is implemented in
httpx/mid.debounce.goand automatically wired into the default Gin engine inhttpx/serv.gowith a 200ms window. - High-concurrency design uses a 32-sharded map with FNV-1a hashing and per-shard RWMutexes to minimize lock contention.
- Memory safety is ensured by a background cleanup routine that clears shards exceeding 2 MiB every minute.
- Flexible configuration allows custom durations per route group, global disabling via
DebounceDisable(), and path whitelisting viaDebouceAw(). - Request fingerprinting combines path, query, and user ID (or IP for anonymous requests) to create unique throttle keys.
Frequently Asked Questions
What is the default debounce duration in Gorig?
The default debounce window is 200 milliseconds, configured automatically in httpx/serv.go at line 82 where the middleware is applied to the global Gin engine with Debounce(200 * time.Millisecond).
How does Gorig prevent memory leaks in the debounce middleware?
Gorig implements a background cleanup goroutine that executes every minute, scanning all 32 shards of the ShardedRequestMap. If a shard's estimated memory consumption exceeds 2 MiB, the routine clears that shard's map entirely, preventing unbounded growth from stale request timestamps.
Can I use different debounce intervals for different user groups?
While the middleware does not natively support per-user-group intervals, you can achieve this by creating separate route groups and applying different Debounce(duration) middleware instances to each group. For example, apply a 500ms window to /api/premium routes and a 200ms window to /api/free routes.
What HTTP status code is returned when a request is debounced?
When a duplicate request is detected within the debounce window, Gorig returns HTTP 429 Too Many Requests using the response.ErrorTooManyRequests function from apix/response/response.go.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →