项目作者: TerminalWitchcraft

项目描述 :
Rate limiter framework for Actix web
高级语言: Rust
项目地址: git://github.com/TerminalWitchcraft/actix-ratelimit.git
创建时间: 2020-01-17T04:18:18Z
项目社区:https://github.com/TerminalWitchcraft/actix-ratelimit

开源协议:MIT License

下载


Travis (.org) Crates.io Crates.io

actix-ratelimit

Rate limiting middleware framework for actix-web

This crate provides an asynchronous and concurrent rate limiting middleware based on actor
model which can be wraped around an Actix application. Middleware contains a store which is used to
identify client request.

Check out the documentation here.

Comments, suggesstions and critiques are welcome!

Usage

Add this to your Cargo.toml:

  1. [dependencies]
  2. actix-ratelimit = "0.3.1"

Version 0.3.* supports actix-web v3. If you’re using actix-web v2, consider using version 0.2.*.

Minimal example:

  1. use actix_web::{web, App, HttpRequest, HttpServer, Responder};
  2. use actix_ratelimit::{RateLimiter, MemoryStore, MemoryStoreActor};
  3. use std::time::Duration;
  4. async fn greet(req: HttpRequest) -> impl Responder{
  5. let name = req.match_info().get("name").unwrap_or("World!");
  6. format!("Hello {}!", &name)
  7. }
  8. #[actix_web::main]
  9. async fn main() -> std::io::Result<()> {
  10. // Initialize store
  11. let store = MemoryStore::new();
  12. HttpServer::new(move ||{
  13. App::new()
  14. // Register the middleware
  15. // which allows for a maximum of
  16. // 100 requests per minute per client
  17. // based on IP address
  18. .wrap(
  19. RateLimiter::new(
  20. MemoryStoreActor::from(store.clone()).start())
  21. .with_interval(Duration::from_secs(60))
  22. .with_max_requests(100)
  23. )
  24. .route("/", web::get().to(greet))
  25. .route("/{name}", web::get().to(greet))
  26. })
  27. .bind("127.0.0.1:8000")?
  28. .run()
  29. .await
  30. }

Sending a request returns a response with the ratelimiting headers:

  1. $ curl -i "http://localhost:8000/"
  2. HTTP/1.1 200 OK
  3. content-length: 13
  4. content-type: text/plain; charset=utf-8
  5. x-ratelimit-remaining: 99
  6. x-ratelimit-reset: 52
  7. x-ratelimit-limit: 100
  8. date: Tue, 04 Feb 2020 21:53:27 GMT
  9. Hello World!

Exceeding the limit returns HTTP 429 Error code.

Stores

A store is a data structure, database connection or anything which can be used to store
ratelimit data associated with a client. A store actor which acts on this store is
responsible for performiing all sorts of operations(SET, GET, DEL, etc). It is Important to
note that there are multiple store actors acting on a single store.

List of features

  • memory (in-memory store based on concurrent hashmap)
  • redis-store (based on redis-rs)
  • memcached (based on r2d2-memcache, see note to developers below)

Implementing your own store

To implement your own store, you have to implement an Actor which can handle ActorMessage type
and return ActorResponse type. Check the module level documentation for
more details and a basic example.

Note to developers

  • By default, all features are enabled. To use a particular feature, for instance redis, put this in your Cargo.toml:

    1. [dependencies]
    2. actix-ratelimit = {version = "0.3.1", default-features = false, features = ["redis-store"]}
  • By default, the client’s IP address is used as the identifier which can be customized
    using ServiceRequest instance.
    For example, using api key header to identify client:

    1. #[actix_web::main]
    2. async fn main() -> std::io::Result<()> {
    3. // Initialize store
    4. let store = MemoryStore::new();
    5. HttpServer::new(move ||{
    6. App::new()
    7. .wrap(
    8. RateLimiter::new(
    9. MemoryStoreActor::from(store.clone()).start())
    10. .with_interval(Duration::from_secs(60))
    11. .with_max_requests(100)
    12. .with_identifier(|req| {
    13. let key = req.headers().get("x-api-key").unwrap();
    14. let key = key.to_str().unwrap();
    15. Ok(key.to_string())
    16. })
    17. )
    18. .route("/", web::get().to(greet))
    19. .route("/{name}", web::get().to(greet))
    20. })
    21. .bind("127.0.0.1:8000")?
    22. .run()
    23. .await
    24. }
  • The memcache store uses a separate key to keep track of expiry since there’s no way to get ttl of keys in memcache natively yet. This means memcache store will use double the number of keys as compared to redis store. If there’s any better way to do this, please considering opening an issue!

  • It is important to initialize store before creating HttpServer instance, or else a store
    will be created for each web worker. This may lead to instability and inconsistency! For
    example, initializing your app in the following manner would create more than one stores:

    1. #[actix_web::main]
    2. async fn main() -> std::io::Result<()> {
    3. HttpServer::new(move ||{
    4. App::new()
    5. .wrap(
    6. RateLimiter::new(
    7. MemoryStoreActor::from(MemoryStore::new()).start())
    8. .with_interval(Duration::from_secs(60))
    9. .with_max_requests(100)
    10. )
    11. .route("/", web::get().to(greet))
    12. .route("/{name}", web::get().to(greet))
    13. })
    14. .bind("127.0.0.1:8000")?
    15. .run()
    16. .await
    17. }
  • To enable ratelimiting across multiple instances of your web application(multiple http servers behind load balancer), consider using a feature called session stickiness supported by popular cloud services such as AWS, Azure, etc.

Status

This project has not reached v1.0, so some instability and breaking changes are to be expected
till then.

You can use the issue tracker in case you encounter any problems.

LICENSE

This project is licensed under MIT license.

License: MIT