Symlink redirects in nginx
Solved an interesting problem this week using nginx.
We have an internal nginx webserver for distributing datasets with
dated filenames, like foobar-20190213.tar.gz
. We also create a symlink
called foobar-latest.tar.gz
, that is updated to point to the latest
dataset each time a new version is released. This allows users to just
use a fixed url to grab the latest release, rather than having to scrape
the page to figure out which version is the latest.
Which generally works well. However, one wrinkle is that when you download
via the symlink you end up with a file named with the symlink filename
(foobar-latest.tar.gz
), rather than a dated one. For some use cases this
is fine, but for others you actually want to know what version of the dataset
you are using.
What would be ideal would be a way to tell nginx to handle symlinks differently
from other files. Specifically, if the requested file is a symlink, look up the
file the symlink points to and issue a redirect to request that file. So you'd
request foobar-latest.tar.gz
, but you'd then be redirected to
foobar-20190213.tar.gz
instead. This gets you the best of both worlds - a
fixed url to request, but a dated filename delivered. (If you don't need dated
filenames, of course, you just save to a fixed name of your choice.)
Nginx doesn't support this functionality directly, but it turns out it's pretty easy to configure - at least as long as your symlinks are strictly local (i.e. your target and your symlink both live in the same directory), and as long as you have the nginx embedded perl module included in your nginx install (the one from RHEL/CentOS EPEL does, for instance.)
Here's how:
1. Add a couple of helper directives in the http
context (that's outside/as
a sibling to your server
section):
# Setup a variable with the dirname of the current uri
# cf. https://serverfault.com/questions/381545/how-to-extract-only-the-file-name-from-the-request-uri
map $uri $uri_dirname {
~^(?<capture>.*)/ $capture;
}
# Use the embedded perl module to return (relative) symlink target filenames
# cf. https://serverfault.com/questions/305228/nginx-serve-the-latest-download
perl_set $symlink_target_rel '
sub {
my $r = shift;
my $filename = $r->filename;
return "" if ! -l $filename;
my $target = readlink($filename);
$target =~ s!^.*/!!; # strip path (if any)
return $target;
}
';
2. In a location
section (or similar), just add a test on $symlink_target_rel
and issue a redirect using the variables we defined previously:
location / {
autoindex on;
# Symlink redirects FTW!
if ($symlink_target_rel != "") {
# Note this assumes that your symlink and target are in the same directory
return 301 https://www.example.com$uri_dirname/$symlink_target_rel;
}
}
Now when you make a request to a symlinked resource you get redirected instead to the target, but everything else is handled using the standard nginx pathways.
$ curl -i -X HEAD https://www.example.com/foobar/foobar-latest.tar.gz
HTTP/1.1 301 Moved Permanently
Server: nginx/1.12.2
Date: Wed, 13 Feb 2019 05:23:11 GMT
Location: https://www.example.com/foobar/foobar-20190213.tar.gz
blog comments powered by Disqus