最近在一次学院大作业中使用Django框架进行后端开发,因为项目中有上传图片以及前端获取图片然后展示的功能,于是便决定使用nginx处理静态图片文件。过程中遇到了因为多个疏忽导致的离奇错误
整体的部署方式
项目后端采用Django4.2.16版本进行开发,前端采用vite+vue3框架进行开发,因为这次错误处理跟前端没有关系所以不进行详细讨论。因为服务器上是测试环境,所以采用了tmux session运行开发服务器,方便可以实时热重载看到修改效果。为了提升静态图片的获取效率,采取了nginx代理静态目录,同时反向代理rest api的形式部署。配置如下。
server {
listen 8000;
server_name
client_max_body_size 100M;
location /media/ {
root /home/hhh/GoodsExchangeBackend;
try_files $uri $uri/ =404;
autoindex on;
}
location /admin/ {
proxy_pass http://localhost:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/ {
proxy_pass http://localhost:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
然而,也就是因为这个反向代理,导致了问题的出现。
问题的出现
因为Django存在自带的管理面板可以方便的管理项目的数据库,所以在配置完成之后我就第一时间访问了面板的路径,结果问题就出现了。
浏览器的地址栏里,原本应该是6699的端口突然变成了8000,而服务器的8000端口根本没有放行也没有运行任何服务,返回值自然是502。起初我认为网页因为某些原因重定向了,事实上也确实被重定向了,但是我当时却没有发现任何301或302的响应(其实是当时的开发者工具开始了XHR/fetch的筛选器)。
在我百思不得其解的时候,我尝试着更换浏览器去访问,结果在chrom浏览器中,admin面板就可以正常访问了。在我仔细的对比了两段之后,我发现了问题的所在。我在原浏览器中访问的地址是/admin,而新浏览器中访问的地址是/admin/,就是少了这一杠导致的nginx找不到路径。这就要涉及到nginx的自动重定向功能了。
nginx的自动重定向
在nginx中,我们可以通过location块对特定规则的url进行相应的处理,而这个location快的url写法是很有讲究的。 如果说写的是/admin这种形式,会匹配以/admin开头的所有url,但是如果写成了/admin/,那么就会在匹配所有以/admin开头的url之外,不匹配/admin。所以,按照我上面的配置,如果我使用/admin访问,应该就会因为没有匹配的路由而404。但是,根据官方文档 https://trac.nginx.org/nginx/ticket/523,nginx有一个自动重定向的功能。如果我使用只配置了/admin/而没有/admin,nginx就会自动处理/admin 301重定向到/admin/。而如果说server块设置了server_name,nginx就会以它来返回host值。
而之所以会被定向到一个完全不同的端口,这就不得不提到我们项目的部署方式了。我们是直接在一个docker容器中部署了一整个应用的所有模块。而我们的容器只映射了宿主机6699端口到容器8000端口,所以nginx监听的是8000端口而请求头的host是6699端口。在重定向的时候采用了nginx的地址就导致了被重定向到了宿主机的8000端口。
那么我们只需要两个都配置一遍就行了。
浏览器的301缓存
按理来说,到这时为止问题应该解决了,可我重新访问时,网页还是被重定向了。此时问题就又变得扑朔迷离了起来。我寻找了好就,最后子啊chrom浏览器中寻找到了答案。还记得我前面说的网络请求筛选器吗,我在edge筛选了XHR/fetch,所以一直看不到301请求。但是因为在chrom里没有进行过这个操作,所以,我可以清楚的在请求中看到from disk cache。没错,破案了,其实问题早就解决,只是说因为浏览器一直在从缓存中读取数据而已。
那么问题来了,为什么即时后端的响应已经不同了,响应头也没有指挥浏览器进行缓存,浏览器却会反复从缓存中读取301呢。这就得从301这个状态码说起了。根据RFC 9110标准,301状态码表明资源已被永久移动。浏览器通常会缓存此信息,避免在未来的请求中重复访问原始 URL。所以浏览器缓存了我们第一次错误的301重定向响应。
最后,我清楚了浏览器缓存,admin面板正常访问,问题圆满解决。
总结
其实事后还是有一点小问题的。因为反向代理没有配置Django的static目录,所以admin面板是没有样式的,但是admin面板的静态资源于项目的静态资源不在一个位置却占用了同一个url,不过我们是前后端分离的项目,并且上传的图片在另一个media目录中,所以也就无所谓了。不过,我在设置static的反向代理的时候意识到了一个事情。那就是除了上传图片的media目录,所有的请求其实都要反向代理到Django应用中,也就是说其实配置可以简单的写成这样。
server {
listen 8000;
server_name
client_max_body_size 100M;
location /media/ {
root /home/hhh/GoodsExchangeBackend;
try_files $uri $uri/ =404;
autoindex on;
}
location / {
proxy_pass http://localhost:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
这么写不仅少,而且根本就不会遇到我上面遇到的重定向的问题。所以说,以后做事情还是多想想先再说。