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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章