You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.9 KiB

自动解析和自动编码设计

用户需求

  1. 自动解析接收到的数据

    • 服务端在给定的协议下自动解析数据
    • 自动提取header如果有和struct数据部分
    • Handler中能够直接使用解析后的结构数据
  2. 自动编码写出的数据

    • 服务端写数据到客户端时能够自动编码
    • Handler返回数据后自动进行编码和协议封装

设计思路

1. 自动解析流程

1. 数据到达 → Connection.Read()
   ↓
2. 协议识别 → Protocol.Identify() (根据配置或数据特征)
   ↓
3. 协议解码 → Protocol.Decode() → Header + Data
   ↓
4. 编解码器识别 → Codec.Identify() (根据配置或数据特征)
   ↓
5. 数据解码 → Codec.Decode() → Go Struct
   ↓
6. 创建Request对象 → 包含Header和Data
   ↓
7. 路由匹配 → 根据Header或Data匹配
   ↓
8. Handler执行 → 直接使用解析后的数据

2. 自动编码流程

1. Handler返回数据 → Response.Write(struct)
   ↓
2. 编解码器编码 → Codec.Encode() → []byte
   ↓
3. 协议编码 → Protocol.Encode() → Header + Data
   ↓
4. 写入连接 → Connection.Write()

新的设计

Request设计

type Request interface {
    // Header 获取协议帧头(已解析)
    Header() FrameHeader
    
    // Data 获取数据部分已解码为Go对象
    Data() interface{}
    
    // Bind 绑定到指定的结构体如果Data是map可以绑定到struct
    Bind(v interface{}) error
    
    // Raw 获取原始字节(如果需要)
    Raw() []byte
}

Response设计

type Response interface {
    // Write 写入数据(自动编码和协议封装)
    Write(data interface{}) error
    
    // WriteWithHeader 写入数据和协议帧头
    WriteWithHeader(data interface{}, header FrameHeader) error
    
    // Header 获取/设置协议帧头
    Header() FrameHeader
    SetHeader(header FrameHeader)
}

Handler签名

// 方式1使用Context推荐
type Handler func(ctx Context) error

// 方式2使用Request和Response更明确
type Handler func(req Request, resp Response) error

// 方式3使用具体类型最方便
type Handler func(header FrameHeader, body MyStruct, resp Response) error

实现方案

方案1在服务器层面自动解析推荐

在服务器接收到数据后,立即进行解析:

// 服务器接收到数据
func (h *eventHandler) OnTraffic(c gnet.Conn) {
    // 1. 读取原始数据
    data, _ := c.Peek(-1)
    
    // 2. 识别协议(根据配置或数据特征)
    protocol := h.identifyProtocol(data)
    
    // 3. 协议解码
    header, bodyBytes, err := protocol.Decode(data)
    if err != nil {
        // 处理错误
        return
    }
    
    // 4. 识别编解码器(根据配置或数据特征)
    codec := h.identifyCodec(bodyBytes)
    
    // 5. 数据解码(需要知道目标类型,可以从路由配置中获取)
    var body interface{}
    if targetType := h.getTargetType(header, bodyBytes); targetType != nil {
        body = reflect.New(targetType).Interface()
        codec.Decode(bodyBytes, body)
    } else {
        // 如果没有目标类型,保持为原始字节
        body = bodyBytes
    }
    
    // 6. 创建Request对象
    req := NewRequest(header, body, data)
    
    // 7. 创建Response对象
    resp := NewResponse(protocol, codec, c)
    
    // 8. 创建Context
    ctx := NewContext(req, resp, c)
    
    // 9. 路由匹配和执行
    handler := h.router.Match(ctx)
    handler(ctx)
}

方案2延迟解析按需解析

在Handler中按需解析

// Request对象延迟解析
type Request struct {
    rawData []byte
    protocol Protocol
    codec Codec
    header FrameHeader
    body interface{}
    parsed bool
}

func (r *Request) Data() interface{} {
    if !r.parsed {
        r.parse()
    }
    return r.data
}

func (r *Request) parse() {
    // 协议解码
    r.header, bodyBytes, _ := r.protocol.Decode(r.rawData)
    
    // 数据解码(需要目标类型)
    r.data = r.decodeBody(bodyBytes)
    r.parsed = true
}

方案3基于路由配置的自动解析最佳

在路由注册时指定目标类型,服务器自动解析:

// 注册路由时指定类型
type UserRequest struct {
    UserID int `json:"user_id"`
}

type UserResponse struct {
    Code int `json:"code"`
    Data string `json:"data"`
}

// 注册路由
router.Register(
    matcher,
    handler,
    WithRequestType(&UserRequest{}),  // 指定请求类型
    WithResponseType(&UserResponse{}), // 指定响应类型(可选)
)

// Handler签名
func handler(ctx Context) error {
    req := ctx.Request()
    
    // 直接使用解析后的数据
    userReq := req.Data().(*UserRequest)
    userID := userReq.UserID
    
    // 返回响应(自动编码)
    return ctx.Response().Write(UserResponse{
        Code: 200,
        Data: "success",
    })
}

推荐实现方案3基于路由配置

路由注册

// 定义请求和响应类型
type LoginRequest struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

type LoginResponse struct {
    Code int    `json:"code"`
    Token string `json:"token"`
}

// 注册路由
router.RegisterString(
    "login",
    loginHandler,
    router.WithRequestType(&LoginRequest{}),
)

// Handler实现
func loginHandler(ctx Context) error {
    req := ctx.Request()
    resp := ctx.Response()
    
    // 直接使用解析后的数据
    loginReq := req.Data().(*LoginRequest)
    
    // 处理业务逻辑
    token := authenticate(loginReq.Username, loginReq.Password)
    
    // 返回响应(自动编码)
    return resp.Write(LoginResponse{
        Code: 200,
        Token: token,
    })
}

服务器实现

// 在路由匹配时自动解析
func (r *router) Match(input MatchInput, ctx Context) (Handler, error) {
    // 1. 匹配路由
    route := r.findRoute(data, ctx)
    if route == nil {
        return nil, ErrRouteNotFound
    }
    
    // 2. 如果路由指定了请求类型,自动解析
    if route.RequestType() != nil {
        req := ctx.Request()
        
        // 协议解码
        header, bodyBytes, err := req.Protocol().Decode(data)
        if err != nil {
            return nil, err
        }
        req.SetHeader(header)
        
        // 数据解码
        body := reflect.New(route.RequestType()).Interface()
        codec := req.Codec()
        if err := codec.Decode(bodyBytes, body); err != nil {
            return nil, err
        }
        req.SetData(body)
    }
    
    return route.Handler(), nil
}

完整的数据流程

读数据(自动解析)

1. 数据到达 → Connection.Read()
   ↓
2. 创建Context和Request/Response
   ↓
3. 路由匹配 → 找到对应的路由
   ↓
4. 检查路由配置 → 是否有RequestType
   ↓
5. 协议解码 → Protocol.Decode() → Header + DataBytes
   ↓
6. 数据解码 → Codec.Decode() → Go Struct
   ↓
7. 设置到Request → Request.SetData(struct)
   ↓
8. Handler执行 → 直接使用req.Data().(*MyStruct)

写数据(自动编码)

1. Handler调用 → resp.Write(struct)
   ↓
2. 编解码器编码 → Codec.Encode() → []byte
   ↓
3. 协议编码 → Protocol.Encode() → Header + Data
   ↓
4. 写入连接 → Connection.Write()

优势

  1. 自动化:无需手动解析,数据自动解析为结构体
  2. 类型安全:使用强类型,编译时检查
  3. 简洁Handler代码简洁直接使用结构体
  4. 灵活:支持有帧头和无帧头协议
  5. 可配置:通过路由配置指定类型

实现步骤

  1. 更新路由接口,支持指定请求/响应类型
  2. 实现自动解析逻辑
  3. 更新Request和Response接口
  4. 更新服务器代码
  5. 编写测试用例