За прокси‑сервером¶
AI coding agents
Use the official FastAPI skill for your installed version. Install it from your project directory:
uvx library-skills
See Develop with AI.
🌐 Перевод выполнен с помощью ИИ и людей
Этот перевод был сделан ИИ под руководством людей. 🤝
В нем могут быть ошибки из-за неправильного понимания оригинального смысла или неестественности и т. д. 🤖
Вы можете улучшить этот перевод, помогая нам лучше направлять ИИ LLM.
Во многих случаях перед приложением FastAPI используется прокси‑сервер, например Traefik или Nginx.
Такие прокси могут обрабатывать HTTPS‑сертификаты и многое другое.
Пересылаемые HTTP-заголовки прокси¶
Прокси перед вашим приложением обычно на лету добавляет некоторые HTTP-заголовки перед отправкой HTTP-запроса на ваш сервер, чтобы сообщить ему, что HTTP-запрос был переслан прокси, а также передать исходный (публичный) URL (включая домен), информацию об использовании HTTPS и т.д.
Сервер, запущенный через fastapi run, умеет интерпретировать эти HTTP-заголовки и передавать соответствующую информацию вашему приложению.
Но из соображений безопасности, пока сервер не уверен, что находится за доверенным прокси, он не будет интерпретировать эти HTTP-заголовки.
Включить пересылаемые HTTP-заголовки прокси¶
Вы можете запустить FastAPI CLI с опцией командной строки --forwarded-allow-ips и передать IP‑адреса, которым следует доверять при чтении этих пересылаемых HTTP-заголовков.
Если указать --forwarded-allow-ips="*", приложение будет доверять всем входящим IP.
Если ваш сервер находится за доверенным прокси и только прокси обращается к нему, этого достаточно, чтобы он принимал любой IP этого прокси.
$ uv run fastapi run --forwarded-allow-ips="*"
Редиректы с HTTPS¶
Например, вы объявили операцию пути /items/:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
def read_items():
return ["plumbus", "portal gun"]
Если клиент обратится к /items, по умолчанию произойдёт редирект на /items/.
Но до установки опции --forwarded-allow-ips редирект может вести на http://localhost:8000/items/.
Однако приложение может быть доступно по https://mysuperapp.com, и редирект должен вести на https://mysuperapp.com/items/.
Указав --proxy-headers, FastAPI сможет редиректить на корректный адрес. 😎
https://mysuperapp.com/items/
Совет
Если хотите узнать больше об HTTPS, смотрите руководство О HTTPS.
Как работают пересылаемые HTTP-заголовки прокси¶
Ниже показано, как прокси добавляет пересылаемые HTTP-заголовки между клиентом и сервером приложения:
sequenceDiagram
participant Client as Клиент
participant Proxy as Прокси/Балансировщик нагрузки
participant Server as FastAPI-сервер
Client->>Proxy: HTTPS-запрос<br/>Host: mysuperapp.com<br/>Path: /items
Note over Proxy: Прокси-сервер добавляет пересылаемые заголовки
Proxy->>Server: HTTP-запрос<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
Note over Server: Сервер интерпретирует HTTP-заголовки<br/>(если --forwarded-allow-ips установлен)
Server->>Proxy: HTTP-ответ<br/>с верными HTTPS URLs
Proxy->>Client: HTTPS-ответ
Прокси перехватывает исходный клиентский HTTP-запрос и добавляет специальные пересылаемые HTTP-заголовки (X-Forwarded-*) перед передачей HTTP-запроса на сервер приложения.
Эти HTTP-заголовки сохраняют информацию об исходном HTTP-запросе, которая иначе была бы потеряна:
- X-Forwarded-For: исходный IP‑адрес клиента
- X-Forwarded-Proto: исходный протокол (
https) - X-Forwarded-Host: исходный хост (
mysuperapp.com)
Когда FastAPI CLI сконфигурирован с --forwarded-allow-ips, он доверяет этим HTTP-заголовкам и использует их, например, чтобы формировать корректные URL в редиректах.
Прокси с функцией удаления префикса пути¶
Прокси может добавлять к вашему приложению префикс пути.
В таких случаях вы можете настроить сервер с помощью fastapi run --root-path.
Механизм root_path определён спецификацией ASGI (на которой построен FastAPI, через Starlette).
root_path используется для обработки таких специфических случаев.
Он также используется внутри при монтировании подприложений.
Прокси с функцией удаления префикса пути в этом случае означает, что вы объявляете путь /app в коде, а затем добавляете сверху слой (прокси), который размещает ваше приложение FastAPI под путём вида /api/v1.
Тогда исходный путь /app фактически будет обслуживаться по адресу /api/v1/app.
Хотя весь ваш код написан с расчётом, что путь один — /app.
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/app")
def read_main(request: Request):
return {"message": "Hello World", "root_path": request.scope.get("root_path")}
Прокси будет «обрезать» префикс пути на лету перед передачей HTTP-запроса на сервер, поддерживая у вашего приложения иллюзию, что его обслуживают по /app, чтобы вам не пришлось менять весь код и добавлять префикс /api/v1.
До этого момента всё будет работать как обычно.
Но когда вы откроете встроенный интерфейс документации (фронтенд), он будет ожидать получить схему OpenAPI по адресу /openapi.json, а не /api/v1/openapi.json.
Поэтому фронтенд (который работает в браузере) попытается обратиться к /openapi.json и не сможет получить схему OpenAPI.
Так как для нашего приложения используется прокси с префиксом пути /api/v1, фронтенду нужно забирать схему OpenAPI по /api/v1/openapi.json.
graph LR
browser("Browser")
proxy["Proxy on http://0.0.0.0:9999/api/v1/app"]
server["Server on http://127.0.0.1:8000/app"]
browser --> proxy
proxy --> server
Совет
IP 0.0.0.0 обычно означает, что программа слушает на всех IP‑адресах, доступных на этой машине/сервере.
Интерфейсу документации также нужна схема OpenAPI, в которой будет указано, что этот API server находится по пути /api/v1 (за прокси). Например:
{
"openapi": "3.1.0",
// Здесь ещё что-то
"servers": [
{
"url": "/api/v1"
}
],
"paths": {
// Здесь ещё что-то
}
}
В этом примере «Proxy» может быть, например, Traefik. А fastapi run запустит сервер, на котором работает ваше приложение FastAPI.
Указание root_path¶
Для этого используйте опцию командной строки --root-path, например так:
$ uv run fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
Технические детали
Спецификация ASGI определяет root_path для такого случая.
А опция командной строки --root-path передаёт этот root_path.
Проверка текущего root_path¶
Вы можете получить текущий root_path, используемый вашим приложением для каждого HTTP-запроса, — он входит в словарь scope (часть спецификации ASGI).
Здесь мы добавляем его в сообщение лишь для демонстрации.
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/app")
def read_main(request: Request):
return {"message": "Hello World", "root_path": request.scope.get("root_path")}
Затем, если вы запустите сервер так:
$ uv run fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
HTTP-ответ будет примерно таким:
{
"message": "Hello World",
"root_path": "/api/v1"
}
Устаревший параметр root_path
В FastAPI 0.145.0 и более ранних версиях root_path также можно было настроить в FastAPI(). Теперь этот параметр устарел, поскольку его поведение не было согласовано с конфигурацией сервера. Вместо него используйте fastapi run --root-path /api/v1.
О прокси с функцией удаления префикса пути¶
Помните, что прокси с функцией удаления префикса пути — лишь один из вариантов настройки.
Во многих случаях по умолчанию прокси будет без функции удаления префикса пути.
В таком случае (без функции удаления префикса пути) прокси слушает, например, по адресу https://myawesomeapp.com, и если браузер идёт на https://myawesomeapp.com/api/v1/app, а ваш сервер слушает на http://127.0.0.1:8000, то прокси (без функции удаления префикса пути) обратится к серверу по тому же пути: http://127.0.0.1:8000/api/v1/app.
Локальное тестирование с Traefik¶
Вы можете легко поэкспериментировать локально с функцией удаления префикса пути, используя Traefik.
Скачайте Traefik — это один бинарный файл; распакуйте архив и запустите его прямо из терминала.
Затем создайте файл traefik.toml со следующим содержимым:
[entryPoints]
[entryPoints.http]
address = ":9999"
[providers]
[providers.file]
filename = "routes.toml"
Это говорит Traefik слушать порт 9999 и использовать другой файл routes.toml.
Совет
Мы используем порт 9999 вместо стандартного HTTP‑порта 80, чтобы не нужно было запускать с правами администратора (sudo).
Теперь создайте второй файл routes.toml:
[http]
[http.middlewares]
[http.middlewares.api-stripprefix.stripPrefix]
prefixes = ["/api/v1"]
[http.routers]
[http.routers.app-http]
entryPoints = ["http"]
service = "app"
rule = "PathPrefix(`/api/v1`)"
middlewares = ["api-stripprefix"]
[http.services]
[http.services.app]
[http.services.app.loadBalancer]
[[http.services.app.loadBalancer.servers]]
url = "http://127.0.0.1:8000"
Этот файл настраивает Traefik на использование префикса пути /api/v1.
Далее Traefik будет проксировать HTTP-запросы на ваш сервер, работающий на http://127.0.0.1:8000.
Теперь запустите Traefik:
$ ./traefik --configFile=traefik.toml
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
И запустите приложение с опцией --root-path:
$ uv run fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
Проверьте HTTP-ответы¶
Теперь, если вы перейдёте на URL с портом сервера: http://127.0.0.1:8000/app, вы увидите обычный HTTP-ответ:
{
"message": "Hello World",
"root_path": "/api/v1"
}
Совет
Обратите внимание, что хотя вы обращаетесь по http://127.0.0.1:8000/app, в HTTP-ответе указан root_path равный /api/v1, взятый из опции --root-path.
А теперь откройте URL с портом Traefik и префиксом пути: http://127.0.0.1:9999/api/v1/app.
Мы получим тот же HTTP-ответ:
{
"message": "Hello World",
"root_path": "/api/v1"
}
но уже по URL с префиксом, который добавляет прокси: /api/v1.
Разумеется, идея здесь в том, что все будут обращаться к приложению через прокси, поэтому вариант с префиксом пути /api/v1 является «правильным».
А вариант без префикса (http://127.0.0.1:8000/app), выдаваемый напрямую сервером, предназначен исключительно для того, чтобы прокси (Traefik) мог к нему обращаться.
Это демонстрирует, как прокси (Traefik) использует префикс пути и как сервер использует root_path, переданный через опцию --root-path.
Проверьте интерфейс документации¶
А вот самое интересное. ✨
«Официальный» способ доступа к приложению — через прокси с заданным префиксом пути. Поэтому, как и ожидается, если открыть интерфейс документации, отдаваемый напрямую сервером, без префикса пути в URL, он не будет работать, так как предполагается доступ через прокси.
Проверьте по адресу http://127.0.0.1:8000/docs:

А вот если открыть интерфейс документации по «официальному» URL через прокси на порту 9999, по /api/v1/docs, всё работает корректно! 🎉
Проверьте по адресу http://127.0.0.1:9999/api/v1/docs:

Именно как и хотелось. ✔️
Это потому, что FastAPI использует root_path, чтобы создать в OpenAPI сервер по умолчанию с URL из root_path.
Дополнительные серверы¶
Предупреждение
Это более продвинутый сценарий. Можно пропустить.
По умолчанию FastAPI создаёт в схеме OpenAPI server с URL из root_path.
Но вы также можете указать дополнительные servers, например, если хотите, чтобы один и тот же интерфейс документации работал и со стейджингом, и с продакшн.
Если вы передадите свой список servers и при этом задан root_path (потому что ваш API работает за прокси), FastAPI вставит «server» с этим root_path в начало списка.
Например, используя ту же конфигурацию сервера fastapi run --root-path /api/v1:
from fastapi import FastAPI, Request
app = FastAPI(
servers=[
{"url": "https://stag.example.com", "description": "Staging environment"},
{"url": "https://prod.example.com", "description": "Production environment"},
],
)
@app.get("/app")
def read_main(request: Request):
return {"message": "Hello World", "root_path": request.scope.get("root_path")}
Будет сгенерирована схема OpenAPI примерно такая:
{
"openapi": "3.1.0",
// Здесь ещё что-то
"servers": [
{
"url": "/api/v1"
},
{
"url": "https://stag.example.com",
"description": "Staging environment"
},
{
"url": "https://prod.example.com",
"description": "Production environment"
}
],
"paths": {
// Здесь ещё что-то
}
}
Совет
Обратите внимание на автоматически добавленный сервер с url равным /api/v1, взятым из root_path.
В интерфейсе документации по адресу http://127.0.0.1:9999/api/v1/docs это будет выглядеть так:

Совет
Интерфейс документации будет взаимодействовать с сервером, который вы выберете.
Технические детали
Свойство servers в спецификации OpenAPI является необязательным.
Если вы не укажете параметр servers, а root_path равен /, то свойство servers в сгенерированной схеме OpenAPI по умолчанию будет опущено. Это эквивалентно серверу со значением url равным /.
Отключить автоматическое добавление сервера из root_path¶
Если вы не хотите, чтобы FastAPI добавлял автоматический сервер, используя root_path, укажите параметр root_path_in_servers=False:
from fastapi import FastAPI, Request
app = FastAPI(
servers=[
{"url": "https://stag.example.com", "description": "Staging environment"},
{"url": "https://prod.example.com", "description": "Production environment"},
],
root_path_in_servers=False,
)
@app.get("/app")
def read_main(request: Request):
return {"message": "Hello World", "root_path": request.scope.get("root_path")}
и тогда этот сервер не будет добавлен в схему OpenAPI.
Монтирование подприложения¶
Если вам нужно смонтировать подприложение (как описано в Подприложения — монтирование), и при этом вы используете прокси с root_path, делайте это обычным образом — всё будет работать, как ожидается.
FastAPI умно использует root_path внутри, так что всё просто работает. ✨