1. 程式人生 > 實用技巧 >基於Nginx+PHP驅動Web應用

基於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_sheaders_out資料結構中,然後通過 ngx_http_header_filter 方法按照 HTTP 規範將其序列化為位元組流緩衝區,最後通過 ngx_http_write_filter 方法將響應頭部發送出去

我們在 PHP 程式碼中通過 headerset_cookie 等網路函式設定的響應頭也會通過 PHP-FPM 傳送給 Nginx