This article was first published at:Walker AI

Rate limits are usually enforced as a defense of the service. Services need to protect themselves from overuse (whether intentional or not) to maintain service availability. In the development process of the Flask project, we met the need to limit the interface, but we did not want to build a wheel. At this time, we needed to use the Flask-Limiter tripartite library. This article explains the use of flask-limiter in detail.

1. Install

The installation depends on the environment.

PIP install Flask = = 1.1.1 Flask - Limiter = = 1.4

2. Start fast

There are two ways to express rate limits:

  • “100 per day”, “20 per hour”, “1 per second”
  • “100/day”, “20/hour”, “5/minute”, “1/second”

Rate limit can be set to global configuration, limit for all interfaces; Local restrictions can also be made through decorators; For interfaces that do not want to be restricted, the @Limiter.Exempt decorator allows you to exempt. The sample code looks like this:

Limiter = limiter (app, key_func=get_remote_address, default_limits=["100 per day", "10/hour"]) # @limiter.limit: @app.route("/slow") @limiter.limit("1 per day") def slow(): return ":(" # override_defaults: Indicates whether the limiter overrides the global limiter limit, Default: True @app.route("/medium") @limiter.limit("1/second", override_defaults=False) def medium(): Return: "|" @ # complete inheritance limiter global configuration app. The route ("/fast "def) fast () : the return" :) "# @ limiter. Exempt: @app.route("/ping") @Limiter.Exempt Ping (): return "Pong"

3. A decorator

Depending on personal preference and usage scenarios, this can be done in the following ways: Single Modification: Constraint strings can be a single constraint or a delimiter-delimited string.

@app.route("...." ) @limiter.limit("100/day; 10/hour; 1/minute") def my_route() ...

Multiple decorators: The constraint string can be a single constraint, a delimiter-delimited string, or a combination of the two.

@app.route("...." ) @limiter.limit("100/day") @limiter.limit("10/hour") @limiter.limit("1/minute") def my_route(): ...

Custom functionality: By default, rate limits are applied based on the key functionality used when the Limiter instance is initialized. Developers can implement their own features.

def my_key_func(): ... @app.route("..." ) @limiter.limit("100/day", my_key_func) def my_route(): ...

Dynamically loaded strings of limits: In some cases, rate limits need to be retrieved from sources outside the code (databases, remote APIs, etc.). This can be done by providing callable objects to the decorator.

Note: Each request on the decorated route invokes the provided callable object; for expensive retrievals, consider caching the response.

def rate_limit_from_config(): return current_app.config.get("CUSTOM_LIMIT", "10/s") @app.route("..." ) @limiter.limit(rate_limit_from_config) def my_route(): ...

4. The restricted domain

Flask_Limiter.util provides two options:

  • flask_limiter.util.get_ipaddr()Use:X-Forwarded-ForThe last IP address in the header, otherwise fall back to the requestedremote_address.
  • flask_limiter.util.get_remote_address(): Use the requestedremote_address.

Note: In real development, most projects are configured with NGINX. If you use get_remote_address directly, you will get the address of the NGINX server, which means that all requests from the NGINX server will be treated as if they were the same IP. Therefore, it is common for projects to use custom key_func!

DEF limit_key_func(): return STR (FLASK_REQUEST. HEADERS. GET (" X-FORWARD_FOR ", '127.0.0.1'))

Depending on how Nginx is configured:

Following each forwarding request, each non-transparent agent adds the upstream IP address to X_FORWARDED-FOR, separated by a comma. X-Real-IP # is generally the last level of agent to add the upstream IP address to the header; X-Forwarded-for # is more than one IP address, and X-REAL-IP is one. # If there is only one agent, the values of the two headers will be the same.

5. Sharing restrictions

Suitable for cases where the rate limit is shared by multiple routes.

Named Shared Restrictions: Decorated through the same shared_limit object.

mysql_limit = limiter.shared_limit("100/hour", scope="mysql") @app.route(".." ) @mysql_limit def r1(): ... @app.route(".." ) @mysql_limit def r2(): ...

Dynamic sharing restriction: The callable object is passed as a range, and the return value of this function will be used as the range. Note that the Callable takes one argument: a string representing the request endpoint.

def host_scope(endpoint_name): return request.host host_limit = limiter.shared_limit("100/hour", scope=host_scope) @app.route(".." ) @host_limit def r1(): ... @app.route(".." ) @host_limit def r2(): ...

6. Store the backend

Record the IP access times, used to determine whether the IP access times have reached the limit. Limiter uses memory as the storage backend by default, but in the actual development, it may involve the problem of multi-process resource not sharing, server memory consumption and so on. Generally, Redis is used as the storage backend.

  • The location of the Redis server is required, along with an optional database number.redis://localhost:6379orredis://localhost:6379/n(for database n).
  • If the Redis server is listening over a UNIX domain socketredis+unix:///path/to/sockredis+unix:///path/to/socket? db=n(for database n).
  • If the database is password protected, you can provide the password in the URL, for example,redis://:foobared@localhost:6379orredis+unix//:foobered/path/to/socket.
Limiter(default_limits=["100 per day"], key_func=limit_key_func, storage_uri=LIMITS_REDIS_STORAGE)

7. To summarize

Restrictions and quotas on API requests can prevent the system from receiving more data than it can handle to a certain extent, and ensure that the system’s resources are properly used. The above is all the content of this article, I hope to help you learn.