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;
}
}
