路由
創建項目成功後,進入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)