Setup CORS in Nginx with ProxyPass upstream

The docker setup

Previously i tried to dockerize the PredictionIO. It’s challenging as many components are involved in the setup such as Apache HBase and Spark. It took me more than a week to make a prototype work. Other than those PredictionIO components, I also added an Nginx container as a reverse proxy for the two containers which runs the PIO event and prediction servers. The event server has a REST Event API so we could feed in real time user events through HTTP POST request. Since we want to collect the events from frontend Javascript, we have to enable CORS in Nginx. The data flow is illustrated below.

Overview

Overview

We would like to turn on CORS for requests which are from the same domain. (i.e. *.pio.com in this example)

The Nginx container port 80 is mapped to the Docker server and it the only route to connect the PIO services. It then routes the request to different upstreams based on hostname. The following are the two Nginx config files.

upstream.conf

1
2
3
4
5
6
7
upstream pio-event-server {
  server pio-event-server:7070;
}

upstream pio-prediction-server {
  server pio-prediction-server:8000;
}

pio-proxy.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# PIO Event Server reverse proxy
server {
  listen 80;
  server_name event.pio.com;

  location / {
    # Check if the origin of th request
    set $cors '';

    if ($http_origin ~* (https?://.*\.pio\.com?(:[0-9]+)?$)) {
      set $cors 'on';
    }

    if ($request_method = OPTIONS) {
      set $cors "${cors}_options";
    }

    # Allow CORS on preflight request
    if ($cors = 'on_options') {
      add_header 'Content-Length' 0;
      add_header 'Content-Type' 'text/plain; charset=utf-8';
      add_header 'Access-Control-Allow-Origin' "$http_origin";
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
      add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept';
      return 204;
    }

    # Proxy pass to upstream
    proxy_pass http://pio-event-server;
    proxy_redirect     off;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Host $server_name;

    # Allow CORS on other requests after returning from the upstreams
    if ($cors = 'on') {
      add_header 'Access-Control-Allow-Origin' "$http_origin";
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
      add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept';
    }
  }
}

# PIO Prediction Server reverse proxy
server {
  listen 80;
  server_name query.pio.com;

  location / {
    # Loadbalance
    proxy_pass http://pio-prediction-server;
    proxy_redirect     off;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Host $server_name;
  }
}

Please note that as highlighted in the proxy-pio.conf, the add_header directives have be to be stated AFTER the proxy_pass statements.

Finally, we could make use of Chrome Inspector to check if the CORS headers are added in the response.