{"id":13694377,"url":"https://github.com/didip/tollbooth","last_synced_at":"2025-05-13T20:06:19.134Z","repository":{"id":32197410,"uuid":"35771021","full_name":"didip/tollbooth","owner":"didip","description":"Simple middleware to rate-limit HTTP requests.","archived":false,"fork":false,"pushed_at":"2025-01-12T17:38:14.000Z","size":253,"stargazers_count":2785,"open_issues_count":7,"forks_count":209,"subscribers_count":46,"default_branch":"master","last_synced_at":"2025-05-06T19:52:11.660Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/didip.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-05-17T15:20:03.000Z","updated_at":"2025-04-24T17:33:37.000Z","dependencies_parsed_at":"2024-01-08T16:08:46.423Z","dependency_job_id":"798a7458-b3b4-4280-975e-92c001d3fdde","html_url":"https://github.com/didip/tollbooth","commit_stats":{"total_commits":188,"total_committers":31,"mean_commits":6.064516129032258,"dds":0.4308510638297872,"last_synced_commit":"d77d7801f784a1c63fc175777bed0301cb6de9bb"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didip%2Ftollbooth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didip%2Ftollbooth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didip%2Ftollbooth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didip%2Ftollbooth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/didip","download_url":"https://codeload.github.com/didip/tollbooth/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254020478,"owners_count":22000750,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T17:01:30.482Z","updated_at":"2025-05-13T20:06:19.077Z","avatar_url":"https://github.com/didip.png","language":"Go","funding_links":[],"categories":["Misc","开源类库","Web Frameworks","Web框架","Open source library","Go","Actual middlewares","Web 框架","XML","Middlewares","中间件### 中间件","中间件"],"sub_categories":["限流器","Middlewares","中间件","Current Limiter","Microsoft Word","版本控制`版本控制相关库`","Fail injection","中間件"],"readme":"[![GoDoc](https://godoc.org/github.com/didip/tollbooth?status.svg)](http://godoc.org/github.com/didip/tollbooth)\n[![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/didip/tollbooth/master/LICENSE)\n\n## Tollbooth\n\nThis is a generic middleware to rate-limit HTTP requests.\n\n**NOTE 1:** This library is considered finished.\n\n**NOTE 2:** Major version changes are backward-incompatible. `v2.0.0` streamlines the ugliness of the old API.\n\n## Versions\n\n**v1.0.0:** This version maintains the old API but all the thirdparty modules are moved to their own repo.\n\n**v2.x.x:** Brand-new API for the sake of code cleanup, thread safety, \u0026 auto-expiring data structures.\n\n**v3.x.x:** Apparently we have been using golang.org/x/time/rate incorrectly. See issue #48. It always limits X number per 1 second. The time duration is not changeable, so it does not make sense to pass TTL to tollbooth.\n\n**v4.x.x:** Float64 for max requests per second\n\n**v5.x.x:** go.mod and go.sum\n\n**v6.x.x:** Replaced `go-cache` with `github.com/go-pkgz/expirable-cache` because `go-cache` leaks goroutines.\n\n**v7.x.x:** Replaced `time/rate` with `embedded time/rate` so that we can support more rate limit headers.\n\n**v8.x.x:** Address `RemoteIP` vulnerability concern by replacing `SetIPLookups` with `SetIPLookup`, an explicit way to pick the IP address. New `HTTPMiddleware` function which is compatible with standard routers.\n\n## Five Minute Tutorial\n\n```go\npackage main\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/didip/tollbooth/v8/limiter\"\n)\n\nfunc HelloHandler(w http.ResponseWriter, req *http.Request) {\n\tw.Write([]byte(\"Hello, World!\"))\n}\n\nfunc main() {\n\t// Create a request limiter per handler.\n\tlmt := tollbooth.NewLimiter(1, nil)\n\n\t// New in version \u003e= 8, you must explicitly define how to pick the IP address.\n\tlmt.SetIPLookup(limiter.IPLookup{\n\t\tName:           \"X-Real-IP\",\n\t\tIndexFromRight: 0,\n\t})\n\n\t// New in version \u003e= 8, HTTPMiddleware is a standard router compatible alternative to the previously used LimitFuncHandler.\n\thttp.Handle(\"/\", tollbooth.HTTPMiddleware(lmt)(http.HandlerFunc(HelloHandler)))\n\t// Old syntax:\n\t// http.Handle(\"/\", tollbooth.LimitFuncHandler(lmt, HelloHandler))\n\n\thttp.ListenAndServe(\":12345\", nil)\n}\n\n```\n\n## Features\n\n1. Rate-limit by request's remote IP, path, methods, custom headers, \u0026 basic auth usernames.\n\n    ```go\n    import (\n        \"time\"\n\n        \"github.com/didip/tollbooth/v8\"\n        \"github.com/didip/tollbooth/v8/limiter\"\n    )\n\n    lmt := tollbooth.NewLimiter(1, nil)\n\n    // or create a limiter with expirable token buckets\n    // This setting means:\n    // create a 1 request/second limiter and\n    // every token bucket in it will expire 1 hour after it was initially set.\n    lmt = tollbooth.NewLimiter(1, \u0026limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})\n\n    // New in version \u003e= 8, you must explicitly define how to pick the IP address.\n    // If IP address cannot be found, rate limiter will not be activated.\n    lmt.SetIPLookup(limiter.IPLookup{\n        // The name of lookup method.\n        // Possible options are: RemoteAddr, X-Forwarded-For, X-Real-IP, CF-Connecting-IP\n        // All other headers are considered unknown and will be ignored.\n        Name: \"X-Real-IP\",\n\n        // The index position to pick the ip address from a comma separated list.\n        // The index goes from right to left.\n        //\n        // When there are multiple of the same headers,\n        // we will concat them together in the order of first to last seen.\n        // And then we pick the IP using this index position.\n        IndexFromRight: 0,\n    })\n\n    // In version \u003e= 8, lmt.SetIPLookups and lmt.GetIPLookups are removed.\n\n    // Limit only GET and POST requests.\n    lmt.SetMethods([]string{\"GET\", \"POST\"})\n\n    // Limit based on basic auth usernames.\n    // You add them on-load, or later as you handle requests.\n    lmt.SetBasicAuthUsers([]string{\"bob\", \"jane\", \"didip\", \"vip\"})\n    // You can remove them later as well.\n    lmt.RemoveBasicAuthUsers([]string{\"vip\"})\n\n    // Limit request headers containing certain values.\n    // You add them on-load, or later as you handle requests.\n    lmt.SetHeader(\"X-Access-Token\", []string{\"abc123\", \"xyz098\"})\n    // You can remove all entries at once.\n    lmt.RemoveHeader(\"X-Access-Token\")\n    // Or remove specific ones.\n    lmt.RemoveHeaderEntries(\"X-Access-Token\", []string{\"limitless-token\"})\n\n    // By the way, the setters are chainable. Example:\n    lmt.SetMethods([]string{\"GET\", \"POST\"}).\n        SetBasicAuthUsers([]string{\"sansa\"}).\n        SetBasicAuthUsers([]string{\"tyrion\"})\n    ```\n\n2. Compose your own middleware by using `LimitByKeys()`.\n\n3. Header entries and basic auth users can expire over time (to conserve memory).\n\n    ```go\n    import \"time\"\n\n    lmt := tollbooth.NewLimiter(1, nil)\n\n    // Set a custom expiration TTL for token bucket.\n    lmt.SetTokenBucketExpirationTTL(time.Hour)\n\n    // Set a custom expiration TTL for basic auth users.\n    lmt.SetBasicAuthExpirationTTL(time.Hour)\n\n    // Set a custom expiration TTL for header entries.\n    lmt.SetHeaderEntryExpirationTTL(time.Hour)\n    ```\n\n4. Upon rejection, the following HTTP response headers are available to users:\n\n    * `X-Rate-Limit-Limit` The maximum request limit.\n\n    * `X-Rate-Limit-Duration` The rate-limiter duration.\n\n    * `X-Rate-Limit-Request-Forwarded-For` The rejected request `X-Forwarded-For`.\n\n    * `X-Rate-Limit-Request-Remote-Addr` The rejected request `RemoteAddr`.\n\n   Upon both success and rejection [RateLimit](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers) headers are sent:\n\n   * `RateLimit-Limit` The maximum request limit within the time window (1s).\n\n   * `RateLimit-Reset` The rate-limiter time window duration in seconds (always 1s).\n\n   * `RateLimit-Remaining` The remaining tokens.\n\n5. Customize your own message or function when limit is reached.\n\n    ```go\n    lmt := tollbooth.NewLimiter(1, nil)\n\n    // New in version \u003e= 8, you must explicitly define how to pick the IP address.\n    lmt.SetIPLookup(limiter.IPLookup{\n        Name:           \"X-Forwarded-For\",\n        IndexFromRight: 0,\n    })\n\n    // Set a custom message.\n    lmt.SetMessage(\"You have reached maximum request limit.\")\n\n    // Set a custom content-type.\n    lmt.SetMessageContentType(\"text/plain; charset=utf-8\")\n\n    // Set a custom function for rejection.\n    lmt.SetOnLimitReached(func(w http.ResponseWriter, r *http.Request) { fmt.Println(\"A request was rejected\") })\n    ```\n\n6. Tollbooth does not require external storage since it uses an algorithm called [Token Bucket](http://en.wikipedia.org/wiki/Token_bucket) [(Go library: golang.org/x/time/rate)](https://godoc.org/golang.org/x/time/rate).\n\n## Other Web Frameworks\n\nSometimes, other frameworks require a little bit of shim to use Tollbooth. These shims below are contributed by the community, so I make no promises on how well they work. The one I am familiar with are: Chi, Gin, and Negroni.\n\n* [Chi](https://github.com/didip/tollbooth_chi)\n\n* [Echo](https://github.com/didip/tollbooth_echo)\n\n* [FastHTTP](https://github.com/didip/tollbooth_fasthttp)\n\n* [Gin](https://github.com/didip/tollbooth_gin)\n\n* [GoRestful](https://github.com/didip/tollbooth_gorestful)\n\n* [HTTPRouter](https://github.com/didip/tollbooth_httprouter)\n\n* [Iris](https://github.com/didip/tollbooth_iris)\n\n* [Negroni](https://github.com/didip/tollbooth_negroni)\n\n## My other Go libraries\n\n* [ErrStack](https://github.com/didip/errstack): A small library to combine errors and also display filename and line number.\n\n* [Stopwatch](https://github.com/didip/stopwatch): A small library to measure latency of things. Useful if you want to report latency data to Graphite.\n\n* [LaborUnion](https://github.com/didip/laborunion): A dynamic worker pool library.\n\n* [Gomet](https://github.com/didip/gomet): Simple HTTP client \u0026 server long poll library for Go. Useful for receiving live updates without needing Websocket.\n\n## Contributions\n\nBefore sending a PR with code changes, please make sure altered code is covered with tests which are passing, and that golangci-lint shows no errors.\n\nTo check the linter output, [install it](https://golangci-lint.run/usage/install/#local-installation) and then run `golangci-lint run` in the root directory of the repository.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidip%2Ftollbooth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdidip%2Ftollbooth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidip%2Ftollbooth/lists"}