Go语言编写HTTP Web网络服务有着各种各样的框架和模型,而阅读成熟的实现也是一个好的高效的学习应用途径。
Docker(moby)中对服务的一个实现我认为是比较好的编程范例。
- 定义一个通用的Http接口。
// https://github.com/moby/moby/blob/master/api/server/httputils/httputils.go
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
- 定义路由接口
// https://github.com/moby/moby/blob/master/api/server/router/router.go
// Router defines an interface to specify a group of routes to add to the docker server.
type Router interface {
// Routes returns the list of routes to add to the docker server.
Routes() []Route
}
// Route defines an individual API route in the docker server.
type Route interface {
// Handler returns the raw function to create the http handler.
Handler() httputils.APIFunc
// Method returns the http method that the route responds to.
Method() string
// Path returns the subpath where the route responds to.
Path() string
}
当然也有一个本地的实现,参见同目录下的 local.go
文件。
- 定义各种逻辑处理
func (h *xxhandler) handler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
......
}
这些逻辑实现在 github.com/docker/docker/api/server/router
目录下,每个子目录都是一组,而且每组都实现了Router
接口。
- 装配
首先需要一个转换函数:
// https://github.com/moby/moby/blob/master/api/server/server.go
func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Define the context that we'll pass around to share info
// like the docker-request-id.
//
// The 'context' will be used for global data that should
// apply to all requests. Data that is specific to the
// immediate function being called should still be passed
// as 'args' on the function call.
ctx := context.WithValue(context.Background(), dockerversion.UAStringKey, r.Header.Get("User-Agent"))
handlerFunc := s.handlerWithGlobalMiddlewares(handler)
vars := mux.Vars(r)
if vars == nil {
vars = make(map[string]string)
}
if err := handlerFunc(ctx, w, r, vars); err != nil {
statusCode := httputils.GetHTTPErrorStatusCode(err)
if statusCode >= 500 {
logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
}
httputils.MakeErrorHandler(err)(w, r)
}
}
}
将httputils.APIFunc
转换为标准http.HandlerFunc
,一个妙招。
下来在createMux
这个函数对所有的Router
进行遍历,组装为标准的mux.Router
,搞定。
点评
这个实现具有一个很好的范例,尤其在httputils.APIFunc
上做的非常棒,不仅兼顾了context
,还有一个返回的error
,可以在处理不同的逻辑时灵活运用。