Maintenance Mode for your API

This article was originally published at esentri.com.

I recently implemented a simple maintenance mode for a REST API. Letting the web server return a 503 Service Unavailable sounds easy enough. But there was a catch displaying a maintenance page in our Angular client application.

Our setup is nothing fancy. We have an AngularJS frontend application serviced by a REST API. nginx is used as reverse proxy in front of our backend application servers. It does SSL offloading and simple round-robin load balancing. This makes nginx the central point to toggle the maintenance mode.

Communication Diagram: Angular, nginx, Java
Communication Diagram

My first approach for returning the maintenance status from nginx looked like this:

if (-f $error_root/maintenance.html) {
  return 503;
}

It checks if the maintenance.html file is present, and if it is there, the server returns 503 Service Unavailable.

I also implemented an HTTP Interceptor for our AngularJS frontend to display a nice maintenance page if any request returns a 503:

$httpProvider.interceptors.push(function ($q) {
  return {
    responseError: function (response) {
      if (response.status === 503) {
        // display maintenance page
      }
      return $q.reject(response);
    },
  };
});

Strangely enough, the maintenance page wasn’t triggered.

After a while of debugging, I found out that it was some kind of CORS issue. Although I added the Access-Control-Allow-Origin: \*-Header to all responses (with and without maintenance mode for debugging purposes) the client interpreted the CORS request with the status code 503 as failed and denied access to all response information to JavaScript. Even to the response status code. JavaScript always reported a status code of 0.

This left me with two options: either display the maintenance page on status code 0 as well or making the CORS request pass. Triggering the maintenance page on every status code 0 could have unwanted side effects, like maintenance page on other failing requests, so I rejected this solution and opted for making the CORS request pass.

I fixed this by adding an extra check in the maintenance switch in the nginx configuration. For the CORS preflight request (which is a OPTIONS request) I always return 200. Even in maintenance mode.

This way the preflight request succeeds and JavaScript has access to the status code of the following request. Now my interceptor was triggered and displayed the maintenance page as expected.

if (-f $error_root/maintenance.html) {
  if ($request_method = OPTIONS) {
    return 200;
  }
  return 503;
}

Did you encounter a similar problem? How did you solve it? Is there a better/cleaner approach to this problem?