NGINX Feature Flag Reverse Proxy

  • Designer

Use NGINX as a reverse proxy to different back-end servers based on feature-flags set in the ngx_http_auth_request_module add-on. In my implementation for Secret Party the subdomain is used to determine which event/party a Note that this is a template file and some variables are set from the environment – $DOMAIN, $PROXY_*_HOST, $PROXY_*_PORT, etc.

Upstream Servers

First create the upstream servers that we can proxy the request to. Here we will use “green”, “blue”, and “red”.

# this is the server that handles the "auth" request
upstream upstream_auth {
  server $PROXY_AUTH_HOST:$PROXY_AUTH_PORT;
}

# backend app server "green"
upstream upstream_green {
  server $PROXY_GREEN_HOST:$PROXY_GREEN_PORT;
}

# backend app server "blue"
upstream upstream_blue {
  server $PROXY_BLUE_HOST:$PROXY_BLUE_PORT;
}

# backend app server "red"
upstream upstream_red {
  server $PROXY_RED_HOST:$PROXY_RED_PORT;
}

Mappings

Next we create a mapping of route name to upstream server. This will let us choose the backend/upstream server without an evil if.

# map service names from auth header to upstream service
map $wildcard_feature_route $wildcard_service_route {
  default upstream_green;
  'green' upstream_green;
  'blue' upstream_blue;
  'red' upstream_red;
}

Optionally we can also support arbitrary response codes in this mapping – note that they will be strings not numbers. This uses the auth response code to choose the route that is used for the proxy from the mapping above – so the HTTP Status Code to string to Upstream Server.

# map http codes from auth response (as string!) to upstream service
map $wildcard_backend_status $wildcard_mapped_route {
  default 'green';
  '480' 'green';
  '481' 'blue';
  '482' 'red';
}

Auth Handler

The Auth Handler is where NGINX sends the auth request so we assume we are handling something like http://upstream_auth/feature-flags/$host. This endpoint chooses the route that we use either by setting a header called X-Feature-Route with a string name that matches the mapping above, or can respond with a 4xx error code to also specify a route from the other mapping above. You get the gist.

function handleFeatureFlag(req, res) {
    // use the param/header data to choose the backend route
    // const hostname = req.params.hostname;
    const route = someFlag? 'green' : 'blue';
    // this header is used to figure out a proxy route
    res.header('X-Feature-Route', route);
    return res.status(200).send();
}
function handleFeatureFlag(req, res) {
    // this http response code can be used to figure out a proxy route too!
    const status = someFlag ? 481 : 482; // blue, red
    return res.status(status).send();
}

Server Configuration

To tie it together create a server that uses an auth request to http://upstream_auth/feature-flags/$host. This API endpoint uses the hostname to choose the upstream service to use to fulfill the request, either by setting a header of X-Feature-Route or returning an error code other than 200 or 401 – anything else will be returned as a 500 to NGINX which can then use the string value of this code as a route hint.

server {
  listen 80;

  # listen on wildcard subdomains
  server_name *.$DOMAIN;

  # internal feature flags route to upstream_auth
  location = /feature-flags {
    internal;
    # make an api request for the feature flags, pass the hostname
    rewrite .* /feature-flags/$host? break;
    proxy_pass http://upstream_auth;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URI $request_uri;
    proxy_set_header X-Original-Remote-Addr $remote_addr;
    proxy_set_header X-Original-Host $host;
  }

  # handle all requests for the wildcard
  location / {
    # get routing from feature flags
    auth_request /feature-flags;
    # set Status Code response to variable
    auth_request_set $wildcard_backend_status $upstream_status;
    # set X-Feature-Route header to variable
    auth_request_set $wildcard_feature_route $upstream_http_x_feature_route;

    # this is a 401 response
    error_page 401 = @process_backend;

    # anything not a 200 or 401 returns a 500 error
    error_page 500 = @process_backend;

    # this is a 200 response
    try_files @ @process_request;
  }

  # handle 500 errors to get the underlying code
  location @process_backend {
    # set the status code as a string mapped to a service name
    set $wildcard_feature_route $wildcard_mapped_route;

    # now process the request as normal
    try_files @ @process_request;
  }

  # send the request to the correct backend server
  location @process_request {
    proxy_read_timeout 10s;
    proxy_cache off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # use the mapping to determine which service to route the request to
    proxy_pass http://$wildcard_service_route;
  }
}

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *