基于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