在 Nginx 的 ProxyPass Upstream 設定 CORS (跨來源資源共享)

設定 Docker

不久前嘗試 Docker 化整套 PredictionIO(PIO) 設置。當中包括 Apache HBase 和 Spark 的設定,令人非常頭痛。最後花了一個多星期才做到一個可以勉強運行的原型。除了這些 PredictionIO 組件外,我還添加了一個 Nginx 容器作為運行 PredictionIO Event(事件)和 Prediction(預測)服務器的兩個容器的 Reverse Proxy(反向代理)。事件服務器有一個 REST Event(事件) API,可以把實時的用戶事件通過 HTTP POST 請求發送到事件服務器。由於我們想從前端 Javascript 收集事件並發送到事件服務器,我們必須在 Nginx 中啟用 CORS(跨來源資源共享)。數據流如下圖所示。

概覽

概覽

我們希望打開來自同一個域的 CORS 請求。(即本例中的* .pio.com)

Nginx 容器端口80被映射到 Docker 伺服器,它是連接兩國個 PIO 服務的唯一通路。然後根據主機名將請求路由到不同的上游。以下是兩個 Nginx 的配置文件。

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;
  }
}

請注意,如 proxy-pio.conf 中強調的那樣,add_header 指令必須寫在 proxy_pass 語句之後。

最後,我們可以利用 Chrome Inspector 來檢查是否在 Response header(回應標頭)中添加了 CORS 的頭欄位。