Pend
Pend

Reputation: 742

Serve multiple versions of multiple Single Page Applications from one nginx server?

I need to serve multiple versions of multiple Single Page Applications (SPAs) from the one server.

If a URL specifies a version that does not exist on the server, I want to serve a fallback version instead.

Some of the versions are actually symlinks to other versions.

# /var/www/apps/example-app
0.1.0
0.1.1
0.1.2
fooversion -> /var/www/apps/example-spa/0.1.2
latest -> /var/www/apps/example-spa/0.1.1

The version latest is the one I want to use as a fallback.

All SPAs have an index.html file.

I specifically do not want to serve a mix of files from different versions. If a version exists on the server, all requests for that version should be served from that folder.

# excerpt from nginx config
...

location ~ ^/apps/(?<appname>.+?)/(?<appversion>.+?)/(?<suffix>.*)
{
    # 'latest' is the name of the folder with the fallback version
    set $effectiveversion latest;

    # NOTE: file check does not take into account `root` directive, use full path
    if (-f /var/www/apps/$appname/$appversion/index.html) {
        set $effectiveversion $appversion;
    }

    # try path, then force to SPA index
    try_files /apps/$appname/$effectiveversion/$suffix /apps/$appname/$effectiveversion/index.html;
}

# catch requests that end without a trailing slash
location ~ ^/apps/(?<appname>.+?)/(?<appversion>[^\/]+)$
{
    try_files /NONEXISTENTFILE /apps/$appname/$appversion/;
}

...

I am aware that the if could be a problem, given the reputation it has in nginx configuration.

(Note: by suffix I mean anything after a slash that is after the version, so both /file.extension and /some/folder/that/does/notexist/ are suffixes. Files should be served if they exist, and all subfolders should serve the version's index.html.

This code currently works for the following URLs:

https://example.com/apps/bar-app/nonexistentversion/nonexistentsuffix
https://example.com/apps/bar-app/nonexistentversion/nonexistentsuffix/
https://example.com/apps/bar-app/nonexistentversion
https://example.com/apps/bar-app/nonexistentversion/
https://example.com/apps/bar-app/nonexistentversion/existentsuffix
https://example.com/apps/bar-app/existentversion/existentsuffix
https://example.com/apps/bar-app/existentversion/
https://example.com/apps/bar-app/existentversion

But it does not work for these:

https://example.com/apps/bar-app/existentversion/nonexistentsuffix
https://example.com/apps/bar-app/existentversion/nonexistentsuffix/

Currently these last two return 404s.

==> /var/log/nginx/error.log <==
2020/06/03 14:23:15 [error] 7100#7100: *1289803 open() "/var/www/apps/example-app/fooversion/somefrontendroute" failed (2: No such file or directory), client: ..., server: , request: "GET /apps/example-app/fooversion/somefrontendroute HTTP/1.1", host: "static.bar.com", referrer: ...

==> /var/log/nginx/access.log <==
[03/Jun/2020:14:23:15 +1000] ... - "GET /apps/example-app/fooversion/somefrontendroute HTTP/1.1" 404 209 - 0.000 - - - 1591158195.572 -"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" "..., ..."

Is there a way to cover these last few cases? Or maybe even a cleaner way to handle this whole problem?

Working Solution:

(99% Richard Smith's answer, I just added a rewrite)

# cover case where version has no trailing `/`
rewrite ^/apps/([^/]+?)/([^/]+)$ /apps/$1/$2/;

location ~ ^/apps/(?<app>.+?)/(?<version>.+?)/(?<rest>.*)$
{
  try_files
    /apps/$app/$version/$rest
    /apps/$app/$version/index.html
    /apps/$app/latest/$rest
    /apps/$app/latest/index.html
  ;
}

If a URI does not exist, it will serve up the index instead of a 404. This was acceptable for me

Upvotes: 1

Views: 926

Answers (1)

Richard Smith
Richard Smith

Reputation: 49692

You can probably remove the if block as the try_files directive is effectively the same as if (-f.

The last parameter of a try_files statement is not a file term. By adding =404 to the end of the statement, your last parameter becomes a regular file term, which may be the underlying cause of your problem cases.

See this document for details.

For example:

location ~ ^/apps/(?<appname>.+?)/(?<appversion>.+?)/(?<suffix>.*)$
{
    try_files 
        $uri
        /apps/$appname/$appversion/index.html
        /apps/$appname/latest/$suffix 
        /apps/$appname/latest/index.html
        =404
    ;
}

Upvotes: 1

Related Questions