← Назад к вопросам

Как реализовать 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 — встрой слой для логирования, авторизации и т.д.
  • Группировка — используй префиксы для организации кода
Как реализовать URL реестр в своем фреймворке? | PrepBro