显示带有FastAPI uvicorn Nginx的网页(Jinja2的模板功能)


0.简介

  • 根据在"使用docker-compose构建快速API uvicorn nginx"中创建的模板,使用Jinja2的Templates函数创建网页模板。

    • 之前,我曾使用Flask等进行试用,我想知道是否可以以相同的方式分发Template和Static文件。

*续集:使用FastAPI uvicorn Nginx(SSL / HTTPS)显示网页

实践:添加网页功能

*前提:从"使用docker-compose构建FastAPI uvicorn nginx的状态"开始

1.添加软件包

  • Jinja2用于模板功能,需要额外安装aiofiles,以分发静态文件

    • 参考:官方文件

  • pyproject.toml添加如下

pyproject.toml(其他)

1
2
3
4
[tool.poetry.dependencies]
# 以下の2つを追加
jinja2 = "*"
aiofiles = "*"

总计,例如:

pyproject.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[tool.poetry]
name = "test_fastapi_app"
version = "0.1.0"
description = "just for test"
authors = ["Your Name <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.8"
uvicorn = "*"
fastapi = "*"
jinja2 = "*"
aiofiles = "*"

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

2.修改app的内容?添加

  • 基本上,只是参考官方例子
  • 在这里,考虑创建多个页面或将这些功能与Rest-API函数等分离的可能性,并参考此内容以子应用程序的形式创建网页。

文件结构(app)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ tree
.
├── app
│&nbsp;&nbsp; ├── Dockerfile
│&nbsp;&nbsp; ├── app
│&nbsp;&nbsp; │&nbsp;&nbsp; ├── __init__.py
│&nbsp;&nbsp; │&nbsp;&nbsp; ├── main.py
│&nbsp;&nbsp; │&nbsp;&nbsp; ├── routers
│&nbsp;&nbsp; │&nbsp;&nbsp; │&nbsp;&nbsp; ├── __init__.py
│&nbsp;&nbsp; │&nbsp;&nbsp; │&nbsp;&nbsp; └── subpage.py
│&nbsp;&nbsp; │&nbsp;&nbsp; ├── static
│&nbsp;&nbsp; │&nbsp;&nbsp; │&nbsp;&nbsp; ├── layout.css
│&nbsp;&nbsp; │&nbsp;&nbsp; │&nbsp;&nbsp; └── subpage
│&nbsp;&nbsp; │&nbsp;&nbsp; │&nbsp;&nbsp;     ├── test.css
│&nbsp;&nbsp; │&nbsp;&nbsp; │&nbsp;&nbsp;     └── test.js
│&nbsp;&nbsp; │&nbsp;&nbsp; └── templates
│&nbsp;&nbsp; │&nbsp;&nbsp;     ├── layout.html
│&nbsp;&nbsp; │&nbsp;&nbsp;     └── subpage
│&nbsp;&nbsp; │&nbsp;&nbsp;         └── index.html
│&nbsp;&nbsp; ├── poetry.lock
│&nbsp;&nbsp; └── pyproject.toml
├── docker-compose.yml
└── web

app/app中的文件已更改或添加,因此我们将在

下查看详细信息

main.py

  • 修改内容如下

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"""                        
app main                      
"""                        

import pathlib
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse

from .routers import subpage

# pathlib.Pathを使って、staticディレクトリの絶対パスを取得
PATH_STATIC = str(pathlib.Path(__file__).resolve().parent / "static")


def create_app():
    """
    create app

    - 少し複雑化してきたので関数化している
    """
    _app = FastAPI()

    # routersモジュールのサブアプリ`subpage`をURL"/subpage/"以下にマウントする
    _app.include_router(
        subpage.router,
        prefix="/subpage",
        tags=["subpage"],
        responses={404: {"description": "not found"}},
    )

    # static
    # URL`/static"以下にstaticファイルをマウントする
    _app.mount(
        "/static",
        StaticFiles(directory=PATH_STATIC, html=False),
        name="static",
    )

    return _app


app = create_app()


@app.get('/')
async def redirect_subpage():
    """redirect webpage"""
    return RedirectResponse( # subpageのサブアプリで作ったWebページにリダイレクトさせている
        "/subpage",
    )

行数略有增加,但是我主要在做

  • 添加static

  • 子应用:挂载并重定向subpage

routers/subpage.py

* __init__.py在同一位置是一个空文件

路由器/ subpage.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"""
test subpage
"""

import pathlib

from fastapi import (
    APIRouter,
    Request,
)
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

# templatesディレクトリの絶対パスを取得
PATH_TEMPLATES = str(
    pathlib.Path(__file__).resolve() \
        .parent.parent / "templates"
)
# Jinja2のobject生成
templates = Jinja2Templates(directory=PATH_TEMPLATES)


# サブアプリ
router = APIRouter()


@router.get("/", response_class=HTMLResponse)
async def site_root(
    request: Request,
):
    """test subpage"""
    title = "test subpage"
    return templates.TemplateResponse(
        "subpage/index.html",   # `templates`ディレクトリにおける相対パス
        context={   # 変数をdict形式で渡すことが出来る
            "request": request,
            "title": title,
        }
    )
  • 使用Jinja2的模板功能,在templates目录下指定文件,并将其作为HTML响应返回。
  • 您可以传递Flask等参数。

templates目录

layout.html

  • 假定由多个html文件共同使用

    • extends从另一个文件调用

layout.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
        <meta name="apple-mobile-web-app-capable" content="yes">
        {% if title %}
            {{ title }}
        {% else %}
            Template
        {% endif %}

        <!-- jQuery & Bootstrap4 -->
        <script
            src="https://code.jquery.com/jquery-3.4.1.min.js"
            integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
            crossorigin="anonymous"></script>
        <link
            rel="stylesheet"
            href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
            integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
            crossorigin="anonymous">

        <script
            src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"
            integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
            crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
            integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
            crossorigin="anonymous"></script>

        <!-- jQuery UI -->
        <script
            src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"
            integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU="
            crossorigin="anonymous"></script>
        <link
            rel="stylesheet"
            type="text/css"
            href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.min.css">

        <!-- CUSTOM STYLE -->
        <link rel="stylesheet" type="text/css" href="{{url_for('static', path='/layout.css')}}">
        {% block head %}{% endblock %}

    </head>

    <body>
        {% block content %}{% endblock %}
    </body>

</html>
  • 读取jQueryjQuery-UIBoostrap

  • 接收title作为参数

  • 在单独的文件中分别填写headcontents的内容。

  • 我正在使用Jinja2的url_for从静态目录加载layout.css进行测试

subpage/index.html

子页面/ index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{% extends "layout.html" %}

{% block head %}
<link
    rel="stylesheet"
    type="text/css"
    href="{{ url_for('static', path='/subpage/test.css')  }}">
<script
    type="text/javascript"
    src="{{ url_for('static', path='subpage/test.js') }}"></script>
{% endblock %}


{% block content %}
<h2>Test Subpage</h2>

<br>

<h3>
    Hello, World.
</h3>

{% endblock %}
  • layout.htmlextends,并且通过从FastAPI端接收必要的参数,它成为完整的HTML。

  • 从静态目录读取test.csstest.js进行测试

static目录

  • 由于仅放置用于测试,几乎没有内容,因此省略了详细信息。
  • 我将确认以后执行时可以正确读取内容

3.执行(第1部分)

执行以下

1
2
3
4
5
# パッケージ追加、ソース修正?追加を行ったのでビルドし直す
docker-compose build

# サービスの起動
docker-compose up -d

如果您在本地环境中运行,请查看http://本地主机

スクリーンショット 2020-08-16 00-28-56.png

スクリーンショット 2020-08-16 00-34-01.png

乍一看似乎可行,但是如果仔细观察,您将无法从HTML读取static文件:

1
2
3
4
5
6
7
8
9
<link rel="stylesheet" type="text/css" href="http://backend/static/layout.css">

<link
    rel="stylesheet"
    type="text/css"
    href="http://backend/static/subpage/test.css">
<script
    type="text/javascript"
    src="http://backend/static/subpage/test.js"></script>
  • 在这种情况下,我希望srchref的URL为http://localhost/<url>,但它变为http://backend/<url>,如↑所示。

    • 尝试在HTML文件上使用url_for时发生上述问题

      • 在FastAPI代码(main.pyrouters/***.py等)中使用url_for没问题。

由于此区域存在代理问题,因此有必要修改uvicorn的启动选项和nginx的设置以处理

4.修改并执行设置(第2部分)

4-1。uvicorn

  • 如果您阅读uvicorn Official的部署和设置部分,则似乎需要设置--proxy-headers选项。

最后,按如下所示更改Dockerfile中的CMD并修复uvicorn

的启动选项

Dockerfile(更正)

1
2
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--proxy-headers", "--forwarded-allow-ips", "*"]

4-2。nginx

接下来,修改Nginx配置文件(web/conf.d/app.conf)
(请参阅部署uvicorn的Nginx示例)

1
2
3
4
5
6
7
$ tree    
.
├── app
├── docker-compose.yml
└── web
    └── conf.d
        └── app.conf

  • 如下修改↑中的app.conf

    • location /中添加了项目:

conf.d / app.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
upstream backend {
    server app:8000;
}

server {
    listen 80;
    # server_name  localhost;
    # index index.html index.htm;

    location / {
        # 以下の5項目を追加
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
        proxy_buffering off;

        proxy_pass http://backend;
    }

    # log
    # access_log /var/log/nginx/access.log;
    # error_log /var/log/nginx/error.log;
}

# server_tokens off;

到目前为止,如果您期望的URL被调用,就会感觉到url_for在HTML文件

上正确完成了。

image.png

  • ↑从static调用的文件的URL是正确的

概要

  • 使用FastAPI uvicorn Nginx(docker-compose)的配置在Flask气氛下创建Web函数
  • 当使用Template函数创建Web函数时,Flask更易于使用,而且我认为它很容易,因为文档很多。

    • FastAPI专用于RestAPI功能,可能不擅长Web功能。但是,如果您可以掌握异步处理,则可能会提高性能。
  • 我也进行了SSL转换(这很麻烦),所以我打算以后再写

    • →续集:使用FastAPI uvicorn Nginx(SSL / HTTPS)显示网页

参考

  • FastAPI官方文档
  • starlette官方文档

    • FastAPI(显然)是starlette的扩展,因此您需要适当地检查starlette规范。
  • uvicorn官方文档
  • https://note.com/yusugomori/n/n9f2c0422dfcd