--[Jinja2](https: //) based on the one created in "FastAPI + uvicorn + nginx built with docker-compose" Create a web page template using the Templates function by jinja.palletsprojects.com/en/2.11.x/) --I've used Flask as a trial before, and I'm looking for ways to distribute Template and Static files in the same way. I wanted
--Jinja2
for Template function requires additional installation of ʻaiofilesto deliver Static files --Reference: [Official Documents](https://fastapi.tiangolo.com/advanced/templates/#install-dependencies) --Added the following to
pyproject.toml`
pyproject.toml(Additions)
[tool.poetry.dependencies]
#Added the following two
jinja2 = "*"
aiofiles = "*"
In total, for example:
pyproject.toml
[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"
--Basically, you can refer to Official example. --Here, consider the possibility of creating multi-pages or separating the functions from Rest-API functions, etc. here Try to create a web page in the form of a sub-app
$ tree
.
├── app
│ ├── Dockerfile
│ ├── app
│ │ ├── __init__.py
│ │ ├── main.py
│ │ ├── routers
│ │ │ ├── __init__.py
│ │ │ └── subpage.py
│ │ ├── static
│ │ │ ├── layout.css
│ │ │ └── subpage
│ │ │ ├── test.css
│ │ │ └── test.js
│ │ └── templates
│ │ ├── layout.html
│ │ └── subpage
│ │ └── index.html
│ ├── poetry.lock
│ └── pyproject.toml
├── docker-compose.yml
└── web
Since the files in ʻapp / app` have been changed / added, we will look at the details below.
main.py
--Corrected the contents as follows
main.py
"""
app main
"""
import pathlib
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse
from .routers import subpage
# pathlib.Use Path to get the absolute path of a static directory
PATH_STATIC = str(pathlib.Path(__file__).resolve().parent / "static")
def create_app():
"""
create app
-It's getting a little complicated, so it's functionalized
"""
_app = FastAPI()
#Sub-app of routers module`subpage`URL"/subpage/"Mount below
_app.include_router(
subpage.router,
prefix="/subpage",
tags=["subpage"],
responses={404: {"description": "not found"}},
)
# static
# URL`/static"Mount the static file below
_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( #Redirecting to a web page created with a subpage sub-app
"/subpage",
)
The number of lines has increased a little, but what I'm doing is mainly
--Addition of static
--Sub-app: Mount and redirect subpage
routers/subpage.py
__Init__.py
in the same location is an empty filerouters/subpage.py
"""
test subpage
"""
import pathlib
from fastapi import (
APIRouter,
Request,
)
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
#Get the absolute path of the templates directory
PATH_TEMPLATES = str(
pathlib.Path(__file__).resolve() \
.parent.parent / "templates"
)
#Jinja2 object generation
templates = Jinja2Templates(directory=PATH_TEMPLATES)
#Sub app
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`Relative path in the directory
context={ #Variables can be passed in dict format
"request": request,
"title": title,
}
)
--Using the Template function of Jinja2, specify the file under the templates
directory and return it as an HTML response.
--You can pass parameters like Flask etc.
templates
directorylayout.html
--Assumed to be used in common by multiple html files --Called by ʻextends` from other files
layout.html
<!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>{{ title }}</title>
{% else %}
<title>Template</title>
{% 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>
--Loading jQuery
, jQuery-UI
, Bootstrap
--title
is received as a parameter
--The contents of the head
and contents
parts are individually entered in separate files.
--Reading layout.css
from a static directory using Jinja2's ʻurl_for` for testing
subpage/index.html
subpage/index.html
{% 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 %}
--It becomes complete HTML by ʻextends`` layout.htmland receiving the necessary parameters from the Fast API side. --Loading
test.css and
test.js` from a static directory for testing
static
directory--The details are omitted because they are only placed for testing and there is virtually no content. ――We will confirm that the contents can be read properly at the time of execution later.
Do the following
#Package added, source modified / added, so rebuild
docker-compose build
#Service startup
docker-compose up -d
If you're running locally, take a look at http: // localhost
At first glance, it seems to work, but if you look closely, you can't read the static
file from HTML:
<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>
-** I want the url of src
and href
to be http: // localhost / <url>
in this case, but like ↑, http: // backend / <url>
turn into**
-** The above problem occurs when trying to use ** ʻurl_for on an HTML file --There is no problem in using ʻurl_for
in ** FastAPI code ** (main.py
, routers / ***. Py
, etc.)
Since there is a proxy problem in this area, it is necessary to modify the startup option of ʻuvicornand the setting of
nginx` to deal with it.
4-1. uvicorn
--If you read the official deployment and setting parts of uvicorn, you will find that It seems that the proxy-headers` option needs to be set
In conclusion, change the CMD
of the Dockerfile as follows to fix the startup option of ʻuvicorn`
Dockerfile (correction)
# 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
Next, modify the Nginx configuration file (web / conf.d / app.conf
)
(Refer to the example of Nginx in deployment of uvicorn)
$ tree
.
├── app
├── docker-compose.yml
└── web
└── conf.d
└── app.conf
-Modify ʻapp.confin ↑ as follows --Added items in
location /`:
conf:conf.d/app.conf
upstream backend {
server app:8000;
}
server {
listen 80;
# server_name localhost;
# index index.html index.htm;
location / {
#Added the following 5 items
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;
If you do so far, the URL that you expected will be called correctly like http: // localhost / <url>
even if you did ʻurl_for` on the HTML file.
-↑ The URL of the file called from static is correct
--FastAPI + uvicorn + Nginx (docker-compose) configuration to create a web function with an atmosphere like Flask ――I felt that Flask is easier to use and has a lot of literature when creating Web functions using Template functions. --FastAPI is specialized for RestAPI functions and may not be good at Web functions. However, if you can master asynchronous processing, there may be performance potential. --I also did SSL conversion (and it was quite troublesome) ~~, so I plan to write more later ~~ -→ Sequel: Display Web page with FastAPI + uvicorn + Nginx (SSL / HTTPS)
-FastAPI Official Document -Starlette Official Document --FastAPI is (apparently) an extension of starlette, so you need to check the starlette specifications as appropriate. -uvicorn Official Document
Recommended Posts