網站首頁:http://www.localhost/
前端API:http://www.localhost/api/
真正API:http://api.localhost/
例如有一則 API 服務 http://www.localhost/api/helloworld 必須重導至 http://api.localhost/helloworld 才行。由於 web server 主要服務 static files 而 app server 則是服務 api ,但所有入口點都是在 web server,因此就需要將 requests 導向給後方的 app server:
upstream backend_hosts { server api.localhost:80; }
set $request_url $request_uri;
if ($uri ~ ^/api(.*)$ ) {
set $request_url $1;
}
location ^~ /api/ {
proxy_read_timeout 300;
proxy_set_header Host $http_host; proxy_redirect off;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend_hosts$request_url$is_args$args;
proxy_buffering off;
}
這樣,看似無限美好,但是有個關鍵要留意,上頭有做一次字串處理:set $request_url $1; 這一步把 /api/helloworld 轉成 /helloworld 沒錯,但 $request_url 變數連帶處理了解碼的問題,當整個 requests 只是常見的英數符號時,都是一切正常的,但如果是帶有中文字等需要做 URLEncode/Decode 時,就會出包啦。
$ sudo apt-get install nginx
$ sudo vim /etc/nginx/conf.d/www.conf
upstream backend_hosts { server 127.0.0.1:8000; }
# Web Server
server {
listen 3000;
set $request_url $request_uri;
if ($uri ~ ^/api(.*)$ ) {
set $request_url $1;
}
location ^~ /api/ {
proxy_read_timeout 300;
proxy_set_header Host $http_host; proxy_redirect off;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend_hosts$request_url$is_args$args;
proxy_buffering off;
}
location ^~ / {
add_header X-WWW-Request-URL $request_url;
add_header X-WWW-Request-URI $request_uri;
return 200;
}
}
# APP Server
server {
listen 8000;
location ^~ / {
add_header X-API-Request-URI $request_uri;
return 200;
}
}
$ sudo service nginx configtest && sudo service nginx restart
接著,使用 curl 來驗證:
$ curl -s -D - 'http://127.0.0.1:3000/'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-WWW-Request-URL: /
X-WWW-Request-URI: /
看見 X-WWW-Request-URL 和 X-WWW-Request-URI 代表為 3000 port 服務。
$ curl -s -D - 'http://127.0.0.1:3000/api/helloworld?abc=123'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-API-Request-URI: /helloworld?abc=123
可以看到 X-API-Request-URI: /helloworld?abc=123 時,代表有將 requests 導向到 8000 port 的服務。
接著,試試看URLEncode吧!
$ curl -s -D - 'http://127.0.0.1:3000/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-WWW-Request-URL: /%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A
X-WWW-Request-URI: /%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A
在 3000 port 服務中,X-WWW-Request-URL 和 X-WWW-Request-URI 一致是非常正常,但是,換成 /api/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A 就會出包啦:
$ curl -s -D - 'http://127.0.0.1:3000/api/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-API-Request-URI: /%e4%bd%a0%e5%a5%bd%e5%97%8e%0d
仔細看會發現 X-API-Request-URI 缺少 %0A 的資料,並且看到 request_uri 有點像是被重新組合過,從原本的大寫變小寫了,但最重要的是有缺資料!
而解法?就是改用 rewrite 來避開了,但是對於這種 URLEncode 的,還必須有前置處理,例如定義新的服務網址: /api/urlencode/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A 用法,接著更新 nginx 設定檔:
upstream backend_hosts { server 127.0.0.1:8000; }
server {
listen 3000;
set $request_url $request_uri;
if ($request_uri ~ ^/api(.*)$ ) {
rewrite ^ $request_uri;
rewrite ^/api(.*)$ $1;
set $request_url $1;
}
location ^~ / {
add_header X-WWW-Request-URL $request_url;
add_header X-WWW-Request-URI $request_uri;
return 200;
}
location ^~ /urlencode {
proxy_read_timeout 300;
proxy_set_header Host $http_host; proxy_redirect off;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend_hosts$request_url;
proxy_buffering off;
}
}
server {
listen 8000;
location ^~ / {
add_header X-API-Request-URI $request_uri;
return 200;
}
}
如此一來,就會看到 URLENCODE 資訊是沒有被解析過,完整的 pass 到 8000 port 服務囉
$ curl -s -D - 'http://127.0.0.1:3000/api/urlencode/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A'
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-API-Request-URI: /urlencode/%E4%BD%A0%E5%A5%BD%E5%97%8E%0D%0A