Files
router/system_design/https-server.md
xiongxiao ab61be4875 Add design documents for HTTP server, router, and WebSocket server
- Created `https-server.md` detailing the HTTP server design, including request normalization, routing, and built-in routes.
- Added `router.md` outlining the router system design, core components, and execution flow.
- Introduced `ws-server.md` for the WebSocket server design, covering connection handling, message protocols, and custom listener registration.
2026-02-28 14:40:20 +08:00

10 KiB
Raw Blame History

HTTP Server 设计文档

概述

HTTP 服务器层负责接收外部 HTTP 请求,将其归一化后传递给 Router 层处理。所有请求统一入口为 /api/router

请求入口

POST /api/router?path=demo&key=01
GET  /api/router?path=demo&key=01
配置项 默认值 说明
path /api/router 请求入口路径

请求参数归一化

HTTP 请求的参数来源于两个部分,最终合并为一个 Message 对象:

1. URL Query (searchParams)

// GET /api/router?path=demo&key=01&token=xxx
{
  path: "demo",
  key: "01",
  token: "xxx"
}

2. POST Body

// POST /api/router
// Body: { "name": "test", "value": 123 }
{
  name: "test",
  value: 123
}

3. 合并规则

最终的 Message = Query + Body后者覆盖前者

// GET /api/router?path=demo&key=01
// POST Body: { "key": "02", "extra": "data" }
{
  path: "demo",
  key: "02",  // body 覆盖 query
  extra: "data"
}

4. payload 参数

如果 query 或 body 中有 payload 字段且为 JSON 字符串,会自动解析为对象:

// GET /api/router?path=demo&key=01&payload={"id":123}
{
  path: "demo",
  key: "01",
  payload: { id: 123 }  // 自动解析
}

5. 认证信息

来源 字段名 说明
Authorization header token Bearer token
Cookie token cookie 中的 token
Cookie cookies 完整 cookie 对象

路由匹配

方式一path + key

# 访问 path=demo, key=01 的路由
POST /api/router?path=demo&key=01

方式二id

# 直接通过路由 ID 访问
POST /api/router?id=abc123

内置路由

所有内置路由使用统一的访问方式:

路由 访问方式 说明
router.list POST /api/router?path=router&key=list 获取路由列表

router.list

获取当前应用所有路由列表。

请求:

POST /api/router?path=router&key=list

响应:

{
  "code": 200,
  "data": {
    "list": [
      {
        "id": "router$#$list",
        "path": "router",
        "key": "list",
        "description": "列出当前应用下的所有的路由信息",
        "middleware": [],
        "metadata": {}
      }
    ],
    "isUser": false
  },
  "message": "success"
}

Go 设计

package server

import (
    "encoding/json"
    "net/http"
)

// Message HTTP 请求归一化后的消息
type Message struct {
    Path     string                 // 路由 path
    Key      string                 // 路由 key
    ID       string                 // 路由 ID (优先于 path+key)
    Token    string                 // 认证 token
    Payload  map[string]interface{} // payload 参数
    Query    url.Values             // 原始 query
    Cookies  map[string]string      // cookie
    // 其他参数
    Extra map[string]interface{}
}

// HandleServer 解析 HTTP 请求
func HandleServer(req *http.Request) (*Message, error) {
    method := req.Method
    if method != "GET" && method != "POST" {
        return nil, fmt.Errorf("method not allowed")
    }

    // 解析 query
    query := req.URL.Query()

    // 获取 token
    token := req.Header.Get("Authorization")
    if token != "" {
        token = strings.TrimPrefix(token, "Bearer ")
    }
    if token == "" {
        if cookie, err := req.Cookie("token"); err == nil {
            token = cookie.Value
        }
    }

    // 解析 body (POST)
    var body map[string]interface{}
    if method == "POST" {
        if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
            body = make(map[string]interface{})
        }
    }

    // 合并 query 和 body
    msg := &Message{
        Token:   token,
        Query:   query,
        Cookies: parseCookies(req.Cookies()),
    }

    // query 参数
    if v := query.Get("path"); v != "" {
        msg.Path = v
    }
    if v := query.Get("key"); v != "" {
        msg.Key = v
    }
    if v := query.Get("id"); v != "" {
        msg.ID = v
    }
    if v := query.Get("payload"); v != "" {
        if err := json.Unmarshal([]byte(v), &msg.Payload); err == nil {
            // payload 解析成功
        }
    }

    // body 参数覆盖 query
    for k, v := range body {
        msg.Extra[k] = v
        switch k {
        case "path":
            msg.Path = v.(string)
        case "key":
            msg.Key = v.(string)
        case "id":
            msg.ID = v.(string)
        case "payload":
            msg.Payload = v.(map[string]interface{})
        }
    }

    return msg, nil
}

// RouterHandler 创建 HTTP 处理函数
func RouterHandler(router *QueryRouter) http.HandlerFunc {
    return func(w http.ResponseWriter, req *http.Request) {
        // CORS
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
        w.Header().Set("Content-Type", "application/json")

        if req.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }

        msg, err := HandleServer(req)
        if err != nil {
            json.NewEncoder(w).Encode(Result{Code: 400, Message: err.Error()})
            return
        }

        // 调用 router.run
        result, err := router.Run(*msg, nil)
        if err != nil {
            json.NewEncoder(w).Encode(Result{Code: 500, Message: err.Error()})
            return
        }

        json.NewEncoder(w).Encode(result)
    }
}

// Server HTTP 服务器
type Server struct {
    Router   *QueryRouter
    Path     string
    Handlers []http.HandlerFunc
}

func (s *Server) Listen(addr string) error {
    mux := http.NewServeMux()

    // 自定义处理器
    for _, h := range s.Handlers {
        mux.HandleFunc(s.Path, h)
    }

    // 路由处理器
    mux.HandleFunc(s.Path, RouterHandler(s.Router))

    return http.ListenAndServe(addr, mux)
}

Rust 设计

use actix_web::{web, App, HttpServer, HttpRequest, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// Message HTTP 请求归一化后的消息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
    pub path: Option<String>,
    pub key: Option<String>,
    pub id: Option<String>,
    pub token: Option<String>,
    pub payload: Option<HashMap<String, Value>>,
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

// Result 调用结果
#[derive(Debug, Serialize)]
pub struct Result {
    pub code: i32,
    pub data: Option<Value>,
    pub message: Option<String>,
}

// 解析 HTTP 请求
pub async fn handle_server(req: HttpRequest, body: web::Bytes) -> Result<Message, String> {
    let method = req.method().as_str();
    if method != "GET" && method != "POST" {
        return Err("method not allowed".to_string());
    }

    // 获取 query 参数
    let query: web::Query<HashMap<String, String>> = web::Query::clone(&req);

    // 获取 token
    let mut token = None;
    if let Some(auth) = req.headers().get("authorization") {
        if let Ok(s) = auth.to_str() {
            token = Some(s.trim_start_matches("Bearer ").to_string());
        }
    }
    if token.is_none() {
        if let Some(cookie) = req.cookie("token") {
            token = Some(cookie.value().to_string());
        }
    }

    // 解析 body (POST)
    let mut body_map = HashMap::new();
    if method == "POST" {
        if let Ok(v) = serde_json::from_slice::<Value>(&body) {
            if let Some(obj) = v.as_object() {
                for (k, val) in obj {
                    body_map.insert(k.clone(), val.clone());
                }
            }
        }
    }

    // 构建 Message
    let mut msg = Message {
        path: query.get("path").cloned(),
        key: query.get("key").cloned(),
        id: query.get("id").cloned(),
        token,
        payload: None,
        extra: HashMap::new(),
    };

    // 处理 payload
    if let Some(p) = query.get("payload") {
        if let Ok(v) = serde_json::from_str::<Value>(p) {
            msg.payload = v.as_object().map(|m| m.clone());
        }
    }

    // body 覆盖 query
    for (k, v) in body_map {
        match k.as_str() {
            "path" => msg.path = v.as_str().map(|s| s.to_string()),
            "key" => msg.key = v.as_str().map(|s| s.to_string()),
            "id" => msg.id = v.as_str().map(|s| s.to_string()),
            "payload" => msg.payload = v.as_object().map(|m| m.clone()),
            _ => msg.extra.insert(k, v),
        }
    }

    Ok(msg)
}

// HTTP 处理函数
async fn router_handler(
    req: HttpRequest,
    body: web::Bytes,
    router: web::Data<QueryRouter>,
) -> impl Responder {
    let msg = match handle_server(req, body).await {
        Ok(m) => m,
        Err(e) => return HttpResponse::BadRequest().json(Result {
            code: 400,
            data: None,
            message: Some(e),
        }),
    };

    // 调用 router.run
    match router.run(msg, None).await {
        Ok(result) => HttpResponse::Ok().json(result),
        Err(e) => HttpResponse::InternalServerError().json(Result {
            code: 500,
            data: None,
            message: Some(e.to_string()),
        }),
    }
}

// Server HTTP 服务器
pub struct Server {
    pub router: QueryRouter,
    pub path: String,
}

impl Server {
    pub async fn listen(self, addr: &str) -> std::io::Result<()> {
        let router = web::Data::new(self.router);

        HttpServer::new(move || {
            App::new()
                .app_data(router.clone())
                .route(&self.path, web::post().to(router_handler))
                .route(&self.path, web::get().to(router_handler))
        })
        .bind(addr)?
        .run()
        .await
    }
}

请求示例

基础请求

# 访问 demo/01 路由
curl -X POST "http://localhost:4002/api/router?path=demo&key=01"

# 带 body
curl -X POST "http://localhost:4002/api/router?path=demo&key=01" \
  -H "Content-Type: application/json" \
  -d '{"name":"test","value":123}'

获取路由列表

curl -X POST "http://localhost:4002/api/router?path=router&key=list"

带认证

# 通过 Header
curl -X POST "http://localhost:4002/api/router?path=demo&key=01" \
  -H "Authorization: Bearer your-token"

# 通过 Cookie
curl -X POST "http://localhost:4002/api/router?path=demo&key=01" \
  -b "token=your-token"