kratos源码解析(三)--http模块

793 阅读5分钟

kratos的http server主要目的只有一个:

把收到的http请求让pb文件定义的grpc service执行,然后把对应的返回转化成http响应返回.

一旦明确后,思路就很清晰了:

  1. 需要有http server来响应http请求
  2. 把pb文件标注的rpc service转化成http路由,http请求转化为grpc request,grpc返回转化为http响应
  3. 把2转化的路由注册到1生成的http server

kratos http server

首先来看看对应的结构

// Server is an HTTP server wrapper.
type Server struct {
	*http.Server //kratos内嵌了一个golang内置的http server
	lis         net.Listener
	tlsConf     *tls.Config
	endpoint    *url.URL
	err         error
	network     string
	address     string
	timeout     time.Duration
	filters     []FilterFunc // 过滤器
	middleware  matcher.Matcher // 中间件
	dec         DecodeRequestFunc // 请求解码
	enc         EncodeResponseFunc // 返回编码
	ene         EncodeErrorFunc// 错误返回编码 
	strictSlash bool
	router      *mux.Router //处理http路由,则采用了mux.Router来处理
}

// NewServer creates an HTTP server by options.
func NewServer(opts ...ServerOption) *Server {
	srv := &Server{
		network:     "tcp",
		address:     ":0",
		timeout:     1 * time.Second,
		middleware:  matcher.New(),
		dec:         DefaultRequestDecoder,
		enc:         DefaultResponseEncoder,
		ene:         DefaultErrorEncoder,
		strictSlash: true,
	}
	for _, o := range opts {
		o(srv)
	}
	srv.router = mux.NewRouter().StrictSlash(srv.strictSlash)
	srv.router.NotFoundHandler = http.DefaultServeMux
	srv.router.MethodNotAllowedHandler = http.DefaultServeMux
	srv.router.Use(srv.filter())
    // 下面是关键,实现了我们目的的第一步:
    // 有http server来响应对应的请求.同时采用了mux的Router来作为http server的handler
    // 那么接下里要做的,就是得想案发
	srv.Server = &http.Server{
		Handler:   FilterChain(srv.filters...)(srv.router),
		TLSConfig: srv.tlsConf,
	}
	return srv
}


func (s *Server) filter() mux.MiddlewareFunc {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			var (
				ctx    context.Context
				cancel context.CancelFunc
			)
			if s.timeout > 0 {
				ctx, cancel = context.WithTimeout(req.Context(), s.timeout)
			} else {
				ctx, cancel = context.WithCancel(req.Context())
			}
			defer cancel()

			pathTemplate := req.URL.Path
			if route := mux.CurrentRoute(req); route != nil {
				// /path/123 -> /path/{id}
				pathTemplate, _ = route.GetPathTemplate()
			}
// 和kratos grpc一样,把这个过滤器作为请求首个过滤器,把对应的服务器等相关信息放入Transport里面
// 然后再把Transport放到Context里面传递给执行函数
			tr := &Transport{
				operation:    pathTemplate,
				pathTemplate: pathTemplate,
				reqHeader:    headerCarrier(req.Header),
				replyHeader:  headerCarrier(w.Header()),
				request:      req,
			}
			if s.endpoint != nil {
				tr.endpoint = s.endpoint.String()
			}
			tr.request = req.WithContext(transport.NewServerContext(ctx, tr))
			next.ServeHTTP(w, tr.request)
		})
	}
}

上面已经实现了第一步了,那么我们得研究一下,怎么样把对应的http 请求转化成路由呢?

我们得看看kratos的两个关键性设计:RouterContext:

首先是Context

// Context is an HTTP Context.
// 我们可以看出,这个Context接口除了包括了原来的golang Context接口的外,还提供了:返回http相关信息,绑定对应http信息等方法
// 简单来说,这个Context主要作用是转化层作用,就像前面说的:把pb文件标注的rpc service转化成http路由
// http请求转化为grpc request,grpc返回转化为http响应
type Context interface {
	context.Context
	Vars() url.Values
	Query() url.Values
	Form() url.Values
	Header() http.Header
	Request() *http.Request
	Response() http.ResponseWriter
	Middleware(middleware.Handler) middleware.Handler
	Bind(interface{}) error
	BindVars(interface{}) error
	BindQuery(interface{}) error
	BindForm(interface{}) error
	Returns(interface{}, error) error
	Result(int, interface{}) error
	JSON(int, interface{}) error
	XML(int, interface{}) error
	String(int, string) error
	Blob(int, string, []byte) error
	Stream(int, string, io.Reader) error
	Reset(http.ResponseWriter, *http.Request)
}

// 具体实现接口的struct
type wrapper struct {
    // 绑定的路由
	router *Router
    // 对应请求的http请求和相应
	req    *http.Request
	res    http.ResponseWriter
    // 输出响应
	w      responseWriter
}
type responseWriter struct {
	code int
	w    http.ResponseWriter
}

然后是Router

// Router is an HTTP router.
// 
type Router struct {
	prefix  string
	pool    sync.Pool
	srv     *Server
	filters []FilterFunc
}

func newRouter(prefix string, srv *Server, filters ...FilterFunc) *Router {
	r := &Router{
		prefix:  prefix,
		srv:     srv,
		filters: filters,
	}
	r.pool.New = func() interface{} {
		return &wrapper{router: r}
	}
	return r
}
// HandlerFunc defines a function to serve HTTP requests.
type HandlerFunc func(Context) error


// Handle registers a new route with a matcher for the URL path and method.
// Handle是关键方法,主要作用是把对应的HandlerFunc注册到http server的mux.Router里面
// 同时可以看到他注册的的路由函数的形式HandlerFunc是接受一个HandlerFunc类型的函数,传入的是前面说的从Context类型,返回的是error

func (r *Router) Handle(method, relativePath string, h HandlerFunc, filters ...FilterFunc) {
	next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
        // 值得注意的是,这里用了sync.Pool来减少内存分配
		ctx := r.pool.Get().(Context)
		ctx.Reset(res, req)
		if err := h(ctx); err != nil {
            // 如果有错误的话,会在此处执行那个响应
			r.srv.ene(res, req, err)
		}
		ctx.Reset(nil, nil)
		r.pool.Put(ctx)
	}))
	next = FilterChain(filters...)(next)
	next = FilterChain(r.filters...)(next)
	r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method)
}


看完上面的介绍,基本可以确定思路了

  1. Router是负责注册路由到http server的mux.Router里面的.同时,接受的注册函数是传参为Context,返回为error
  2. Context含有多个用途:包括解码http请求后解析为grpc请求,编码grpc返回为http响应的.

具体的生成http代码解析

// 利用server生产新的路由
func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) {
	r := s.Route("/")
	r.GET("/helloworld/{name}", _Greeter_SayHello0_HTTP_Handler(srv))
}

func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error {
	return func(ctx http.Context) error {
		var in HelloRequest
        // 解析路由参数和请求参数到grpc请求
		if err := ctx.BindQuery(&in); err != nil {
			return err
		}
		if err := ctx.BindVars(&in); err != nil {
			return err
		}
		http.SetOperation(ctx, "/helloworld.v1.Greeter/SayHello")
        // 后面会着重讲一下Middleware的实现
		h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
			return srv.SayHello(ctx, req.(*HelloRequest))
		})
        // 值得注意的是,如果是执行返回err,就直接返回
        // 如果成功没有err的话,这边就直接进行http响应
		out, err := h(ctx, &in)
		if err != nil {
			return err
		}
		reply := out.(*HelloReply)
		return ctx.Result(200, reply)
	}
}

// 可以看到,这边调用的中间件和kratos的grpc模块一样,都使用了同样的Matcher
// 通过这个使得kratos写的中间件可以为http和grpc模块共用
func (c *wrapper) Middleware(h middleware.Handler) middleware.Handler {
	if tr, ok := transport.FromServerContext(c.req.Context()); ok {
		return middleware.Chain(c.router.srv.middleware.Match(tr.Operation())...)(h)
	}
	return middleware.Chain(c.router.srv.middleware.Match(c.req.URL.Path)...)(h)
}

  1. 通过上面可以看出,生成的代码中,主要是要把形式func(ctx http.Context) error的函数注册到Router

  2. 这个函数除了简单的解析请求外,还会把中间件给串联起来

  3. 值得注意的是,如果返回没有error,会在这个函数里面就响应请求了,否则,则直接返回,交由Router来处理.

    		if err := h(ctx); err != nil {
                // 如果有错误的话,会在此处执行那个响应
    			r.srv.ene(res, req, err)
    		}
    

    而如果没有err返回的响应代码是:

    func (c *wrapper) Result(code int, v interface{}) error {
    	c.w.WriteHeader(code)
    	return c.router.srv.enc(&c.w, c.req, v)
    }
    
    

    个人认为实际上可以统一交由Router来决定响应会好点