Golang Web 的一个编程范式


Go语言编写HTTP Web网络服务有着各种各样的框架和模型,而阅读成熟的实现也是一个好的高效的学习应用途径。

Docker(moby)中对服务的一个实现我认为是比较好的编程范例。

1、 定义一个通用的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

2、 定义路由接口

//  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文件。

3、 定义各种逻辑处理

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接口。

4、 装配

首先需要一个转换函数:

// 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,可以在处理不同的逻辑时灵活运用。