〇、前言
本文为 nginx 简单实践系列文章之一,主要简单实践了两个内容:静态资源部署、重写,仅供参考。
关于 Nginx 基础,以及安装和配置详解,可以参考博主过往文章:
https://www.cnblogs.com/hnzhengfy/p/Nginx.html
一、静态资源部署
当前项目的结构基本上都是前后端分离,前端的相关资源,如 html,js,css 或者图片,变更频率比较低但访问量比较大,若想保证系统性能,可以将这些文件放在指定目录下,通过 nginx 的 root 或 alias 配置方式来指定静态资源的路径。
基于高并发高性能的 ningx,用户就可以通过域名加路径的方式,高效快速的进行页面访问。
下面是一个简单的示例。
nginx 配置示例(部分):
server {
listen 8080; # 绑定端口
server_name localhost;
charset utf-8; # 指定字符集,不然 html 文件中的汉字会乱码
location /test { # 路径匹配规则
alias /usr/tmp/test; # 别名,将当期路径对应到新的目录中
index index.html index.htm;
}
location /test_2 { # 路径匹配规则
alias /usr/tmp/test/test_2; # 别名,将当期路径对应到新的目录中
index index.html index.htm;
}
}
示例文件示例,文件位置为 /usr/tmp:
/usr/tmp/test/test_page.html
/usr/tmp/test/test_2/test_2_page.html
/usr/tmp/test/test_2/westlage.jpg
测试结果如下图:
参考:https://www.cnblogs.com/qingshan-tang/p/12763522.html
二、Rewrite:URL 重写
2.1 简介
rewrite 就是 URL 重写,是一个将传入的 web 重定向到其他 URL 的过程。
上一部分静态资源部署,主要用的是 location 相关配置来实现静态资源的直接访问,下面先看下 rewrite 和 location 的区别。
- rewrite 和 location 比较
rewrite 更侧重于 URL 路径的重写和跳转,而 location 则侧重于请求路径的匹配和处理。在实际使用中,两者往往结合使用,以实现复杂的请求处理逻辑。
功能目的
rewrite 主要用于重写 URL 路径 ,可以实现内部跳转(不改变浏览器地址栏的URL)或外部跳转(使用 redirect 标志时),通常用于在同一域名内更改获取资源的路径。
location 用于匹配请求的 URL 路径,本身不实现跳转,而是对匹配到的路径进行相应的处理,如访问控制、反向代理或静态资源服务。
配置位置
rewrite 只能放在 server {}, location {}, if {} 中,并且只能对域名后边的除去传递的参数外的字符串起作用。
location 作为 server {} 块中的一个指令,可以基于请求字符串、虚拟主机名称(IP或域名)和URL进行匹配。
执行顺序
rewrite 在 server 块中的 rewrite 指令会首先执行,然后是 location 匹配,最后是 location 块中的 rewrite 指令。
location 直接根据请求的 URL 路径进行匹配,一旦匹配成功,就会执行对应的处理逻辑。
2.2 rewrite 的优势
- **隐藏真实目录结构:**通过隐藏服务器上的真实文件路径和目录结构,防止攻击者通过直接访问文件路径来获取敏感信息。
- **规范化 URL:**强制规范化 URL 格式既可以避免一些常见的安全漏洞,如路径遍历攻击(Directory Traversal)或路径参数欺骗等等,又可以保证 URL 格式的一致性。
- 防止盗链: 通过 Rewrite 可以实施防盗链策略,防止其他网站直接链接到本站的资源。这有助于减轻服务器负载,防止不法分子盗用网站的带宽,并提高资源的安全性。
- HTTP 到 HTTPS 的强制重定向:可以确保数据在传输过程中的安全性。这是保障通信安全的一种有效手段,尤其对于涉及用户敏感信息的网站至关重要。
- 条件性重写:可以根据请求中的条件来选择是否进行重写,有助于实现访问控制和强化安全性。例如,只有特定 IP 范围的请求才允许进行某种操作。
- 跨站点脚本(XSS)防护:Rewrite 可以对请求参数进行过滤或修改,防止恶意用户通过注入脚本来进行 XSS 攻击。对 URL 和参数进行适当的重写可以减轻 XSS 攻击的风险。
- 统一资源标识符(URI)规范化:通过强制规范化 URI,可以防止攻击者尝试混淆或绕过安全策略。规范化的URI有助于提高应用程序的安全性,防范一些常见的攻击手法。
- 避免敏感信息泄露:Rewrite 可以限制对某些敏感信息或文件的访问,确保只有授权用户能够获取特定内容。这有助于防止敏感信息泄露和未授权访问。
2.3 Rewrite 相关指令
Rewrite 相关指令有 if、rewrite、set、return。
2.3.1 if 语句
Nginx 中的 if 语句常用于实现精细的访问控制策略。例如,可以设置只有特定IP或内网IP可以访问某些资源,其他IP则被重定向到停服更新页面,也可以通过 if 语句实现 URL 重写、限制访问速度等功能。
# 基本语法
if (condition) {
......
}
在匹配中可以用的一些符号和常量:
# if 可以支持如下条件判断匹配符号
~ 正则匹配(区分大小写)
~* 正则匹配(不区分大小写)
!~ 正则不匹配(区分大小写)
!~* 正则不匹配(不区分大小写)
-f 和!-f 用来判断是否存在文件,! 表示不存在
-d 和!-d 用来判断是否存在目录
-e 和!-e 用来判断是否存在文件或目录
-x 和!-x 用来判断文件是否可执行
# 在匹配过程中可以引用一些 Nginx 的全局变量
$args 请求中的参数
$document_root 针对当前请求的根路径设置值
$host 请求信息中的 Host,如果请求中没有Host行,则等于设置的服务器名
$limit_rate 对连接速率的限制
$request_method 请求的方法,比如 GET、POST 等
$remote_addr 客户端地址
$remote_port 客户端端口号
$remote_user 客户端用户名,认证用
$request_filename 当前请求的文件路径名(带网站的主目录 /usr/local/nginx/html/images/a.jpg)
$request_uri 用于表示客户端请求的完整 URI,也就是请求地址,它记录了客户端发起的请求地址
$query_string 与 $args 相同
$scheme 用的协议,比如 http 或者是 https
$server_protocol 请求的协议版本,HTTP/1.0 或 HTTP/1.1
$server_addr 服务器地址,如果没有用 listen 指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费)
$server_name 请求到达的服务器名
$document_uri 与 $uri 一样,URI 地址
$server_port 请求到达的服务器端口号
后文将进行示例演示。
2.3.2 rewrite flag 简介:redirect、permanent、break、last
Nginx 中的 rewrite 指令可以包含一个可选的 flag(标志),用于控制重写操作后的行为。
这个 flag 的类型总共有四种,如下:
- redirect:这是一种临时重定向,状态码为 302。当重写完成后,Nginx 会以临时重定向的方式返回重写后生成的新 URL 给客户端。浏览器地址栏会显示跳转后的 URL 地址,但爬虫不会更新 URL。
- permanent:这是一种永久重定向,状态码为301 。与 redirect 类似,permanent 也会直接对 URL 地址进行重定向,并显示跳转后的 URL 地址。从实现功能的角度看,redirect 和 permanent 是一样的,不存在好坏和性能上的问题。然而,对于 SEO(搜索引擎优化)来说,使用 permanent 更为友好,因为它告诉搜索引擎这是一个永久性的移动,把原始页面的所有排名信号都传递到新的URL上,无需再请求到原地址,也避免了地址栏的重复跳转,更友好。
- break:在重写完成后,break 会停止处理 当前 URL 在当前 location 以及其他 location 中后续的其他重写操作 。它不会跳出 location 作用域,也不会重新搜索与更改后的 URI 相匹配的 location。适用于一个 URL 只需要一次重写的场景。2
- last:重写完成后,last 会停止处理 当前 URI 在当前 location 中后续的其他重写操作 。但它会对新的 URL 启动新一轮重写检查,并可能跳出当前 location 作用域,搜索与更改后的 URI 相匹配的 location。适用于一个 URL 可能需要多次重写的场景。
2.3.3 permanent 简单示例
注意:可以使用测试域名,但前提是要修改 hosts 文件。
路径和重启:Linux(/etc/hosts)(重启命令:/etc/init.d/network restart);Windows(C:\Windows\System32\drivers\etc\hosts)。
- 实例:变更整个资源路径
示例:从 http://www.testczzj.com:8080/test/test_page.html
------> http://www.testczzj.com:8080/test2/test2.html
示例文件夹结构:
/usr/tmp/test/test_page.html
/usr/tmp/test2/test2_page.html
<div id="div1">
<h1>这是test2_page页面</h1>
</div>
server {
listen 8080;
server_name www.testczzj.com;
charset utf-8;
location /test {
index index.html index.htm;
# 匹配成功之后的全部路径,全部替换成指定的地址
rewrite .* /test2/test2_page.html permanent;
}
location /test2 {
alias /usr/tmp/test2;
index index.html index.htm;
}
}
自动跳转:(输入第一行地址回车自动跳转到新地址)
- 实例:变更路径中的一部分
示例:http://www.testczzj.com:8080/2024/test/test_page.html
------> http://www.testczzj.com:8080/2025/test/test_page.html
目的就是将 URL 中的 /2024/ 自动换成 /2025/,当然,实际业务中可以一般没有这么简单。
示例文件夹结构:
/usr/tmp/2025/test/test_page.html
<div id="div1">
<h1>这是test_page页面</h1>
</div>
server {
listen 8080;
server_name www.testczzj.com;
charset utf-8;
location /2024/test {
index index.html index.htm;
# 先匹配 2024 之后的全部路径,再拼接到 2025 之后
rewrite ^/2024/(.*) /2025/$1 permanent; # (.*):表示以任意结尾,全部匹配取到
}
location /2025/test2 {
alias /usr/tmp/test2;
index index.html index.htm;
}
}
自动跳转:(输入第一行地址回车自动跳转到新地址)
- 实例:将旧域名永久重定向到新域名
示例:http://www.testczzj.com/2024/test/test_page.html
------> http://www.testczzj_new.com
根据路径中的标识:/2024,来拦截,将全部请求直接跳转到新的地址主页。
server {
listen 8080;
server_name www.testczzj.com www.testczzj_new.com;
charset utf-8;
location /2024 {
root /usr/tmp/2024
index index.html index.htm;
if ($host ~* www.testczzj.com){ # 将 testczzj 转到 testczzj_new
rewrite .* http://www.testczzj_new.com permanent;
}
}
}
自动跳转:
- 实例:只更新协议和域名,url 路径不变
示例目标:http://www.testczzj.com/2024/test/test_page.html
------> https://cn.bing.com/2024/test/test_page.html
配置如下:
server {
listen 80;
server_name www.testczzj.com;
charset utf-8;
location /2024 {
root /usr/tmp/2024;
index test_page.html index.html index.htm;
if ($host ~* www.testczzj.com){ # $request_uri 用于表示客户端请求的完整 URI,也就是请求地址
rewrite .* http://cn.bing.com$request_uri permanent;
}
}
}
自动跳转:(由于 bing 后路径没有资源,因此页面会加载失败)
- 实例:在 URL 地址末尾没有 / 时,默认添加
示例目标:http://www.testczzj.com/2024/test
------> http://www.testczzj.com/2024/test/
注:示例文件路径:/usr/tmp/2024/test/test_page.html。
为什么要添加这个斜线?
搜索引擎对带有和不带有斜杠的 URL 有着不同的处理方式。通常,带有斜杠的 URL 被视为目录,而不带斜杠的 URL 被视为文件或特定资源。
当 URL 的斜杠使用不一致时,可能会导致不必要的重定向,从而影响页面加载速度和用户体验。一致的 URL 结构有助于提高用户体验。用户期望目录级别的 URL 以斜杠结尾,而文件级别的 URL 则不带斜杠。
server {
listen 80;
server_name www.testczzj.com;
charset utf-8;
location /2024/test {
root /usr/tmp;
index test_page.html index.html index.htm;
if (-d $request_filename){ # 用于检查请求的文件名是否是一个目录。如果是目录,则执行大括号内的指令
# (.*):捕获任意字符(除了换行符)零次或多次,并将其存储在第一个捕获组中
# ([^/]):捕获除斜线(/)之外的任意单个字符,并将其存储在第二个捕获组中
# $host:当前请求的主机名
rewrite ^(.*)([^/])$ http://$host$1$2/ permanent;
}
}
}
自动补上斜线:
- 实例:重定向后,在 URL 末尾添加 Query 参数
示例目标:http://www.testczzj.com/login/czzj.html
------> http://www.testczzj.com/2024/test/login.html?name=czzj
示例文件路径:/usr/tmp/2024/test/login.html。
把功能隐藏到 URL 路径中去。
server {
listen 80;
server_name www.testczzj.com;
charset utf-8;
location /login {
root /usr/tmp/2024;
index login.html index.html index.htm;
# 正则表达式的目的:匹配以 /login/ 开头并以 .html 结尾的内容为第一捕获组:$1
rewrite ^/login/(.*)\.html$ http://$host/test/login.html?name=$1;
}
location /test { # 处理重写后的请求:/test
alias /usr/tmp/2024/test;
index login.html index.htm;
}
}
如下图自动跳转:
- 实例:将固定格式 /2025-01-02/ 重定向为 /2025/01/02/
示例目标:http://www.testczzj.com/test/2025-01-03/test_page.html
------> http://www.testczzj.com/test/2025/01/03/test_page.html
示例文件路径:/usr/tmp/test/2025/01/03/test_page.html
server {
listen 80;
server_name www.testczzj.com;
charset utf-8;
location /test {
root /usr/tmp;
index test_page.html index.html index.htm;
# ([0-9]+): 这是一个捕获组,用于匹配一个或多个数字(即至少一个数字)
# (.*): 这是第四个捕获组,用于匹配任意数量的任意字符(包括空字符)
rewrite ^/test/([0-9]+)-([0-9]+)-([0-9]+)(.*)$ /test/$1/$2/$3$4 permanent;
}
}
如下图自动跳转:
2.4 set 指令
set 指令用于设置一个变量的值。这个指令可以在多个上下文中使用,例如 http、server、location 和 if 块中。
# 基本语法
set $variable_name value;
# $variable_name:要设置的变量名,必须以 $ 符号开头
# value:要赋予变量的值,可以是字符串、数字或由其他变量组成的表达式
- 实例:将域名转换为路径参数
示例目标:
http://username1.testczzj.com/test/userPage1.html
------> http://www.testczzj.com/username1/userPage1.html
http://username2.testczzj.com/test/userPage2.html
------> http://www.testczzj.com/username2/userPage2.html
示例文件:
/usr/tmp/username1/userPage1.html
/usr/tmp/username2/userPage2.html
# hosts 添加配置,注意请将'服务器IP地址'替换为自己真实的服务器 IP
测试机IP地址 www.testczzj.com
测试机IP地址 username1.testczzj.com
测试机IP地址 username2.testczzj.com
# Linux(编辑:/etc/hosts)(重启命令:/etc/init.d/network restart)
# Windows(编辑:C:\Windows\System32\drivers\etc\hosts)
nginx 配置如下:
server {
listen 80;
server_name www.testczzj.com;
charset utf-8;
location /test {
root /usr/tmp;
index index.html index.htm;
if ( $host ~* ^www.testczzj.com$ ){
break; # 终止 nginx 在当前请求中的所有后续 location 的匹配
}
if ( $host ~* "^(.*)\.testczzj\.com$" ) {
set $user $1; # 将正则表达式中第一个捕获组的值赋给变量 $user
rewrite .* http://www.testczzj.com/$user permanent;
}
}
location /username1 {
alias /usr/tmp/username1;
# root /usr/tmp;
index userPage1.html index.html index.htm;
}
location /username2 {
alias /usr/tmp/username2;
# root /usr/tmp;
index userPage2.html index.html index.htm;
}
}
效果如下图:
2.5 return 指令
return 指令是 nginx 配置中用于立即返回指定状态码 和可选的响应体的指令。它通常用于处理特定的请求,并立即终止进一步的处理。
# 语法
return code [text];
# code: 要返回的 HTTP 状态码,例如 200, 404, 500 等
# [text]: 可选的响应体文本。如果省略,Nginx 将使用默认的响应体
# 简单示例
location / {
return 200; # 对所有匹配 / 路径的请求返回 HTTP 200 状态码
}
# 返回状态码和自定义响应体
location /custom {
return 403 "Access Denied"; # 对所有匹配 /custom 路径的请求返回 HTTP 403 状态码,并在响应体中包含 "Access Denied" 文本
}
# 重定向到另一个 URL
location /redirect {
return 301 http://example.com; # 将所有匹配 /redirect 路径的请求重定向到 http://example.com,并返回 HTTP 301 状态码
}
return 指令会立即终止当前请求的处理,不会继续执行后续的配置指令。因此,它通常放在 location 块的开头。
如果需要返回带有复杂内容的响应体,建议使用 add_header 和 echo 模块,或者通过内部重定向到一个专门处理这些内容的 location。
- 实例:若 URL 中有调用 .sh 脚本文件,直接返回 403 拒绝操作
示例目标:
------>正常加载指定页面:http://www.testczzj.com/test/test_page.html
http://www.testczzj.com/test/ts.sh
------> 返回禁止访问:403 Forbidden
nginx 配置如下:
server {
listen 80;
server_name www.testczzj.com;
charset utf-8;
location ~* \.sh$ { # 写在最前边
return 403;
}
location /test {
alias /usr/tmp/test;
index test_page.html index.html index.htm;
}
}
效果如下图:
- 实例:http 转 https(端口 80 转 443)
注意,这个测试需要有 SSL 证书。
如下配置项供参考:(未经实际验证)
server {
listen 80;
server_name www.testpm.cn;
access_log /var/log/nginx/http_access.log main;
return 301 https://www.testpm.cn$request_uri; #返回301永久重定向
}
server {
listen 443 ssl;
server_name www.testpm.cn;
access_log /var/log/nginx/https_access.log main;
#ssl on;
ssl_certificate /etc/nginx/cert/2447549_www.testpm.cn.pem;
ssl_certificate_key /etc/nginx/cert/2447549_www.testpm.cn.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
可以通过 curl 命令进行检验:
# 使用 curl 命令行工具发送 HTTP 请求的命令
# -I 或 --head 选项告诉 curl 只获取 HTTP 响应的头部信息,而不下载整个内容
# 这对于检查服务器响应头信息非常有用
# 当你运行这个命令时,curl 会向目标服务发送一个 HTTP HEAD 请求,并返回该请求的响应头信息
# 响应头信息通常包括状态码、内容类型、服务器信息、日期等。
[root@localhost ~]# curl -I http://www.testpm.cn
HTTP/1.1 301 Moved Permanently
Server: nginx/1.16.0
Date: Wed, 03 Jul 2019 13:52:30 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: https://www.testpm.cn/
2.6 last(重新进行匹配)、break(终止) 指令
last 和 break 指令是用于控制 rewrite 模块的行为,它们在处理重写规则时有不同的作用。
last 指令在当前的 rewrite 规则执行完后,会停止处理后续的 rewrite 规则,并根据重写后的新 URI 重新发起一次请求,并从服务器配置的顶部开始重新匹配 location 块。
示例:假设有一个 location 块/old_path
,使用rewrite ^/old_path(.*)$ /new_path$1 last;
,当访问/old_path/something
时,nginx 会将其重写为/new_path/something
并重新匹配 location 块。
break 指令在当前的 rewrite 规则匹配成功后,会停止处理后续的 rewrite 规则,但不会重新开始匹配 location 块,即在满足特定条件后终止重写过程。
示例:同样的 location 块/old_path
,如果使用rewrite ^/old_path(.*)$ /new_path$1
break;,当访问/old_path/something
时,nginx 会将其重写为/new_path/something
,但不会重新匹配 location 块,而是继续处理当前请求的其他阶段。
参考:https://blog.csdn.net/m0_62396418/article/details/135747521