背景
在像微服务这样的分布式架构中,常常会有一些需求须要你调用多个服务,可是还须要确保服务的安全性、统一化每次的 请求日志或者追踪用户完整的行为等等。
你可能须要一个框架来帮助你实现这些功能。好比说帮你在一些关键路径的请求上配置必要的鉴权 或超时策略。那样服务间的调用会被多层中间件所过滤并检查,确保总体服务的稳定性。
git
设计目标
- 性能优异,不该该掺杂太多业务逻辑的成分
- 方便开发使用,开发对接的成本应该尽量地小
- 后续鉴权、认证等业务逻辑的模块应该能够经过业务模块的开发接入该框架内
- 默认配置已是 production ready 的配置,减小开发与线上环境的差别性
kratos的http服务架构-blademaster
blademaster
设计的整套HTTP框架参考了gin
,去除 gin 中不须要的部分逻辑。
blademaster由几个很是精简的内部模块组成。其中Router
用于根据请求的路径分发请求,Context
包含了一个完整的请求信息,Handler
则负责处理传入的Context
,Handlers
为一个列表,一个串一个地执行。
全部的middlerware
均以Handler
的形式存在,这样能够保证blademaster
自身足够精简且扩展性足够强。
github
blademaster
处理请求的模式很是简单,大部分的逻辑都被封装在了各类Handler
中。通常而言,业务逻辑做为最后一个Handler
。shell
正常状况下每一个Handler
按照顺序一个一个串行地执行下去,可是Handler中也能够中断整个处理流程,直接输出Response
。这种模式常被用于校验登录的middleware
中:一旦发现请求不合法,直接响应拒绝。数据库
请求处理的流程中也可使用Render
来辅助渲染Response
,好比对于不一样的请求须要响应不一样的数据格式JSON
、XML
,此时可使用不一样的Render
来简化逻辑。json
快速开始
建立http项目:api
kratos new httpdemo --http
能够指定名字和目录:浏览器
kratos new kratos-demo -o YourName -d YourPath
建立项目成功后,进入 internal/server/http 目录下,默认生成的 server.go 模板:
缓存
package http import ( "net/http" pb "httpdemo/api" "httpdemo/internal/model" "github.com/go-kratos/kratos/pkg/conf/paladin" "github.com/go-kratos/kratos/pkg/log" bm "github.com/go-kratos/kratos/pkg/net/http/blademaster" ) var svc pb.DemoServer // New new a bm server. func New(s pb.DemoServer) (engine *bm.Engine, err error) { var ( cfg bm.ServerConfig ct paladin.TOML ) if err = paladin.Get("http.toml").Unmarshal(&ct); err != nil { return } if err = ct.Get("Server").UnmarshalTOML(&cfg); err != nil { return } svc = s engine = bm.DefaultServer(&cfg) pb.RegisterDemoBMServer(engine, s) initRouter(engine) err = engine.Start() return } //路由 func initRouter(e *bm.Engine) { e.Ping(ping) // engine自带的"/ping"接口,用于负载均衡检测服务健康状态 g := e.Group("/httpdemo") // // e.Group 建立一组 "/httpdemo" 起始的路由组 { g.GET("/start", howToStart) // // g.GET 建立一个 "httpdemo/start" 的路由,使用GET方式请求,默认处理Handle r为howToStart方法 } } //engine自带Ping方法,用于设置 /ping 路由的handler,该路由统一提供于负载均衡服务作健康检测。服务是否健康,可自 定义 ping handler 进行逻辑判断,如检测DB是否正常等。 func ping(ctx *bm.Context) { if _, err := svc.Ping(ctx, nil); err != nil { log.Error("ping error(%v)", err) ctx.AbortWithStatus(http.StatusServiceUnavailable) } } // bm的handler方法. func howToStart(c *bm.Context) { k := &model.Kratos{ Hello: "Golang 大法好 !!!", } c.JSON(k, nil) }
默认路由
默认路由有:安全
- /metrics 用于prometheus信息采集
- /metadata 能够查看全部注册的路由信息
打开浏览器访问:
路径参数
咱们在路由中增长一些内容,增长一个handler方法showParam:bash
func initRouter(e *bm.Engine) { e.Ping(ping) g := e.Group("/httpdemo") { g.GET("/start", howToStart) // 路径参数有两个特殊符号":"和"*" // ":" 跟在"/"后面为参数的key,匹配两个/中间的值 或 一个/到结尾(其中再也不包含/)的值 // "*" 跟在"/"后面为参数的key,匹配从 /*开始到结尾的全部值,全部*必须写在最后且没法多个 // NOTE:这是不被容许的,会和 /start 冲突 // g.GET("/:xxx") // NOTE: 能够拿到一个key为name的参数。注意只能匹配到/param1/soul,没法匹配/param1/soul/hao(该路径会404) g.GET("/param1/:name", showParam) // NOTE: 能够拿到多个key参数。注意只能匹配到/param2/soul/male/hello,没法匹配/param2/soul或/param2/soul/hello g.GET("/param2/:name/:gender/:say", showParam) // NOTE: 能够拿到一个key为name的参数 和 一个key为action的路径。 // NOTE: 如/params3/soul/hello,action的值为"/hello" // NOTE: 如/params3/soul/hello/hi,action的值为"/hello/hi" // NOTE: 如/params3/soul/hello/hi/,action的值为"/hello/hi/" g.GET("/param3/:name/*action", showParam) } } func showParam(c *bm.Context) { name, _ := c.Params.Get("name") gender, _ := c.Params.Get("gender") say, _ := c.Params.Get("say") action, _ := c.Params.Get("action") path := c.RoutePath // NOTE: 获取注册的路由原始地址,如: /httpdemo/param1/:name c.JSONMap(map[string]interface{}{ "name": name, "gender": gender, "say": say, "action": action, "path": path, }, nil) }
打开浏览器访问:
http://localhost:8000/httpdemo/param2/Soul/male/hello
输出内容:
{ "action": "", "code": 0, "gender": "male", "message": "0", "name": "Soul", "path": "/httpdemo/param2/:name/:gender/:say", "say": "hello" }
Context
如下是 blademaster
中 Context
对象结构体声明的代码片断:
// Context is the most important part. It allows us to pass variables between // middleware, manage the flow, validate the JSON of a request and render a // JSON response for example. type Context struct { context.Context //嵌入一个标准库中的 Context实例,对应bm中的 Context,也是经过该实例来实现标准库中的 Context 接口 Request *http.Request //获取当前请求信息 Writer http.ResponseWriter //输出响应请求信息 // flow control index int8 //标记当前正在执行的 handler 的索引位 handlers []HandlerFunc //中存储了当前请求须要执行的全部 handler // Keys is a key/value pair exclusively for the context of each request. Keys map[string]interface{} //在 handler 之间传递一些额外的信息 Error error //存储整个请求处理过程当中的错误 method string //检查当前请求的 Method 是否与预约义的相匹配 engine *Engine //指向当前 blademaster 的 Engine 实例 }
- 首先
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
中全部的公开的方法:
// 用于 Handler 的流程控制 func (c *Context) Abort() func (c *Context) AbortWithStatus(code int) func (c *Context) Bytes(code int, contentType string, data ...[]byte) func (c *Context) IsAborted() bool func (c *Context) Next() // 用户获取或者传递请求的额外信息 func (c *Context) RemoteIP() (cip string) func (c *Context) Set(key string, value interface{}) func (c *Context) Get(key string) (value interface{}, exists bool) // 用于校验请求的 payload func (c *Context) Bind(obj interface{}) error func (c *Context) BindWith(obj interface{}, b binding.Binding) error // 用于输出响应 func (c *Context) Render(code int, r render.Render) func (c *Context) Redirect(code int, location string) func (c *Context) Status(code int) func (c *Context) String(code int, format string, values ...interface{}) func (c *Context) XML(data interface{}, err error) func (c *Context) JSON(data interface{}, err error) func (c *Context) JSONMap(data map[string]interface{}, err error) func (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