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

453 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
```typescript
// GET /api/router?path=demo&key=01&token=xxx
{
path: "demo",
key: "01",
token: "xxx"
}
```
### 2. POST Body
```typescript
// POST /api/router
// Body: { "name": "test", "value": 123 }
{
name: "test",
value: 123
}
```
### 3. 合并规则
最终的 Message = Query + Body后者覆盖前者
```typescript
// 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 字符串,会自动解析为对象:
```typescript
// 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
```bash
# 访问 path=demo, key=01 的路由
POST /api/router?path=demo&key=01
```
### 方式二id
```bash
# 直接通过路由 ID 访问
POST /api/router?id=abc123
```
## 内置路由
所有内置路由使用统一的访问方式:
| 路由 | 访问方式 | 说明 |
|------|----------|------|
| router.list | POST /api/router?path=router&key=list | 获取路由列表 |
### router.list
获取当前应用所有路由列表。
**请求:**
```bash
POST /api/router?path=router&key=list
```
**响应:**
```json
{
"code": 200,
"data": {
"list": [
{
"id": "router$#$list",
"path": "router",
"key": "list",
"description": "列出当前应用下的所有的路由信息",
"middleware": [],
"metadata": {}
}
],
"isUser": false
},
"message": "success"
}
```
## Go 设计
```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 设计
```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
}
}
```
## 请求示例
### 基础请求
```bash
# 访问 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}'
```
### 获取路由列表
```bash
curl -X POST "http://localhost:4002/api/router?path=router&key=list"
```
### 带认证
```bash
# 通过 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"
```