golang servmux

主要是介绍golang中servermux的相关内容,还有一些在golang中的http的基本知识,最后还大致介绍了go-restful的库。整体上想的话,步骤应该是比较简单的,首先写好对应的路由要执行的函数,之后就是把路由信息注册到某个地方,信息过来再分发给对应的路由。

其实本质上了解就是很简单的,当代码结构变得复杂的时候,转发的工作就变得复杂了,这一部分的工作就要单独提取出来做了,这个功能就是由servermux来完成的。

这大概就是项目中的分层的思想,就像社会分工一样,只有术业专攻,各尽其职,整个系统才能正常运行,感觉算是某种系统观吧,当系统足够复杂的时候,就需要将单独的某一层拆分出来。

主要是围绕golang的net/http包来进行分析。

简易的http服务

首先让我们从一个最基本的http服务来入手,之后顺藤摸瓜,看看各个关键结构的定义。

package main
import (
"io"
"log"
"net/http"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world version 1.")
}
func main() {
err := http.ListenAndServe(":8080", &a{})
if err != nil {
log.Println(err.Error())
}
}

相信这个例子基本上一看就懂了,这个服务监听8080端口,之后在main函数中,调用http.ListenAndServer函数,监听8080端口。第二个参数是一个自定义的结构 a 的实例,我们进入ListenAndServer中来看一下其定义:

func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

可以发现,第一个参数addr是一个string,第二个参数handler是一个Handler结构,之后把这两个参数分别传入到Server结构中,再调用这个Server结构的ListenAndServe()方法。

接着我们看一下Handler接口的的定义:

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

明白的看到,只要是实现了ServeHTTP方法的实例就实现了Handler接口,可以作为ListenAndServer的第二个参数传入。这个ServeHTTP的两个参数就是web服务中典型的request以及response的那种形式。其中request是一个实际的结构,而response是一个接口。

之后我们来看一下Server结构,这个结构定义了运行一个Http服务的相关的参数:

type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
ReadTimeout time.Duration // maximum duration before timing out read of the request
WriteTimeout time.Duration // maximum duration before timing out write of the response
MaxHeaderBytes int // maximum size of request headers, DefaultMaxHeaderBytes if 0
TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS
// TLSNextProto optionally specifies a function to take over
// ownership of the provided TLS connection when an NPN
// protocol upgrade has occurred. The map key is the protocol
// name negotiated. The Handler argument should be used to
// handle HTTP requests and will initialize the Request's TLS
// and RemoteAddr if not already set. The connection is
// automatically closed when the function returns.
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ConnState specifies an optional callback function that is
// called when a client connection changes state. See the
// ConnState type and associated constants for details.
ConnState func(net.Conn, ConnState)
// ErrorLog specifies an optional logger for errors accepting
// connections and unexpected behavior from handlers.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
}

目前在这个例子中,我们就先深入到这个程度,只要知道在最后的server.ListenAndServe()中,server的Handler参数的serverHTTP方法会被调用就可以了,当然还做了许多额外的操作,具体细节先不做分析,。就是从server.ListenAndServer到handler的ServerHTTP方法被调用的这个过程暂不分析。只需要知道ServerHTTP方法被调用了就好。

使用serverMux

之前的那个例子就像是学会了1+1=2一样,但是实际情况并没有那么简单,要是100个1相加该怎么办?还用之前的套路未免就太过原始了。越深入思考就越离本质更靠近一些。之前的思路,用一个函数来处理所有的路由情况。不论什么样的路由请求过来之后,都要去执行实例a的ServeHTTP方法,这样,实际情况下,要处理多路由的时候,ServeHTTP就要写成类似下面这样:

func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.String()
switch path {
case "/":
io.WriteString(w, "<h1>root</h1><a href=\"abc\">abc</a>")
case "/abc":
io.WriteString(w, "<h1>abc</h1><a href=\"/\">root</a>")
case "/def":
io.WriteString(w, "<h1>abc</h1><a href=\"/\">root</a>")
}
}

简单的情况下。看起来这样是可以的,如果路由很复杂呢,甚至还要分层呢?很容易想到空间换时间的思路,肯定需要一个结构把路由和对应的应该执行的函数的映射关系存起来,然后每有一个路由过来,就由一个服务去在映射关系中进行匹配,找到对应的函数去执行,再返回结果。这就用到了serverMux结构。

serverMux的主要功能就是对发送过来的http请求进行分发,之后调用对应的handler来处理请求。

可以看一下serverMux以及其中的muxEntry的结构:

type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}

可以看到,ServeMux中的m用于存储映射关系,key值为一个string,value值为一个muxEntry结构,其中包含Handler对象。

之后我们为之前的server加上新的ServeMux的功能:

package main
import (
"io"
"log"
"net/http"
)
type a struct{}
type b struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//log.Println("the request url", r.RequestURI)
//log.Println("r.Method", r.Method)
io.WriteString(w, "hello world by mux the route is /a.")
}
func (*b) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world by mux the route is /b.")
}
func main() {
mux := http.NewServeMux()
//往mux中注册新的路由
mux.Handle("/a", &a{})
mux.Handle("/b", &b{})
//开启服务 具体的路由操作由 新生成的mux来负责
err := http.ListenAndServe(":8080", mux)
if err != nil {
log.Println(err.Error())
}
}

按照之前的分析,首先应该通过mux.Handler方法把路由注册进来,还可以看到,ListenAndServer之前填入Handler实例的地方现在变成了mux实例,说明mux应该也实现了Handler接口的serverHTTP方法。首先来看一下mux.Handle方法:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
if handler == nil {
panic("http: nil handler")
}
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
// If pattern contains a host name, strip it and use remaining
// path for redirect.
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]
}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern}
}
}

可以看到,Handle方法首先会把传入的pattern参数以及Handler接口存入到mux的map中,并且进行一些检测,比如该路由是否已经注册进来(explicit参数),handle是否为空等等。之后会判断pattern[0]是否为’/‘,如果不是的话,说明传过来的pattern可能有包含了主机的名称(这个还是不太明确,不知道什么时候会出现这种请情况)。此外还进行了一些额外的操作,比如把名称为 /path/ 的路由重定向到 /path 上面 。

之后再来看一下http.ListenAndServer的时候,mux的ServerHTTP方法是如何进行操作的:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
url := *r.URL
url.Path = p
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
}
return mux.handler(r.Host, r.URL.Path)
}
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}

可以看到比较关键的两句:h,_:=mux.Handler(r)会根据request来返回一个合适的handler,之后调用这个返回回来的handler的ServerHTTP方法,进行具体的执行,这一段就相当于是一个分发request请求的作用了。再看一下后面的Handler的方法,如果r.Method不是CONNECT的时候,就调用mux.handler函数,这个函数会去具体执行匹配操作,最后返回对应的handler实例。之后会调用这个实例的ServeHTTP方法,执行真正的对应的最底层的操作。举一个极端的例子,比如有好多组件都声称自己执行的是调用数据库的操作,实际真正的操作可能是最后一个组件执行,前面几个组件只是负责转发,由于项目很大,并且为了方便扩展,就需要分层,分层之后必然虽然顶层结构清晰了,但一层一层包装下来,请求转发的工作也需要专门有组件来负责,就是将请求转到合适的下一个组件去执行。

serverMux中的HandleFunc方法

在上面的几个基本例子中,虽然我们自定义了结构体,但是只为了实现Handler接口,并没有添加什么个性化的操作,当然实际情况中并不总是像这样简单,但是从框架的角度讲,我们最终希望实现的是,能直接把一个方法注册给一个接口。实时上这就是HandleFunc方法所做的,看下面的例子:

package main
import (
"io"
"net/http"
)
func main() {
http.HandleFunc("/", sayhello)
http.ListenAndServe(":8080", nil)
}
func sayhello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world by handlefunc")
}

我们直接通过HandleFunc方法把自己写的sayhello方法注册到了”/“的路由下,内部实现起来,无非就是程序自己帮我们生成了一个ServMux实例:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

其中的DefaultServeMux是系统帮我们默认生成的:

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = NewServeMux()

之后按照上面的分析,还会执行mux.Handler方法,只不过此时的Mux是系统自动帮我们new出来的,本质上和之前的是一样的。

项目中的例子 关于go-rest

其他还用到比较流行的golang 中的 router 或者 api dispatcher的库比如gorilla/mux也都比较容易上手,这里就不再赘述。

在实际项目中,比如k8s的apiserver中,并没有像上面介绍的那么简单,为了实现路由注册时候的分层,还是有一些额外的工作要坐的,因此常常会用到各种框架,k8s的apiserver中用到的是go-restful的工具。

go-restful本质上来说就是对上面提到的serverMux的进一步封装,再起基础上又做了许多额外的工作。相当于对路由进行了分类,每次往HandleFunc中注册过去的路由都是一个新的类别,里面可能包含许多具体的CURD子路由。

比如下面这个例子(https://github.com/emicklei/go-restful/blob/master/examples/restful-user-resource.go):

package main
import (
"log"
"net/http"
"strconv"
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful/swagger"
)
type User struct {
Id, Name string
}
type UserResource struct {
// normally one would use DAO (data access object)
users map[string]User
}
//将路由以webservice的方式注册到container中
func (u UserResource) Register(container *restful.Container) {
ws := new(restful.WebService)
//这个是根路径
ws.
Path("/users").
Doc("Manage Users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
//后面是根路径之后的每个具体的方法
ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a user").
Operation("findUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Writes(User{})) // on the response
ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
// docs
Doc("update a user").
Operation("updateUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
ReturnsError(409, "duplicate user-id", nil).
Reads(User{})) // from the request
ws.Route(ws.POST("").To(u.createUser).
// docs
Doc("create a user").
Operation("createUser").
Reads(User{})) // from the request
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
// docs
Doc("delete a user").
Operation("removeUser").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
container.Add(ws)
}
// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
...
}
// POST http://localhost:8080/users
// <User><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
...
}
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
...
}
// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
...
}
func main() {
//创建一个新的container 将user的路由放到container当中
wsContainer := restful.NewContainer()
u := UserResource{map[string]User{}}
u.Register(wsContainer)
//配置swagger
config := swagger.Config{
WebServices: wsContainer.RegisteredWebServices(), // you control what services are visible
WebServicesUrl: "http://localhost:8080",
ApiPath: "/apidocs.json",
// Optionally, specifiy where the UI is located
SwaggerPath: "/apidocs/",
SwaggerFilePath: "/Users/emicklei/xProjects/swagger-ui/dist"}
swagger.RegisterSwaggerService(config, wsContainer)
//开启服务 监听8080端口
log.Printf("start listening on localhost:8080")
server := &http.Server{Addr: ":8080", Handler: wsContainer}
log.Fatal(server.ListenAndServe())
}

结合之前的分析,可以看到server := &http.Server{Addr: ":8080", Handler: wsContainer}传入的Handler实例是一个wsContainer,说明wsContainer也是Handler接口的一个实现,我们来看一下它的具体结构及其ServerHTTP方法:

type Container struct {
webServices []*WebService
ServeMux *http.ServeMux
isRegisteredOnRoot bool
containerFilters []FilterFunction
doNotRecover bool // default is false
recoverHandleFunc RecoverHandleFunction
serviceErrorHandleFunc ServiceErrorHandleFunction
router RouteSelector // default is a RouterJSR311, CurlyRouter is the faster alternative
contentEncodingEnabled bool // default is false
}
func (c Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) {
c.ServeMux.ServeHTTP(httpwriter, httpRequest)
}

可以看到,container结构中包含了我们之前提到的ServeMux路由分发器,其ServerHTTP方法就是直接调用的ServeMux实例的ServerHTTP方法。从这里也可以明显看出来,container实例是对golang中的ServeMux实例的进一步封装。

结合最初的user的例子大致看一下go-restful的使用,首先是生成一个container实例,此时其中的路由是空的,之后在具体注册路由的时候,由于要进行分层的处理,每一类路由会被封装成为一个webservice实例,其中的route实例是可以替换的,默认是按照jsr311标准实现的:

type WebService struct {
rootPath string
pathExpr *pathExpression // cached compilation of rootPath as RegExp
routes []Route
produces []string
consumes []string
pathParameters []*Parameter
filters []FilterFunction
documentation string
apiVersion string
}

一个webservice实例包含一系列的 routes , 每个route实例都有对应的执行方法,参数以及url,以及执行额外操作时候所需要的一些参数比如filter的相关函数对象。

可以看到,go-restful涉及到的对象主要分为以下几个层次:

  • Container
  • webService
  • Route
    其中container是最上层的对象,相当于是对serveMux实例的一个封装,其中有多个webService,每个webService相当于是包含了一类api请求,里面包含了多个Route。

每次把router注册(对应的路由及函数)到webservice中后,还要通过container.Add(ws)将这一类的webservice加入到对应的container当中。

在add方法中,会对container中的serverMux进行处理(就是按照上面介绍的 根据HandFunc往进去注册一些路由和方法的映射关系)调用上面所介绍的ServeMux.HandleFunc方法,将对应的pattern注册给serverMux,container对路由信息进行一些处理之后,serverMux就只进行第一层的请求分发:c.ServeMux.HandleFunc(pattern+"/", c.dispatch),第二层的请求分发由c.dispatch函数来完成。这个函数主要是将过来的子类别的请求再次分发给对应的route来处理,默认情况下,会按照jsr311的标准,选择出对应的webservice中的对应的路由,并且执行路由的对应方法。此外,还会处理filter函数并且进行一些额外操作,具体可参考源码。

go-restful还支持对每一层对象添加对应的fliter方法,用于对方法进行一层封装,用于进行 pre function以及after function操作,使用起来也很简单,比如像下面这个例子(https://github.com/emicklei/go-restful/blob/master/examples/restful-pre-post-filters.go):

package main
import (
"github.com/emicklei/go-restful"
"io"
"log"
"net/http"
)
// This example shows how the different types of filters are called in the request-response flow.
// The call chain is logged on the console when sending an http request.
//
// GET http://localhost:8080/1
// GET http://localhost:8080/2
var indentLevel int
func container_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
log.Printf("url path:%v\n", req.Request.URL)
trace("container_filter_A: before", 1)
chain.ProcessFilter(req, resp)
trace("container_filter_A: after", -1)
}
func container_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("container_filter_B: before", 1)
chain.ProcessFilter(req, resp)
trace("container_filter_B: after", -1)
}
func service_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("service_filter_A: before", 1)
chain.ProcessFilter(req, resp)
trace("service_filter_A: after", -1)
}
func service_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("service_filter_B: before", 1)
chain.ProcessFilter(req, resp)
trace("service_filter_B: after", -1)
}
func route_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("route_filter_A: before", 1)
chain.ProcessFilter(req, resp)
trace("route_filter_A: after", -1)
}
func route_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
trace("route_filter_B: before", 1)
chain.ProcessFilter(req, resp)
trace("route_filter_B: after", -1)
}
//用于定义输出结果中的层级关系 使得输出结果好看一些
func trace(what string, delta int) {
indented := what
if delta < 0 {
indentLevel += delta
}
for t := 0; t < indentLevel; t++ {
indented = "." + indented
}
log.Printf("%s", indented)
if delta > 0 {
indentLevel += delta
}
}
func main() {
//这里采用了默认自动生成的container实例
//当然也可以使用 新生成的container 来调用其Filter方法
restful.Filter(container_filter_A)
restful.Filter(container_filter_B)
ws1 := new(restful.WebService)
ws1.Path("/1")
ws1.Filter(service_filter_A)
ws1.Filter(service_filter_B)
ws1.Route(ws1.GET("").To(doit1).Filter(route_filter_A).Filter(route_filter_B))
ws2 := new(restful.WebService)
ws2.Path("/2")
ws2.Filter(service_filter_A)
ws2.Filter(service_filter_B)
ws2.Route(ws2.GET("").To(doit2).Filter(route_filter_A).Filter(route_filter_B))
restful.Add(ws1)
restful.Add(ws2)
log.Print("go-restful example listing on http://localhost:8080/1 and http://localhost:8080/2")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func doit1(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "nothing to see in 1")
}
func doit2(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "nothing to see in 2")
}
/*output
2015/08/18 18:03:53 go-restful example listing on http://localhost:8080/1 and http://localhost:8080/2
2015/08/18 18:04:10 url path:/1
2015/08/18 18:04:10 container_filter_A: before
2015/08/18 18:04:10 .container_filter_B: before
2015/08/18 18:04:10 ..service_filter_A: before
2015/08/18 18:04:10 ...service_filter_B: before
2015/08/18 18:04:10 ....route_filter_A: before
2015/08/18 18:04:10 .....route_filter_B: before
2015/08/18 18:04:10 .....route_filter_B: after
2015/08/18 18:04:10 ....route_filter_A: after
2015/08/18 18:04:10 ...service_filter_B: after
2015/08/18 18:04:10 ..service_filter_A: after
2015/08/18 18:04:10 .container_filter_B: after
2015/08/18 18:04:10 container_filter_A: after
*/

参考资料

http://www.cnblogs.com/yjf512/archive/2012/08/22/2650873.html

http://my.oschina.net/u/943306/blog/151293#OSC_h2_6

https://github.com/emicklei/go-restful

推荐文章