|
|
# 自动解析和自动编码设计
|
|
|
|
|
|
## 用户需求
|
|
|
|
|
|
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设计
|
|
|
|
|
|
```go
|
|
|
type Request interface {
|
|
|
// Header 获取协议帧头(已解析)
|
|
|
Header() FrameHeader
|
|
|
|
|
|
// Data 获取数据部分(已解码为Go对象)
|
|
|
Data() interface{}
|
|
|
|
|
|
// Bind 绑定到指定的结构体(如果Data是map,可以绑定到struct)
|
|
|
Bind(v interface{}) error
|
|
|
|
|
|
// Raw 获取原始字节(如果需要)
|
|
|
Raw() []byte
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Response设计
|
|
|
|
|
|
```go
|
|
|
type Response interface {
|
|
|
// Write 写入数据(自动编码和协议封装)
|
|
|
Write(data interface{}) error
|
|
|
|
|
|
// WriteWithHeader 写入数据和协议帧头
|
|
|
WriteWithHeader(data interface{}, header FrameHeader) error
|
|
|
|
|
|
// Header 获取/设置协议帧头
|
|
|
Header() FrameHeader
|
|
|
SetHeader(header FrameHeader)
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Handler签名
|
|
|
|
|
|
```go
|
|
|
// 方式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:在服务器层面自动解析(推荐)
|
|
|
|
|
|
在服务器接收到数据后,立即进行解析:
|
|
|
|
|
|
```go
|
|
|
// 服务器接收到数据
|
|
|
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中按需解析:
|
|
|
|
|
|
```go
|
|
|
// 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:基于路由配置的自动解析(最佳)
|
|
|
|
|
|
在路由注册时指定目标类型,服务器自动解析:
|
|
|
|
|
|
```go
|
|
|
// 注册路由时指定类型
|
|
|
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(基于路由配置)
|
|
|
|
|
|
### 路由注册
|
|
|
|
|
|
```go
|
|
|
// 定义请求和响应类型
|
|
|
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,
|
|
|
})
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 服务器实现
|
|
|
|
|
|
```go
|
|
|
// 在路由匹配时自动解析
|
|
|
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. 编写测试用例
|
|
|
|