Returning a 429 without a net::err_failed

I have some code in my haproxy.cfg file that rate limits requests and returns a 429 and it works beautifully. However, it returns a net::ERR_FAILED and skips past some error handling code I have on the front end. I’ve tried to update the haproxy.cfg to send a status with a content type of “text/plain” so I can handle the status code and let the user know what’s going on, but I’m still getting the net::ERR_FAILED. My update code is as follows:

frontend s4p
    bind 192.168.250.146:80
    stick-table  type binary  len 20  size 100k  expire 10s  store http_req_rate(10s)

    # Track client by base32+src (Host header + URL path + src IP)
    http-request track-sc0 base32+src

    # Check map file to get rate limit for path
    http-request set-var(req.rate_limit)  path,map_beg(/etc/haproxy/rates.map,20)

    # Client's request rate is tracked
    http-request set-var(req.request_rate)  base32+src,table_http_req_rate()

    # Subtract the current request rate from the limit
    # If less than zero, set rate_abuse to true
    acl rate_abuse var(req.rate_limit),sub(req.request_rate) lt 0

    # Deny if rate abuse
    http-request return status 429 content-type "text/plain" if rate_abuse
    http-request lua.cors "*" "*" "*"
    http-response lua.cors
    default_backend api_servers

Can someone help me figure out how to send a response back to the client with a net::ERR_FAILED?

Thanks!

Your browser interprets the 429 response without body and shows you a net::ERR_FAILED response. Haproxy does not return that.

You say your error handling configuration does not work. Can you show that part of the configuration?

Hi @lukastribus The error handling happens on the front end in my react site, not on the front end of the haproxy config. But here is my entire haproxy config:

# Ansible placed
global
        lua-load /etc/haproxy/cors.lua
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
        stats timeout 30s


        user haproxy
        group haproxy
        daemon

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  60s
        timeout server  60s
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http

cache mycache
    total-max-size 4095   # MB
    max-object-size 40000 # bytes
    max-age 3600          # seconds

frontend s4p
    bind 192.168.250.146:80
    stick-table  type binary  len 20  size 100k  expire 10s  store http_req_rate(10s)

    # Track client by base32+src (Host header + URL path + src IP)
    http-request track-sc0 base32+src

    # Check map file to get rate limit for path
    http-request set-var(req.rate_limit)  path,map_beg(/etc/haproxy/rates.map,20)

    # Client's request rate is tracked
    http-request set-var(req.request_rate)  base32+src,table_http_req_rate()

    # Subtract the current request rate from the limit
    # If less than zero, set rate_abuse to true
    acl rate_abuse var(req.rate_limit),sub(req.request_rate) lt 0

    # Deny if rate abuse
    http-request return status 429 content-type "text/plain" if rate_abuse
    http-request lua.cors "*" "*" "*"
    http-response lua.cors
    default_backend api_servers

backend api_servers
    balance leastconn
    default-server check maxconn 20
    http-request cache-use mycache
    http-response cache-store mycache
    server server1 192.168.250.145:80

Haproxy has a simple job and does just that:

It returns a 429 response or it does not, depending on the stick table results. That is it.

If your react site is behind haproxy, the request causing haproxy to return 429 will never even reach react, so whatever error handling you have in react there is irrelevant in this case.

If your react code is somehow between the client and haproxy, I’m not sure what you are actually trying to achieve.

So, my site is outside of haproxy. the client hits my site, which then reaches out to an api server that is behind haproxy. The goal is to stop people from hammering the api server with requests. I just want to get a 429 status code from haproxy when the client has hit a limit and let them know to slow down.

Then your problem is in the react site, as simple as that.

ok, thanks for taking a look at it.