Search This Blog

Writing a download server. Part V: Throttle download speed

In the age of botnets that you can rent for few hundred bucks and run your very own distributed-denial-of-service attack, having emergency switches that selectively turn off expensive functionality or degrade performance gratefully is a huge win. Your application is still operational while you mitigate the problem. Of course such safety measure are also valuable under peaks or business hours. One of such mechanisms applying to download servers is throttling download speed dynamically. In order to prevent distributed denial of service attack and excessively high cloud invoices, consider built-in download throttling, that you can enable and fine-tune at runtime. The idea is to limit maximum download speed, either globally or per client (IP? Connection? Cookie? User agent?).

I must admit, I love java.io design with lots of simple Input/OutputStream and Reader/Writer implementations, each having just one responsibility. You want buffering? GZIPing? Character encoding? File system writing? Just compose desired classes that always work with each other. All right, it's still blocking, but it was designed before reactive hipsters were even born. Nevermind, java.io also follows open-closed principle: one can simply enhance existing I/O code without touching built-in classes - but by plugging in new decorators. So I created a simple decorator for InputStream that slows down reading resource on our side in order to enforce given download speed. I am using my favorite RateLimiter class:

Arbitrary InputStream can be wrapped with ThrottlingInputStream so that reading is actually slowed down. You can either create new RateLimiter per each ThrottlingInputStream or one global, shared by all downloads. Of course one might argue that simple sleep() (what RateLimiter does underneath) wastes a lot of resources, but let's keep this example simple and avoid non-blocking I/O. Now we can easily plug decorator in:

Example above limits download speed to 64 KiB/s - obviously in real life you would want to have such number configurable, preferably at runtime. BTW we already talked about the importance of Content-Length header. If you monitor the progress of download with pv, it will correctly estimate remaining time, which is a nice feature to have: