NGINX Feature Flag Reverse Proxy
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; } }