HAProxy community

Issue with rate limiting large post requests

I am using HAProxy v2.0.13 in front of an API and have attempted to implement URL based rate limiting to try and limit connections to 5 within a 30 minute sliding window per source IP for the “/get_link” path:

frontend fe_dev
  mode http
  bind *:8081,[::]:8081
  stick-table type ip size 100k expire 30m store http_req_rate(30m)
  http-request track-sc0 src if METH_POST { path -i -m beg /get_link }
  http-request deny deny_status 429 if { sc_http_req_rate(0) gt 5 }
  default_backend be_dev

This API endpoint is called from a JavaScript function using an XMLHttpRequest() request and I am using Google Chrome v83.

var xHR = new XMLHttpRequest();
xHR.open("POST", "get_link", true);

xHR.onload = function() {
 console.log('status code is ' + this.status);
};

xHR.onerror = function() {
 console.log("onerror()");
};

var obj = {};

xHR.setRequestHeader("Content-Type", "application/json");
xHR.send(JSON.stringify(obj));

When the size of my POST request is small (i.e. a few hundred bytes) then everything works fine - after 5 requests I start getting HTTP 429 returned. I then tried with a large POST request (the content length was around 35500 bytes) and this is when Chrome started to trigger the onerror function.

I have done a tcpdump and it looks like HAProxy doesn’t wait for the whole request before sending back a 429 (output trimmed for brevity):

POST /get_link HTTP/1.1
Host: server:8081
Connection: keep-alive
Content-Length: 35687
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://server:8081
Referer: http://server:8081/index.html
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

{"req1":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXHTTP/1.1 429 Too Many Requests
content-length: 117
cache-control: no-cache
content-type: text/html
connection: close

<html><body><h1>429 Too Many Requests</h1>
You have sent too many requests in a given amount of time.
</body></html>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

From looking at tcpdump I can also see that HAProxy sends a TCP RST as soon as it has sent back the 429 even though Chrome is still sending POST data. How do I get HAProxy to play nicely and wait until it has received the whole request before rejecting it?