Перейти к содержанию

Веб-приложение

Описание

Application — это сердце веб-сервера, отвечающее за обработку всех входящих запросов.

С его помощью можно регистрировать и управлять:

  • веб-обработчиками (endpoint)
  • промежуточными обработчиками (middleware)
  • дочерними приложениями
  • фоновыми задачами

rapidy.Rapidy является псевдонимом для rapidy.web.Application

Сущности Application

Endpoint

Endpoint — это конечная точка веб-сервиса, к которой клиентское приложение обращается для выполнения определённых операций или получения данных, например, /api/user/get-data.

from rapidy.http import get

@get('/')
async def handler() -> dict[str, str]:
    return {'hello': 'rapidy'}

Подробнее о создании endpoint смотрите в разделе Handlers.


Middleware

Middleware позволяют выполнять действия над запросом до и после его обработки веб-обработчиком.

from rapidy.http import middleware, Request, StreamResponse, Header
from rapidy.typedefs import CallNext

TOKEN_REGEXP = '^[Bb]earer (?P<token>[A-Za-z0-9-_=.]*)'

@middleware
async def get_bearer_middleware(
        request: Request,
        call_next: CallNext,
        bearer_token: str = Header(alias='Authorization', pattern=TOKEN_REGEXP),
) -> StreamResponse:
    # process token here...
    return await call_next(request)

Подробнее о создании middleware смотрите в разделе Middlewares.

Применение middleware для разных версий API.
from rapidy import web
from rapidy.typedefs import Handler

routes = web.RouteTableDef()


def get_token_data(token: str) -> ...:
    ...


def get_session_data(session: str) -> ...:
    ...


@web.middleware
async def keycloak_auth_middleware(
        request: web.Request,
        handler: Handler,
        bearer_token: str = web.Header(alias='Authorization'),
) -> web.StreamResponse:
    try:
        parsed_token_data = get_token_data(bearer_token)
    except Exception:
        return web.HTTPUnauthorized(text='Failed to authenticate with bearer')

    return await handler(request)


@web.middleware
async def cookie_session_auth_middleware(
        request: web.Request,
        handler: Handler,
        session: str = web.Cookie(alias='UserSession'),
) -> web.StreamResponse:
    try:
        parsed_session_data = get_session_data(session)
    except Exception:
        return web.HTTPUnauthorized(text='Failed to authenticate with session')

    return await handler(request)


@routes.get('/get_hello')
async def handler() -> dict[str, str]:
    return {'hello': 'rapidy'}


v1_app = web.Application(middlewares=[cookie_session_auth_middleware])
v1_app.add_routes(routes)

v2_app = web.Application(middlewares=[keycloak_auth_middleware])
v2_app.add_routes(routes)

app = web.Application()
app.add_subapp('/v1', v1_app)
app.add_subapp('/v2', v2_app)

Routing

Здесь показано, как можно организовывать группы web-обработчиков.

Пример плохой практики.
@routes.get('/api/v1/get_hello')  # <-- bad practice
async def handler() -> ...:

Рекомендуется использовать rapidy.http.HTTPRouter или дочерние rapidy.web.Application.

HTTPRouter

Использование HTTPRouter делает код более лаконичным и удобным.

from rapidy import Rapidy  # <-- rapidy.Rapidy == rapidy.web.Application
from rapidy.http import get, HTTPRouter

@get('/get_hello')
async def handler() -> dict[str, str]:
    return {'hello': 'rapidy'}

v1_app = HTTPRouter('/v1', route_handlers=[handler])
rapidy = Rapidy(http_route_handlers=[v1_app])
curl -X GET http://127.0.0.1:8080/v1/get_hello

Подробнее о HTTPRouter смотрите в разделе HTTPRouter.

Подприложение (aiohttp-style)

Создание дочерних Application:

from rapidy import web

routes = web.RouteTableDef()

@routes.get('/get_hello')
async def handler() -> dict[str, str]:
    return {'hello': 'rapidy'}

v1_app = web.Application()
v1_app.add_routes(routes)

app = web.Application()
app.add_subapp('/v1', v1_app)
curl -X GET http://127.0.0.1:8080/v1/get_hello

Использование дочерних приложений в стиле aiohttp при написании нового кода не рекомендуется.


Lifespan

Lifespan — это механизм управления жизненным циклом фоновых задач в Rapidy.

Он управляет задачами, которые должны запускаться: до или после старта сервера, а также работать постоянно.

from contextlib import asynccontextmanager
from typing import AsyncGenerator
from rapidy import Rapidy

async def startup() -> None:
    print('startup')

async def shutdown() -> None:
    print('shutdown')

async def cleanup() -> None:
    print('cleanup')

@asynccontextmanager
async def bg_task() -> AsyncGenerator[None, None]:
    try:
        print('starting background task')
        yield
    finally:
        print('finishing background task')

rapidy = Rapidy(
    on_startup=[startup],
    on_shutdown=[shutdown],
    on_cleanup=[cleanup],
    lifespan=[bg_task()],
)

Подробнее о lifespan смотрите в разделе Lifespan.


Атрибуты Application

Дополнительные атрибуты в Rapidy

server_info_in_response

Определяет, нужно ли включать информацию о сервере в заголовке Server.

server_info_in_response: bool = False

lifespan

Список фоновых задач, которые запускаются и завершаются вместе с сервером.

lifespan: Optional[List[LifespanCTX]] = None

on_startup

Список задач, запускаемых сразу после старта приложения.

on_startup: Optional[List[LifespanHook]] = None

on_shutdown

Задачи, выполняемые при остановке сервера.

on_shutdown: Optional[List[LifespanHook]] = None

on_cleanup

Задачи, выполняемые после on_shutdown.

on_cleanup: Optional[List[LifespanHook]] = None

http_route_handlers

HTTP-роутеры, которые могут представлять собой как отдельные обработчики, так и группы HTTPRouter.

http_route_handlers: Iterable[HTTPRouterType] = ()

Атрибуты aiohttp

middlewares

Список middleware, применяемых ко всем обработчикам, включая дочерние приложения.

middlewares: Optional[Iterable[Middleware]] = None

client_max_size

Максимальный размер запроса в байтах.

client_max_size: int = 1024**2

logger

Логгер для приема логов от Application.

logger: logging.Logger = logging.getLogger("aiohttp.web")

Запуск приложения

Простой запуск

Скопируйте в файл main.py.

from rapidy import Rapidy, run_app
from rapidy.http import post

@post('/')
async def handler() -> dict[str, str]:
    return {'hello': 'rapidy'}

rapidy = Rapidy()
rapidy.add_http_router(handler)

if __name__ == '__main__':
    run_app(rapidy)
Запустите сервер:
python3 main.py

Можно указать параметры, например, host или port.

run_app(app, host='0.0.0.0', port=8080)

WSGI запуск (Gunicorn)

Установите gunicorn:

pip install gunicorn
Скопируйте код в main.py:
from rapidy import Rapidy
from rapidy.http import post

@post('/')
async def handler() -> dict[str, str]:
    return {'hello': 'rapidy'}

rapidy = Rapidy()
rapidy.add_http_router(handler)
Запустите сервер:
gunicorn main:rapidy --bind localhost:8080 --reload --worker-class aiohttp.GunicornWebWorker

Команда gunicorn main:app обращается к:

  • main: файл main.py (модуль Python).
  • rapidy: объект, созданный внутри файла main.py в строке rapidy = Rapidy().
  • --reload: перезапускает сервер после изменения кода. Используйте только для разработки.