動機

之前有用過nginx做reverse proxy連到signal server 提供HTTPS連線

現在想把nginx拿來做別的事,所以先survey一下nginx的基本功能吧

location

exact match

= <pattern> 要剛好一樣,遇到後就停止搜尋,優先序最高

longest prefix match

^~ <pattern> 只要前綴符合就結束其他的比對,優先序第二高

regex match

~ <regex> or ~* <regex> 可以用?<name>來設定變數 (Perl 5.10 compatible syntax, supported since PCRE-7.0) 同時這個設定變數(name capture)也可以用在server_name

優先序第三高

prefix match

一般常見的那種,就算把這種match寫在第一個,也會先看之後有沒有前面三種的可以match 優先序最低,如果有regex的match就會用regex的match

named location

類似function

location / {
	try_files $uri $uri/ @custom
}

location @custom { 
	# ...do something
}

proxy

X-Forwarded-For & Forwarded header

X開頭的header都是自訂的header,現在有標準化就可以用標準化的header

Before

X-Forwarded-For: 12.34.56.78, 23.45.67.89
X-Real-IP: 12.34.56.78
X-Forwarded-Host: example.com
X-Forwarded-Proto: https

After

Forwarded: for=12.34.56.78;host=example.com;proto=https, for=23.45.67.89

用頓號區分ip

在官網上有把$remote_addr (srcIP)與$http_forwarded (srcIP,port)轉成Forwardedheader的code

map $remote_addr $proxy_forwarded_elem {
    # IPv4 addresses can be sent as-is
    ~^[0-9.]+$          "for=$remote_addr";

    # IPv6 addresses need to be bracketed and quoted
    ~^[0-9A-Fa-f:.]+$   "for=\"[$remote_addr]\"";

    # Unix domain socket names cannot be represented in RFC 7239 syntax
    default             "for=unknown";
}

map $http_forwarded $proxy_add_forwarded {
    # If the incoming Forwarded header is syntactically valid, append to it
    "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";

    # Otherwise, replace it
    default "$proxy_forwarded_elem";
}

proxy_set_header Forwarded $proxy_add_forwarded;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Coexistence with X-

但要注意ticket #1316的問題,$http_forwarded只會有一個for!!

simple example

server {
    listen myhost:80;
    server_name  myhost;
    location / {
        root /path/to/myapp/public;
        proxy_set_header X-Forwarded-Host $host:$server_port;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://myapp:8080;
    }
}

redirect: rewrite, if, return, set

set

set $<name> val

return & break

return status_code [text | URL] 或是 return URL break就是break

兩個都是讓rewrite或是if停下來,但return會丟status code與URL

rewrite

rewrite path1 path2 [opt] path1的比對值是$URI 也就是像hi.php?a=b path1的pattern只會與hi.php比對

如果需要$query_string被改寫 要額外處理像

if ($query_string ~* "name=(.*)") {
      set $name $1;
      rewrite /info.php /result.php?newname=$name break;
      ## rewrite /info.php /result.php?newname=$1 break;
}

rewrite /info.php /result.php?newname=$1 break;的坑是 $1是看path1比對的結果

break, last, redirect , perminate, 空白

  1. break: 不看其他的rewrite
  2. last: 回到location再跑一次
  3. redirect: 302, 臨時的redirect
  4. perminate: 301, 永久的轉移
  5. 空白: 繼續往下跑其他rewrite(好像switch阿)

last & break

借一下這裡的例子來分析

rewrite /test2 /tt break;
  location /test { ## 1
      rewrite /test2 /test3 break;
      rewrite /test /test2 last; ## 2 
      rewrite /test2 /test3 break;
  }
  location /test2 { ## 3
      return 508;
  }
  location /test3 {
      return 503;
  }

  ## /test => 508

現在先把rewrite分成 server -> locations -> location

所以在location的last,會回到上一層在跑一次比對

rewrite /tt /index.html break;
rewrite /test2 /tt last; ## 1
location /test {
	rewrite /test2 /test3 break;
	rewrite /test /test2 last;
	rewrite /test2 /test3 break;
}
location /test2 {
	return 508;
}
location /test3 {
	return 503;
}

location / {
	root   html;
	index  index.html index.htm;
}
## 2 , no match
## test2 => not exist

如果把last看成回到上一層再跑一次, server的last因為沒有上一層可以回去,所以她的行為與break,就是直接到下一層的比對

if

就是bash的if,但是沒有else的部分 可以用location的比對像regex等等

safe if

根據這裡的分析 if會產生自己的location block,如果其中有自己的content handler,就會用,不然就繼承外面的

整個rewrite module,會把他的directive翻成自己的指令 先跑自己所有的指令後才跑其他的directive

location /if-try-files {
     try_files  /file  @fallback; ## wont work

     set $true 1;

     if ($true) { ## here is new location!! forget previous location
         # nothing
     }
}

所以安全使用if的原則是

  1. if中盡量只放rewrite module的directive
  2. 把所有if放在非rewrite module的directive前面
  3. 或是用其他directive取代if,像if(-f ...)換成try_files

Virtual Host

想像有很多nginx.conf(對應一台主機) 都放在sites-available

而實際對外看到的就是在sites-enabled中有symbol link的conf

XSendfile (X-Accel)

就是當網站要送檔案時可以直接用header的內容來告訴nginx去送某個檔案。 而不是把檔案放在body中再丟出去

## /protected/iso.img
location /protected/ {
 internal;
 root   /some/path;    ## path: /some/path/protected/iso.img
 ## alias /some/path/; ## path: /some/path/iso.img
}

basic server conf

server {
    listen 8080;
    root /data/up1; #從哪邊開始找檔案

    location / {
	fastcgi_pass  localhost:9000; # fastcgi的server在哪?
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # 與bash串接string的方式一樣
        fastcgi_param QUERY_STRING    $query_string;
    }

    location ~ \.(gif|jpg|png)$ {
        root /data/images;
    }
}

full example

user       www www;  ## Default: nobody
worker_processes  5;  ## Default: 1, 能開幾個process
error_log  logs/error.log;
pid        logs/nginx.pid;
worker_rlimit_nofile 8192; ## 一個process最多能開幾個檔案

events { ## 處理與接收連線有關的參數,實際連線(socket)的參數要去http調
  worker_connections  4096;  ## Default: 1024 一個process最多能accept幾條連線
}

http {
  include    conf/mime.types; ## include就是macro展開
  include    /etc/nginx/proxy.conf;
  include    /etc/nginx/fastcgi.conf;
  index    index.html index.htm index.php;

  default_type application/octet-stream;
  log_format   main '$remote_addr - $remote_user [$time_local]  $status '
    '"$request" $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';
  server_tokens off; ## 在錯誤頁面顯示nginx的版本號?
  access_log   logs/access.log  main;


  ## 可以調tcp的參數
  sendfile     on; ## 類似splice,實現zerocopy
  tcp_nopush   on; ## TCP_CORK,等到tcp pkt到一定大小,再丟出封包
  server_names_hash_bucket_size 128; # this seems to be required for some vhosts

  ## 壓縮
  gzip on;
  gzip_vary on; ## vary是cache的key的額外key
  gzip_disable "msie6";
  gzip_proxied any; ## 找有沒有壓縮過的
  gzip_min_length 1000; 
  gzip_comp_level 6;
  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;


  server { # php/fastcgi
    listen       80;
    server_name  domain1.com www.domain1.com;
    access_log   logs/domain1.access.log  main;
    root         html;

    location ~ \.php$ {
      fastcgi_pass   127.0.0.1:1025;
    }
  }

  server { # simple reverse-proxy
    listen       80;
    server_name  domain2.com www.domain2.com;
    access_log   logs/domain2.access.log  main;

    # serve static files
    location ~ ^/(images|javascript|js|css|flash|media|static)/  {
      root    /var/www/virtual/big.server.com/htdocs;
      expires 30d;
    }

    # pass requests for dynamic content to rails/turbogears/zope, et al
    location / {
      proxy_pass      http://127.0.0.1:8080;
    }
  }

  upstream big_server_com { ## 做簡單的load balance,用rr
    server 127.0.0.3:8000 weight=5;
    server 127.0.0.3:8001 weight=5;
    server 192.168.0.1:8000;
    server 192.168.0.1:8001;
  }

  server { # simple load balancing
    listen          80;
    server_name     big.server.com;
    access_log      logs/big.server.access.log main;

    location / {
      proxy_pass      http://big_server_com;
    }
  }
}

Ref

nginx beginner full example advanced conf server name forwarded rewrite_module if is eval rewrite & query_string bunch of NGINX var list x-accel