创建项目成功后,进入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)
}
}
默认路由有:
查看加载的所有路由信息:
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
参考:
以下是 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
}
以下为 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 方法的。
代码位于pkg/net/http/blademaster/recovery.go内,用于recovery panic。会被DefaultServer默认注册,建议使用NewServer的话也将其作为首个中间件注册。
代码位于pkg/net/http/blademaster/trace.go内,用于trace设置,并且实现了net/http/httptrace的接口,能够收集官方库内的调用栈详情。会被DefaultServer默认注册,建议使用NewServer的话也将其作为第二个中间件注册。
代码位于pkg/net/http/blademaster/logger.go内,用于请求日志记录。会被DefaultServer默认注册,建议使用NewServer的话也将其作为第三个中间件注册。
代码位于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)
代码位于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)