jsonnginxhttp-accept-header

nginx error pages based on accept header do not work for json requests


I'm trying to build a nginx-based maintenance mode application, that catches all requests to my applications and returns a predefined response as a 503.

I currently have applications requesting json responses as well as users accessing the pages with their browsers. So in case the request contains the header Accept: application/json, I want to respond with the content of a json file maintenance.json, otherwise with an html file maintenance.html.

My current nginx config looks like this:

server {
    listen  8080;
    root   /usr/share/nginx/maintenance;
    server_tokens off;

    error_page 503 = @unavailable;

    location ^~ / {
      return 503;
    }

    location @unavailable {
      set $maintenanceContentType text/html;
      set $maintenanceFile /maintenance.html;
      if ($http_accept = 'application/json') {
        set $maintenanceContentType application/json;
        set $maintenanceFile /maintenance.json;
      }

      default_type $maintenanceContentType;
      try_files $uri $maintenanceFile;
    }
}

For browser requests to any path this works out fine: "https://maintenance.my-domain.local/some-path". I get the response code and the html content.

But for requests with header Accept: application/json I get a 404 html page. And the nginx log shows [error] 21#21: *1 open() "/usr/share/nginx/maintenance/some-path" failed (2: No such file or directory), client: 10.244.2.65, server: , request: "GET /asd HTTP/1.1", host: "maintenance.my-domain.local".

It seems like json requests are ignoring my location for some reason. When I remove the directive to set the appropriate file and just always return the html this also works for json-requests.

Anyone any idea?

I'm not necessarily looking for a fix for this specific config, but rather for something that fits my needs of responding with different "error pages" based on the Accept header.

Thanks in advance!

EDIT: For some reason this now results in an HTTP 200 instead of a 503. Don't know what I changed..

EDIT2: Managed to fix a part of it:

server {
    listen  8080;
    root /usr/share/nginx/maintenance;
    server_tokens off;

    location ^~ / {
      if ($http_accept = 'application/json') {
        return 503;
      }
      try_files /maintenance.html =404;
    }

    error_page 503 /maintenance.json;
    location = /maintenance.json {
      internal;
    }

}

With this config I now get the maintenance page when using the browser and the maintenance json, when defining the header Accept: application/json. The browser response code is 200 now though...


Solution

  • Ok, I found the solution to my problem.

    # map the incoming Accept header to a file extension
    map $http_accept $accept_ext {
      default html;
      application/json json;
    }
    
    server {
        listen 8080;
        root /usr/share/nginx/maintenance;
        server_tokens off;
    
        # return 503 for all incoming requests
        location ^~ / {
          return 503;
        }
    
        # a 503 redirects to the internal location `@maintenance`. the
        # extension of the returned file is decided by the Accept header map
        # above (404 in case the file is not found).
        error_page 503 @maintenance;
        location @maintenance {
          internal;
          try_files /maintenance.$accept_ext =404;
        }
    
    }
    

    Key was the map on the top. I just added application/json there and mapped everything else to the html file by default. But you could add multiple other files/file types there of course.