go-kratos 微服务框架 bm 模块使用

路由

创建项目成功后,进入internal/server/http目录下,打开http.go文件,其中有默认生成的blademaster模板。其中:

func New(s api.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)    // 创建引擎
	api.RegisterDemoBMServer(engine, s)
	initRouter(engine) // 注册路由
	err = engine.Start()
	return
}

initRouter(engine)

func initRouter(e *bm.Engine) {
	e.Ping(ping)
	g := e.Group("/kratos-demo")
	{
		g.GET("/start", howToStart)
		// 路径参数有两个特殊符号":"和"*"
		// ":" 跟在"/"后面为参数的key,匹配两个/中间的值 或 一个/到结尾(其中不再包含/)的值
		// "*" 跟在"/"后面为参数的key,匹配从 /*开始到结尾的所有值,所有*必须写在最后且无法多个

		// NOTE:这是不被允许的,会和 /start 冲突
		// g.GET("/:xxx")

		// NOTE: 可以拿到一个key为name的参数。注意只能匹配到/param1/felix,无法匹配/param1/felix/hao(该路径会404)
		g.POST("/start", howToStart)
		g.GET("param/:name", pathParam)
		// NOTE: 可以拿到多个key参数。注意只能匹配到/param2/felix/hao/love,无法匹配/param2/felix或/param2/felix/hao
		g.GET("/param2/:name/:value/:felid", pathParam)
		// NOTE: 可以拿到一个key为name的参数 和 一个key为action的路径。
		// NOTE: 如/params3/felix/hello,action的值为"/hello"
		// NOTE: 如/params3/felix/hello/hi,action的值为"/hello/hi"
		// NOTE: 如/params3/felix/hello/hi/,action的值为"/hello/hi/"
		g.GET("/param3/:name/*action", pathParam)
	}
}

Ping
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)
	}
}

默认路由

默认路由有:

  • /metrics 用于prometheus信息采集
  • /metadata 可以查看所有注册的路由信息

查看加载的所有路由信息:

curl 'http://127.0.0.1:8000/metadata'

输出:

{"code":0,"message":"0","ttl":1,"data":{"/debug/pprof/":{"method":"GET"},"/debug/pprof/allocs":{"method":"GET"},"/debug/pprof/block":{"method":"GET"},"/debug/pprof/cmdline":{"method":"GET"},"/debug/pprof/goroutine":{"method":"GET"},"/debug/pprof/heap":{"method":"GET"},"/debug/pprof/mutex":{"method":"GET"},"/debug/pprof/profile":{"method":"GET"},"/debug/pprof/symbol":{"method":"GET"},"/debug/pprof/threadcreate":{"method":"GET"},"/debug/pprof/trace":{"method":"GET"},"/demo.service.v1.Demo/Ping":{"method":"GET"},"/demo.service.v1.Demo/SayHello":{"method":"GET"},"/kratos-demo/param/:name":{"method":"GET"},"/kratos-demo/param2/:name/:value/:felid":{"method":"GET"},"/kratos-demo/param3/:name/*action":{"method":"GET"},"/kratos-demo/say_hello":{"method":"GET"},"/kratos-demo/start":{"method":"POST"},"/metadata":{"method":"GET"},"/metrics":{"method":"GET"},"/ping":{"method":"GET"}}}

性能分析

启动时默认监听了2333端口用于pprof信息采集,如:

go tool pprof http://127.0.0.1:8000/debug/pprof/profile

改变端口可以使用flag,如:-http.perf=tcp://0.0.0.0:12333
参考:

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

	Request *http.Request
	Writer  http.ResponseWriter

	// flow control
	index    int8
	handlers []HandlerFunc

	// Keys is a key/value pair exclusively for the context of each request.
	Keys map[string]interface{}
	// This mutex protect Keys map
	keysMutex sync.RWMutex

	Error error

	method string
	engine *Engine

	RoutePath string

	Params Params
}

  • 首先可以看到 blademaster 的 Context 结构体中会 embed 一个标准库中的 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)

参数解析方法 Bind 与 BindWith:
将 howtostart 方法改为:

// example for http request handler.
func howToStart(c *bm.Context) {
	type arg struct {
		Id int `json:"id" form:"id" validate:"required"`
		Data string `json:"data" form:"data"`
	}
	args := arg{}
	err := c.BindWith(&args,binding.Query)
	if err != nil {
		return
	}
	err = c.Bind(&args)
	if err != nil {
		return
	}
	fmt.Println(args)

	k := &model.Kratos{
		Hello: "Golang 大法好 !!!",
	}
	c.JSON(k, nil)
}
func (c *Context) BindWith(obj interface{}, b binding.Binding) error 方法可以将数据绑定到结构体中,binding.Binding 有:
	JSON          = jsonBinding{}
	XML           = xmlBinding{}
	Form          = formBinding{}
	Query         = queryBinding{}
	FormPost      = formPostBinding{}
	FormMultipart = formMultipartBinding{}

中间件

middleware本质上就是一个handler,接口和方法声明如下代码:

// Handler responds to an HTTP request.
type Handler interface {
	ServeHTTP(c *Context)
}

// HandlerFunc http request handler function.
type HandlerFunc func(*Context)

// ServeHTTP calls f(ctx).
func (f HandlerFunc) ServeHTTP(c *Context) {
	f(c)
}

创建demo.go 文件

package middleware

import bm "github.com/go-kratos/kratos/pkg/net/http/blademaster"

type Demo struct {
	Key string
	Value string
}

func (d *Demo) ServeHTTP(ctx *bm.Context) {
	ctx.Set(d.Key, d.Value)
}

在路由中注册

func initRouter(e *bm.Engine) {
	e.Ping(ping)
	d := &middleware.Demo{Key: "demo", Value: "test"}
	g := e.Group("/kratos-demo")
	g.Use(d) // 或者 d.UseFunc(d.ServeHTTP)
	{
		g.GET("/start", howToStart)
		g.POST("/start", howToStart)
		g.GET("param/:name", pathParam)
		g.GET("/param2/:name/:value/:felid", pathParam)
		g.GET("/param3/:name/*action", pathParam)
	}
}

在 howToStart 方法中可以获取参数

func howToStart(c *bm.Context) {
	fmt.Println(c.Get("demo"))
	k := &model.Kratos{
		Hello: "Golang 大法好 !!!",
	}
	c.JSON(k, nil)
}

全局中间件

在http/server.go 中 加入

func New(s api.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)
	d := &middleware.Demo{Key: "demo", Value: "test"}
	engine.Use(d)  // 全局
	api.RegisterDemoBMServer(engine, s)
	initRouter(engine)
	err = engine.Start()
	return
}

局部中间件

func initRouter(e *bm.Engine) {
	e.Ping(ping)
	g := e.Group("/kratos-demo")
	g.Use(middleware.TimeHandler())
	{
		g.GET("/start", howToStart, middleware.EndHandler())
		g.POST("/start", howToStart)
		g.GET("param/:name", pathParam)
		g.GET("/param2/:name/:value/:felid", pathParam)
		g.GET("/param3/:name/*action", pathParam)
	}
}

局部路由可以针对组,或者方法。但要注意执行顺序。一般来说:全局 > 局部 > 方法 > 方法中间

Logger() > TimeHandler() > howToStart > EndHandler()

如果在EndHandler() 中调用 context.Next() 是不会执行到 howToStart 方法的。

内置中间件

Recovery

代码位于pkg/net/http/blademaster/recovery.go内,用于recovery panic。会被DefaultServer默认注册,建议使用NewServer的话也将其作为首个中间件注册。

Trace

代码位于pkg/net/http/blademaster/trace.go内,用于trace设置,并且实现了net/http/httptrace的接口,能够收集官方库内的调用栈详情。会被DefaultServer默认注册,建议使用NewServer的话也将其作为第二个中间件注册。

Logger

代码位于pkg/net/http/blademaster/logger.go内,用于请求日志记录。会被DefaultServer默认注册,建议使用NewServer的话也将其作为第三个中间件注册。

CSRF

代码位于pkg/net/http/blademaster/csrf.go内,用于防跨站请求。如要使用如下:

e := bm.DefaultServer(nil)
// 挂载自适应限流中间件到 bm engine,使用默认配置
csrf := bm.CSRF([]string{"bilibili.com"}, []string{"/a/api"})
e.Use(csrf)
// 或者
e.GET("/api", csrf, myHandler)
CORS

代码位于pkg/net/http/blademaster/cors.go内,用于跨域允许请求。请注意该:

使用该中间件进行全局注册后,可"省略"单独为OPTIONS请求注册路由,如示例一。
使用该中间单独为某路由注册,需要为该路由再注册一个OPTIONS方法的同路径路由,如示例二。
示例一:

e := bm.DefaultServer(nil)
// 挂载自适应限流中间件到 bm engine,使用默认配置
cors := bm.CORS([]string{"github.com"})
e.Use(cors)
// 该路由可以默认针对 OPTIONS /api 的跨域请求支持
e.POST("/api", myHandler)

示例二:

e := bm.DefaultServer(nil)
// 挂载自适应限流中间件到 bm engine,使用默认配置
cors := bm.CORS([]string{"github.com"})
// e.Use(cors) 不进行全局注册
e.OPTIONS("/api", cors, myHandler) // 需要单独为/api进行OPTIONS方法注册
e.POST("/api", cors, myHandler)
自适应限流

更多关于自适应限流的信息可参考:kratos 自适应限流。如要使用如下:

e := bm.DefaultServer(nil)
// 挂载自适应限流中间件到 bm engine,使用默认配置
limiter := bm.NewRateLimiter(nil)
e.Use(limiter.Limit())
// 或者
e.GET("/api", csrf, myHandler)

猜你喜欢

转载自blog.csdn.net/luslin1711/article/details/106194209
BM