本文简单的介绍go-restrul 的使用,简单剖析go-restful 的代码。
目前开源界的go语言rest(http server)框架比较多,其中具有代表性的有beego, restful。 beego在国内非常著名, 功能完善且稳定。但是go-restful 则更轻量化, go-restful是按照java的JAX-RS使用golang语言的再实现。
go-restful 框架中最基础的几个概念包括: route, webservice, container。
route是http server的基本概念,是指一条http请求的URL, 根据此URL来确定那个函数为其服务。 go-restful中的route也是一样的,不过代码实现的时候跟准确的说法是注册路由。路由包含两种,RouterJSR311和CurlyRouter, CurlyRoute是base于routerJSR311的,但是支持正则表达式和动态参数,更加轻量化。
CurlyRouter在设定式包含:请求路径(URL Path),输入输出类型(JSON/YAML),请求对应的处理函数, 请求的参数,文档描述以及对应的回掉函数restful.RouteFunction,响应内容类型(Accept)等。
webservice实际上是一组route的集合。这组route拥有相同的rootpath或者base path,拥有相同的输入输出格式,基本一致的请求数据类型等一些通用的属性,举例说明:User为例,webservice是以/user/为基础路径的一个route集合,其下列的请求都是请求route相关操作的route。
换一种说法是将一组相关性非常强的request URL封装成为一个webserviice。举个例子来说明我们在公司开发过程中有项目信息和用户信息; 这样项目可以作为一个webservice, 用户可以作为另一个webservice。
webservice只是一组route的集合,其必须加入到container中才能够生效。
Container 在http的角度就成为一个Server, 其包含一组webservice, 一个serveMUx,以及对应的routeselect负责请求派发。
一个container实际上是对外提供的一个(从http的基本角度看)http Server; 一个container里面可以有很多个webservice, 一个webservice可以看做是一组对象的服务,是一个种类的服务请求的一个合集或者看成是子服务; 一个 route则是单个请求的路由,一个webservice包括一组route,这一组路由有相同的base路径。其三者的相互关系如下图:
go-restful的route 在注册的时候有很多其他描述,例如 Filter, Doc, Param, Metadata, Writes,Reads and Returns。
go-restful主要的Filter分为三类, 分别属于Container, webservice和route,其作用分别是其对应的Container, webservice和route.因此Filetr 分为Container.Filter, WebService.Filter跟Route.Filter。 Container.Filter作用在所有WebService之前, 而WebService.Filter作用与该WebService的所有Route之前。
其他的暂时先不做说明,(我也没弄明白)
简单的介绍了go-restful框架之后这里使用简单的example来对go-restful的源码进行简单剖析,也做个简单的示例。
func main() {
wsContainer := restful.NewContainer()
wsContainer.Router(restful.CurlyRouter{})
u := UserResource{map[string]User{}}
u.Register(wsContainer)
log.Printf("start listening on localhost:8080")
server := &http.Server{Addr: ":8080", Handler: wsContainer}
log.Fatal(server.ListenAndServe())
}
以上是一个简单的基于go-restful的服务的main()函数,也就是主逻辑或者说使用总纲以及使用步骤。
一开始调用restful.NewContainer() 创建了一个webservice,然后调用container对象的Router(restful.CurlyRouter{})方法为container配置路由种类。该方法可以不用调用,因为restful.NewContainer()默认使用CurlyRouter, 但是你若想使用JSR311就必须调用Router(restful.RouterJSR311{})。
接下来创建了userResource用来存放User信息,然后u.Register是自己定义的一个函数,我们简单看一下,该函数:
func (u UserResource)Register(container *restful.Container) {
ws := new(restful.WebService)
ws.
Path("/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))
ws.Route(ws.POST("").To(u.updateUser))
ws.Route(ws.PUT("/{user-id}").To(u.createUser))
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser))
container.Add(ws)
}
以上函数主要是new了一个WebService,之后给Webservice配置了一组路由path, 然后将webservice添加到Container中。
上面的函数非常简单明了,再返回main()函数之后就定义了一个Server并把Container作为该Server的handler,之后就直接server.ListenAndServe()了。
再看WebService添加路由,函数调用非常简单:
ws.Route(ws.PUT("/{user-id}").To(u.createUser))
其中ws为WebService, Route是定义路由Path,ws.Put对应的是该Path的方法,之前的“golang http server 源码解析与说明”中也说过定义路由path的http方法, 然后在根据http header中的方法进行选择和过滤,这里的定义就非常简单,go-restful框架使用Route函数来帮你处理这个定义。 To() 定义了该路径下该方法的处理函数, 是一个http的 handler func(ResponseWriter, *Request)。
接下来我们来看一下该route函数:
route()函数的参数只有一个,是*RouteBuilder类型的。
以官方example为例来看:
ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a users").
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes([]User{}).
Returns(200, "OK", []User{}))
其中:
“ws.GET(“/”).To(u.findAllUsers).
// docs
Doc(“get all users”).
Param(ws.PathParameter(“user-id”, “identifier of the user”).DataType(“integer”).DefaultValue(“1”)).
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes(User{}). // on the response
Returns(200, “OK”, User{}).
Returns(404, “Not Found”, nil))”
是参数,让我们一层一层来看:
// GET is a shortcut for .Method("GET").Path(subPath
func (w *WebService) GET(subPath string) *RouteBuilder {
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath)
} //定义了该routebuild的typeNameHandler 对应的的方法是GET, 路径是w.rootPath+subPath
// 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
} //定义了该routebuild对应的的function是 参数传递尽量的function
// Doc tells what this route is all about. Optional.
func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
b.doc = documentation
return b
}
// Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
if b.parameters == nil {
b.parameters = []*Parameter{}
}
b.parameters = append(b.parameters, parameter)
return b
}
// Writes tells what resource type will be written as the response payload. Optional.
func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder {
b.writeSample = sample
return b
}
func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
err := ResponseError{
Code: code,
Message: message,
Model: model,
IsDefault: false,
}
// lazy init because there is no NewRouteBuilder (yet)
if b.errorMap == nil {
b.errorMap = map[int]ResponseError{}
}
b.errorMap[code] = err
return b
}
调用完以上所有函数后构成了一个RouteBuilder, 其实以上这些个函数时间就是给RouterBuilder的各个属性进行赋值, 之后再调用Route函数构成route,
// 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()) //append 添加webservice的路由,
return w
}
以上代码张还有一个builder.Build() 该函数是将创建来的RouterBuilder的信息转换成为route信息。,其函数内容如下:
// 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("[restful] Invalid path:%s because:%v", b.currentPath, err)
os.Exit(1)
}
if b.function == nil {
log.Printf("[restful] 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
}
至此, 给webservice添加路由的工作完成。
接下来,我们看一下 container.Add(ws)的实际处理:
// 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("[restful] 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.dispatch
}
c.webServices = append(c.webServices, service)
return c
}
// 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
}
至此go-restrul 的路由注册于绑定函数结束。
在上面我们知道go-restful有三组Filters,分别是 containerFilters, WebServiceFilters,routeFliters, 他们怎样被执行,以及go-restful的路由如何解析下面函数中进行分析。
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
writer := httpWriter
// CompressingResponseWriter should be closed after all operations are done
defer func() {
if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
compressWriter.Close()
}
}()
// Instal panic recovery unless told otherwise
if !c.doNotRecover { // catch all for 500 response
defer func() {
if r := recover(); r != nil {
c.recoverHandleFunc(r, writer)
return
}
}()
}
// Detect if compression is needed
// assume without compression, test for override
if c.contentEncodingEnabled {
doCompress, encoding := wantsCompressedResponse(httpRequest)
if doCompress {
var err error
writer, err = NewCompressingResponseWriter(httpWriter, encoding)
if err != nil {
log.Print("[restful] unable to install compressor: ", err)
httpWriter.WriteHeader(http.StatusInternalServerError)
return
}
}
}
// Find best match Route ; err is non nil if no match was found
var webService *WebService
var route *Route
var err error
func() {
c.webServicesLock.RLock()
defer c.webServicesLock.RUnlock()
webService, route, err = c.router.SelectRoute(
c.webServices,
httpRequest)
}()
if err != nil {
// a non-200 response has already been written
// run container filters anyway ; they should not touch the response...
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
switch err.(type) {
case ServiceError:
ser := err.(ServiceError)
c.serviceErrorHandleFunc(ser, req, resp)
}
// TODO
}}
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
return
}
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest)
// 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)
}
httpRequest)
}
其中上面webService, route,error=c.router.SelectRoute 是用来解析路由的,解析到那个webService以及对应的那个route, 那么该route对应的function即为该request请求的处理函数,既然如此为何还要找到webService呢?
因为WebService有Filter需要处理。 最后一部分(if–else–)添加所有的fliters(containerFilters, WebServcie.Filters, route.Filters)到allFilters,生产chain,然后调用chain.ProcessFilter函数进行处理。chain.ProcessFilter函数中调用完所有的Filter处理后,调用target函数,target函数为对应的route的function,其具体的代码如下:
func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
if f.Index < len(f.Filters) {
f.Index++
f.Filters[f.Index-1](request, response, f)
} else {
f.Target(request, response)
}
}
至此,go-restful的源码简单分析结束,纵观以上源码比较多,多级也比较清晰,使用非常简单, 我总结了一下其使用过程基本如下:
go-restful 最著名的例子便是docker领域的kubernetes,kubernetes 的api-server大量使用了go-restful框架。只是这个需要各位自己去看,这里不做分析。