← Назад к вопросам
Как реализовать URL реестр в своем фреймворке?
2.0 Middle🔥 81 комментариев
#FastAPI и Flask#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация URL реестра в своём фреймворке
URL реестр (URL registry/routing system) — это механизм для сопоставления URL-пути с обработчиками (handlers). Это основа web фреймворка.
Простая реализация
class SimpleRouter:
"""Простой маршрутизатор без regex"""
def __init__(self):
self.routes = {} # path -> handler
def register(self, path, method, handler):
"""Зарегистрировать обработчик для пути"""
key = (method, path)
self.routes[key] = handler
def match(self, method, path):
"""Найти обработчик для пути"""
key = (method, path)
return self.routes.get(key)
# Использование
router = SimpleRouter()
router.register("GET", "/users", list_users_handler)
router.register("POST", "/users", create_user_handler)
handler = router.match("GET", "/users")
if handler:
response = handler()
else:
response = 404 # Not found
Продвинутая реализация с параметрами
import re
from typing import Callable, Dict, Tuple, Optional
class AdvancedRouter:
"""Маршрутизатор с поддержкой параметров в пути"""
def __init__(self):
self.routes = [] # Список (pattern, handler, methods)
def register(self, path: str, handler: Callable, methods: list = None):
"""Зарегистрировать обработчик
path: '/users/<user_id>' или '/posts/<slug:slug>'
"""
methods = methods or ['GET']
# Преобразовать Django-like паттерн в regex
pattern = self._path_to_regex(path)
param_names = self._extract_params(path)
self.routes.append({
'pattern': pattern,
'handler': handler,
'methods': methods,
'param_names': param_names
})
def _path_to_regex(self, path: str) -> str:
"""Преобразовать /users/<user_id> в regex"""
# <user_id> или <int:user_id> или <slug:slug>
pattern = re.escape(path)
# Заменить <type:name> на regex группы
pattern = re.sub(
r'\\<(int|str|uuid|slug):(\w+)\\>',
r'(?P<\2>\\d+)' if 'int' else r'(?P<\2>[\\w-]+)',
pattern
)
# Заменить просто <name> на строку
pattern = re.sub(r'\\<(\w+)\\>', r'(?P<\1>[\\w-]+)', pattern)
return f'^{pattern}$'
def _extract_params(self, path: str) -> list:
"""Извлечь имена параметров из пути"""
matches = re.findall(r'<(?:(?:\w+):)?(\w+)>', path)
return matches
def match(self, method: str, path: str) -> Tuple[Optional[Callable], Dict]:
"""Найти обработчик и параметры"""
for route in self.routes:
if method not in route['methods']:
continue
match = re.match(route['pattern'], path)
if match:
params = match.groupdict()
# Конвертировать параметры в правильные типы
return route['handler'], params
return None, {}
# Использование
router = AdvancedRouter()
router.register('/users/<int:user_id>', get_user_handler, ['GET'])
router.register('/posts/<slug:slug>', get_post_handler, ['GET'])
router.register('/users', create_user_handler, ['POST'])
handler, params = router.match('GET', '/users/123')
if handler:
response = handler(**params) # get_user_handler(user_id=123)
else:
response = 404
Декоратор для регистрации маршрутов
class Framework:
"""Минималистичный фреймворк"""
def __init__(self):
self.router = AdvancedRouter()
self.middleware_stack = []
def route(self, path: str, methods: list = None):
"""Декоратор для регистрации маршрутов"""
def decorator(handler):
self.router.register(path, handler, methods or ['GET'])
return handler
return decorator
def middleware(self, fn: Callable):
"""Декоратор для регистрации middleware"""
self.middleware_stack.append(fn)
return fn
async def handle_request(self, method: str, path: str, **kwargs):
"""Обработать HTTP запрос"""
# Пройти через middleware (перед обработчиком)
request = {'method': method, 'path': path, **kwargs}
for middleware in self.middleware_stack:
request = await middleware(request)
# Найти и выполнить обработчик
handler, params = self.router.match(method, path)
if not handler:
return {'status': 404, 'body': 'Not found'}
response = await handler(**params)
return response
app = Framework()
@app.route('/users/<int:user_id>', ['GET'])
async def get_user(user_id: int):
# user_id автоматически извлечён из URL
user = await db.get_user(user_id)
return {'status': 200, 'data': user}
@app.route('/users', ['POST'])
async def create_user(body: dict):
user = await db.create_user(body)
return {'status': 201, 'data': user}
@app.middleware
async def auth_middleware(request):
"""Проверить авторизацию"""
if 'Authorization' not in request.get('headers', {}):
return {'status': 401, 'body': 'Unauthorized'}
return request
REST API с группировкой маршрутов
class APIRouter:
"""Роутер для REST API с префиксами"""
def __init__(self, prefix: str = ''):
self.prefix = prefix
self.routes = []
def get(self, path: str):
return self._register('GET', path)
def post(self, path: str):
return self._register('POST', path)
def put(self, path: str):
return self._register('PUT', path)
def delete(self, path: str):
return self._register('DELETE', path)
def _register(self, method: str, path: str):
def decorator(handler):
full_path = f"{self.prefix}{path}"
self.routes.append({
'method': method,
'path': full_path,
'handler': handler
})
return handler
return decorator
# Использование
users_router = APIRouter(prefix='/api/v1/users')
@users_router.get('') # GET /api/v1/users
async def list_users():
return {'data': await db.list_users()}
@users_router.get('/<int:user_id>') # GET /api/v1/users/123
async def get_user(user_id: int):
return {'data': await db.get_user(user_id)}
@users_router.post('') # POST /api/v1/users
async def create_user(body: dict):
return {'data': await db.create_user(body)}
@users_router.put('/<int:user_id>') # PUT /api/v1/users/123
async def update_user(user_id: int, body: dict):
return {'data': await db.update_user(user_id, body)}
@users_router.delete('/<int:user_id>') # DELETE /api/v1/users/123
async def delete_user(user_id: int):
return {'status': 204}
# Регистрировать все маршруты
app.register_router(users_router)
Оптимизация: использование Trie дерева
Для больших приложений с тысячами маршрутов:
class TrieNode:
def __init__(self):
self.children = {} # Следующий символ -> TrieNode
self.handler = None
self.is_param = False # Это параметр <id>
self.param_name = None
class TrieRouter:
"""Оптимизированный роутер на основе Trie"""
def __init__(self):
self.root = TrieNode()
def register(self, path: str, handler: Callable):
"""Регистрировать маршрут в Trie"""
segments = path.strip('/').split('/')
node = self.root
for segment in segments:
if segment.startswith('<') and segment.endswith('>'):
# Параметр: <user_id>
if 'param' not in node.children:
param_node = TrieNode()
param_node.is_param = True
param_node.param_name = segment[1:-1]
node.children['param'] = param_node
node = node.children['param']
else:
# Статический сегмент
if segment not in node.children:
node.children[segment] = TrieNode()
node = node.children[segment]
node.handler = handler
def match(self, path: str) -> Tuple[Optional[Callable], Dict]:
"""Найти обработчик в Trie"""
segments = path.strip('/').split('/')
node = self.root
params = {}
for segment in segments:
if segment in node.children:
# Точное совпадение
node = node.children[segment]
elif 'param' in node.children:
# Параметр
param_node = node.children['param']
params[param_node.param_name] = segment
node = param_node
else:
# Маршрут не найден
return None, {}
return node.handler, params
# Использование
router = TrieRouter()
router.register('/users/<user_id>', get_user_handler)
router.register('/posts/<slug>/comments/<comment_id>', get_comment_handler)
handler, params = router.match('/users/123')
# handler = get_user_handler, params = {'user_id': '123'}
Ключевые особенности
- Простота — начни с простого словаря, усложняй по мере необходимости
- Типизация — используй type hints для параметров
- Производительность — для больших приложений используй Trie
- Гибкость — поддержи wildcards и регулярные выражения
- Middleware — встрой слой для логирования, авторизации и т.д.
- Группировка — используй префиксы для организации кода