这篇文章解决如下问题:
- nginx中的location块匹配规则是什么?
- nginx中的alais,root try_files指令理解
- 对应部署单页面经常遇到的配置(跨域,子路径,history路由模式的部署)
- 静态文件缓存的处理
- 什么时候会导致301,重定向到末尾加了
/的路径
nginx需要知道的前置知识
一份前端部署最基本的Nginx配置文件,理解如下:
# 一个http中可以有多个server
http {
# 配置虚拟主机
server {
listen 80; # 端口
server_name your-domain.com; # 域名
#access_log /path/log/access.log; #成功日志
#error_log /path/log/error.log; #错误日志
# (root指令)指定服务器块的根目录 也可放在location块中
root /opt/front/dist;
# location块 处理请求路径
location / {
# (index指令)指定默认的索引文件,Nginx将尝试按照指定的顺序查找并返回符合条件的索引文件
# 当用户访问 http://your-domain.com/时
# Nginx会查找/opt/front/index.html是否存在,存在返回,不存在继续查找/opt/front/index.htm
index index.html index.htm;
}
location /api/ {
# 将/api前缀的请求转发到localhost:6000可以解决前后端跨域问题
proxy_pass http://localhost:6000;
}
# 404的时候(比如访问http://your-domain.com/aaa找不到指定的文件或者目录)
# 直接重定向到/opt/front/404.html页面
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
# 精准匹配 只有访问http://your-domain.com/50x.html进入
# 重新指定了目录,执行了nginx默认的放置文件的目录
root html;
}
}
}
理解location匹配规则
nginx匹配由两种类型组成:
-
a prefix string: 前缀匹配 可以添加
=and^~两个修饰符=精确匹配^~优先于其他正则表达式匹配的 location 块,匹配到后不再进行正则匹配查找
-
a regular expression 正则匹配,可以添加
~,~*两个修饰符~:表示正则表达式匹配,区分大小写。~*:表示正则表达式匹配,不区分大小写。
- 前缀匹配(prefix locations),最长位置匹配被记住(没有遇到修饰符,走2)
- 如果最长位置匹配遇到
=修饰符,搜索停止 - 如果最长位置匹配具有
^~,搜索停止
- 如果最长位置匹配遇到
- 正则匹配,按配置文件中定义的顺序进行匹配。
- 如果第2条规则产生匹配的话,结果被使用。否则,使用第1条规则的结果。
location = / {
[ configuration A ]
}
location / {
[ configuration B ]
}
location /documents/ {
[ configuration C ]
}
location ^~ /images/ {
[ configuration D ]
}
location ~* .(gif|jpg|jpeg)$ {
[ configuration E ]
}
请求 / 将会精确匹配 A,请求 /index.html 将会匹配 B,请求 /documents/document.html 将会匹配 C,请求 /images/1.gif 将会匹配 D,请求 /documents/1.jpg 将会匹配 E。
下面的所有例子都以上面Nginx配置文件作为基础
(history|hash)router
hash模式
当用户访问 http://your-domain.com#subpath,由于 #后的这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理,上面的Nginx的基础配置文件中实际上匹配到的就是Nginx的location / 块,访问的就是根目录的index.html
history
当用户访问 http://your-domain.com/subpath,Nginx的基础配置文件中实际上匹配到的就是Nginx的location / 块,这时候nginx默认会去找/opt/front/dist下是否有subpath文件或者subpath目录下的index.html index.htm。而我们是单页应用,只有根目录一个index.html。
解决:使用Nginx的try_files指令添加一个简单的回退路由, 按照一定的顺序处理请求,将匹配不到的都指向根index.html 。
location / {
try_files $uri $uri/ /index.html;
}
如上的配置当你访问http://your-domain.com/subpath,进入这个location块,$uri为nginx的变量,表示请求的URI,这里就是Nginx在寻找subpath文件在不,不在继续,$uri/ 表示请求的 URI 对应的目录,Nginx寻找subpath目录在不,不在寻找第三个到/index.html,这里使用的是绝对路径,代表根目录下的index.html
跨域处理
proxy_pass
前端web使用nginx服务部署在your-domain.com,后端服务localhost:6000
当你在浏览器访问 http://your-domain.com里面请求的API地址是http://your-domain.com/api/getUsers实际上是访问http://localhost:6000/api/getUsers
location /api/ {
# 将/api前缀的请求转发到localhost:6000可以解决前后端跨域问题
proxy_pass http://localhost:6000;
}
本地开发以vite为例,配置
server: {
proxy: {
"/api": {
target: "http://localhost:6000",
},
},
}
实现CROS
后端服务localhost:6000不支持CROS,前端服务http://your-domain.com
当你在浏览器访问 http://your-domain.com里面请求API的地址是 localhost:9001/api/getUsers=》实际访问 http://localhost:3000/api/getUsers以下配置是将不支持CROS的后台服务支持CROS (对于跨域的访问,浏览器每次都会发送一次OPTIONS预检请求,去询问后台是否支持跨域)
server {
listen 9001;
server_name localhost;
location / {
proxy_pass http://localhost:6000;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'http://your-domain.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'http://your-domain.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}
实际中后台服务部署会加入Access-Control-Allow-Origin等从而实现CROS,上面只是举例后台服务没有实现CROS而已
子路径部署
假设要把开发的前端项目部署在http://your-domain.com/web/h5,首先前端的打包需要支持/web/h5,以vite作为例子,如下
import { defineConfig } from "vite";
export default defineConfig({
base: "/web/h5/"
});
location /web/h5 {
try_files $uri $uri/ /index.html;
}
遇到一个问题,请求的静态文件会都返回index.html。比如请求/web/h5/assets/index-B7RyahvK.js文件,$uri $uri/找不到,因为我们打包是直接将静态文件放在根目录下的rootDirectory/assets/index-B7RyahvK.js,所以选择最后的回退路由rootDirectory/index.html
这就要将location加入alias指令了,当请求/web/h5/assets/index-B7RyahvK.js时,nginx查找的是/opt/front/dist/assets/index-B7RyahvK.js就可以正确返回了
root /opt/front/dist;
location /web/h5 {
alias /opt/front/dist;
try_files $uri $uri/ /index.html;
}
root vs alias 指令
- alias 只能配置在location 中, 而root 可以配置在 server, http 和 location 中
- 使用 root 时, 服务器里真实的资源路径是 root 的路径拼接上 location 指定的路径,alias是不包括,举例如下:
location /i/test/ {
root /data/w3;
}
如上配置,当请求/i/test/top.gif进入这个location后,Nginx查找的地址是/data/w3/i/test/top.gif
location /i/test/ {
alias /data/w3/;
}
如上配置,当请求/i/test/top.gif进入这个location后,Nginx查找的地址是/data/w3/top.gif
no-cache index.html
- HTTP协议被设计尽可能多地缓存,从而提高效率
- http caching 建议都显式指定Cache-Control,不指定会进入
Heuristic caching启发式缓存机制,条件满足的情况下也会被缓存和重用,不可控,由客户端的实现会有差异 no-cache不是不缓存,而是每次强制发送一个条件请求(conditional request 含有If-Modified-Since``If-None-Match请求头)去确认资源是否已更改,返回304不会带有响应体,no-store才是不缓存
对于采用Nginx部署我们的单页面应用时,最有效的是搭配index.html 为no-cache,其他的静态资源搭配现有hash打包,可以设置为Cache-Control: public, max-age=31536000, immutable配置适当的缓存失效时间,添加 immutable可以防止重新加载时进行发送请求进行验证。配置如下:
location / {
if ($request_filename ~* ^.*[.](html|htm)$) {
add_header Cache-Control "no-cache";
}
try_files $uri $uri/ /index.html;
}
location ~ ^/assets/.*.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
扩展
关于URL末尾的 /
浏览器访问的URL中加入 / vs 不加 /
- 对于访问域名
http://your-domain.com,如果末尾没有加斜杠,浏览器会自动在请求中添加斜杠,以确保请求的是域名下的默认资源或默认页面 - 直接访问子路径
http://your-domain.com/subpath/Nginx会去查找/opt/front/dist/subpath文件夹下有没有指定的索引文件index.html - 对于子路径
http://your-domain.com/subpath浏览器不会自动添加/,nginx会去查找/opt/front/dist下有没有subpath文件,没有就查找有没有subpath这个目录,(两者都没有返回404)有这个目录的情况下返回301,redirect到subpath/去查找目录下有没有指定的默认索引文件 index.html
location块末尾的 /
root /opt/front/dist;
# (1)
location /user/ {
proxy_pass http://user.example.com;
}
# (2)
location = /user {
proxy_pass http://login.example.com;
}
采用(1)location块,用户访问http://your-domain.com/user,nginx服务器接收到 /user的URI,发现没有user文件,user目录也没有。会返回404 not found。发现有user这个目录,301重定向/user/。如果不希望重定向,直接采用(2)的精确匹配
总结: Nginx对于没有加/的子路径,如/subpath,找寻不到subpath文件,却有这个subpath文件夹,会返回一个301给浏览器,重定向到含有/subpath/的地址
PS: 上面的所有配置都是最基本的配置,可以让你了解基本的单页面部署涉及到的一些知识,基本是Nginx的,实际业务中更为复杂