go-kit http请求解析

1,685 阅读4分钟

注:本文是在go-kit微服务:HTTP REST文章的基础上,结合go-kit源码做的流程梳理。 go-kit该作者有一个系列的文章,详见go-kit微服务系列目录

一个基本的http服务 net/http包

提供http服务,使用的是golang自带的net/http包。 按照net/http包的实现,任何实现了http包Handler接口的对象,都可以处理http请求:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

更多关于net.http包的资料可见:深入学习用Go编写http服务器

go-kit分层:

go-kit是一个松散的工具型脚手架,对框架目录并没有严格要求。在go-kit中主要分为三层:service、endpoints、transport。

  • service:关注业务逻辑实现。
  • endpoints:调用service中的相应方法,处理请求对象,返回响应对应。
  • transport:接收网络请求并将其转为endpoints可以处理的对象,返回响应对象。
  • main方法:需要按照go-kit的要求将service、endpoints、transport组织起来,使用http或其它协议发布服务。

transport层:

transport:接收网络请求并将其转为endpoints可以处理的对象,返回响应对象。

根据transport的作用,需要在transport层定义http请求路由,这里采用的gorilla/mux处理路由,该包可以通过如下来设计路由:

r.Methods("POST").Path("/calculate/{type}/{a}/{b}").Handler(kithttp.NewServer(
        endpoint,
	decodeArithmeticRequest,
	encodeArithmeticResponse,
	options...,
))

如上面所示,最终通过.Handler传入一个http.Handler接口类型的值处理http请求。

通过看kithttp的源码,可以发现,kithttp.NewServer()最终返回的是Server的实现类:

go-kit transport Server源码
server实现了http.Handler接口,所以,可以用来处理http请求:

// ServeHTTP implements http.Handler.
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	if len(s.finalizer) > 0 {
		iw := &interceptingWriter{w, http.StatusOK, 0}
		defer func() {
			ctx = context.WithValue(ctx, ContextKeyResponseHeaders, iw.Header())
			ctx = context.WithValue(ctx, ContextKeyResponseSize, iw.written)
			for _, f := range s.finalizer {
				f(ctx, iw.code, r)
			}
		}()
		w = iw
	}

	for _, f := range s.before {
		ctx = f(ctx, r)
	}

        // 调用dec解析请求参数
	request, err := s.dec(ctx, r)
	if err != nil {
		s.errorHandler.Handle(ctx, err)
		s.errorEncoder(ctx, err, w)
		return
	}

        // 调用endpoint处理请求
	response, err := s.e(ctx, request)
	if err != nil {
		s.errorHandler.Handle(ctx, err)
		s.errorEncoder(ctx, err, w)
		return
	}

	for _, f := range s.after {
		ctx = f(ctx, w)
	}

        // 对返回数据进行encode
	if err := s.enc(ctx, w, response); err != nil {
		s.errorHandler.Handle(ctx, err)
		s.errorEncoder(ctx, err, w)
		return
	}
}

通过上面代码可以看出来,最终处理请求时通过调用s.e(ctx, request)即endpoint来处理请求,所以,要求在endpoint中实现处理请求的具体方法。

endpoint层:

endpoint层:通过上面可以看到,在处理请求时,最终是通过s.e(ctx, request)即调用endpoint()方法来处理http请求,所以要求endpoint层对外输出一个endpoint给transport层用,且该endpoint能够调用service层提供的处理能力(因为最终的处理能力都是service层提供的)。

// endpoint层对外提供的endpoint
// MakeArithmeticEndpoint make endpoint
func MakeArithmeticEndpoint(svc Service) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
		req := request.(ArithmeticRequest)

		var (
			res, a, b int
			calError  error
		)

		a = req.A
		b = req.B

		if strings.EqualFold(req.RequestType, "Add") {
			res = svc.Add(a, b)
		} else if strings.EqualFold(req.RequestType, "Subtract") {
			res = svc.Subtract(a, b)
		} else if strings.EqualFold(req.RequestType, "Multiply") {
			res = svc.Multiply(a, b)
		} else if strings.EqualFold(req.RequestType, "Divide") {
			res, calError = svc.Divide(a, b)
		} else {
			return nil, ErrInvalidRequestType
		}

		return ArithmeticResponse{Result: res, Error: calError}, nil
	}
}

// go-kit中 endpoint定义
// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)

从上面的代码中可以看出,endpoint层主要用于提供transport层可用的endpoint,MakeArithmeticEndpoint()函数返回一个endpoint.Endpoint,go-kit中Endpoint是一个函数类型,所以,在MakeArithmeticEndpoint中返回了一个函数,在函数中对请求参数进行了获取,并且调用了service的具体处理函数:例如:srv.Divide(a, b)

service层:

service层:该层比较简单,主要定义该路由请求(例如:两个数字计算)提供的接口,并且定义了一个结构体,用于具体的请求实现。

// Service Define a service interface
type Service interface {

	// Add calculate a+b
	Add(a, b int) int

	// Subtract calculate a-b
	Subtract(a, b int) int

	// Multiply calculate a*b
	Multiply(a, b int) int

	// Divide calculate a/b
	Divide(a, b int) (int, error)
}

//ArithmeticService implement Service interface
type ArithmeticService struct {
}

// Add implement Add method
func (s ArithmeticService) Add(a, b int) int {
	return a + b
}

当然,这里service层提供了4个方法,另外一种方式,我们也可以仅提供一种方法,在该方法内部判断请求类型(Add/Subtract/Multiply/Divide),但是整体的分层逻辑不变,在endpoint层调用service层提供的处理能力。

main入口:

main入口:在像上面那样,组织完分层后,需要通过入口文件将各层串起来,进行执行。 按照上面各层的职责,我们可以大概猜到main的流程:

  • 接收请求。
  • 初始化service,将service层传入endpoint,获取可以供transport使用的endpoint。
  • 将endpoint传入transport,获取一个具体路由且能够处理http请求的handler。
  • 使用transport提供的handler,启用服务(也就是监听某个端口的http请求)。
func main() {

	ctx := context.Background()
	errChan := make(chan error)

        // 初始化service
	var svc Service
	svc = ArithmeticService{}
    
    // 创建endpoint
	endpoint := MakeArithmeticEndpoint(svc)

	var logger log.Logger
	{
		logger = log.NewLogfmtLogger(os.Stderr)
		logger = log.With(logger, "ts", log.DefaultTimestampUTC)
		logger = log.With(logger, "caller", log.DefaultCaller)
	}

        // transport提供http.handler
	r := MakeHttpHandler(ctx, endpoint, logger)

        // 启动http服务
	go func() {
		fmt.Println("Http Server start at port:9000")
		handler := r
		errChan <- http.ListenAndServe(":9000", handler)
	}()

	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errChan <- fmt.Errorf("%s", <-c)
	}()

	fmt.Println(<-errChan)
}

流程小结:

当一个http请求到来时,会找到transport提供的handler,最终调用的是kithttp.NewServer().ServeHTTP()方法,在ServeHTTP()方法中调用了endpoint处理请求,在endpoint中调用service提供的处理接口。