背景

在像微服务这样的分布式架构中,经常会有一些需求需要你调用多个服务,但是还需要确保服务的安全性、统一化每次的 请求日志或者追踪用户完整的行为等等。
你可能需要一个框架来帮助你实现这些功能。比如说帮你在一些关键路径的请求上配置必要的鉴权 或超时策略。那样服务间的调用会被多层中间件所过滤并检查,确保整体服务的稳定性。

设计目标

  • 性能优异,不应该掺杂太多业务逻辑的成分
  • 方便开发使用,开发对接的成本应该尽可能地小
  • 后续鉴权、认证等业务逻辑的模块应该可以通过业务模块的开发接入该框架内
  • 默认配置已经是 production ready 的配置,减少开发与线上环境的差异性

kratos的http服务架构-blademaster

blademasterginRouterContextHandlerContextHandlersmiddlerwareHandlerblademaster

bladmaster架构

blademasterHandlerHandler
HandlerResponsemiddleware
RenderResponseJSONXMLRender

快速开始

创建http项目:

kratos new httpdemo --http

可以指定名字和目录:

kratos new kratos-demo -o YourName -d YourPath

创建http项目
创建项目成功后,进入 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:

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/felix,无法匹配/param1/felix/hao(该路径会404)
		g.GET("/param1/:name", showParam)
		// NOTE: 可以拿到多个key参数。注意只能匹配到/param2/felix/hao/love,无法匹配/param2/felix或/param2/felix/hao
		g.GET("/param2/:name/:value/:felid", showParam)
		// 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", showParam)
	}
}

func showParam(c *bm.Context) {
	name, _ := c.Params.Get("name")
	value, _ := c.Params.Get("value")
	felid, _ := c.Params.Get("felid")
	action, _ := c.Params.Get("action")
	path := c.RoutePath // NOTE: 获取注册的路由原始地址,如: /kratos-demo/param1/:name
	c.JSONMap(map[string]interface{}{
		"name":   name,
		"value":  value,
		"felid":  felid,
		"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

blademasterContext
// 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 实例
}
blademasterContextContextContextcontext deadline exceededRequestWriterhandlerindexKeyshandlerErrormethodMethodengineblademaster
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

blademasterHandlerbmHandler
RoutermiddlewareHandlerContexthandlersindex0Next()middlewareAbort()middlewareHandlerHandlerNext()Next()

性能分析

2333pprof
go tool pprof http://127.0.0.1:8000/debug/pprof/profile
-http.perf=tcp://0.0.0.0:12333