Nginx入门实践总结

Posted 炒粉加蛋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx入门实践总结相关的知识,希望对你有一定的参考价值。

前言

nginx是俄罗斯人 Igor Sysoev 以事件驱动的方式编写的开源、高效、可靠和轻量的Web服务器。在开发上,它属于开源软件,允许商用化,且网络与程序配置通俗易懂,支持24小时不间断运行,还能不中断服务的情况下对软件版本进行热更新;在性能上,在Web服务器中对比,它的内存消耗很小,并且能支持五万个的并发连接数,可以达到更高的访问效率;在功能上,支持HTTPS、反向代理、负载均衡、拦截请求、动静分离、浏览器缓存、跨域限制和防盗链等功能;在安装配置上,Nginx安装简单,而且配置灵活。


详细请参考web服务器的受欢迎程度图,如下:


上图数据来自于https://w3techs.com,截止2020年4月1日在可以看出Nginx在web服务器占有率为32.1%,排名第二,并且还在不断增长。

大致了解Nginx的背景,那么赶紧进入正题!!!


该篇文章代码演示使用的配置如下:

1. 服务器配置:Linux CentOS 7.6

2. Nginx版本:1.16.1


PS:该篇文章需要对Linux有一定的了解,否则不利于理解本文,建议自行学习相关Linux基础知识。




安装


Nginx安装方法推荐使用Linux CentOS的yum包管理器进行下载安装即可,可以实现快速安装,减少不必要的操作,安装步骤如下:


PS:yum,是Yellow dog Updater, Modified 的简称,是杜克大学为了提高RPM 软件包安装性而开发的一种软件包管理器。


1. 查询是否安装了yum包管理器,检测yum是否已配置nginx源

yum list | grep nginx


已安装yum包管理器,并且存在nginx源,如下图显示:

Nginx入门实践总结


PS:yum包管理器的源版本,对比Nginx官网的发行正式版本会低一些,如需安装最新的nginx版本,需手动配置nginx源进行安装,最新版本请参考 http://nginx.org/


2. 出现上图的话,即可直接使用yum安装nginx

yum install nginx


3. 安装完成,则使用 nginx -v 检测是否安装成功,安装成功如下图:

Nginx入门实践总结


该方法只是Nginx安装方法之一,这里不补充其他安装方法了。


Nginx安装完成之后,安装在什么目录呢?




目录


查看nginx安装目录可以使用 rpm 命令查看:

rpm -ql nginx


PS:rpm 命令是RPM软件包的管理工具让Linux易于安装,升级,提升了Linux的适用度。

-q 代表使用询问模式,当遇到任何问题时,rpm指令会先询问用户

-l  代表显示套件的文件列表


回车输入显示,如下图:

Nginx入门实践总结


了解安装的目录及相关文件,如何启动nginx呢?



命令


默认情况下,nginx是不会自行启动,需要手动启动,只需要往控制台输入如下命令:

nginx  # 该方法支持 Linux Centos 7.4以上systemctl start nginx  # 常规 Linux 系统启动


PS:Linux systemctl 是一个系统管理守护进程、工具和库的集合,初始进程主要负责控制systemd系统和服务管理器。


完成启动后,如何检测是否启动成功呢?

可以使用组合命令 ps - aux | grep nginx 查询服务运行状况


Nginx入门实践总结


输出如上图三条记录,则说明服务nginx已经启动。


接着验证是否正常访问网页,输入命令 curl 127.0.0.1 ,看能否正常输出html内容,如下图(该图为部分截图):

Nginx入门实践总结


显示如上图,则表示nginx正常启动。


PS:
一般情况下,Nginx启动后会监听80端口,从而提供HTTP服务,如80端口被占用,则会启动失败。因此可以使用 netstat -tlnp 命令查看端口号的占用情况。


只了解启动命令是不够的,还需要了解更多Nginx常用命令。

在控制台中输入
 nginx -h 就可以看到完整的命令,如下图


Nginx入门实践总结


这里整理出常用命令,如下:

nginx -s reload  # 向主进程发送信号,重新加载配置文件,热重启 nginx -s reopen  # 重启 Nginx nginx -s stop    # 快速关闭 nginx -s quit    # 等待工作进程处理完成后关闭 nginx -T         # 查看当前 Nginx 最终的配置 nginx -t -c <配置路径>   # 检查配置是否有问题,如果已经在配置目录,则不需要-c


对应的 systemctl 命令如下

systemctl start nginx    # 启动 Nginx systemctl stop nginx     # 停止 Nginx systemctl restart nginx  # 重启 Nginx systemctl reload nginx   # 重新加载 Nginx,用于修改配置后 systemctl enable nginx   # 设置开机启动 Nginx systemctl disable nginx  # 关闭开机启动 Nginx systemctl status nginx   # 查看 Nginx 运行状态


有了一定的理解,接下来就是介绍Nginx的核心配置。




配置


Nginx主配置文件为 /etc/nginx/nginx.conf(默认存放路径),文件内容结构如下图所示:


Nginx入门实践总结


大致结构代码表示为如下:

...              # 配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。events {         # 配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。 ...}http {           # 可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。    server {     # 配置虚拟主机的相关参数,一个http中可以有多个server。        location [PATTERN] {  # 配置请求的路由,以及各种页面的处理情况。 ... }        location [PATTERN] { ... } }    server { ...    }}


配置文件的语法规则如下:

  1. 配置文件都是以 block 的形式组织;

  2. 配置项由指令与指令块构成;

  3. 每条指令以 ; 分号结尾,指令与参数间以空格符号分隔;

  4. 指令块以 {} 大括号将多条指令组织在一起;

  5. include 语句允许组合多个配置文件以提升可维护性;

  6. 使用 # 符号添加注释,提高可读性;

  7. 使用 $ 符号使用变量;

  8. 部分指令的参数支持正则表达式;


大致理解配置文件的结构和语法,接下来就是查看 nginx.conf 的配置:

# 运行用户,默认即是nginxuser  nginx;
# Nginx 进程数,一般设置为和 CPU 核数一样                     worker_processes  1;
# Nginx 的错误日志存放目录 error_log  /var/log/nginx/error.log warn;
# Nginx 进程 pid 存放位置 pid /var/run/nginx.pid;
events {    # 每个进程允许最大并发数     worker_connections 1024;}
http {    # 文件扩展名与类型映射表     include             /etc/nginx/mime.types;
    # 默认文件类型           default_type        application/octet-stream;
    # 设置日志模式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';
    # Nginx访问日志存放位置 access_log /var/log/nginx/access.log main;
    sendfile            on;   # 开启高效传输模式     tcp_nopush          on;   # 减少网络报文段的数量     tcp_nodelay         on;     keepalive_timeout   65;   # 保持连接的时间,也叫超时时间,单位秒     types_hash_max_size 2048;
    include /etc/nginx/conf.d/*.conf;   # 加载 /etc/nginx/conf.d 目录下子配置项
server {      listen       80;       # 配置监听的端口      server_name  localhost;    # 配置的域名
location / {        root   /usr/share/nginx/html;  # 网站根目录        index  index.html index.htm;   # 默认首页文件        deny all;   # 禁止访问的ip地址,可以为all        allow all;# 允许访问的ip地址,可以为all }
error_page 500 502 503 504 /50x.html; # 默认50x对应的访问页面      error_page 400 404 error.html;   # 同上 }}


server 块可以包含多个 location 块,location 指令用于匹配 uri。location 关键字,后面跟着可选的修饰符,修饰符后面是要匹配的字符,花括号中是要执行的操作,语法规则如下:

location [ = | ~ | ~* | ^~ ] uri { ... }location @name { ... } 


location 指令修饰符的含义:

  1. =    表示精确匹配路径,用于不含正则表达式的 uri 前,如果匹配成功,不再进行后续的查找(根据操作系统进行大小写区分);

  2. ^~ 表示如果该符号后面的字符是最佳匹配,采用该规则,不再进行后续的查找;

  3. ~    表示用该符号后面的正则去匹配路径,区分大小写;

  4. ~*  表示用该符号后面的正则去匹配路径,不区分大小写。


当有多条 location 规则时,nginx匹配规则优先级如下:

    1. 精确匹配 =

    2. 前缀匹配 ^~(立刻停止后续的正则搜索)

    3. 按文件中顺序的正则匹配 ~或~*

    4. 匹配不带任何修饰的前缀匹配,优先使用返回最长匹配配置,其次是文件顺序

根据上述规则,可以在子配置项目录 
/etc/nginx/conf.d 建立一个新的配置,如下:

server {  listen 8081;  server_name localhost;    location = /document {        return 701;    }    location ^~ /doc {        return 702; }    location ~* ^/docu[a-z]+$ {        return 703;    }    location ~ ^/docu[a-z]+$ {        return 704; }    location /document {        return 705;    }    location /aa {        return 801; }    location /aaa {        return 802; }}
# 以下是调试信息curl -I localhost:8081/test # HTTP/1.1 404 Not Found(无匹配)curl -I localhost:8081/document # 返回HTTP/1.1 701curl -I localhost:8081/docu # 返回HTTP/1.1 702curl -I localhost:8081/documents # 返回HTTP/1.1 702curl -I localhost:8081/DOCUMENTS # 返回HTTP/1.1 703
curl -I localhost:8081/a # HTTP/1.1 404 Not Found(无匹配)curl -I localhost:8081/aa # 返回HTTP/1.1 801(文件顺序)curl -I localhost:8081/aaaa # 返回HTTP/1.1 802(优先使用返回最长匹配)


更改完配置,需要使用命令 nginx -s reload 使 nginx 重新加载配置!!!


PS:curl 是常用的命令行工具,用来请求 Web 服务器。curl -I xxx.com 表示向服务器发出 HEAD 请求,然会将服务器返回的 HTTP 标头打印出来。


nginx 内置预定义变量,可以在配置文件的任何位置使用它们,常见的有如下:

$host
请求信息中的 Host,若请求中没有 Host 行,等于设置的服务器名,不含端口
$request_method 客户端请求类型,如 GET、POST
$args 请求参数
$arg_PARAMETER

GET 请求中变量名 PARAMETER 参数的值
例如:$http_HEADER(请求头),$http_user_agent(Uaer-Agent 值)

$content_length 请求头中的 Content-length 字段
$http_user_agent 客户端agent信息
$http_cookie 客户端cookie信息
$remote_addr 客户端的IP地址
$remote_port 客户端的端口
$server_protocol 请求使用的协议,如 HTTP/1.0、HTTP/1.1
$server_addr 服务器地址
$server_name‍‍ 服务器名称
$server_port 服务器的端口号
$scheme HTTP方法,如HTTP、HTTPS


上述表格为常用预定义变量,如需了解更多,请自行咨询查询。

了解Nginx基础,应该怎么应用呢?



应用

域名


有了服务器和域名,搭建好了Nginx,就可以配置域名映射关系了。首先将二级域名映射到对应服务器,然后Nginx上配置一下应用的访问监听,就可以接收配置的二级域名的请求,域名配置如下图所示:

Nginx入门实践总结


现配置了一个 test 的二级域名,也就是说在外网访问 test.iwenhaha.cn 的时候,即可访问到服务器了。


server { listen 80; server_name test.iwenhaha.cn; location / {    root x/xx/xxx; index index.html; }}




代理


Nginx 经常被用作代理服务,那么什么是代理?


代理(英語:Proxy)也称网络代理,是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接


代理可以分为 正向代理 和 反向代理。

不使用代理的情况,访问流程是客户端直接向目标服务器发送请求并获取内容。

正向代理:客户端向代理服务器发送请求,代理服务器转发至指定目标服务器并获得内容,再返回给客户端。

正向代理隐藏了真实的客户端,为客户端收发请求,使真实客户端对服务器不可见,应用
如某翻墙工具等。

如下图所示:

Nginx入门实践总结



反向代理:客户端请求
代理服务器,代理服务器将请求转发给内部网络上真正进行处理的服务器,得到的结果返回给客户端。


反向代理隐藏了真实的服务器,为服务器收发请求,使真实服务器对客户端不可见。目前大多数大型网站都设置了反向代理。


如下图所示:

Nginx入门实践总结


一般来
说,给客户端做代理的都是正向代理,给服务器做代理的就是反向代理。

实际使用中,可以将请求转发到该服务器上的不同应用上,根据访问的路径跳转到不同端口的应用中,实现反向代理功能。


可以启动并监听服务器 8080 端口,接收请求并通过规则区分并分发至不同应用,在http模块下增加相应 server 配置,如下:

server {  listen 8080;  server_name localhost;  location ~ /service1/ {    proxy_pass http://127.0.0.1:9090;  }  location ~ /service2/ {    proxy_pass http://127.0.0.1:9091; }}


则可以达到以下效果:

1. 将访问 http://127.0.0.1:8080/service1 的请求转发到 http://127.0.0.1:9090

2. 将访问 http://127.0.0.1:8080/service2 的请求转发到 http://127.0.0.1:9091


补充相关Nginx的反向代理的其它指令:

 proxy_set_header:在将客户端请求发送给后端服务器之前,更改来自客户端的请求头信息。

 proxy_connect_timeout:配置Nginx与后端代理服务器尝试建立连接的超时时间。

 proxy_read_timeout:配置Nginx向后端服务器组发出read请求后,等待相应的超时时间。

 proxy_send_timeout:配置Nginx向后端服务器组发出write请求后,等待相应的超时时间。

 proxy_redirect:用于修改后端服务器返回的响应头中的Location和Refresh。




跨域:


浏览器同源策略:用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。假设A、B为非"同源"网页,则浏览器中的A网页无法操作B网页内容,主要为了保证用户信息的安全,防止恶意的网站窃取数据。


"同源" :表示两个 URL 的 表示两个 URL 的协议 (protocol)、端口(port) 和 域名(host) 都相同的话,则判断两个 URL 是同源


以 http://www.xxx.com 进行对比示栗,如下:

http://www.Xxx.com:80/dir/inner/another.html # 同源  大小写区别 ( http:// 默认端口是80)http://www.xxx.com/dir2/other.html  # 同源  只有路径不同https://www.xxx.com/secure.html  # 失败  协议不同http://www.xxx.com:81/dir/etc.html  # 失败  端口不同 ( http:// 默认端口是80)http://blog.xxx.com/dir/other.html  # 失败  域名不同


"非同源"一般有以下限制条件:

1. 无法获取非同源网页的 cookie、localstorage 和 indexedDB。

2. 无法访问非同源网页的 DOM (iframe)。





server {  listen 80;  server_name a.xxx.com;  location / {     proxy_pass http://b.xxx.com; # web访问地址,请求接口为 http://api.xxx.com }}


修改完成后,需要执行命令 nginx -t 验证配置是否异常,无异常则可以使用 nginx -s reload 使 Nginx 加载最新配置,执行完命令后则可以使用浏览器正常访问,请求会被正常重定向。

这里页面的请求和接口的请求都是以 b.xxx.com 开始,不利于区分,所以为了实现后端服务请求的统一转发,因此在后端接口接口路径统一加上前缀 /apis/ 或者其他的标识用于划分前后端服务,配置如下:

# 请求跨域,约定代理后端服务请求path以/apis/开头 location ^~/apis/ {    # 这里重写了请求,将正则匹配中的第一个分组的path拼接到真正的请求后面,并用break停止后续匹配    rewrite ^/apis/(.*)$ /$1 break;    proxy_pass b.xxx.com;
    # 两个域名之间cookie的传递与回写    proxy_cookie_domain a.xxx.com b.xxx.com;}

 
那么,网页访问 b.xxx.com/xxx.html,接口访问 b.xxx.com/apis/xxx,因此浏览器访问页面时和页面调用的接口均属于同一域名,绕过了浏览器的同源策略。



除了上述代理方法,还有CROS跨域解决方案。


CROS:HTTP访问控制,又称跨域资源共享,是一种机制,它使用额外的 HTTP 头来告诉浏览器  让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。


CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。


整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。


因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。


日常开发中,浏览器在访问跨源接口时,一般是在接口的代码中添加允许跨域请求返回头来解决浏览器跨域问题,该方法适用性和便捷性并不高,要是把这件事交给Nginx,那么可适性、便捷性就高了。


假设 test.iwenhaha.cn 网页需要访问 www.iwenhaha.cn 的跨域资源,网页源码示例如下:

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>test.iwenhaha.cn</title></head><body> Welcome to test.iwenhaha.cn!</body><script> var xmlhttp = new XMLHttpRequest() xmlhttp.open("GET", "https://www.iwenhaha.cn/index.html", true); xmlhttp.send(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { console.log(xmlhttp.responseText); } }</script></html>


打开浏览器访问 www.iwenhaha.cn 显示如下:

Nginx入门实践总结


上述出现了浏览器跨域请求异常,由于 https://www.iwenhaha.cn 请求了跨域资源 https://www.iwenhaha.cn/index.html.


因此,需在 www.iwenhaha.cn 对应的 nginx 配置进行调整,示例如下:

server {  listen 443;  server_name www.iwenhaha.cn;  add_header 'Access-Control-Allow-Origin' "$http_origin";   # 允许跨域访问的域名,可以是一个域的列表,也可以是通配符*,带cookie的请求不支持*  add_header 'Access-Control-Allow-Credentials' 'true';    # 是否允许请求带有验证信息,true 表示允许  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'# 允许使用的请求方法,以逗号隔开  add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With';  # 允许控制访问的请求返回头  add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'# 允许自定义的头部,以逗号隔开,大小写不敏感  if ($request_method = 'OPTIONS') {     return 204;  }  location / {    root  /xxx/xx;    index index.html; }}


添加对应配置后,nginx -s reload热加载一下配置,再访问浏览器就可以看见对应设置的请求返回头:


Nginx入门实践总结


解决了跨域问题,但是在这里发现了个问题,继续深入探讨一下。


那就是为什么没有发送预请求?什么时候会发送预请求?


浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)

简单请求需满足下述两个条件:

1. 请求方法是 HEAD、GET、POST 三种之一;

2. HTTP 头信息不超出一下几个字段:
① Accept

② Accept-Language

③ Content-Language

④ Last-Event-ID

⑤ Content-Type
并且 Content-Type 只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain;


凡是不同时满足上述两个条件的,都属于非简单请求。

浏览器在处理简单请求和非简单请求的处理过程不一样:

简单请求

对于简单请求,浏览器会在头信息中增加 Origin 字段后直接发出,Origin 字段用来说明,本次请求来自的哪个源(协议+域名+端口)。


浏览器处理简单请求过程可分为两种情况:


情况一:服务器发现 Origin 指定的源不在许可范围内,即不在允许的跨源列表内,则服务器会返回一个正常的 HTTP 回应,浏览器取到回应之后发现回应的头信息中没有包含 Access-Control-Allow-Origin 字段,就抛出一个错误给 XHR 的 error 事件;


情况二:服务器发现 Origin 指定的域名在许可范围内,即在允许的跨院列表内,服务器返回的响应会多出几个 Access-Control- 开头的头信息字段,但一定包含 Access-Control-Allow-Origin。


非简单请求

非简单请求是对服务器有特殊要求的请求,比如请求方法是 PUT 或 DELETE,或 Content-Type 值为 application/json。浏览器会在正式通信之前,发送一次 HTTP 预检 OPTIONS 请求,先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 请求方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XHR 请求,否则报错。


举两个相关栗子证实上述定义:


简易请求示例:


https://test.iwenhaha.cn 网页POST请求跨域文件 https://www.iwenhaha.cn/test.json.


https://test.iwenhaha.cn 网页代码调整如下:

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>test.iwenhaha.cn</title></head><body> Welcome to test.iwenhaha.cn!</body><script> var xmlhttp = new XMLHttpRequest()  xmlhttp.open("POST", "https://www.iwenhaha.cn/test.json", true);  xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.send(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { console.log(xmlhttp.responseText); } }</script></html>


由于 Nginx 不允许POST访问静态资源,会出现405的错误异常,因此需要对405的错误异常进行处理,指向为200成功状态码并指定对应请求URI


https://www.iwenhaha.cn 对应的 nginx 配置调整如下:

server {  listen 443;  server_name www.iwenhaha.cn;  add_header 'Access-Control-Allow-Origin' "$http_origin";   # 允许跨域访问的域名,可以是一个域的列表,也可以是通配符*,带cookie的请求不支持*  add_header 'Access-Control-Allow-Credentials' 'true';    # 是否允许请求带有验证信息,true 表示允许  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';  # 允许使用的请求方法,以逗号隔开  add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With';  # 允许控制访问的请求返回头  add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'# 允许自定义的头部,以逗号隔开,大小写不敏感  if ($request_method = 'OPTIONS') {    return 204;  }  location / {    root  /x/xx/xxx;    index index.html;    error_page 405 =200 $uri; }}


请求完成,结果如下图所示:

Nginx入门实践总结


非简易请求示例:

https://test.iwenhaha.cn 网页POST请求跨域文件 https://www.iwenhaha.cn/test.json,设置请求头 Content-Type 为 application/json,由于是非简易请求,那么就是发送两次请求,第一次为预请求,第二次为实际请求。


https://test.iwenhaha.cn 网页代码调整如下:

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>test.iwenhaha.cn</title></head><body> Welcome to test.iwenhaha.cn!</body><script> var xmlhttp = new XMLHttpRequest() xmlhttp.open("POST", "https://www.iwenhaha.cn/test.json", true); xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.send(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { console.log(xmlhttp.responseText); } }</script></html>


请求结果如下,当请求为非简易请求,确实出现了两次请求,一次预请求,一次真实请求,真实请求就不再截图显示了,下图为预请求截图:


Nginx入门实践总结


浏览器在处理CROS请求的流程图大致如下:


Nginx入门实践总结




Gzip压缩:

Gzip 是 GNUzip 的缩写,最早用于UNIX系统的文件压缩。


HTTP 协议上的gzip编码是一种用来改进 WEB 应用程序性能的技术,WEB 服务器和客户端(浏览器)必须共同支持 Gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的 WEB 服务器如 Apache,Nginx,IIS 同样支持 gzip。


Gzip压缩比率在 3 到 10 倍左右,可以大大节省服务器的网络带宽。而在实际应用中,并不是对所有文件进行压缩,通常只是压缩静态文件。


浏览器与服务器利用 gzip 编码进行通信流程如下:

1. 浏览器请求url,并在request header中设置属性accept-encoding: gzip。表明浏览器支持gzip(IE5 之后所有的浏览器都支持了,是现代浏览器的默认设置)。

2. 服务器收到浏览器发送的请求之后,判断浏览器是否支持gzip,如支持,则向浏览器传送压缩的内容,不支持则向浏览器发送未压缩内容。一般情况下,浏览器和服务器都支持gzip,response headers返回包含content-encoding: gzip。

3. 浏览器接收到服务器的响应之后判断内容是否被压缩,如被压缩则解压缩显示页面内容。


PS:
浏览器的 accept-encoding 会显示浏览器支持的压缩方式,不止 gzip一种 ,常见的还有 deflate,sdch 和 br.


浏览器与服务器利用 gzip 编码进行通信流程图如下:


Nginx入门实践总结


那么,如何使用 nginx 配置 gzip 呢?


在 conf.d 目录下新建文件 gzip.conf 作为统一配置,如有其余 Nginx 应用需要用 gzip 的相同的配置,可以通过include 语法引入到主配置文件的 http 块内即可,主要是为了后续便捷管理。


gzip.conf 具体配置如下:

# 是否开启gzip,默认offgzip on;
# 采用 gzip 压缩的 MIME 文件类型, text/html 被系统强制启用gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 启用后,Nginx 首先检查是否存在请求静态文件的 gz 结尾的文件,如果有则直接返回该 .gz 文件内容,默认 offgzip_static on;
# 反向代理时启用,用于设置启用或禁用从代理服务器上收到相应内容 gzip 压缩,默认 offgzip_proxied any;
# 用于在请求返回头中添加 Vary:Accept-Encoding,使代理服务器根据请求头中的 Accept-Encoding 识别是否启用 gzip 压缩gzip_vary on;
# gzip 压缩比,压缩级别是 1-9,1 压缩级别最低,9 最高,级别越高压缩率越大,压缩时间越长,建议 4-6gzip_comp_level 6;
# 获取多少内存用于缓存压缩结果,16 8k 表示以 8k * 16 为单位获得gzip_buffers 16 8k;
# 允许压缩的页面最小字节数,页面字节数从 header头中的 Content-Length 中进行获取。默认值是 0,不管页面多大都压缩。建议设置成大于 1k 的字节数,小于 1k 可能会越压越大;gzip_min_length 1k;
# 默认 1.1,启用 gzip 所需的 HTTP 最低版本;gzip_http_version 1.1; 


相关更全的配置信息可以查看 Nginx 官网文档 <ngx_http_gzip_module>.

配置完成后,nginx 重载配置,就可以看到请求返回头有了 content-encoding: gzip,则可以说明 gzip 生效了。


Nginx入门实践总结

源文件大小和gzip压缩传输大小差距,如下图:

Nginx入门实践总结


实际文件大小为87.3KB,gzip 压缩过后大小为30.8KB,减少了64%的传输大小,对网页性能优化起着显著作用。

PS:
检测是否开启gzip还可以使用检测工具,而且给出压缩率,搜索<网页GZIP检测>即可查询得到.


补充说明一点,webpack构建版本时候,是可以开启gzip压缩,那么有什么用呢?

使用 Nginx 进行文件压缩,增加服务器的负担,如 nginx 的 gzip_comp_level 配置较高,就会额外增加服务器开销,相应增加客户端的请求时间。

如果 gzip 压缩的动作在前端自动化构建打包的时候做了,把打包之后的高压缩等级文件作为静态资源放在服务器上,Nginx 会优先查找这些压缩之后的文件返回给客户端,相当于把压缩文件的动作从 Nginx 提前给 Webpack 打包的时候完成,节约了服务器资源,所以一般推介在生产环境应用 Webpack 配置 gzip 压缩。


Webpack 3.6.0版本核心示例代码如下:

const CompressionWebpackPlugin = require('compression-webpack-plugin')webpackConfig.plugins.push(  new CompressionWebpackPlugin({ asset: '[path].gz[query]', // 目标资源名称  algorithm: 'gzip', // 压缩算法  test: /.js$|.html$|.css/, // 匹配文件名  threshold: 10240, // 压缩阈值,对超过10k的进行压缩  minRatio: 0.6, // 压缩比例  deleteOriginalAssets: false // 是否删除源文件  }))


构建完成之后,对应文件夹如下:


Nginx入门实践总结


最后,将Nginx的gzip_static配置设置为on,当请求资源访问进来,Nginx默认就会返回对应的.gz文件内容。




适配PC或移动端:


目前许多网站是有 PC 端和 H5 端,可以根据客户设备的不同进行切换,用户体验更好。

例如常见的淘宝、京东等国内知名网站没有采用响应式布局适配,而是用分开制作的方式,主要通过用户请求的 user-agent 来判断是返回 PC 还是 H5 网页。

举个栗子:

新建目录PC,存放文件index.html,内容为Hello PC,

新建目录H5,存放文件index.html,内容为Hello H5,

使用 test.iwenhaha.cn 二级域名对应的 nginx 配置进行调整,调整如下:

server {   listen 80;    server_name test.iwenhaha.cn;    location / {        root /x/xx/xxx/pc;        if ($http_user_agent ~* '(android|webOS|iPhone|iPod|BlackBerry)') {            root /x/xx/xxx/h5;        }        index index.html; }}


配置完成之后,nginx热加载最新配置,即可看到一下效果:

Nginx入门实践总结




负载均衡:

定义:高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性。

没有负载均衡的web服务架构,如下图:

Nginx入门实践总结

没有负债均衡的web服务架构,用户是通过网络连接到 web 服务器,如web服务器宕机了,那么用户就没办法访问了。另外,如果同时有很多用户访问服务器,超过了web服务器能处理的极限,就会出现加载速度缓慢或根本无法连接的情况。


搭建负载均衡的web服务架构,如下图:

Nginx入门实践总结

搭建负载均衡的web服务架构,用户先通过网络访问负载均衡器,再由负载均衡器的分发规则将请求转发给相应的服务器。


负载均衡器:一种把网络请求分散到一个服务器集群中的可用服务器上去,通过管理进入的Web数据流量和增加有效的网络带宽的工具。可分为硬件负载均衡器和软件负载均衡器,这里只讲述软件负载均衡器。


负载均衡器一般根据两个因素来决定要将请求转发到哪个服务器,如下:

1. 确保所选择的服务器能够对请求做出响应

2. 根据预先配置的规则从健康服务器池(healthy pool)中进行选择


负载均衡器应当只选择能正常做出响应的后端服务器,因此就需要有一种判断 WEB 服务器是否"健康"的方法。为了监视 WEB 服务器的运行状况,运行状态检查服务会定期尝试使用转发规则定义的协议和端口去连接对应 WEB 服务器。如果,服务器无法通过健康检查,就会从池中剔除,保证流量不会被转发到该服务器,直到其再次通过健康检查为止。


负载均衡器的规则,列举了常见的几种:

  • Round Robin(轮询):为第一个请求选择列表中的第一个服务器,然后按顺序向下移动列表直到结尾,然后循环

  • Least Connections(最少连接):优先选择连接数最少的服务器,在普遍会话较长的情况下推荐使用

  • IP hash(哈希分配):根据请求源的 IP 来决定分发给哪个服务器,保证了同一个用户会访问相同的服务器


对应Nginx规则配置示例如下:


nginx 负载均衡基础配置代码如下:

http {  upstream testServer {     server 127.0.0.1:8081;    server 127.0.0.1:8080;    server 127.0.0.1:8082 weight=10;  }  server {    location / {      proxy_pass http://testServer;      proxy_connect_timeout 10; } }}


nginx 负载均衡模式示例代码如下:

    #   <默认轮询-权重分配>    #   默认轮询,请求会按时间顺序派发分配到你配置的服务器上,weight的值越高被派发请求的概率也就越高,可以根据服务器配置的不同来设置。    #   配置方式:        upstream item {            server 127.0.0.1:81 weight=1;  server 127.0.0.1:80 weight=2;        }          #   <哈希分配>    #  原理:他的根据客户端IP来分配服务器,比如我第一次访问请求被派发给了192.168.101.60这台服务器,那么我之后的请求就都会发送这台服务器上,这样的话session共享的问题也就解决了。        upstream item { ip_hash;            server 127.0.0.1:81; server 127.0.0.1:80;        }        #   <最少连接分配>    #   原理:根据上添加的服务器判断哪台服务器分的连接最少就把请求给谁。        upstream item { least_conn; server 127.0.0.1:81; server 127.0.0.1:80;        } 


PS:有两台或以上提供相同服务的Web服务器,不然负载均衡配置就没有意义!!!



双机热备:

含义:应用于服务器的一种解决方案,其构造思想是主机和从机通过TCP/IP网络连接,正常情况下主机处于工作状态,从机处于监视状态,一旦从机发现主机异常,从机将会在很短的时间之内代替主机,完全实现主机的功能。


为什么需要双机热备呢?

正如之前负载均衡的内容所述,常见的没有负债均衡的web服务架构容易出现故障,并且可能是致命的。


没有负债均衡的web服务架构,用户是通过网络连接到 web 服务器,如web服务器宕机了,那么整个系统就无法提供服务了,这台web服务器就是常说的系统中的"单点故障"。


其实搭建了负载均衡的web服务架构存在相同问题。


搭建负债均衡的web服务架构,用户是先通过网络访问负载均衡器,再由负载均衡器的分发规则将请求转发给相应的服务器,如负载均衡器出故障了,那么整个系统也无法提供服务了,因此负载均衡器本身就是一个"单点故障"隐患


单点故障:系统中一旦失效,就会让整个系统无法运作的部件。


"单点故障"隐患是致命的,双机热备就是极力降低该情况发生概率的一种解决方案。


因此在双机热备方案中,其引入了第二个负载均衡器,当主节点故障之后,自动切换备用节点



讲了这么多,Nginx 如何实现双机热备?


仅 nginx 暂无法实现,现采用了比较热门的一种高可用处理方案 keepalived + nginx 组合。


keepalived 是集群管理中保证集群高可用的一个服务软件,用来防止单点故障。

Keepalived 的作用是检测web服务器的状态,如果有一台web服务器工作出现故障或者异常,Keepalived 检测到并将有故障的web服务器从系统中剔除,当web服务器工作正常后Keepalived 自动将web服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的web服务器。


keepalived是以VRRP协议为实现基础的,VRRP全称Virtual Router Redundancy Protocol,即虚拟路由冗余协议。


虚拟路由冗余协议,可以认为是实现路由器高可用的协议,即将N台提供相同功能的路由器组成一个路由器组,这个组里面有一个master和多个backup,master上面有一个对外提供服务的vip(该路由器所在局域网内其他机器的默认路由为该vip),master会发组播,当backup收不到VRRP包时就认为master宕掉了,这时就需要根据VRRP的优先级来选举一个backup当master。这样的话就可以保证路由器的高可用了。


VRRP—虚拟路由冗余协议,如下图所示:




服务器安装 keepalived 应用,如下:

yum install keepalived -y


编辑 /etc/keepalived/keepalived.conf 配置文件:

! Configuration File for keepalived
#全局配置global_defs { notification_email { # 指定keepalived在发生切换时需要发送email到的对象,一行一个 XXX@XXX.com } notification_email_from XXX@XXX.com # 指定发件人 #smtp_server XXX.smtp.com # 指定smtp服务器地址 #smtp_connect_timeout 30 # 指定smtp连接超时时间 router_id LVS_DEVEL # 运行keepalived机器的一个标识}
vrrp_instance VI_1 { state MASTER # 标示状态为MASTER 备份机为BACKUP interface eth0 # 设置实例绑定的网卡 virtual_router_id 51 # 同一实例下virtual_router_id必须相同 priority 100 # MASTER权重要高于BACKUP 比如BACKUP为99 advert_int 1 # MASTER与BACKUP负载均衡器之间同步检查的时间间隔,单位是秒 authentication { # 设置认证 auth_type PASS # 主从服务器验证方式 auth_pass 8888 } virtual_ipaddress { # 设置vip        xxx.xxx.xx.xxx       # 可以多个虚拟IP,换行即可 }}


以上配置要注意的地方:
1、注意网卡绑定要与虚拟机一致,一般都是eth0,可以通过ifconfig查看
2、state 属性主机为MASTER、备机位BACKUP
3、priority 权重属性主机要高于备机


完成以上配置就可以通过命令 service keepalived start 分别启动主从服务器的 keepalived。


如何检测双机热备启动生效?





动静分离:

概述:请求资源可分为动态资源和静态资源,根据用户的请求划分为动、静资源,对应动、静资源规则响应返回对应内容。


在Web开发中,静态资源指HTML,JavaScript,CSS,img等文件,动态资源指数据库资源等


为什么需要使用动静分离呢?


与后端应用(动态资源)分开部署,提高用户访问静态文件(静态资源)的速度,降低对后端应用等不必要请求访问消耗,即使动态服务不可用,但不影响静态资源的访问。


Nginx配置动静分离示例如下:

http { server {      listen 80;      server_name  localhost;
# 拦截后台请求 location / {        proxy_pass http://localhost:8080; }
# 拦截静态资源 location ~ .*.(html|htm|gif|jpg|jpeg|bmp|png|ico|js|css)$ {        root /static;       }    }}




HTTPS配置:


超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。

为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。

需要使用HTTPS,那么就必须拥有证书。

我使用的是腾讯云服务器,腾讯云平台提供的亚洲诚信机构颁发的免费证书只能一个域名使用,二级域名需单独申请,申请审批一般在几分钟可完成,申请成功即可下载证书的压缩文件,里面会有不同WEB应用需要配置的文件夹,找到 nginx 文件夹,把 xxx.crt 和 xxx.key 文件拷贝到服务器目录,调整对应 nginx 配置如下:

server {    listen 443 ssl http2 default_server;   # SSL 访问端口号为 443    server_name iwenhaha.cn;         # 填写绑定证书的域名
    ssl_certificate /etc/nginx/https/1_iwenhaha.cn_bundle.crt;   # 证书文件地址    ssl_certificate_key /etc/nginx/https/2_iwenhaha.cn.key;      # 私钥文件地址    ssl_session_timeout 10m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;      #请按照以下协议配置    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;    ssl_prefer_server_ciphers on;
    location / {       root         /usr/share/nginx/html;       index        index.html index.htm; }}

完成配置 nginx -t -q 校验通过,就可以 nginx -s reload 热载配置了。

即可正常访问HTTPS 站点 https://iwenhaha.cn 。


一般还可以加上几个增强安全性的命令:

add_header X-Frame-Options DENY; # 减少点击劫持add_header X-Content-Type-Options nosniff; # 禁止服务器自动解析资源类型add_header X-Xss-Protection 1; # 防XSS攻击


PS:
建议使用购买服务器的平台,申请免费服务器证书,平台一般有具体申请和使用指南。



HTTP转HTTPS:


server {    listen 80;    server_name www.iwenhaha.cn;
    # 单域名重定向    if ($host = 'www.iwenhaha.cn') {        return 301 https://www.sherlocked93.club$request_uri; }
    # 全局非 https 协议时重定向    if ($scheme != 'https') {        return 301 https://$server_name$request_uri; }
    # 或者全部重定向    return 301 https://$server_name$request_uri;
        # 以上配置选择自己需要的即可,不用全部加}




静态服务:

server {  listen 80;  server_name static.iwenhaha.cn;  charset utf-8# 防止中文文件名乱码  location /download {    alias /usr/share/nginx/html/static;  # 静态资源目录    autoindex               on;    # 开启静态资源列目录    autoindex_exact_size    off;   # on(默认)显示文件的确切大小,单位是byte;off显示文件大概大小,单位KB、MB、GB    autoindex_localtime     off;   # off(默认)时显示的文件时间为GMT时间;on显示的文件时间为服务器时间 }}




请求过滤:

# 非指定请求全返回 403if ( $request_method !~ ^(GET|POST|HEAD)$ ) {  return 403;}
location / {  # IP访问限制(只允许IP是 192.168.0.2 机器访问)  allow 192.168.0.2;  deny all;
  root   html;  index  index.html index.htm;}




浏览器缓存:


一般图片、字体、音频、视频等静态文件在前端打包自动化构建的时候会生成动态文件名 hash,所以缓存可以设置的长一点,先设置强制缓存,再设置协商缓存;如果存在没有 hash 值的静态文件,建议不设置强制缓存,仅通过协商缓存判断是否需要使用缓存。

# 图片缓存时间设置location ~ .*.(css|js|jpg|png|gif|swf|woff|woff2|eot|svg|ttf|otf|mp3|m4a|aac|txt)$ {    expires 10d;}
# 如果不希望缓存expires -1;




单页面History路由:

server {  listen  80;  server_name  www.iwenhaha.club;
  location / {    root       /usr/share/nginx/html/dist;  # 前端打包后的文件夹    index      index.html index.htm;    try_files  $uri $uri/ /index.html @rewrites; expires -1; # 首页一般没有强制缓存    add_header Cache-Control no-cache;  }
  location @rewrites {    rewrite ^(.+)$ /index.html break;  }}




图片防盗链


server {  listen 80;       server_name  *.iwenhaha.cn;  # 图片防盗链  location ~* .(gif|jpg|jpeg|png|bmp|swf)$ {    valid_referers none blocked server_names ~.google. ~.baidu.  *.qq.com;  # 只允许本机 IP 外链引用,将百度/谷歌/QQ加入白名单    if ($invalid_referer) {      return 403; } }}




泛域名路径分离:


假设需要配置二级或者三级域名,希望通过 Nginx 自动指向对应目录,比如:


server {    listen 80;    server_name  ~^([w-]+).doc.iwenhaha.cn$;    root /usr/share/nginx/html/doc/$1;}




泛域名转发:


假设需要把二级或者三级域名链接重写到我们希望的路径,让后端就可以根据路由解析不同的规则:

1. test1.server.iwenhaha.cn/api?a=1 自动转发到 127.0.0.1:8080/test1/api?name=a

2. test2.server.iwenhaha.cn/api?a= 自动转发到 127.0.0.1:8080/test2/api?name=a


server {    listen 80;    server_name ~^([w-]+).server.iwenhaha.cn$;    location / {        proxy_set_header        X-Real-IP $remote_addr;        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header        Host $http_host;        proxy_set_header        X-NginX-Proxy true;        proxy_pass              http://127.0.0.1:8080/$1$request_uri; }}




规范


Nginx 无统一标准规范,这里总结了个人认为较好的规范,如下:

  1. 每个站点创建一个根据站点名命名单独的配置文件,统一存储在 /etc/nginx/conf.d 目录,根据需求可以创建任意多个独立的配置文件

  2. 每个站点独立的日志文件,日志文件名以站点名进行命名,方便查询和查找

  3. 提取公用的配置文件,根据功能来命名,并且在开头注释标明主要功能和引入位置,指定存放统一目录,在需要使用的地方通过 include 方式引入


PS:<站点>.conf,比如域名是 iwenhaha.cn,那么你的配置文件的应该是这样的 /etc/nginx/conf.d/iwenhaha.cn.conf.



总结





参考


  • Nginx中文社区

  • Nginx官方文档

  • 知乎 野路子小选手 <你真的了解 gzip 吗?>

  • 知乎 Hevin <什么是负载均衡>

  • 简书 先生_吕 <keepalived+nginx高可用-双机热备>

  • 前端下午茶 微信公众号 <Nginx 从入门到实践,万字详解!>




炒粉不加蛋,香味少一半


以上是关于Nginx入门实践总结的主要内容,如果未能解决你的问题,请参考以下文章

Nginx入门与实践

121Nginx入门到实践Nginx中间件

Django入门与实践 17-26章总结

Nginx从入门到实践

Nginx入门到实践-Nginx 中间件

Nginx反向代理入门到实践