问题

在将django写的网站部署到服务器上的时候,静态文件总是404。
部署的架构是django+uwsgi+supervisor+nginx,即常见的nginx在最前端,处理静态文件,动态部分交给uwsgi处理。
这种部署方式,之前部署过很多次,均没出现过今天这么顽固的情况。

测试

发现问题后,我首先复习了django的setting里关于静态文件的部分。这部分还是有点乱的,每次隔一段时间不用django,再拿起来第一步就是先复习静态文件部分,因为官方的设置里所有的参数都叫STATIC*,并不对应开发或者生产环境,所以需要开发人员自己对应开发生产环境设置相应的参数。

  • STATIC_URL:主要是对应模板中使用static关键字生成的url
  • STATIC_FILES:主要是在开发环境中,默认django会对app下的static目录分别查找静态文件,有时候有额外的目录希望django去查找静态文件的时候应该加入这个列表中
  • STATIC_ROOT:这个路径是针对运行python manage.py collectstatic的,最后会把所有的静态文件collect到这个目录中。相应的,nginx也应该把匹配到的静态url对应到这个目录。
  1. 反复确认django中的静态文件设置,并无错误。
  2. 之后又反复对比了一个以同样方式部署的已经在线上运行的django项目,发现二者nginx配置文件除了目录部分的不同,并没有什么本质上的区别
  3. 项目里还有一个media目录,也是静态文件,在nginx中配置location后就可以正常访问
  4. 最奇怪的一点:在服务器的静态文件目录里创建一个测试的a.txt文件,竟然是可以正常通过nginx访问的。后来发现,只有js、css、jpg等几种文件访问不到。
  5. 查看uwsgi的日志以及nginx的accesslog,发现所有不能访问的静态资源url都仿佛跳过了nginx,被uwsgi处理了,而uwsgi负责的django里并没有接管静态文件url的功能,所以就404了。

分析

在反复搜索、测试后,结合测试结果,感觉到问题应该是出现在nginx部分。
所以开始在google搜索nginx location not working static,没想到立马就搜到了Stack Overflow上跟我相似的问题

问题根本上是由于nginx对location的匹配顺序造成的,而在遇到问题之前,我一直没重视过这方面,以为对location的匹配是从上而下的第一个。
然而并不是,nginx对location的匹配是正则类型的优先,非正则类型的次之,具体的。而我的服务器使用了宝塔面板,新建站点后,默认的站点nginx配置中会有如下的配置

location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
        expires      30d;
        error_log off;
        access_log off;
    }
    
    location ~ .*\.(js|css)?$
    {
        expires      12h;
        error_log off;
        access_log off; 

恰恰是它们,优先劫持了一部分也是最重要的那部分静态文件,使我写的location没能生效,自然这部分请求就转到了uwsgi处理,而django中只有对/media的url路由,并没有专门路由static,自然就404了。
那么为什么我的另一个同样架构的站点采用的是一样的配置方法而它却没有遇到这个问题呢?

于是我又测试了一波:

  1. 先改掉nginx配置里的/static的location,果然静态文件依然能访问,说明之前压根就不是这里在起作用
  2. 怀疑是不是因为开了DEBUG,所以django才依然能够自己处理静态文件,然而发现关了DEBUG并不影响~
  3. 查看目录,发现app下和STATIC_ROOT目录同时都有静态文件,通过每次移除一个目录测试发现,实际生效的确实是STATIC_ROOT目录
  4. 修改settings.py里的STATIC_URL和STATIC_ROOT。发现修改STATIC_URL后静态文件挂掉,而注释掉STATIC_ROOT不会影响。
  5. 同时修改STATIC_URL和目录里实际生效的那个静态文件存放目录,发现,只要二者名字相同,即可正常访问。

解决

比较简单直接的方法是:在nginx配置文件中,合并存在冲突的location或者直接移除面板自带的location即可。
优化思考:面板自带的是根据文件类型匹配,控制不同类型静态文件过期时间的location。那么如何既实现static的路由,又实现文件类型分别控制过期时间呢?答案是使用嵌套的方式实现