背景
在像微服務這樣的分佈式架構中,常常會有一些需求須要你調用多個服務,可是還須要確保服務的安全性、統一化每次的 請求日誌或者追蹤用戶完整的行爲等等。
你可能須要一個框架來幫助你實現這些功能。好比說幫你在一些關鍵路徑的請求上配置必要的鑑權 或超時策略。那樣服務間的調用會被多層中間件所過濾並檢查,確保總體服務的穩定性。
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