Failure and maintenance mode for your API

Create a maintenance page for your single-page web application

Useful to create a maintenance page for AngularJS, Angular, Ember.js, ExtJS, Knockout.js, Meteor.js, React, Vue.js, Sveite

History

I recently got the task of implementing a simple maintenance page in our Angular app. The current backend returns a simple HTML maintenance page in the maintenance mode. It sounds easy to catch the HTML page with Angular and display it. Our setup is nothing fancy. We have an Angular frontend application serviced by a REST API. Haproxy, as a reverse proxy and load balancer in front of our backend application servers, makes Haproxy the central point to toggle the maintenance mode.

My first approach for displaying the HTML page was to check the MIME type 'text/html' and then check the content of the HTML content like this:

if ('maintenance' in htmlData) return showMaintPage(htmlData);

Unfortunately, the maintenance page wasn't triggered.

After a while of debugging, I found out that it was because of the CORS issue. I found a solution by adding those CORS headers directly in haproxy here: https://stackoverflow.com/questions/34022378/haproxy-different-503-errorfile-for-options-and-post-methods
The downside was that it always returns the same static CORS headers (defined in the error file options.http) for the OPTIONS requests, even when the default backend is online. If the backend is online, the answer of an OPTIONS request must be answered by our backend because it is a response with dynamic CORS headers.

For security reasons, this is also not a good idea:

Access-Control-Allow-Origin: *

Better define which URL (front-end) is allowed to access the API

Access-Control-Allow-Origin: https://yourapp.yourdomain.com

To generate those headers dynamically, an additional app is required for this.

I'm sure others face the same issue and try to find a simple solution. The idea came up to create a separate service for this, and why not make this public for others?

Failure and Maintenance mode as a separate backend

Example response

HTTP/1.1 503 Service unavailable
Cache-Control: no-cache
Connection: close
Content-Type: application/json
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: X-Requested-With, Authorization, Origin, Content-Type, Version, Cache-Control
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin: https://yourdomain.com
Access-Control-Expose-Headers: X-Requested-With, Authorization, Origin, Content-Type, Content-Disposition, Cache-Control
Access-Control-Max-Age: 3600

{
    "errorTitle": {
        "Maintenance."
    },
    "errorMessage": {
        "We are currently performing maintenance."
    }
}
      

Example haproxy configuration

global
    maxconn 5000
    log /dev/log local0
    stats socket ${HOME}/haproxy/admin.sock user "${USER}" mode 660 level admin
    nbthread 4
    ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
    ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets

defaults
    timeout connect 10s
    timeout client 60s
    timeout server 60s
    timeout check 3s
    log global
    mode http
    option httplog
    option forwardfor
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    maxconn 3000
    http-send-name-header X-SST-Backend

frontend balancer
    bind 127.0.0.1:8080
    mode http
    default_backend server_backend

backend server_backend
    default-server check inter 10s fall 2 rise 2

    option httpchk
    http-check expect ! rstatus (502|503)

    server server1 s1.yourdomain.com:443 cookie server1 ssl verify none
    server server1 s2.yourdomain.com:443 cookie server2 ssl verify none
    server failure username.maintpage.net:443 check inter 60s fastinter 3s downinter 5s backup ssl verify none
    server maintenance username.maintpage.net:443 check inter 60s fastinter 3s downinter 5s disabled ssl verify none
    http-request add-header X-Maintpage-Response-Group 9e428b69-47e2-48b7-a694-bb2222bf48c3

nginx configuration example

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name api.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:62005/;
        proxy_intercept_errors on;
        error_page 502 503 = @fallback;
    }
    location /ws/ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://unix:/tmp/websocket.sock;
    }
    location @fallback {
        proxy_set_header  X-Maintpage-Response-Group "e8ccf76c-32c6-48d3-b67d-dbd38d474c87";
        proxy_pass https://username.maintpage.net;
    }
    ssl    on;
    ssl_certificate_key    /etc/ssl/wcenter/api.yourdomain.com.key;
    ssl_certificate        /etc/ssl/wcenter/api.yourdomain.com.pem;
    ssl_client_certificate /etc/ssl/wcenter/api.yourdomain.com.ca;
}