Search code examples
httpnginxhttp2

nginx serve page depending on protocol


I want to simultaneously optimize my site for HTTP/2 and HTTP/1.x. For HTTP/2 (and SPDY), since there are no additional round-trips for requests, I'd like to serve my CSS and JS files separately, to gain the benefit of independently caching each file. However, if I only did that, HTTP/1.x clients would suffer from additional round-trips; so for them, I'd like to serve my CSS and JS files concatenated.

Ideally, HTTP/2 users would be served this HTML:

<html>
  <head>
    <link rel="stylesheet" href="stylesheet-1.css">
    <link rel="stylesheet" href="stylesheet-2.css">
  </head>
  <body>
    <script src="script-1.js"></script>
    <script src="script-2.js"></script>
  </body>
</html>

And HTTP/1.x users would be served this HTML:

<html>
  <head>
    <link rel="stylesheet" href="all-stylesheets.css">
  </head>
  <body>
    <script src="all-scripts.js"></script>
  </body>
</html>

Is it possible to configure nginx to serve different HTML files depending on the client's protocol?


Solution

  • Yes, you can do so via the $server_protocol variable. I would usually recommend to interpolate file locations by variable expansion. But in this case I fear this would leave you open to injection attacks as the content of this variable seems to be copied verbatim from the request line.

    There is a solution by exploiting the ngx_http_map_module, though. Assuming your site sits in /srv/www:

    map $server_protocol $version {
        default       "1.1";
        "HTTP/2.0"    "2.0";
        # extra case for any SPDY version
        "~SPDY/"      "2.0";
    }
    
    server {
        listen            [::]:80;
        # The line below requires a working SSL configuration!
        listen            [::]:443 ssl http2;
        server_name       example.com
        root              /srv/www/http-1.1/htdocs;
    
        location / {
            root          /srv/www/http-$version/htdocs;
            try_files     $uri $uri/ @fallback;
        }
    
        # fallback for HTTP/1.1 files. If this fails as well, we get a 404.
        location @fallback {
            try_files     $uri $uri/ =404;
        }
    }
    

    This would serve all requests out of /srv/www/http-2.0/htdocs for HTTP/2.0 requests and out of /srv/www/http-1.1/htdocs for all others. If a resource specially crafted for HTTP/2.0 cannot be found, the coresponding file for HTTP/1.1 is being served as a fallback.