注:本文是在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的实现类:
// 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提供的处理接口。