本文是一篇简要的技术笔记。Nginx 作为反向代理,根据不同的 HTTP 方法,选择不同的 upstream。
1 if is evil
根据需求,我们很自然的想到在 location
块中添加 if
判断语句。 如
if ($request_method = POST ) {
return 405;
}
在 location
块编排逻辑是可以完成分流的。但请务必参考 nginx 官网的警告文章:
文章中警告,在 location
使用多个 if
编排逻辑时,容易出现反直觉的异常现象。并提示到这不是 bug,是因为 nginx 对 if 的实现原理。这种错误甚至可以严重到 SIGSEGV
。
笔者也直接在这里粘贴官方给的 evil 实例,看看是否符合读者的预期呢?
# Here is collection of unexpectedly buggy configurations to show that
# if inside location is evil.
# only second header will be present in response
# not really bug, just how it works
location /only-one-if {
set $true 1;
if ($true) {
add_header X-First 1;
}
if ($true) {
add_header X-Second 2;
}
return 204;
}
# request will be sent to backend without uri changed
# to '/' due to if
location /proxy-pass-uri {
proxy_pass http://127.0.0.1:8080/;
set $true 1;
if ($true) {
# nothing
}
}
# try_files wont work due to if
location /if-try-files {
try_files /file @fallback;
set $true 1;
if ($true) {
# nothing
}
}
# nginx will SIGSEGV
location /crash {
set $true 1;
if ($true) {
# fastcgi_pass here
fastcgi_pass 127.0.0.1:9000;
}
if ($true) {
# no handler here
}
}
# alias with captures isn't correcly inherited into implicit nested
# location created by if
location ~* ^/if-and-alias/(?<file>.*) {
alias /tmp/$file;
set $true 1;
if ($true) {
# nothing
}
}
另外文中指出,对于复杂的 if
逻辑,可以使用 lua-nginx-module。
2 使用 map 分流 upstream
如果不使用额外的 lua
脚本,笔者在生产环境中使用 map
数据结构,参考了这篇 segmentfault 答案。
upstream webdav_default {
server ...;
}
upstream webdav_upload {
server ...;
}
upstream webdav_download {
server ...;
}
map $request_method $upstream_location {
GET webdav_download;
HEAD webdav_download;
PUT webdav_upload;
LOCK webdav_upload;
default webdav_default;
}
server {
location / {
proxy_pass https://$upstream_location;
}
}
注:
map
可以和upstream
同级共同管理,储存在server
块之外。- 实际生产环境比较复杂,有各类
header
和rewrite
等操作。避免在locaiton
中使用if
。
3 多级 map 分流
可以使用多个连续 map, 如:
location / {
proxy_pass http://$upstream_server_by_host;
...
在 upstream.conf
中:
map $host $upstream_server_by_host {
example.com $upstream_server_1_by_method;
default $upstream_server_2_by_method;
}
map $request_method $upstream_server_1_by_method {
GET upstream_server1;
HEAD upstream_server2;
default upstream_server3;
}