基於Nginx+PHP驅動Web應用
配置檔案與虛擬主機
檢視Nginx的配置檔案nginx.conf
(通常位於/etc/nginx/nginx.conf
):
user vagrant; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; # multi_accept on; } http { ## # Basic Settings ## sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # server_tokens off; server_names_hash_bucket_size 64; # server_name_in_redirect off; include /etc/nginx/mime.types; default_type application/octet-stream; ## # SSL Settings ## ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE ssl_prefer_server_ciphers on; ## # Logging Settings ## access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; ## # Gzip Settings ## gzip on; ## # Virtual Host Configs ## include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; }
該配置檔案中提供了Nginx伺服器的一些基本配置,Nginx是由模組驅動的,負責HTTP服務的是http
模組。
基於Nginx驅動的所有Web站點都是通過server
模組以虛擬主機的方式配置在各自的配置檔案中,然後在nginx.conf
中通過include /etc/nginx/sites-enabled/*;
這行程式碼引入的。
我們看下 Nginx 自帶的一個虛擬主機配置 /etc/nginx/sites-enabled/default
:
server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /usr/share/nginx/html; index index.html index.htm; # Make site accessible from http://localhost/ server_name localhost; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; # Uncomment to enable naxsi on this location # include /etc/nginx/naxsi.rules } }
如果Nginx伺服器沒有配置其他站點,則訪問IP地址解析到該伺服器上的所有域名都會指向這個配置檔案,因為這個配置檔案監聽埠上指定了default_server
由於是預設虛擬主機配置,所以一個Nginx伺服器只允許配置一個標識為default_server
的虛擬主機。如果配置了多個,啟動Nginx的時候會報錯。
檢視一個laravel專案的配置檔案
server { listen 80; listen 443 ssl http2; server_name .blog.test; root "/home/vagrant/code/blog/public"; index index.html index.htm index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } access_log off; error_log /var/log/nginx/blog.test-error.log error; sendfile off; location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_intercept_errors off; fastcgi_buffer_size 16k; fastcgi_buffers 4 16k; fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; } location ~ /\.ht { deny all; } ssl_certificate /etc/nginx/ssl/blog.test.crt; ssl_certificate_key /etc/nginx/ssl/blog.test.key; }
Nginx伺服器支援幾個Web站點,就配置幾個虛擬主機,通常的做法是將虛擬主機配置到/etc/nginx/sites-available
目錄下,然後對於啟用的站點,在/etc/nginx/sites-enabled
目錄下建立對應的軟連線。
配置檔案的含義及用途:
- 監聽埠(listen):本站點監聽的埠號,一般預設為80;
- 站點域名(server_name):本站點域名,由於一天伺服器上搭建了多個站點,而TCP連線的標識中只有IP地址和埠號,伺服器如何識別客戶端訪問的是哪個站點呢?HTTP/1.1的做法是要求請求首部中必須包含Host欄位來指定訪問的域名,Nginx在接收請求時,會將解析出來的Host首部欄位值與虛擬主機的server_name值進行匹配,匹配成功則應用該虛擬主機中的配置。
- 專案根目錄(root):站點部署的目錄,一般是入口索引檔案所在的目錄;
- 索引檔案:請求URL中未指定具體資源時預設的入口檔案,可配置多個,然後以空格分割。
location
配置塊:會與請求起始行中的相對 URL 路徑進行匹配,匹配成功則應用對應配置塊中的配置,location / {...}
可以匹配所有請求,try_files
會依次訪問後面配置的每個路徑,如果通過對應 URL 可以直接訪問($uri
),比如靜態資原始檔,則直接返回響應給客戶端;否則嘗試以目錄方式訪問($uri/
);最後嘗試訪問/index.php$is_args$args
,即以 Laravel 入口檔案 + 動態引數形式訪問資源,由於該路徑包含了.php
,所以會進入下一個匹配的location
配置塊 ——location ~ \.php$ {...}
,然後通過 FastCGI 閘道器(PHP-FPM)讓後端 PHP 程式來處理動態請求。指定 PHP-FPM 程序時,可以通過 Unix 套接字,比如unix:/run/php/php7.1-fpm.sock
,也可以通過 IP 地址+埠號的形式,比如http://127.0.0.1:9000
,前者僅適用於 PHP-FPM 與 Nginx 執行在一臺伺服器,後者適用於所有場景,不過前者直接讀取本地檔案,沒有額外的網路開銷,因此從效能上來說更優,然後我們將請求的路徑、引數傳遞給 PHP-FPM,同時設定快取和超時配置;- 日誌資訊:可以通過
error_log
指定錯誤日誌路徑,access_log
指定訪問日誌路徑。
請求處理與響應傳送
建立連線
Nginx服務啟動後會啟動一個master
程序和多個worker
程序(一般與CPU個數相同),master
主要負責處理Nginx主服務的啟動、關閉與過載,以及維護worker
程序的執行狀態,具體的HTTP連線與請求處理工作由worker
程序來完成,每個worker
程序上可以處理多個連線請求,底層實現的原理是事件驅動和多路IO複用。
當我們在客戶端瀏覽器輸入應用 URL 進行訪問時,在傳送請求報文前,會先通過 DNS 查詢域名對應的伺服器 IP 地址(如果在本地 /etc/hosts
檔案有定義,會直接從這裡返回 IP 地址,不走 DNS 服務),對於 HTTP 應用來說,預設埠號是 80
,有了對方的 IP 地址和埠號,就可以通過三次握手建立與對端 Web 伺服器應用的 TCP 連線了,這個對端 Web 伺服器應用正是 Nginx,Nginx 的 master
程序在接收客戶端連線訊號後會將這個網路事件傳送給某個 worker
程序,由該 worker
程序來接管後續的連線建立和請求處理,經過這一步,就建立起了 Nginx 伺服器與本地客戶端的連線。
關於 Nginx 預設監聽埠,也可以通過應用對應的 Nginx 虛擬主機配置檔案進行修改,如果配置為其它埠號,需要在客戶端訪問該應用的時候手動指定,這樣對使用者來說不太方便,所以一般都使用預設值:
listen 80;
接收請求
Nginx的worker
程序在與客戶端建立HTTP連線後(這一步對應Socket程式設計中的accept
操作),就開始從這條連線上讀取請求報文資料(對應Socket程式設計中的read
操作)並進行解析,將解析出的資料儲存到Nginx對應的資料結構ngx_http_request_s
中。
處理請求
Nginx能對映到對應的虛擬主機配置檔案,主要依靠Nginx將從請求首部解析出的Host
欄位值與所有虛擬主機配置檔案中的server_name
配置項做對比。
通過root
配置項可以獲取到應用部署的根目錄。在通過URL訪問指定資源時,為了安全起見,我們並不會在請求中顯示指定伺服器資源的絕對路徑,而是僅僅指定資源的相對路徑,在與伺服器上的root
配置項拼接成對應資源的絕對路徑。
構建& 傳送響應
Nginx通過ngx_http_send_header
方法構造HTTP響應的起始行、響應首部,並將響應頭資訊儲存在ngx_http_request_s
的headers_out
資料結構中,然後通過 ngx_http_header_filter
方法按照 HTTP 規範將其序列化為位元組流緩衝區,最後通過 ngx_http_write_filter
方法將響應頭部發送出去
我們在 PHP 程式碼中通過
header
、set_cookie
等網路函式設定的響應頭也會通過 PHP-FPM 傳送給 Nginx