Throttling Endpoints With Node-Tastypie & Hapijs
hen you decide to open up parts of your API to the public, you will need to prepare for bad citizens, or consumers that may abuse your API. One way to safe guard against this might be throttling certain endpoints restricting them to a certain number of request per second.
Throttle ['Thraudl] -n --noun., -v --verb
- a device controlling the flow
- to choke or suffocate in any way.
Tastypie's base resource has hooks for easily implementing throttling behaviors. The default implementations are mostly for testing and debugging be provide the just such a behavior, allowing you to define a number of requests allowed during a given time frame. Setting up is very easy, and looks something like this:
Define A resource
To get started we need to define a resource. For the purposes of this post, it doesn't matter what it does. It is what it doesn't do that is more important.
var Hapi = require('hapi')
var tasypie = require('tastypie')
var Throttle = tastypie.Throttle
var Api = tastypie.Api
var Endpoints = Resource.extend({
get_list:function( bundle ){},
put_detail: function( bundle ){},
delete_detail: function( bundle ){},
post_list: function( bundle ){}
});
Define An API Namespace
An Api instance allows you to define a url namespace and will remap routes defined on resource instances when registered.
var v1 = new Api('api/v1');
Register A Throttled Resource
There is memory based throttle class that ships with tastypie for development and testing. It has three important options
at
- defines the number of requests after which to start throttling the endpointtimeframe
- defines the window in ms in which the number of requests should fallexpires
- defines an interval time in ms to check for stale attempts
The following set up would allow 100 requests in a 500ms window, and never expire attempts. A very restrictive example, but illustrates the point.
v1.use('throttle', new Endpoints({
throttle: new Throttle.Memory({ at: 100, timeframe:500, expires:null })
});
Plugin to Hapi
That is it. All that is left is to plugin your namespace into a hapi server.
var server = new Hapi.Server();
server.connection({
port:3000,
host:'0.0.0.0',
labels:['api']
});
server.register([v1], function( err ){
server.start(console.log);
});
Done! We could hammer on our resource with ab or some other bench mark tool
ab -n 3000 -c 150 http://localhost:2300/api/v1/throttled
Server Software:
Server Hostname: 192.168.0.13
Server Port: 2300
Document Path: /api/v1/throttle
Document Length: 7641 bytes
Concurrency Level: 150
Time taken for tests: 2.319 seconds
Complete requests: 3000
Failed requests: 2970
(Connect: 0, Receive: 0, Length: 2970, Exceptions: 0)
Non-2xx responses: 2970
Total transferred: 1012890 bytes
HTML transferred: 466830 bytes
Requests per second: 1293.50 [#/sec] (mean)
Time per request: 115.964 [ms] (mean)
Time per request: 0.773 [ms] (mean, across all concurrent requests)
Transfer rate: 426.49 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 6 48 168.6 15 1019
Processing: 7 35 60.8 17 442
Waiting: 7 33 60.7 16 440
Total: 16 83 180.1 33 1060
Percentage of the requests served within a certain time (ms)
50% 33
66% 38
75% 42
80% 47
90% 232
95% 367
98% 1028
99% 1032
100% 1060 (longest request)
Our results show we ended up with 2970 of 3000 requests fail ( get throttled ). Simple as that. Now, in a real world application, you may want to implement throttling based on the requesting account, user, or have different windows for GET
, PUT
, POST
, etc. But this is a pretty good start.