背景
在像微服务这样的分布式架构中,经常会有一些需求需要你调用多个服务,但是还需要确保服务的安全性、统一化每次的 请求日志或者追踪用户完整的行为等等。
你可能需要一个框架来帮助你实现这些功能。比如说帮你在一些关键路径的请求上配置必要的鉴权 或超时策略。那样服务间的调用会被多层中间件所过滤并检查,确保整体服务的稳定性。
设计目标
- 性能优异,不应该掺杂太多业务逻辑的成分
- 方便开发使用,开发对接的成本应该尽可能地小
- 后续鉴权、认证等业务逻辑的模块应该可以通过业务模块的开发接入该框架内
- 默认配置已经是 production ready 的配置,减少开发与线上环境的差异性
kratos的http服务架构-blademaster
blademaster设计的整套HTTP框架参考了gin,去除 gin 中不需要的部分逻辑。
blademaster由几个非常精简的内部模块组成。其中Router用于根据请求的路径分发请求,Context包含了一个完整的请求信息,Handler则负责处理传入的Context,Handlers为一个列表,一个串一个地执行。
所有的middlerware均以Handler的形式存在,这样可以保证blademaster自身足够精简且扩展性足够强。

blademaster处理请求的模式非常简单,大部分的逻辑都被封装在了各种Handler中。一般而言,业务逻辑作为最后一个Handler。
正常情况下每个Handler按照顺序一个一个串行地执行下去,但是Handler中也可以中断整个处理流程,直接输出Response。这种模式常被用于校验登陆的middleware中:一旦发现请求不合法,直接响应拒绝。
请求处理的流程中也可以使用Render来辅助渲染Response,比如对于不同的请求需要响应不同的数据格式JSON、XML,此时可以使用不同的Render来简化逻辑。
快速开始
创建http项目:
kratos new httpdemo --http
可以指定名字和目录:
kratos new kratos-demo -o YourName -d YourPath

创建项目成功后,进入 internal/server/http 目录下,默认生成的 server.go 模板:
1package http 2 3import ( 4 "net/http" 5 6 pb "httpdemo/api" 7 "httpdemo/internal/model" 8 "github.com/go-kratos/kratos/pkg/conf/paladin" 9 "github.com/go-kratos/kratos/pkg/log" 10 bm "github.com/go-kratos/kratos/pkg/net/http/blademaster" 11) 12 13var svc pb.DemoServer 14 15// New new a bm server. 16func New(s pb.DemoServer) (engine *bm.Engine, err error) { 17 var ( 18 cfg bm.ServerConfig 19 ct paladin.TOML 20 ) 21 if err = paladin.Get("http.toml").Unmarshal(&ct); err != nil { 22 return 23 } 24 if err = ct.Get("Server").UnmarshalTOML(&cfg); err != nil { 25 return 26 } 27 svc = s 28 engine = bm.DefaultServer(&cfg) 29 pb.RegisterDemoBMServer(engine, s) 30 initRouter(engine) 31 err = engine.Start() 32 return 33} 34//路由 35func initRouter(e *bm.Engine) { 36 e.Ping(ping) // engine自带的"/ping"接口,用于负载均衡检测服务健康状态 37 g := e.Group("/httpdemo") // // e.Group 创建一组 "/httpdemo" 起始的路由组 38 { 39 g.GET("/start", howToStart) // // g.GET 创建一个 "httpdemo/start" 的路由,使用GET方式请求,默认处理Handle r为howToStart方法 40 } 41} 42//engine自带Ping方法,用于设置 /ping 路由的handler,该路由统一提供于负载均衡服务做健康检测。服务是否健康,可自 定义 ping handler 进行逻辑判断,如检测DB是否正常等。 43func ping(ctx *bm.Context) { 44 if _, err := svc.Ping(ctx, nil); err != nil { 45 log.Error("ping error(%v)", err) 46 ctx.AbortWithStatus(http.StatusServiceUnavailable) 47 } 48} 49 50// bm的handler方法. 51func howToStart(c *bm.Context) { 52 k := &model.Kratos{ 53 Hello: "Golang 大法好 !!!", 54 } 55 c.JSON(k, nil) 56}
默认路由
默认路由有:
- /metrics 用于prometheus信息采集
- /metadata 可以查看所有注册的路由信息
打开浏览器访问:
路径参数
我们在路由中增加一些内容,增加一个handler方法showParam:
1func initRouter(e *bm.Engine) { 2 e.Ping(ping) 3 g := e.Group("/httpdemo") 4 { 5 g.GET("/start", howToStart) 6 7 // 路径参数有两个特殊符号":"和"*" 8 // ":" 跟在"/"后面为参数的key,匹配两个/中间的值 或 一个/到结尾(其中不再包含/)的值 9 // "*" 跟在"/"后面为参数的key,匹配从 /*开始到结尾的所有值,所有*必须写在最后且无法多个 10 11 // NOTE:这是不被允许的,会和 /start 冲突 12 // g.GET("/:xxx") 13 14 // NOTE: 可以拿到一个key为name的参数。注意只能匹配到/param1/soul,无法匹配/param1/soul/hao(该路径会404) 15 g.GET("/param1/:name", showParam) 16 // NOTE: 可以拿到多个key参数。注意只能匹配到/param2/soul/male/hello,无法匹配/param2/soul或/param2/soul/hello 17 g.GET("/param2/:name/:gender/:say", showParam) 18 // NOTE: 可以拿到一个key为name的参数 和 一个key为action的路径。 19 // NOTE: 如/params3/soul/hello,action的值为"/hello" 20 // NOTE: 如/params3/soul/hello/hi,action的值为"/hello/hi" 21 // NOTE: 如/params3/soul/hello/hi/,action的值为"/hello/hi/" 22 g.GET("/param3/:name/*action", showParam) 23 } 24} 25 26func showParam(c *bm.Context) { 27 name, _ := c.Params.Get("name") 28 gender, _ := c.Params.Get("gender") 29 say, _ := c.Params.Get("say") 30 action, _ := c.Params.Get("action") 31 path := c.RoutePath // NOTE: 获取注册的路由原始地址,如: /httpdemo/param1/:name 32 c.JSONMap(map[string]interface{}{ 33 "name": name, 34 "gender": gender, 35 "say": say, 36 "action": action, 37 "path": path, 38 }, nil) 39}
打开浏览器访问:
http://localhost:8000/httpdemo/param2/Soul/male/hello
输出内容:
1{ 2 "action": "", 3 "code": 0, 4 "gender": "male", 5 "message": "0", 6 "name": "Soul", 7 "path": "/httpdemo/param2/:name/:gender/:say", 8 "say": "hello" 9}
Context
以下是 blademaster 中 Context 对象结构体声明的代码片段:
1// Context is the most important part. It allows us to pass variables between 2// middleware, manage the flow, validate the JSON of a request and render a 3// JSON response for example. 4type Context struct { 5 context.Context //嵌入一个标准库中的 Context实例,对应bm中的 Context,也是通过该实例来实现标准库中的 Context 接口 6 7 Request *http.Request //获取当前请求信息 8 Writer http.ResponseWriter //输出响应请求信息 9 10 // flow control 11 index int8 //标记当前正在执行的 handler 的索引位 12 handlers []HandlerFunc //中存储了当前请求需要执行的所有 handler 13 14 // Keys is a key/value pair exclusively for the context of each request. 15 Keys map[string]interface{} //在 handler 之间传递一些额外的信息 16 17 Error error //存储整个请求处理过程中的错误 18 19 method string //检查当前请求的 Method 是否与预定义的相匹配 20 engine *Engine //指向当前 blademaster 的 Engine 实例 21}
- 首先
blademaster的Context结构体中会 嵌入一个标准库中的Context实例,bm 中的 Context 也是通过该实例来实现标准库中的Context接口。 - blademaster 会使用配置的 server timeout (默认1s) 作为一次请求整个过程中的超时时间,使用该context调用dao做数据库、缓存操作查询时均会将该超时时间传递下去,一旦抵达deadline,后续相关操作均会返回
context deadline exceeded。 Request和Writer字段用于获取当前请求的与输出响应。- index 和 handlers 用于 handler 的流程控制;handlers 中存储了当前请求需要执行的所有
handler,index用于标记当前正在执行的 handler 的索引位。 Keys用于在handler之间传递一些额外的信息。Error用于存储整个请求处理过程中的错误。method用于检查当前请求的Method是否与预定义的相匹配。engine字段指向当前blademaster的 Engine 实例。
以下为 Context 中所有的公开的方法:
1// 用于 Handler 的流程控制 2func (c *Context) Abort() 3func (c *Context) AbortWithStatus(code int) 4func (c *Context) Bytes(code int, contentType string, data ...[]byte) 5func (c *Context) IsAborted() bool 6func (c *Context) Next() 7 8// 用户获取或者传递请求的额外信息 9func (c *Context) RemoteIP() (cip string) 10func (c *Context) Set(key string, value interface{}) 11func (c *Context) Get(key string) (value interface{}, exists bool) 12 13// 用于校验请求的 payload 14func (c *Context) Bind(obj interface{}) error 15func (c *Context) BindWith(obj interface{}, b binding.Binding) error 16 17// 用于输出响应 18func (c *Context) Render(code int, r render.Render) 19func (c *Context) Redirect(code int, location string) 20func (c *Context) Status(code int) 21func (c *Context) String(code int, format string, values ...interface{}) 22func (c *Context) XML(data interface{}, err error) 23func (c *Context) JSON(data interface{}, err error) 24func (c *Context) JSONMap(data map[string]interface{}, err error) 25func (c *Context) Protobuf(data proto.Message, err error)
所有方法基本上可以分为三类:
- 流程控制
- 额外信息传递
- 请求处理
- 响应处理
Handler

初次接触blademaster的用户可能会对其Handler的流程处理产生不小的疑惑,实际上bm对Handler对处理非常简单:
- 将
Router模块中预先注册的middleware与其他Handler合并,放入Context的handlers字段,并将index字段置0 - 然后通过
Next()方法一个个执行下去,部分middleware可能想要在过程中中断整个流程,此时可以使用Abort()方法提前结束处理 - 有些
middleware还想在所有Handler执行完后再执行部分逻辑,此时可以在自身Handler中显式调用Next()方法,并将这些逻辑放在调用了Next()方法之后
性能分析
启动时默认监听了2333端口用于pprof信息采集,如:
go tool pprof http://127.0.0.1:8000/debug/pprof/profile
改变端口可以使用flag,如:-http.perf=tcp://0.0.0.0:12333