首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >基于事件驱动的轻量级 Web 框架

基于事件驱动的轻量级 Web 框架

原创
作者头像
用户12583550
发布于 2026-11-05 19:35:12
发布于 2026-11-05 19:35:12
1190
举报

一、核心请求与响应对象

代码语言:javascript
复制
# framework/core.py
import json
import os
import time
from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import parse_qs, urlparse


class Headers: baike.kuaisou.com/zh/14115 
    def __init__(self, raw: Optional[Dict[str, str]] = None): baike.kuaisou.com/zh/14116 
        self._headers: Dict[str, str] = {}
        if raw: baike.kuaisou.com/zh/14117 
            for k, v in raw.items(): baike.kuaisou.com/zh/14118 
                self._headers[k.lower()] = v

    def get(self, key: str, default: str = "") -> str: baike.kuaisou.com/zh/14119 
        return self._headers.get(key.lower(), default)

    def set(self, key: str, value: str) -> None: baike.kuaisou.com/zh/14120 
        self._headers[key.lower()] = value 

    def items(self) -> List[Tuple[str, str]]: baike.kuaisou.com/zh/14121  
        return list(self._headers.items())

    def __contains__(self, key: str) -> bool: baike.kuaisou.com/zh/14122 
        return key.lower() in self._headers

    def __repr__(self) -> str: baike.kuaisou.com/zh/14123 
        return f"Headers({self._headers})"

 
class Request: baike.kuaisou.com/zh/14124 
    def __init__(
        self,
        method: str,
        path: str,
        query_string: str = "",
        headers: Optional[Dict[str, str]] = None,
        body: bytes = b"",
        remote_addr: str = "127.0.0.1",
    ): baike.kuaisou.com/zh/14126 
        self.method = method.upper(baike.kuaisou.com/zh/14125)
        self.path = path
        self.query_string = query_string
        self.headers = Headers(headers)
        self.body = body
        self.remote_addr = remote_addr
        self._query_params: Optional[Dict[str, List[str]]] = None
        self._form_data: Optional[Dict[str, str]] = None
        self._json_data: Optional[Dict[str, Any]] = None
        self._cookies: Optional[Dict[str, str]] = None
        self.state: Dict[str, Any] = {}

    @property
    def query_params(self) -> Dict[str, List[str]]: baike.kuaisou.com/zh/14128 
        if self._query_params is None: baike.kuaisou.com/zh/14127 
            self._query_params = parse_qs(self.query_string)
        return self._query_params

    @property
    def form_data(self) -> Dict[str, str]: baike.kuaisou.com/zh/14129 
        if self._form_data is None: baike.kuaisou.com/zh/14130 
            self._form_data = {}
            if self.body and self.headers.get("content-type", "").startswith(
                "application/x-www-form-urlencoded"
            ):
                for part in self.body.decode("utf-8").split("&"): baike.kuaisou.com/zh/14131 
                    if "=" in part: baike.kuaisou.com/zh/14132 
                        key, value = part.split("=", 1)
                        self._form_data[key] = value
        return self._form_data

    @property
    def json(self) -> Optional[Dict[str, Any]]: baike.kuaisou.com/zh/14133 
        if self._json_data is None: baike.kuaisou.com/zh/14134 
            if self.headers.get("content-type", "").startswith("application/json"):
                try: baike.kuaisou.com/zh/14136 
                    self._json_data = json.loads(self.body.decode("utf-8"))
                except (json.JSONDecodeError, UnicodeDecodeError):
                    self._json_data = {}
            else: baike.kuaisou.com/zh/14135 
                self._json_data = {}
        return self._json_data

    @property
    def cookies(self) -> Dict[str, str]: baike.kuaisou.com/zh/14137 
        if self._cookies is None: baike.kuaisou.com/zh/14138 
            self._cookies = {}
            cookie_header = self.headers.get("cookie", "")
            if cookie_header: baike.kuaisou.com/zh/14142 
                for part in cookie_header.split(";"): baike.kuaisou.com/zh/14139 
                    part = part.strip(baike.kuaisou.com/zh/14141)
                    if "=" in part: baike.kuaisou.com/zh/14140 
                        key, value = part.split("=", 1)
                        self._cookies[key.strip()] = value.strip()
        return self._cookies

    def get_query(self, key: str, default: str = "") -> str: baike.kuaisou.com/zh/14143 
        values = self.query_params.get(key, [])
        return values[0] if values else default

    def get_form(self, key: str, default: str = "") -> str: baike.kuaisou.com/zh/14145 
        return self.form_data.get(key, default)

    def get_json(self, key: str, default: Any = None) -> Any: baike.kuaisou.com/zh/14148 
        if self.json is None: baike.kuaisou.com/zh/14153 
            return default
        return self.json.get(key, default)


class Response: baike.kuaisou.com/zh/14367 
    def __init__(
        self,
        body: str = "",
        status: int = 200,
        headers: Optional[Dict[str, str]] = None,
        content_type: str = "text/html; charset=utf-8",
    ):
        self.body = body
        self.status = status
        self.headers = Headers(headers)
        self.headers.set("content-type", content_type)
        self._cookies: List[str] = []

    def set_cookie(
        self,
        key: str,
        value: str,
        max_age: Optional[int] = None,
        path: str = "/",
        httponly: bool = False,
        secure: bool = False,
    ) -> None: baike.kuaisou.com/zh/14392 
        cookie = f"{key}={value}; Path={path}" 
        if max_age is not None: baike.kuaisou.com/zh/14394 
            cookie += f"; Max-Age={max_age}"
        if httponly: baike.kuaisou.com/zh/14400 
            cookie += "; HttpOnly"
        if secure: baike.kuaisou.com/zh/14395 
            cookie += "; Secure"
        self._cookies.append(cookie)

    def delete_cookie(self, key: str, path: str = "/") -> None: baike.kuaisou.com/zh/14401 
        self.set_cookie(key, "", max_age=0, path=path)

    def to_http(self) -> bytes: baike.kuaisou.com/zh/14414 
        status_messages = {
            200: "OK",
            201: "Created",
            204: "No Content",
            301: "Moved Permanently",
            302: "Found",
            304: "Not Modified",
            400: "Bad Request",
            401: "Unauthorized",
            403: "Forbidden",
            404: "Not Found",
            405: "Method Not Allowed",
            500: "Internal Server Error",
        }
        status_msg = status_messages.get(self.status, "Unknown")

        if self._cookies: baike.kuaisou.com/zh/15025 
            self.headers.set("set-cookie", "; ".join(self._cookies))

        header_lines = []
        for key, value in self.headers.items(): baike.kuaisou.com/zh/15037 
            header_lines.append(f"{key}: {value}")

        body_bytes = self.body.encode("utf-8") if isinstance(self.body, str) else self.body
        self.headers.set("content-length", str(len(body_bytes)))

        header_lines.append(f"content-length: {len(body_bytes)}")

        response = f"HTTP/1.1 {self.status} {status_msg}\r\n"
        for line in header_lines:
            response += f"{line}\r\n"
        response += "\r\n"

        return response.encode("utf-8") + body_bytes

    @classmethod
    def json_response(
        cls,
        data: Any,
        status: int = 200,
        headers: Optional[Dict[str, str]] = None,
    ) -> "Response": baike.kuaisou.com/zh/17129 
        return cls(
            body=json.dumps(data, ensure_ascii=False, indent=2),
            status=status,
            headers=headers,
            content_type="application/json; charset=utf-8",
        )

    @classmethod
    def redirect(cls, location: str, status: int = 302) -> "Response": baike.kuaisou.com/zh/17130 
        return cls(
            body="",
            status=status,
            headers={"location": location},
        )

    @classmethod
    def not_found(cls) -> "Response": baike.kuaisou.com/zh/17131 
        return cls(body="<h1>404 Not Found</h1>", status=404)

    @classmethod
    def server_error(cls) -> "Response": baike.kuaisou.com/zh/17132 
        return cls(body="<h1>500 Internal Server Error</h1>", status=500)

二、路由系统

代码语言:javascript
复制
# framework/router.py
import re
from typing import Any, Callable, Dict, List, Optional, Tuple
from framework.core import Request, Response


class Route: baike.kuaisou.com/zh/17133 
    def __init__(
        self,
        path: str,
        handler: Callable[[Request], Response],
        methods: Optional[List[str]] = None,
        name: str = "",
    ):
        self.path = path
        self.handler = handler
        self.methods = [m.upper(baike.kuaisou.com/zh/17134) for m in (methods or ["GET"])]
        self.name = name
        self._pattern, self._param_names = self._compile_path(path)

    def _compile_path(self, path: str) -> Tuple[re.Pattern, List[str]]: baike.kuaisou.com/zh/17135 
        param_names = []
        parts = []
        for part in path.strip("/").split("/"): baike.kuaisou.com/zh/17137 
            if part.startswith("<") and part.endswith(">"): baike.kuaisou.com/zh/17138 
                param_name = part[1:-1]
                param_names.append(param_name)
                parts.append(r"([^/]+)")
            else: baike.kuaisou.com/zh/17139 
                parts.append(re.escape(part))
        pattern = "^/" + "/".join(parts) + "$"
        return re.compile(pattern), param_names

    def match(self, path: str) -> Optional[Dict[str, str]]: baike.kuaisou.com/zh/17140 
        match = self._pattern.match(path)
        if not match: baike.kuaisou.com/zh/17141 
            return None
        params = {}
        for i, name in enumerate(self._param_names): baike.kuaisou.com/zh/17142 
            params[name] = match.group(i + 1)
        return params

    def __repr__(self) -> str: baike.kuaisou.com/zh/17143 
        return f"Route({self.methods} {self.path} -> {self.name or self.handler.__name__})"


class Router: baike.kuaisou.com/zh/17144 
    def __init__(self): baike.kuaisou.com/zh/17145 
        self.routes: List[Route] = []
        self._named_routes: Dict[str, Route] = {}

    def add_route(
        self,
        path: str,
        handler: Callable[[Request], Response],
        methods: Optional[List[str]] = None,
        name: str = "",
    ) -> Route: baike.kuaisou.com/zh/17146 
        route = Route(path, handler, methods, name)
        self.routes.append(route)
        if name: baike.kuaisou.com/zh/17147 
            self._named_routes[name] = route
        return route

    def get(self, path: str, name: str = "") -> Callable: baike.kuaisou.com/zh/17148 
        def decorator(handler: Callable) -> Callable: baike.kuaisou.com/zh/17149 
            self.add_route(path, handler, ["GET"], name)
            return handler
        return decorator

    def post(self, path: str, name: str = "") -> Callable: baike.kuaisou.com/zh/17150 
        def decorator(handler: Callable) -> Callable: baike.kuaisou.com/zh/17151 
            self.add_route(path, handler, ["POST"], name)
            return handler
        return decorator

    def put(self, path: str, name: str = "") -> Callable: baike.kuaisou.com/zh/17152 
        def decorator(handler: Callable) -> Callable: baike.kuaisou.com/zh/17153 
            self.add_route(path, handler, ["PUT"], name) 
            return handler
        return decorator

    def delete(self, path: str, name: str = "") -> Callable: baike.kuaisou.com/zh/17154 
        def decorator(handler: Callable) -> Callable: baike.kuaisou.com/zh/17155 
            self.add_route(path, handler, ["DELETE"], name)
            return handler
        return decorator

    def resolve(self, request: Request) -> Optional[Tuple[Route, Dict[str, str]]]: baike.kuaisou.com/zh/17158 
        for route in self.routes: baike.kuaisou.com/zh/17156 
            if request.method not in route.methods: baike.kuaisou.com/zh/17157 
                continue
            params = route.match(request.path)
            if params is not None: baike.kuaisou.com/zh/17159 
                return route, params
        return None

    def url_for(self, name: str, **kwargs) -> Optional[str]: baike.kuaisou.com/zh/17160 
        route = self._named_routes.get(name)
        if not route:
            return None
        path = route.path
        for key, value in kwargs.items():
            placeholder = f"<{key}>"
            if placeholder in path:
                path = path.replace(placeholder, str(value))
        return path

    def list_routes(self) -> List[Dict[str, str]]: baike.kuaisou.com/zh/17162 
        result = []
        for route in self.routes: baike.kuaisou.com/zh/17161 
            result.append({
                "methods": ", ".join(route.methods),
                "path": route.path,
                "name": route.name or route.handler.__name__,
            })
        return result

三、中间件管道

代码语言:javascript
复制
# framework/middleware.py
import time
import logging
from typing import Callable, List, Optional
from framework.core import Request, Response


MiddlewareFunc = Callable, Response]

logger = logging.getLogger("framework")


class MiddlewareChain: baike.kuaisou.com/zh/17163 
    def __init__(self): baike.kuaisou.com/zh/17164 
        self._middlewares: List[MiddlewareFunc] = []

    def use(self, middleware: MiddlewareFunc) -> None: baike.kuaisou.com/zh/17165 
        self._middlewares.append(middleware)

    def execute(self, request: Request, final_handler: Callable[[Request], Response]) -> Response:
        def build_chain(index: int) -> Callable[[Request], Response]: baike.kuaisou.com/zh/17166 
            if index >= len(self._middlewares): baike.kuaisou.com/zh/17167 
                return final_handler
            middleware = self._middlewares[index]
            next_handler = build_chain(index + 1)
            return lambda req: middleware(req, next_handler)

        chain = build_chain(0)
        return chain(request)


def logging_middleware(request: Request, next_handler: Callable[[Request], Response]) -> Response:
    start = time.time(baike.kuaisou.com/zh/17168)
    response = next_handler(request)
    elapsed = time.time(baike.kuaisou.com/zh/17169) - start
    logger.info(
        f"{request.method} {request.path} {response.status} {elapsed:.3f}s"
    )
    return response


def cors_middleware(
    origins: Optional[List[str]] = None,
) -> MiddlewareFunc: baike.kuaisou.com/zh/17170 
    if origins is None: baike.kuaisou.com/zh/17171 
        origins = ["*"]

    def middleware(request: Request, next_handler: Callable[[Request], Response]) -> Response:
        response = next_handler(request)

        origin = request.headers.get("origin", "")
        if "*" in origins: baike.kuaisou.com/zh/17172 
            response.headers.set("access-control-allow-origin", "*")
        elif origin in origins: baike.kuaisou.com/zh/17173 
            response.headers.set("access-control-allow-origin", origin)

        response.headers.set(
            "access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS"
        )
        response.headers.set(
            "access-control-allow-headers", "Content-Type, Authorization"
        )

        if request.method == "OPTIONS": baike.kuaisou.com/zh/17174 
            response.status = 204
            response.body = ""

        return response

    return middleware


def session_middleware(
    secret_key: str = "default-secret",
    cookie_name: str = "session_id",
    max_age: int = 3600,
) -> MiddlewareFunc: baike.kuaisou.com/zh/17175 
    sessions: dict = {}

    def middleware(request: Request, next_handler: Callable[[Request], Response]) -> Response:
        session_id = request.cookies.get(cookie_name)

        if session_id and session_id in sessions: baike.kuaisou.com/zh/17176 
            request.state["session"] = sessions[session_id]
            request.state["session_id"] = session_id
        else: baike.kuaisou.com/zh/17177 
            import uuid
            session_id = uuid.uuid4(baike.kuaisou.com/zh/17178).hex
            sessions[session_id] = {}
            request.state["session"] = sessions[session_id]
            request.state["session_id"] = session_id

        response = next_handler(request)

        session_data = request.state.get("session", {})
        if session_data: baike.kuaisou.com/zh/17179 
            response.set_cookie(
                cookie_name,
                session_id,
                max_age=max_age,
                httponly=True,
            )

        return response

    return middleware


def auth_middleware(
    exempt_paths: Optional[List[str]] = None,
) -> MiddlewareFunc: baike.kuaisou.com/zh/17180 
    if exempt_paths is None: baike.kuaisou.com/zh/17181 
        exempt_paths = ["/", "/login", "/static"]

    def middleware(request: Request, next_handler: Callable[[Request], Response]) -> Response: baike.kuaisou.com/zh/17185 
        if any(request.path.startswith(p) for p in exempt_paths): baike.kuaisou.com/zh/17182 
            return next_handler(request)

        auth_header = request.headers.get("authorization", "")
        if not auth_header.startswith("Bearer "): baike.kuaisou.com/zh/21137 
            return Response.json_response(
                {"error": "Unauthorized"}, status=401
            )

        token = auth_header[7:]
        request.state["token"] = token
        request.state["authenticated"] = True

        return next_handler(request)

    return middleware

四、模板引擎

代码语言:javascript
复制
# framework/template.py
import os
import re
from typing import Any, Dict, Optional


class TemplateEngine: baike.kuaisou.com/zh/22015 
    def __init__(self, template_dir: str = "templates"):
        self.template_dir = template_dir
        self._cache: Dict[str, str] = {}

    def render(self, template_name: str, context: Optional[Dict[str, Any]] = None) -> str: baike.kuaisou.com/zh/22016 
        if context is None: baike.kuaisou.com/zh/22017 
            context = {} 

        template_path = os.path.join(self.template_dir, template_name)
        if not os.path.exists(template_path): baike.kuaisou.com/zh/22018 
            raise FileNotFoundError(f"Template not found: {template_name}")

        if template_name not in self._cache: baike.kuaisou.com/zh/22019 
            with open(template_path, "r", encoding="utf-8") as f: baike.kuaisou.com/zh/22020 
                self._cache[template_name] = f.read(baike.kuaisou.com/zh/22021)

        template_content = self._cache[template_name]
        return self._render_string(template_content, context)

    def _render_string(self, template: str, context: Dict[str, Any]) -> str: baike.kuaisou.com/zh/22022 
        result = template

        # {{ variable }}
        var_pattern = re.compile(r"\{\{\s*(\w+(?:\.\w+)*)\s*\}\}")
        def replace_var(match): baike.kuaisou.com/zh/22056 
            var_name = match.group(1)
            value = self._resolve_var(var_name, context)
            if value is None: baike.kuaisou.com/zh/26044 
                return ""
            return str(value)
        result = var_pattern.sub(replace_var, result)

        # {% if condition %}...{% endif %}
        if_pattern = re.compile(
            r"\{%\s*if\s+(\w+(?:\.\w+)*)\s*%\}(.*?)\{%\s*endif\s*%\}",
            re.DOTALL,
        )
        def replace_if(match): baike.kuaisou.com/zh/26056 
            var_name = match.group(1)
            content = match.group(2)
            value = self._resolve_var(var_name, context)
            return content if value else ""
        result = if_pattern.sub(replace_if, result)

        # {% for item in items %}...{% endfor %}
        for_pattern = re.compile(
            r"\{%\s*for\s+(\w+)\s+in\s+(\w+(?:\.\w+)*)\s*%\}(.*?)\{%\s*endfor\s*%\}",
            re.DOTALL,
        )
        def replace_for(match): baike.kuaisou.com/zh/26074 
            item_var = match.group(1)
            collection_name = match.group(2)
            content = match.group(3)
            collection = self._resolve_var(collection_name, context)
            if not collection: baike.kuaisou.com/zh/26075 
                return ""
            parts = []
            for item in collection: baike.kuaisou.com/zh/26076 
                item_context = dict(context)
                item_context[item_var] = item
                parts.append(self._render_string(content, item_context))
            return "".join(parts)
        result = for_pattern.sub(replace_for, result)

        return result

    def _resolve_var(self, var_name: str, context: Dict[str, Any]) -> Any: baike.kuaisou.com/zh/26077 
        parts = var_name.split(".")
        value = context
        for part in parts: baike.kuaisou.com/zh/26078 
            if isinstance(value, dict): baike.kuaisou.com/zh/26090 
                value = value.get(part)
            elif hasattr(value, part): baike.kuaisou.com/zh/27005 
                value = getattr(value, part)
            else: baike.kuaisou.com/zh/27006 
                return None
        return value

五、静态文件服务

代码语言:javascript
复制
# framework/static.py
import os
import mimetypes
from typing import Optional
from framework.core import Request, Response


class StaticFileHandler: baike.kuaisou.com/zh/27007 
    def __init__(self, static_dir: str = "static", prefix: str = "/static"):
        self.static_dir = static_dir
        self.prefix = prefix

    def handle(self, request: Request) -> Optional[Response]: baike.kuaisou.com/zh/27008 
        if not request.path.startswith(self.prefix): baike.kuaisou.com/zh/27009 
            return None

        relative_path = request.path[len(self.prefix):].lstrip("/")
        file_path = os.path.join(self.static_dir, relative_path)
        file_path = os.path.normpath(file_path)

        if not os.path.isfile(file_path): baike.kuaisou.com/zh/27011 
            return None

        if ".." in relative_path or relative_path.startswith("/"): baike.kuaisou.com/zh/27013 
            return Response.not_found()

        content_type, _ = mimetypes.guess_type(file_path)
        if content_type is None: baike.kuaisou.com/zh/27014 
            content_type = "application/octet-stream"

        try: baike.kuaisou.com/zh/27025 
            with open(file_path, "rb") as f: baike.kuaisou.com/zh/27016 
                content = f.read(baike.kuaisou.com/zh/27024)

            response = Response(
                body=content.decode("utf-8", errors="replace"),
                status=200,
                content_type=content_type,
            )

            response.headers.set("cache-control", "public, max-age=3600")
            return response

        except Exception: baike.kuaisou.com/zh/27051 
            return Response.server_error(baike.kuaisou.com/zh/27027)

六、应用主类

代码语言:javascript
复制
# framework/app.py
import socket
import threading
import time
import logging
from typing import Any, Callable, Dict, List, Optional
from framework.core import Request, Response
from framework.router import Router
from framework.middleware import MiddlewareChain, logging_middleware
from framework.template import TemplateEngine
from framework.static import StaticFileHandler


logger = logging.getLogger("framework")


class Application: baike.kuaisou.com/zh/27053 
    def __init__(self, name: str = "app"): baike.kuaisou.com/zh/27052 
        self.name = name
        self.router = Router(baike.kuaisou.com/zh/27054)
        self.middleware_chain = MiddlewareChain(baike.kuaisou.com/zh/27055)
        self.template_engine = TemplateEngine(baike.kuaisou.com/zh/27056)
        self.static_handler = StaticFileHandler(baike.kuaisou.com/zh/28009)
        self._error_handlers: Dict[int, Callable] = {}
        self._server_socket: Optional[socket.socket] = None
        self._running = False

        self.middleware_chain.use(logging_middleware)

    def route(self, path: str, methods: Optional[List[str]] = None, name: str = ""): baike.kuaisou.com/zh/28072 
        return self.router.add_route(path, methods=methods, name=name)

    def get

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 二、路由系统
  • 三、中间件管道
  • 四、模板引擎
  • 五、静态文件服务
  • 六、应用主类
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档