Some time ago I need to implement rate limiter in ruby, to use in my Ruby on Rails applications.
I splitted all code for two parts:
Rails gem called Bottleneck.
And Rails Application to demonstrate gem usage.
So, let’s move deeper and check what throttling exactly mean.
Throttling is a process that is used to control the usage of APIs by consumers during a given period. You can define throttling at the application level and API level. Throttling limit is considered as cumulative at API level.
Administrators and publishers of API manager can use throttling to limit the number of API requests per day/week/month. For example, you can limit the number of total API requests as 10000/day.
When a throttle limit is crossed, the server sends 429 message as HTTP status to the user with message content as “too many requests”.
Great, in my case, I just need to implemept two things:
-
Maximum request count per IP address.
-
Time limit for reset all counters.
Great. Let’s move one.
Step one - storage.
It can be Rails cache store, Database, NoSQL storage, Key-Value storage etc
I decided to use Redis here - extremely fast in-memory key-value data structure store with built-in key expiration functionality. It can be used as a database, cache or message broker for example.
P.S. One interesting and great thing - Rails 5.2 will be shipped with built-in Redis Cache Store.
Step two - our Bottleneck module.
What interesting here?
We read our limits from bottleneck.yml file:
And redis.yml file:
Firstly we need to init our Redis::Namespace object with Redis.new instance. We use :bottleneck namespace for our limiter.
Second thing here - load_config method uses a filename and load config file from Rails root path, or from current directory.
And third step - check method, which receive current request’s IP address. It’s a starting point for main logic of our gem.
Step three - Constants class.
Simple step with constants class. Just success status for our response, expiration status and fixed message.
Step four - main Core class.
Very simple logic, and all power of Redis EXPIRE and TTL commands usage.
Let’s check this class step-by-step:
-
We initialize class with IP address from request. Init our storage and get limits from config
-
run method creates uniq key, based on IP address, and default response.
-
then we checking our storage for existing key, and setting it, if key is nil.
-
also we set expiration time in seconds from our config file, using expire method
-
next we check if our requests count in our limit, and if no - we set status and message in our result hash, with seconds from period method, which uses ttl method for remaining number of seconds.
-
otherwise, we increment our key in Redis with incr method.
That’s it! :)