go restful源码剖析-1

tcuze · · 1690 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

restful hello world


首次浏览下go-restful的工程结构,从工程组织上面来看,工程包括两个部分:source文件及example文件,其中source文件组成了工程的主体,包括restful主要功能接口的实现及单元测试文件(以test.go结尾命名的文件),另外example目录中主要包括了接口的使用案例。

第一次阅读go-restful源码,以example目录下的restful-hello-world.go作为入坑样例,来跟踪了解下restful如何组织封装webservice及route的维护。

restful-hello-world.go代码如下:

func main() { ws := new(restful.WebService) ws.Route(ws.GET("/hello").To(hello)) restful.Add(ws) log.Fatal(http.ListenAndServe(":8080", nil)) } func hello(req *restful.Request, resp *restful.Response) { io.WriteString(resp, "world") } 

restful初始化流程


1. ws := new(restful.WebService)

webservice定义在web_service.go中, 通过new分配了webservicef空间, 传递给ws指向新分配零值的指针

// WebService holds a collection of Route values that bind a Http Method + URL Path to a function. 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 typeNameHandleFunc TypeNameHandleFunction dynamicRoutes bool // protects 'routes' if dynamic routes are enabled routesLock sync.RWMutex } 
1.png

2.ws.Route(ws.GET("/hello").To(hello))

2.1 ws.GET("/hello")

第一步执行的函数,GET方法绑定在WebServices结构体上,调用改函数参数为subPath = /hello, 返回RouteBuilder指针

func (w *WebService) GET(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath) } 
2.1.1 new(RouteBuilder)

在Get函数中,首先执行new(RouteBuilder), 返回routebuilder指针,在webservice的初始化后期可以发现,RouteBuilder的作用是通过用户定义的参数,初始化route结构,route结构最终由websocket中的routes结构进行存储维护

// RouteBuilder is a helper to construct Routes. type RouteBuilder struct { rootPath string currentPath string produces []string consumes []string httpMethod string // required function RouteFunction // required filters []FilterFunction conditions []RouteSelectionConditionFunction typeNameHandleFunc TypeNameHandleFunction // required // documentation doc string notes string operation string readSample, writeSample interface{} parameters []*Parameter errorMap map[int]ResponseError metadata map[string]interface{} deprecated bool } 
2.1.2 typeNameHandler(w.typeNameHandleFunc)

typeNameHandler方法绑定在RouteBuilder上, 该函数为赋值函数,将webservice中定义的typeNameHandleFunc TypeNameHandleFunction // required传递给routebuilder, 该变量定义了一类环函数的通用化模板,在初始化前RouteBuilder结构体中已经初始化定义了typeNameHandleFunc TypeNameHandleFunction

// TypeNameHandleFunction declares functions that can handle translating the name of a sample object // into the restful documentation for the service. type TypeNameHandleFunction func(sample interface{}) string 
// typeNameHandler sets the function that will convert types to strings in the parameter // and model definitions. func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder { b.typeNameHandleFunc = handler return b } 
2.1.3 servicePath(w.rootPath)

w.rootPath 初始值为"",在该函数中, 将webservice中定义的rootpath初始值传递给RouteBuilder,在RouteBuilder中默认的rootPath初始值也为""

func (b *RouteBuilder) servicePath(path string) *RouteBuilder { b.rootPath = path return b } 
2.1.4 Method("GET")

设置http方法, 在该例子中,传递了GET方法

// Method specifies what HTTP method to match. Required. func (b *RouteBuilder) Method(method string) *RouteBuilder { b.httpMethod = method return b } 
2.1.5 Path(subPath)

设置RouteBuilder的currentPath设置为subPath

// Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/". func (b *RouteBuilder) Path(subPath string) *RouteBuilder { b.currentPath = subPath return b } 
2.2 To(hello)

改函数绑定用户自定义的的处理函数handler,当用户发起的http访问, 命中method=GET path = subPath后,执行相关逻辑的function handler

func hello(req *restful.Request, resp *restful.Response) { io.WriteString(resp, "world") } 

实际上在执行webservice GET()函数,直接生成了RouteBuilder并返回对象, 因此function的绑定,直接在RouteBuilder中进行

// RouteFunction declares the signature of a function that can be bound to a Route. type RouteFunction func(*Request, *Response) 
// To bind the route to a function. // If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required. func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder { b.function = function return b } 

从代码中可以看出自定义的函数类型包括两部分, 分别是*Request, *Response在restful中, 定义的request和response都是结构体, 该结构体中定义了http包数据各个模块的存储及获取格式

type Request struct { Request *http.Request pathParameters map[string]string attributes map[string]interface{} // for storing request-scoped values selectedRoutePath string // root path + route path that matched the request, e.g. /meetings/{id}/attendees } type Response struct { http.ResponseWriter requestAccept string // mime-type what the Http Request says it wants to receive routeProduces []string // mime-types what the Route says it can produce statusCode int // HTTP status code that has been written explicitly (if zero then net/http has written 200) contentLength int // number of bytes written for the response body prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses. err error // err property is kept when WriteError is called hijacker http.Hijacker // if underlying ResponseWriter supports it } 
2.3 ws.Route(RouteBuilder))]

该函数主要创建新的路由关系,并将产生的routebuilder添加到路由表中

// Route creates a new Route using the RouteBuilder and add to the ordered list of Routes. func (w *WebService) Route(builder *RouteBuilder) *WebService { w.routesLock.Lock() defer w.routesLock.Unlock() builder.copyDefaults(w.produces, w.consumes) w.routes = append(w.routes, builder.Build()) return w } 
2.3.1 w.routesLock.Lock()

在webservice初始化中,routesLock初始化为routesLock sync.RWMutex,该变量主要作用是在动态路由使用后,保护路由表,防止被多线程同时读写

2.3.2 defer w.routesLock.Unlock()

采用defer方式golang特性, 函数执行完成后,stack执行,释放掉持有的锁

2.3.3 builder.copyDefaults(w.produces, w.consumes)

将produces和consumes赋值给RouteBuilder

2.3.4 RouteBuilder.build()
// Build creates a new Route using the specification details collected by the RouteBuilder func (b *RouteBuilder) Build() Route { pathExpr, err := newPathExpression(b.currentPath) if err != nil { log.Printf("Invalid path:%s because:%v", b.currentPath, err) os.Exit(1) } if b.function == nil { log.Printf("No function specified for route:" + b.currentPath) os.Exit(1) } operationName := b.operation if len(operationName) == 0 && b.function != nil { // extract from definition operationName = nameOfFunction(b.function) } route := Route{ Method: b.httpMethod, Path: concatPath(b.rootPath, b.currentPath), Produces: b.produces, Consumes: b.consumes, Function: b.function, Filters: b.filters, If: b.conditions, relativePath: b.currentPath, pathExpr: pathExpr, Doc: b.doc, Notes: b.notes, Operation: operationName, ParameterDocs: b.parameters, ResponseErrors: b.errorMap, ReadSample: b.readSample, WriteSample: b.writeSample, Metadata: b.metadata, Deprecated: b.deprecated} route.postBuild() return route } 

build的过程主要是初始化参数的检查及route初始化过程, 在route的初始化中除了使用routebuiler之前初始化的部分参数,还针对path处理了pathexpr,newPathExpression(b.currentPath)

RouteBuilder.png

pathExpr.png

2.3.5 w.routes = append(w.routes, builder.Build())

将创建的route添加到routes slice中。

  • 可以看出来结构上 route是由routebuilder进行创建
  • 实际上产生作用的是webservice中的route列表
3.restful.Add(ws)
// Add registers a new WebService add it to the DefaultContainer. func Add(service *WebService) { DefaultContainer.Add(service) } 

在ws外面在加了一层container, 改container在init中进行了初始化

func init() { DefaultContainer = NewContainer() DefaultContainer.ServeMux = http.DefaultServeMux } 
3.1 DefaultContainer = NewContainer()

创建container对象,并进行初始化, 因此可以看出ws可以有很多个,统一由container进行维护,默认的route路径/就是在这里进行的赋值

// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter) func NewContainer() *Container { return &Container{ webServices: []*WebService{}, ServeMux: http.NewServeMux(), isRegisteredOnRoot: false, containerFilters: []FilterFunction{}, doNotRecover: true, recoverHandleFunc: logStackOnRecover, serviceErrorHandleFunc: writeServiceError, router: CurlyRouter{}, contentEncodingEnabled: false} } 
container.png
3.2 DefaultContainer.Add(service)

从代码中可以看出来webservice的区分是通过rootPath进行区分的, webservices要保证rootpath的唯一性

// Add a WebService to the Container. It will detect duplicate root paths and exit in that case. func (c *Container) Add(service *WebService) *Container { c.webServicesLock.Lock() defer c.webServicesLock.Unlock() // if rootPath was not set then lazy initialize it if len(service.rootPath) == 0 { service.Path("/") } // cannot have duplicate root paths for _, each := range c.webServices { if each.RootPath() == service.RootPath() { log.Printf("WebService with duplicate root path detected:['%v']", each) os.Exit(1) } } // If not registered on root then add specific mapping if !c.isRegisteredOnRoot { c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux) } c.webServices = append(c.webServices, service) return c } 

其中有一点仍然需要注意,在container的绑定中,只将根目录注册到了ServeMux中, 绑定的对应的函数为container.dispatch(),这样做的原因是在http请求中,通过golang中net包中的ServeMux进行路由转发,将所有命中根目录的uri流量分发到ws中。

// addHandler may set a new HandleFunc for the serveMux // this function must run inside the critical region protected by the webServicesLock. // returns true if the function was registered on root ("/") func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool { pattern := fixedPrefixPath(service.RootPath()) // check if root path registration is needed if "/" == pattern || "" == pattern { serveMux.HandleFunc("/", c.dispatch) return true } // detect if registration already exists alreadyMapped := false for _, each := range c.webServices { if each.RootPath() == service.RootPath() { alreadyMapped = true break } } if !alreadyMapped { serveMux.HandleFunc(pattern, c.dispatch) if !strings.HasSuffix(pattern, "/") { serveMux.HandleFunc(pattern+"/", c.dispatch) } } return false } 
4. log.Fatal(http.ListenAndServe(":8080", nil))

创建端口监听,将用户的http请求route到对应的函数中, ListenAndServe 在net package中, http routes相关由ServMux进行维护, 由于在上文hello worlde的样子中,container将根目录及c.dispatch初始化到ServeMux中。因此,当发送get请求后,会跳转到container.dispatch中进行二次http route。

// ListenAndServe always returns a non-nil error. func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } 

restful响应流程


1. 路由

上文中通过container、webservice、route的初始化流程,在helloworld的样例中,外部通过http get访问时,http package中的servMux进行了第一次路由找到了对应的container,后调用container.dispatch进行二次路由查找

//闭包 func() { c.webServicesLock.RLock() defer c.webServicesLock.RUnlock() webService, route, err = c.router.SelectRoute( c.webServices, httpRequest) }() 
2 c.router.SelectRoute
func (c CurlyRouter) SelectRoute( webServices []*WebService, httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) { requestTokens := tokenizePath(httpRequest.URL.Path) detectedService := c.detectWebService(requestTokens, webServices) if detectedService == nil { if trace { traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path) } return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found") } candidateRoutes := c.selectRoutes(detectedService, requestTokens) if len(candidateRoutes) == 0 { if trace { traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path) } return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found") } selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest) if selectedRoute == nil { return detectedService, nil, err } return detectedService, selectedRoute, nil } 

在路由转换的逻辑中, 主要包含了3次路由转换

2.1 路由查找webservice
//从url中split路径为token列表 requestTokens := tokenizePath(httpRequest.URL.Path) /* 将获取到的tokens与webServices中的pathExpr.tokens进行计算最大分值(计算分值的方式是token比对 直到出现不同对于{分数自增1跳转到下一步),并返回分值最高的匹配 */ c.detectWebService(requestTokens, webServices) 
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService { var best *WebService score := -1 for _, each := range webServices { //在该例子中each.pathExpr.tokens为空,如果为空,默认范围列表中第一个ws matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens) if matches && (eachScore > score) { best = each score = eachScore } } return best } 
2.2 路由查找Route
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes { candidates := sortableCurlyRoutes{} for _, each := range ws.routes { //遍历路由查找,将routes中的tokens进行遍历查找, 找到能够匹配到当前路径的route matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens) if matches { candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers? } } sort.Sort(sort.Reverse(candidates)) return candidates } 
func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string) (matches bool, paramCount int, staticCount int) { if len(routeTokens) < len(requestTokens) { // proceed in matching only if last routeToken is wildcard count := len(routeTokens) if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") { return false, 0, 0 } // proceed } for i, routeToken := range routeTokens { if i == len(requestTokens) { // reached end of request path return false, 0, 0 } requestToken := requestTokens[i] if strings.HasPrefix(routeToken, "{") { paramCount++ if colon := strings.Index(routeToken, ":"); colon != -1 { // match by regex matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken) if !matchesToken { return false, 0, 0 } if matchesRemainder { break } } } else { // no { prefix if requestToken != routeToken { return false, 0, 0 } staticCount++ } } return true, paramCount, staticCount } 

在routes中查找到的对应的route,统计放在sortableCurlyRoute中进行处理,sortableCurlyRoute是一个封装的按照一定规则进行排序的curlyRoute数组

type curlyRoute struct { route Route paramCount int //正则命中 staticCount int //完全匹配命中 } 
2.3. route属性相关匹配

在上面中通过path的比对拿到了一个route列表, 列表中记录了route的优先级,在列表中的route都保证了对path的命中, 在function的匹配过程中需要依次检查:

  • http method
     // http method methodOk := []Route{} for _, each := range ifOk { if httpRequest.Method == each.Method { methodOk = append(methodOk, each) } } if len(methodOk) == 0 { if trace { traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(routes), httpRequest.Method) } return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed") } inputMediaOk := methodOk 
  • content-type
    contentType := httpRequest.Header.Get(HEADER_ContentType) inputMediaOk = []Route{} for _, each := range methodOk { if each.matchesContentType(contentType) { inputMediaOk = append(inputMediaOk, each) } } 
  • accept
    outputMediaOk := []Route{} accept := httpRequest.Header.Get(HEADER_Accept) if len(accept) == 0 { accept = "*/*" } for _, each := range inputMediaOk { if each.matchesAccept(accept) { outputMediaOk = append(outputMediaOk, each) } } if len(outputMediaOk) == 0 { if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(inputMediaOk), accept) } return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable") } 
    pathProcessor, routerProcessesPath := c.router.(PathProcessor) if !routerProcessesPath { pathProcessor = defaultPathProcessor{} } 

在go-restful中支持fliter的定义, 在执行对应function之前,需要对fliter检查是否命中

pathProcessor, routerProcessesPath := c.router.(PathProcessor) if !routerProcessesPath { pathProcessor = defaultPathProcessor{} } pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path) wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams) // pass through filters (if any) if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 { // compose filter chain allFilters := []FilterFunction{} allFilters = append(allFilters, c.containerFilters...) allFilters = append(allFilters, webService.filters...) allFilters = append(allFilters, route.Filters...) chain := FilterChain{Filters: allFilters, Target: func(req *Request, resp *Response) { // handle request by route after passing all filters route.Function(wrappedRequest, wrappedResponse) }} chain.ProcessFilter(wrappedRequest, wrappedResponse) } else { // no filters, handle request by route route.Function(wrappedRequest, wrappedResponse) } 

在hello world例子中没有fliter相关功能, 因此可以直接跳转到route.Function(wrappedRequest, wrappedResponse),执行hello world对应的function, function中调用io.WriteString(resp, "world")将resp写回到net/server缓冲区中,io loop并写回到socket fd中完成通信。

func hello(req *restful.Request, resp *restful.Response) { fmt.Println("hello world") io.WriteString(resp, "world") } 

层级关系


Route

路由包含两种,一种是标准JSR311接口规范的实现RouterJSR311,一种是快速路由CurlyRouter。
CurlyRouter支持正则表达式和动态参数,相比RouterJSR11更加轻量级,apiserver中使用的就是这种路由。
一种Route的设定包含:请求方法(http Method),请求路径(URL Path),输入输出类型(JSON/YAML)以及对应的回掉函数restful.RouteFunction,响应内容类型(Accept)等。

WebService

webservice中维护了route的集合,功能上主要维护了一组route的rootpath、method、fliter等属性

Container

Container逻辑上是WebService的集合,功能上可以实现多终端的效果,不同的container可以绑定到不同的ip或者port上面


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:tcuze

查看原文:go restful源码剖析-1

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1690 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传