29

I'm actually a little surprised that I couldn't find anything after a couple hours of googling, but the problem is as follows:

I want nginx to serve as my throttle for my API.

My config file contains a well-cited example of limit_req_zone:

limit_req_zone $binary_remote_addr zone=limit:2m rate=10r/m;

along with my location directive containing the expected limit_req zone=limit nodelay;

I would love to have nginx attach headers to the response message for both the X-RateLimit-Remaining and X-RateLimit-Reset attributes. Basically have nginx use the active count of the rate=10r/m to populate X-RateLimit-Remaining and timeframe of the same rate=10r/m value to populate X-RateLimit-Reset with how many seconds are left before a refresh.

http {
    limit_req_zone $binary_remote_addr zone=login:10m rate=2r/s;
    limit_req_status 429;
    limit_conn_status 429;

    server {
        listen       80;
        server_name  [removed];

        location / {
            limit_req zone=limit nodelay;

            proxy_pass http://reverse-proxy-example;
            add_header  X-RateLimit-Remaining [nginx variable?];
            add_header  X-RateLimit-Reset [nginx variable?]
        }
}

Thoughts? Possible? Would love to avoid hitting the application to get these numbers.

shaselton
  • 578
  • 4
  • 10
  • FWIW I grepped the NGinx source code and couldn't find any references to Remaining or Reset values. – Mike Purcell Aug 13 '19 at 22:44
  • I know some frameworks have built in rate limiting, but think about that, we have to pass the requests to the app, load / execute the code, make connections to redis just to track ratelimit params. It would be much better to handle it at the server level, especially with NGinx's better and built-in memory management. – Mike Purcell Aug 14 '19 at 20:18

3 Answers3

3

I would say that this isn't possible with the upstream version of nginx.

You can find the documentation for the limit_req directive through http://nginx.org/r/limit_req, which redirects to http://nginx.org/docs/http/ngx_http_limit_req_module.html#limit_req, which conclusively shows that the module doesn't have any known variables within it.

Looking at http://ngx.su/src/http/modules/ngx_http_limit_req_module.c confirms the conjecture.

Another option is to look at http://nginx.org/docs/varindex.html, which lists all the variables — looking for limit will only get you to $limit_rate, which is an unrelated variable.


P.S. Consider that the limit_req is done through a leaky bucket method.

Without going into further details or making stuff up (the wikipedia article is huge!), I'd guess that it may not be entirely trivial to present this information to the end user in a consistent and actionable manner.

cnst
  • 25,870
  • 6
  • 90
  • 122
0

Just like @cnst's answer, there is no an approprivate varible to save the value you want, if you really want the message, you can implement a lua function to save this message. The nginx config would like this:

http {
    limit_req_zone $binary_remote_addr zone=login:10m rate=2r/s;
    limit_req_status 429;
    limit_conn_status 429;

    server {
        listen       80;
        server_name  [removed];
        set $x-ratelimit-ramaining 0;
        set $x-ratelimit-reset 0;
        access_by_lua_file your_file_name.lua;

        location / {
            limit_req zone=limit nodelay;

            proxy_pass http://reverse-proxy-example;
            add_header  X-RateLimit-Remaining $x-rate-limit-remaining;
            add_header  X-RateLimit-Reset $x-ratelimit-reset;
        }
}

thus, each request will trigger the lua script, and you can update the variables $x-rate-limit-remaining $x-ratelimit-reset in your lua function.

robin
  • 1
  • 2
-5

Packngo from Packet has a function that might be a good fit for what you need to do.

Sample from packngo:

func (r *Response) populateRate() {
    // parse the rate limit headers and populate Response.Rate
    if limit := r.Header.Get(headerRateLimit); limit != "" {
        r.Rate.RequestLimit, _ = strconv.Atoi(limit)
    }
    if remaining := r.Header.Get(headerRateRemaining); remaining != "" {
        r.Rate.RequestsRemaining, _ = strconv.Atoi(remaining)
    }
    if reset := r.Header.Get(headerRateReset); reset != "" {
        if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
            r.Rate.Reset = Timestamp{time.Unix(v, 0)}
        }
    }
}
cigloo
  • 21
  • 2