Files
go-omni/transport/README.md

8.1 KiB
Raw Blame History

Transport adapters

业务 Handler 依赖 GoOmni 的 httpx.Context,Service 接收标准 context.Context。适配器只在启动入口组装:

import (
    omni "git.yuntuo.hk/k2safe/go-omni"
    netrouter "git.yuntuo.hk/k2safe/go-omni/transport/nethttp"
)

application := omni.NewWithRouter(netrouter.New())
registerRoutes(application) // 与 omni.New() 复用同一份业务注册代码

transport/nethttp 是当前内置的标准库路由实现。它提供一套可移植的路径、方法和错误回退规则;自研网络层可以直接实现同一个 httpx.Router 契约。

transport/native 是第一版自研 HTTP/1.1 服务端:它自己管理 TCP listener、连接、Keep-Alive、请求解析、响应写回和优雅关闭,再把请求交给现有 http.Handler。请求体支持 Content-Length 和 chunked,并统一受 MaxBodyBytes 限制;其他传输编码会被拒绝。它仍使用标准 http.Request / http.ResponseWriter 作为业务桥接,因此不会影响现有 Handler / Service。

native 提供 server.Stats() 读取已接受连接数、当前连接数和当前请求数,适合和 metrics.Metrics 的业务请求指标一起采集;读取不会阻塞连接生命周期。

业务 Handler 需要流式返回时可以断言 http.Flusher 并调用 Flush();HTTP/1.1 会自动使用 chunked 响应,普通未 Flush 的响应仍自动生成 Content-Length。HTTP/1.0 没有 chunked framing,会退回到写完后关闭连接。

Handler 运行超过约 5 ms 时,native 启动一次阻塞读取监测;读到 EOF 或连接错误会取消 Request.Context(),流式写入失败也会触发取消。Handler 返回后先停止监测,再复用连接和 Reader;普通短请求不会启动监测 goroutine。

如果客户端提前发送了下一条请求,监测器会保留管线数据并退出,避免预读不断占用内存。这种情况下,断开取消会延后到写失败或 Handler 返回;业务仍应为下游调用设置自己的超时。

服务端不是把连接交给一个全局串行 worker,而是每条连接独立处理请求;Keep-Alive 负责复用客户端连接,Server 内部的 sync.Pool 负责复用小型 Reader、响应对象和连接状态。Reader 默认只有 4 KiB,需要处理大请求头时按需增长。需要保护内存上限时,可以设置 MaxConnections 和 MaxConcurrentRequests;超出连接上限的连接会被关闭,超出请求并发上限的请求返回 503,不会无限排队。

这个设计借鉴了 Nginx 的资源边界,但没有照搬它的进程模型:Nginx 用少量 worker 进程和事件机制管理大量连接,并用 worker_connections 限制每个 worker 的同时连接数;Go runtime 自己已经用 epoll / kqueue 等网络轮询器唤醒阻塞的 goroutine,因此 native 采用“accept loop + 每连接轻量 goroutine + 请求并发闸门”的等价实现。这样业务 Handler 可以继续执行同步代码,慢请求不会阻塞其他连接;只有在真实压测证明 goroutine 调度或协议状态机成为瓶颈时,才需要继续下沉为手写事件循环。

使用自研服务端:

application := omni.New()
server := native.New(nil)
if err := application.RunWith(ctx, server, ":8080"); err != nil {
    log.Fatal(err)
}

omni.New() 默认仍使用 transport/nethttp 路由;native 服务端是显式启用的预览实现,等协议和连接边界稳定后再考虑切换默认启动器。

TLS 可以直接作为 App.RunWith 的启动配置:

server := native.New(nil)
server.TLSConfig = &tls.Config{Certificates: certificates}
if err := application.RunWith(ctx, server, ":8443"); err != nil {
    log.Fatal(err)
}

需要自己持有 listener 或配置证书文件时,分别使用 ServeTLS 或 RunTLS;TLS 握手仍受 native 的读超时、连接上限和优雅关闭控制,Handler 可通过 Request.TLS 读取标准 tls.ConnectionState。

可移植路由规则(v0.3.0)

项目 约定
路径 顶层路由必须以 / 开头;区分大小写。禁止重复斜杠、. / .. 段、查询串和片段标识。
参数 /users/:id 匹配一个非空路径段;参数必须占据整个段,同一路由内名称唯一。
通配 /files/*path 仅允许出现在末尾,值包含开头的 /;/files/ 对应 /,/files 不匹配。
路由优先级 相同方法内,从左向右比较,静态段优先于参数段;更具体的分支匹配失败后允许回退。
注册冲突 同方法重复注册、相同前缀处参数名冲突、catch-all 与同级分支冲突,均在注册时 panic。不同方法可用同一路径。
分组 支持嵌套与相对路径。Group("/api").Handle("GET", "", h) 注册 /api,传 "/" 则注册 /api/。
尾部斜杠 /users 与 /users/ 是不同路由;不自动重定向、补斜杠或清理请求路径。
URL 编码 使用已解码的 URL.Path;例如 %2F 作为路径分隔符,+ 保留为加号。
方法 Handle 接受大写字母组成的方法名;HEAD 不自动复用 GET,OPTIONS 不自动成功。
Any 注册 GET、POST、PUT、PATCH、HEAD、OPTIONS、DELETE、CONNECT、TRACE 九种方法;其他方法需显式注册。
404 / 405 未找到路径返回纯文本 404;路径存在但方法不支持时返回纯文本 405,Allow 去重并按字母排序。
并发 启动前完成路由注册;允许并发处理请求,各请求参数、业务值、requestId 必须隔离。

HTTP 回退响应属于路由层,不进入应用路由中间件,不携带统一 JSON 封装或 requestId。业务 Handler 返回的错误则经过统一 JSON 错误处理。

业务层通过 application.Handle("HEAD", path, handler) 或 group.Handle("OPTIONS", path, handler) 显式注册其他方法,同样经过框架中间件和错误处理。

业务行为

  • 同一套 Handler、Service、中间件用于两种适配器;应用、分组、嵌套分组、路由中间件依次进入、反序退出。
  • Use 只影响后续注册的路由。鉴权失败短路处理链,panic 转为统一 500。
  • URI / Query / JSON 绑定、业务错误、requestId 与业务日志关联一致。
  • JSON / OK 等方法先编码再发送;编码失败返回统一 500。已通过 Context 提交响应后,后续错误、panic 或重复响应不会追加 JSON。
  • 客户端断开会取消传入 Service 的 context;适配时保留原有 context 的值、截止时间和取消信号。
  • Raw() 是高级逃生口;直接写入、流式响应、Hijack 等行为不属于上述 Context 响应追踪契约,勿与自动响应方法混用。

自研适配器验收

实现 httpx.Router / httpx.RouterGroup,在调用 Handler 前通过 httpx.WithRouteParams 和 httpx.WithRoutePattern 注入请求元数据。使用外部测试包接入公共验收套件:

package myrouter_test

import (
    "testing"
    "git.yuntuo.hk/k2safe/go-omni/httpx"
    "git.yuntuo.hk/k2safe/go-omni/transport/transporttest"
    "example.com/project/myrouter"
)

func TestContract(t *testing.T) {
    transporttest.Run(t, func() httpx.Router { return myrouter.New() })
}

工厂需每次返回新的空路由器。套件依次创建路由器,内部会发送并发请求、打开本地 HTTP 测试服务,运行时应启用 race 检查:

go test -race ./transport/... ./examples/reference
python3 scripts/verifyTransports.py --version v0.6.0-preview

第一条验证标准库适配器复用完整 Handler / Service / Repository。第二条在源码目录外创建无 replace 的消费模块,从远程版本导入同一套验收包;自研适配器也应使用同一个公共测试包。

当前替换边界分成两层:transport/nethttp 提供标准库路由,transport/native 进一步接管 TCP 监听、连接管理和 HTTP/1.1 协议 I/O。App.Run / App.RunContext 仍使用标准库 net/http.Server,需要显式调用 App.RunWith(ctx, native.New(nil), address) 才会启用 native 预览实现。native 当前通过标准 http.Request / http.ResponseWriter 桥接到业务层,后续可在不改 Handler / Service 的前提下逐步替换解析器和响应写回。