FastAPI与nginx一起使用时,无法通过HTTPS提供静态文件服务。

4

我有一个小的FastAPI测试Web应用程序,它提供一个简单的HTML页面,需要一个位于static文件夹中的css样式表。它安装在Linode服务器(Ubuntu 20.04 LTS)上,使用nginx、gunicorn、uvicorn工作进程和supervisorctl。我已经添加了一个证书,使用certbot进行管理。

该应用程序在http下工作正常,但在https下无法访问静态文件。在http访问时,所有基于静态文件的功能都有效,但在https访问时,缺少来自css样式表的所有样式。我需要使其工作,以便我可以加载一个需要css和其他静态文件夹存储功能的更复杂的应用程序。

文件结构如下:

/home/<user_name>/application
- main.py
- static
   |_ css
   |_ bootstrap
- templates
   |_ index.html

main.py:

import fastapi
import uvicorn
from fastapi import Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates


api = fastapi.FastAPI()

api.mount('/static', StaticFiles(directory='static'), name='static')
templates = Jinja2Templates(directory="templates")


@api.get('/')
@api.get('/index', response_class=HTMLResponse)
def index(request: Request):
    message = None
    return templates.TemplateResponse("index.html", {"request": request,
        'message': message})


if __name__ == '__main__':
    uvicorn.run(api, port=8000, host='127.0.0.1')

nginx位于/etc/nginx/sites-enabled/ <my_url>.nginx

server {
    listen 80;
    server_name www.<my_url>.com <my_url>.com;
    server_tokens off;
    charset utf-8;

    location / {
        try_files $uri @yourapplication;
    }

    location /static {
        gzip            on;
        gzip_buffers    8 256k;

        alias /home/<user_name>/application/static;
        expires 365d;
    }


    location @yourapplication {
        gzip            on;
        gzip_buffers    8 256k;

        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Protocol $scheme;
    }
  }

server {
    listen              443 ssl;
    server_name         www.<my_url>.com;
    ssl_certificate /etc/letsencrypt/live/<my_url>.com/fullchain.pem; # mana>
    ssl_certificate_key /etc/letsencrypt/live/<my_url>.com/privkey.pem; # ma>
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    location / {
        try_files $uri @yourapplication;
    }

  location /static {
        gzip            on;
        gzip_buffers    8 256k;

        alias /home/<user_name>/application/static;
        expires 365d;
    }

    location @yourapplication {
        gzip            on;
        gzip_buffers    8 256k;

        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Protocol $scheme;
    }
}

我正在使用supervisor脚本进行服务:

[program:api]
directory=/home/<user_name>/application
command=gunicorn -b 127.0.0.1:8000 -w 4 -k uvicorn.workers.UvicornWorker main:api
environmentenvironment=PYTHONPATH=1
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stderr_logfile=/var/log/app/app.err.log
stdout_logfile=/var/log/app/app.out.log

CSS样式表可以通过在HTML页面中使用url_for来调用,如下所示:
<link href="{{ url_for('static', path='/css/ATB_style.css') }}" rel="stylesheet">

我尝试了许多修改Nginx中“location /static”块的方法,包括:

  • 在任一行或两行后面添加斜杠
  • 尝试添加https://static或https://www.<my_url>.com/home/<my_url>/application/static
  • 在http和https行中添加和删除location static
  • 将proxy_pass更改为https://127.0.0.1:8000;
  • 在server部分中添加root /home/<user_name>/application

我已经加载了这个服务器两次,第一次是让certbot修改Nginx文件,第二次则是手动进行配置。但我完全不知道该怎么做。


如果您访问一个HTTPS的URL,浏览器会阻止通过HTTP请求加载的资源。请确保您的img/css/js URL没有硬编码为“http://”。 - emptyhua
一切看起来都没问题。尝试在服务器的https和http中添加一个尾随斜杠。 然后将根目录更改为您的应用程序目录。尝试使用systemctl重新启动服务器。 请记住,您需要重新启动nginx和gunicorn。并且请通过您的fastapi应用程序进行文件服务,因为nginx在文件共享方面很快。如果仍未解决,请通知我。 - Akram Khan
1
将以下代码添加到您的HTML页面中:<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> 如果您遇到了硬编码HTTP URL的问题,它将重定向所有HTTP请求到HTTPS。 - Akram Khan
@AkramKhan,谢谢你,添加元数据解决了问题。哇! - Brad Allen
@Brad Allen,所以你在网站中已经硬编码了http路由。 更好的做法是不要依赖元标记。 只需找到网站中的http路由,然后将其更改为https即可。 - Akram Khan
显示剩余3条评论
3个回答

5

感谢@AdramKhan的评论,为重要的演示提供了解决方法。我在我的HTML页面中添加了一行元数据,允许使用https访问CSS样式表:

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

这只是一个替代方法,因为代码中某个位置处理了硬编码的HTTP请求,参考如何使用内容安全策略元标记允许混合内容(http和https)?

解决根本原因是更改静态内容在HTML文件头部的调用方式。有三个问题,其中引用了jinja2 url_for而不是直接使用href:

<link href="{{ url_for('static', path='/css/MH_style.css') }}" rel="stylesheet">

当使用 href 格式的引用替换时:
<link rel="stylesheet" href="/static/css/MH_style.css"/>

没有 Content-Security-Policy 元标签也可以正常工作。


0

如果使用upstream,则proxy_pass应设置如下:

http {
    upstream myapp {
        server application_container:4001;
    }

    server {

        listen 80;
        server_name localhost;

        location / {
            ...
        }

        location /myapp/ {
                proxy_pass http://myapp;
                ...
        }

        location /static/ {
            proxy_pass http://myapp;
            alias ...
        }
}

0
我认为您希望服务器处理它。如果您只是在端口80上设置一个单独的块,将所有请求永久转换为443(HTTPS),那么您就可以了:
server {
    listen 80;
    server_name yourserver.com;
    return 301 https://yourserver.com$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourserver.com;
    ...
}```

1
将端口80更改为重定向,如此答案中所示,然后在HTML中注释掉meta http-equiv行。 重定向效果很好,但又失去了静态目录访问,并且没有使用CSS样式表。 添加http-equiv行后,它与重定向一起工作。 希望能够找出我与其他人明显不同并导致此问题的原因。 - Brad Allen
这与在静态文件夹中使用Bootstrap 4.1有关吗? - Brad Allen
1
肯尼迪,我采纳了你的建议,找到了直接的 HTTP 调用。编辑答案以显示结果。 - Brad Allen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接