Rate Limit HTTP Requests
Problem this snippet solves:
This rule limits HTTP POST requests by user decoded from the Authorization: header. Only $::maxRate requests are allowed within a $::windowSecs -second window for any single user. $::maxRate per user may be specified in a class, or use the default. If no Authorization header exists, 401 Not Auth response is generated. If rate limit is reached, 501 Server Error response is generated.
Rule was tested under load, maxed out @ just under 200 TPS.
Code :
class MaxPOSTRates { #userrate "DAllen 20" "JCaples 10" "CWalker 10" "AGerace 20" } rule RateLimit_HTTPPost { # # Deb Allen, F5 Networks # April 2006 # Tested on LTM v9.2.2 and 9.2.3 # when RULE_INIT { set ::maxRate 10 ;#set later per user from class set ::windowSecs 10 ;#global #init array if non-existent array set ::postHistory { } #wipe array if already existent array unset ::postHistory } when HTTP_REQUEST { if { [HTTP::method] eq "POST" } { if {[HTTP::header exists Authorization]} { #Extract encoded user/pass from header, decode and grab only the user name set myUserID [HTTP::username] #Look up the per-user maxRate in the table set myMaxRate [findclass $myUserID $::MaxPOSTRates " "] #If none exists, use the global maxRate set above if { $myMaxRate eq "" }{ set myMaxRate $::maxRate } } else { #If no Auth header, respond "Not Auth" HTTP::respond 401 return } set currentTime [clock seconds] #we need to count requests in last $windowSecs seconds, so mark the cutoff time set windowStart [expr {$currentTime - $::windowSecs}] #find POSTs for this userID set postCount 0 #count POSTs within the window, delete those that are older foreach { requestID requestTime } [array get ::postHistory ${myUserID}*] { #count POSTs with start time > $windowStart, delete the rest if { $requestTime > $windowStart } { incr postCount 1 } else { unset ::postHistory($requestID) } } if { $postCount < $myMaxRate } { #Allow request and add new record to array w/myUserID.rand + currentTime set requestID "${myUserID}.[expr { int(10000000 * rand()) }]" set ::postHistory($requestID) $currentTime } else { #Reject request with 501 server error log local0. "Service Unavailable - User $myUserID - Current rate: $postCount - Max rate: $myMaxRate" HTTP::respond 501 return } } } }
Published Mar 18, 2015
Version 1.0CodeCentral_194
Cirrus
Joined May 05, 2019
CodeCentral_194
Cirrus
Joined May 05, 2019
- Sebastian_JäkelNimbostratus
Would not return a 501, it's not a server fault, it's a client fault and the client is rate limited. Maybe a "429 Too Many Requests" would be better, see https://tools.ietf.org/html/rfc6585