Throttling Endpoints With Node-Tastypie & Hapijs

W

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

  1. a device controlling the flow
  2. 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 endpoint
  • timeframe - defines the window in ms in which the number of requests should fall
  • expires - 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.

tastypie REST hapi node.js