k8s使用的web框架:go-restful 源码分析
| 阅读 | 共 5950 字,阅读约
Overview
Go-Restful
概述
go-restful是一个用go语言开发的快速构建restful风格的web框架。k8s最核心的组件kube-apiserver使用到了该框架,该框架的代码比较精简,这里做个简单的功能介绍,然后分析相关源码。
go-restful基于golang官方的net/http实现,在深入学习之前,建议先看一下本人之前整理的关于官方http源码分析的文章
go-restful定义了三个重要的数据结构:
- Router:表示一条路由,包含url、回调处理函数
- Webservice:表示一个服务
- Container:表示一个服务器
三者的关系如下:
- go-restful支持多个container,一个container相当于一个http server,不同的container监控不同的地址和端口
- 每个container可以包含多个webservice,相当于一组不同服务的分类
- 每个webservice包含多个Router(路由),Router根据http请求的URL路由到对应的处理函数(Handler Func)
快速上手
引入包:
1go get github.com/emicklei/go-restful/v3
hello-world 代码示例如下,启动后访问loclahost:8080/hello
可以看到页面响应 world
1package main
2
3import (
4 "github.com/emicklei/go-restful/v3"
5 "io"
6 "log"
7 "net/http"
8)
9
10func main() {
11 // 创建WebService
12 ws := new(restful.WebService)
13 // 为WebService设置路由和回调函数
14 ws.Route(ws.GET("/hello").To(hello))
15 // 将WebService添加到默认生成的Container中
16 // 默认生成的container的代码在web_service_container.go的init方法中
17 restful.Add(ws)
18 // 启动服务
19 log.Fatal(http.ListenAndServe(":8080", nil))
20}
21
22// 路由对应的回调函数
23func hello(req *restful.Request, resp *restful.Response) {
24 io.WriteString(resp, "world")
25}
源码分析
看到前面的快速上手代码,你有没有这样的疑惑:创建完成WebServie之后,添加到默认的Container,也没有把container传给谁,只是启动了服务监听就能自动识别到container呢?
想要揭开答案,让我们一起分析下源码吧。在这之前,还是建议先看下本人之前整理的关于官方http源码分析的文章,因为go-restful会基于官方提供的http包去实现功能
下图是整理的源码核心逻辑图。
核心数据结构
Route
前面提到过Route是go-restful的三个概念之一是路由,内部的数据结构是Route,先看一下源码。
源码位置:github.com/emicklei/go-restful/router.go
1type Route struct {
2 Method string
3 Produces []string
4 Consumes []string
5 // 请求的路径:root path + described path
6 Path string
7 // handler处理函数
8 Function RouteFunction
9 // 拦截器
10 Filters []FilterFunction
11 If []RouteSelectionConditionFunction
12
13 // cached values for dispatching
14 relativePath string
15 pathParts []string
16 pathExpr *pathExpression // cached compilation of relativePath as RegExp
17
18 // documentation
19 Doc string
20 Notes string
21 Operation string
22 ParameterDocs []*Parameter
23 ResponseErrors map[int]ResponseError
24 DefaultResponse *ResponseError
25 ReadSample, WriteSample interface{} // structs that model an example request or response payload
26
27 // Extra information used to store custom information about the route.
28 Metadata map[string]interface{}
29
30 // marks a route as deprecated
31 Deprecated bool
32
33 //Overrides the container.contentEncodingEnabled
34 contentEncodingEnabled *bool
35}
RouteBuilder
RouteBuilder用于构造Route信息,根据名字就知道使用了建造者模式
源码位置:github.com/emicklei/go-restful/router.go
1// 大部分属性和Route一样
2type RouteBuilder struct {
3 rootPath string
4 currentPath string
5 produces []string
6 consumes []string
7 httpMethod string // required
8 function RouteFunction // required
9 filters []FilterFunction
10 conditions []RouteSelectionConditionFunction
11
12 typeNameHandleFunc TypeNameHandleFunction // required
13 ...
14}
Webservice
WebService拥有一组Route,这组Router有公共的rootPath,在逻辑上将具有相同前置的路由请求放到一起
源码位置:github.com/emicklei/go-restful/web_service.go
1type WebService struct {
2 // Webservice中的Route共享一个rootPath
3 rootPath string
4 pathExpr *pathExpression // cached compilation of rootPath as RegExp
5 routes []Route
6 produces []string
7 consumes []string
8 pathParameters []*Parameter
9 filters []FilterFunction
10 documentation string
11 apiVersion string
12
13 typeNameHandleFunc TypeNameHandleFunction
14
15 dynamicRoutes bool
16
17 // 保护路由,防止多线程写操作出现并发问题
18 routesLock sync.RWMutex
19}
Container
一个Container包含多个Service,不同的Container监听不同的ip地址或端口,他们之间提供的服务的独立的。
源码位置:github.com/emicklei/go-restful/container.go
1type Container struct {
2 webServicesLock sync.RWMutex
3 // Container内部有多个webservice
4 webServices []*WebService
5 ServeMux *http.ServeMux
6 isRegisteredOnRoot bool
7 containerFilters []FilterFunction
8 doNotRecover bool // default is true
9 recoverHandleFunc RecoverHandleFunction
10 serviceErrorHandleFunc ServiceErrorHandleFunction
11 router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
12 contentEncodingEnabled bool // default is false
13}
核心代码流程梳理
从前面demo中的代码开始入手,分析整个调用流程。
整体流程包括:
- 创建WebService对象
- 为WebService对象添加路由地址和处理函数
- 将WebService添加到Container中(这里没有声明Containerr,用的默认Container)
- 启动服务监听端口,等待服务请求
1func main() {
2 ws := new(restful.WebService)
3 ws.Route(ws.GET("/hello").To(hello))
4 restful.Add(ws)
5 log.Fatal(http.ListenAndServe(":8080", nil))
6}
WebService添加路由
主要分析ws.Route(ws.GET("/hello").To(hello))
构造RouteBuilder对象
1// Get方法内部new了一个RouteBuilder,用于构造Route对象
2func (w *WebService) GET(subPath string) *RouteBuilder {
3 // 典型的建造者模式用法
4 return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath)
5}
6
7// 建造者模式:给属性赋值
8// 其他的方法类似,就不再展开
9func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
10 b.typeNameHandleFunc = handler
11 return b
12}
13
14// Get方法后,属性并没有完全构造完,handler处理函数是用单独的To方法赋值的
15func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
16 b.function = function
17 return b
18}
根据RouteBuilder生成Route对象
1func (w *WebService) Route(builder *RouteBuilder) *WebService {
2 w.routesLock.Lock()
3 defer w.routesLock.Unlock()
4 // 填充默认值
5 builder.copyDefaults(w.produces, w.consumes)
6 // 调用RouteBuilder的Build方法,构造Route
7 // 并将Route添加到routes列表中
8 w.routes = append(w.routes, builder.Build())
9 return w
10}
11
12// Build方法返回Route对象
13func (b *RouteBuilder) Build() Route {
14 ...
15 route := Route{
16 ...
17 }
18 route.postBuild()
19 return route
20}
WebService添加到Container
主要分析restful.Add(ws)
。这里要特别注意的是:
- 将http的DefaultServeMux传给了DefaultServeMux的ServeMux
- 调用Golang官方http包中的ServeMux.HandleFunc函数处理请求
- 处理函数统一为c.dispatch,dispatch再在内部做路由分发
源码位置:github.com/emicklei/go-restful/web_service_container.go
1// 定义全局变量,作为默认的Container
2var DefaultContainer *Container
3
4// init函数在别的包import时,自动触发。也就是只要引用了go-restful框架,就会默认有一个Container
5func init() {
6 DefaultContainer = NewContainer()
7 // 这里将Golang中标准http包下的默认路由对象DefaultServeMux赋值给Container的ServeMux
8 // 这里要特别注意,正是因为这个地方的逻辑,就能回答前面我们提出的问题。go-restful和http库,通过这个赋值建立了关联关系。
9 DefaultContainer.ServeMux = http.DefaultServeMux
10}
11
12// 生成默认的container
13func NewContainer() *Container {
14 return &Container{
15 webServices: []*WebService{},
16 ServeMux: http.NewServeMux(),
17 isRegisteredOnRoot: false,
18 containerFilters: []FilterFunction{},
19 doNotRecover: true,
20 recoverHandleFunc: logStackOnRecover,
21 serviceErrorHandleFunc: writeServiceError,
22 // 默认的路由选择器用的是CurlyRouter
23 router: CurlyRouter{},
24 contentEncodingEnabled: false}
25}
26
27// 将WebService添加到默认Container中
28func Add(service *WebService) {
29 DefaultContainer.Add(service)
30}
31
32// Add
33func (c *Container) Add(service *WebService) *Container {
34 ...
35 // if rootPath was not set then lazy initialize it
36 if len(service.rootPath) == 0 {
37 service.Path("/")
38 }
39
40 // 判断有没有重复的RootPath,不同的WebService,rootPath不能重复
41 for _, each := range c.webServices {
42 if each.RootPath() == service.RootPath() {
43 log.Printf("WebService with duplicate root path detected:['%v']", each)
44 os.Exit(1)
45 }
46 }
47
48 if !c.isRegisteredOnRoot {
49 // 核心逻辑:为servcie添加handler处理函数
50 // 这里将c.ServeMux作为参数传入,这个值是前面提到的http.DefaultServeMux
51 c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
52 }
53 // 将webServices添加到container的webservice列表中
54 c.webServices = append(c.webServices, service)
55 return c
56}
57
58// addHandler
59func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
60 pattern := fixedPrefixPath(service.RootPath())
61 ...
62 // 这里的关键函数:serveMux.HandleFunc,是Golang标准包中实现路由的函数
63 // go-restful中将路由处理函数统一交给c.dispatch函数,可以看出整个go-restful框架中,最核心的就是这个函数了
64 if !alreadyMapped {
65 serveMux.HandleFunc(pattern, c.dispatch)
66 if !strings.HasSuffix(pattern, "/") {
67 serveMux.HandleFunc(pattern+"/", c.dispatch)
68 }
69 }
70 return false
71}
路由分发函数dispatch
如何由container -> webservice -> handler 实现层级分发?
go-restful框架通过serveMux.HandleFunc(pattern, c.dispatch)
函数,一边连接了Golang提供的官方http扩展机制,另一边通过一个dispatch实现了路由的分发,这样就不用单独写很多的handler了。
这个函数的核心是c.router.SelectRoute
,根据请求找到合适的webservice和route
源码位置:github.com/emicklei/go-restful/container.go
1func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
2 ...
3 // 根据请求,找到最合适的webService和route
4 // 这个方法后面单独介绍
5 func() {
6 ...
7 webService, route, err = c.router.SelectRoute(
8 c.webServices,
9 httpRequest)
10 }()
11 ...
12 if err != nil {
13 // 构造过滤器
14 chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
15 switch err.(type) {
16 case ServiceError:
17 ser := err.(ServiceError)
18 c.serviceErrorHandleFunc(ser, req, resp)
19 }
20 // TODO
21 }}
22 // 运行Container的过滤器
23 chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
24 return
25 }
26
27 // 尝试将router对象转为PathProcessor对象
28 // 我们使用的是默认的Container,前面介绍过router默认用的CurlyRouter,
29 // SelectRoute的其中一个实现类RouterJSR311,也实现了PathProcessor。所以如果用了RouterJSR311,这里接口转换才能获取到值
30 // 而默认的CurlyRouter并没有实现PathProcessor接口,因此这里转换后是空值,会走到下一个if语句
31 pathProcessor, routerProcessesPath := c.router.(PathProcessor)
32 if !routerProcessesPath {
33 // 使用默认的路处理器
34 pathProcessor = defaultPathProcessor{}
35 }
36 // 从request的url请求中抽取参数
37 pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
38 wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
39 // 如果有filter的话,处理将所有的filter添加到filter链中
40 if size := len(c.containerFilters) + len(webService.filters) + len(route.Filters); size > 0 {
41 // compose filter chain
42 allFilters := make([]FilterFunction, 0, size)
43 allFilters = append(allFilters, c.containerFilters...)
44 allFilters = append(allFilters, webService.filters...)
45 allFilters = append(allFilters, route.Filters...)
46 chain := FilterChain{Filters: allFilters, Target: route.Function}
47 chain.ProcessFilter(wrappedRequest, wrappedResponse)
48 } else {
49 // no filters, handle request by route
50 // 没有filter,通过route直接处理请求
51 route.Function(wrappedRequest, wrappedResponse)
52 }
53}
路由选择
前面的dispatch中提到的c.router.SelectRoute
的作用是选择合适的webservice和route,这里专门介绍一下。
container中的router属性是一个RouteSelector
接口
1type RouteSelector interface {
2 // SelectRoute根据输入的http请求和webservice列表,找到一个路由并返回
3 SelectRoute(
4 webServices []*WebService,
5 httpRequest *http.Request) (selectedService *WebService, selected *Route, err error)
6}
go-restful框架中共有两个实现类:
- CurlyRouter
- RouterJSR311
前面分析代码知道CurlyRouter是默认实现,所以这里我们主要分析CurlyRouter的SelectRoute函数
1// 选择路由功能
2func (c CurlyRouter) SelectRoute(
3 webServices []*WebService,
4 httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
5 // 解析url,根据'/'拆分为token列表
6 requestTokens := tokenizePath(httpRequest.URL.Path)
7 // 根据tokens列表和webservice的路由表做匹配,返回一个最合适的webservice
8 detectedService := c.detectWebService(requestTokens, webServices)
9 ...
10 // 返回webservice中匹配的routes集合
11 candidateRoutes := c.selectRoutes(detectedService, requestTokens)
12 ...
13 // 从前面的list中找到最合适的route
14 selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
15 if selectedRoute == nil {
16 return detectedService, nil, err
17 }
18 return detectedService, selectedRoute, nil
19}
20
21// 选择webservice
22func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
23 var best *WebService
24 score := -1
25 for _, each := range webServices {
26 // 计算webservice的得分
27 matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
28 // 返回得分最高的webservice
29 if matches && (eachScore > score) {
30 best = each
31 score = eachScore
32 }
33 }
34 // 将得分最高的webservice返回
35 return best
36}
37
38// 计算webservice得分
39func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
40 if len(tokens) > len(requestTokens) {
41 return false, 0
42 }
43 score := 0
44 for i := 0; i < len(tokens); i++ {
45 each := requestTokens[i]
46 other := tokens[i]
47 if len(each) == 0 && len(other) == 0 {
48 score++
49 continue
50 }
51 if len(other) > 0 && strings.HasPrefix(other, "{") {
52 // no empty match
53 if len(each) == 0 {
54 return false, score
55 }
56 score += 1
57 } else {
58 // not a parameter
59 if each != other {
60 return false, score
61 }
62 score += (len(tokens) - i) * 10 //fuzzy
63 }
64 }
65 return true, score
66}
67
68// 初选:匹配path,返回一批Route作为备选
69func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
70 // 选中的Route存放到sortableCurlyRoutes中
71 candidates := make(sortableCurlyRoutes, 0, 8)
72 // 遍历webservice下所有的route
73 for _, each := range ws.routes {
74 // paramCount:正则命中
75 // staticCount:完全匹配命中
76 matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens, each.hasCustomVerb)
77 // 如果匹配,加入到备选列表中
78 if matches {
79 candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
80 }
81 }
82 // 排序备选的route
83 sort.Sort(candidates)
84 return candidates
85}
86
87// 二次筛选:匹配属性等信息。返回最合适的Route
88func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
89 // tracing is done inside detectRoute
90 return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
91}
92
93// 匹配多个属性是否匹配:method、content-type、accept
94func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
95 candidates := make([]*Route, 0, 8)
96 for i, each := range routes {
97 ok := true
98 for _, fn := range each.If {
99 if !fn(httpRequest) {
100 ok = false
101 break
102 }
103 }
104 if ok {
105 candidates = append(candidates, &routes[i])
106 }
107 }
108 if len(candidates) == 0 {
109 if trace {
110 traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
111 }
112 return nil, NewError(http.StatusNotFound, "404: Not Found")
113 }
114
115 // 判断 http method 是否匹配
116 previous := candidates
117 candidates = candidates[:0]
118 for _, each := range previous {
119 if httpRequest.Method == each.Method {
120 candidates = append(candidates, each)
121 }
122 }
123 if len(candidates) == 0 {
124 if trace {
125 traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method)
126 }
127 allowed := []string{}
128 allowedLoop:
129 for _, candidate := range previous {
130 for _, method := range allowed {
131 if method == candidate.Method {
132 continue allowedLoop
133 }
134 }
135 allowed = append(allowed, candidate.Method)
136 }
137 header := http.Header{"Allow": []string{strings.Join(allowed, ", ")}}
138 return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, "405: Method Not Allowed", header)
139 }
140
141 // 判断 Content-Type 是否匹配
142 contentType := httpRequest.Header.Get(HEADER_ContentType)
143 previous = candidates
144 candidates = candidates[:0]
145 for _, each := range previous {
146 if each.matchesContentType(contentType) {
147 candidates = append(candidates, each)
148 }
149 }
150 if len(candidates) == 0 {
151 if trace {
152 traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType)
153 }
154 if httpRequest.ContentLength > 0 {
155 return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
156 }
157 }
158
159 // 判断 accept 是否匹配
160 previous = candidates
161 candidates = candidates[:0]
162 accept := httpRequest.Header.Get(HEADER_Accept)
163 if len(accept) == 0 {
164 accept = "*/*"
165 }
166 for _, each := range previous {
167 if each.matchesAccept(accept) {
168 candidates = append(candidates, each)
169 }
170 }
171 if len(candidates) == 0 {
172 if trace {
173 traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept)
174 }
175 available := []string{}
176 for _, candidate := range previous {
177 available = append(available, candidate.Produces...)
178 }
179 return nil, NewError(
180 http.StatusNotAcceptable,
181 fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")),
182 )
183 }
184 // 如果有多个匹配,返回第一个
185 return candidates[0], nil
186}
启动服务
前面介绍过,go-restful直接操作的golang标准库http的路由对象http.DefaultServeMux,所以服务启动这一步只需要调用http标准的服务启动即可,不需要做额外的处理。即http.ListenAndServe(":8080", nil)
总结
go-restful并不是一个热度很高的golang web框架,但是k8s中用到了它,本篇文章通过源码分析对go-restful的内部实现做了简单的分析。从分析的过程来看,确实是一个精悍小巧的框架。内部更深入的功能我们没有继续研究了,只要达到能看懂k8s kube-apiserver组件源码的目的就行。
内部核心实现只要是:
- 通过http包默认的路由对象DefaultServeMux添加处理函数dispatch
- 路由分发功能全部转给dispatch
- dispatch内部调用RouteSelector的默认实现类CurlyRouter的SelectRoute方法选择合适的Route
- 调用Route中注册的handler方法,处理请求