一个 WebService 由若干个 Routes 组成,并且 WebService 内的 Routes 拥有同一个 RootPath、输入输出格式、基本一致的请求数据类型等等一系列的通用属性。通常的,我们会根据需要将一组相关性非常强的 API 封装成为一个 WebServiice,继而将 Web Application 所拥有的全部 APIs 划分若干个 Group。
go-restful/web_service.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 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 }
Root Path
WebService 有一个 Root Path,通过 ws.Path() 方法设置,例如:/users,作为 Group 的 根。
1 2 3 4 5
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
Group 下属的 APIs 都是 RootRoute(RootPath)下属的 SubRoute(SubPath)。每个 Group 就是提供一项服务的 API 集合,每个 Group 会维护一个 Version。Group 的抽象是为了能够安全隔离的对各项服务进行敏捷迭代,当我们对一项服务进行升级时,只需要通过对特定版本号的更新来升级相关的 APIs,而不会影响到整个 Web Server。视实际情况而定,可能是若干个 APIs 分为一个 Group,也有可能一个 API 就是一个 Group。
// 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 }
// GET is a shortcut for .Method("GET").Path(subPath) func(w *WebService)GET(subPath string) *RouteBuilder { returnnew(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath) }
// Method specifies what HTTP method to match. Required. func(b *RouteBuilder)Method(method string) *RouteBuilder { b.httpMethod = method return b }
// 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 }
// 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 }
funchello(req *restful.Request, resp *restful.Response) { io.WriteString(resp, "hello world from houmin\n") }
上面的代码比较简单,包含一个 hello 的 Handler,通过 ws.Route(ws.GET("/hello").To(hello)) 将其注册到 WebService,然后启动了一个 WebServer,就可以了通过 GET 方法访问了,如下所示:
Container
上一小节虽然 Work 了,但是还是有一个问题,我们通过 Go 自带的 net/http 包启动的 WebServer 是如何和我们定义的 WebService 联系起来的呢?在解答这个问题之前,我们首先来了解下 Container。
Container 表示一个 Web Server,由多个 WebServices组成,此外还包含了若干个 Filters、一个 http.ServeMux多路复用器以及一个 dispatch。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests. // The requests are further dispatched to routes of WebServices using a RouteSelector type Container struct { webServicesLock sync.RWMutex webServices []*WebService ServeMux *http.ServeMux isRegisteredOnRoot bool containerFilters []FilterFunction doNotRecover bool// default is true recoverHandleFunc RecoverHandleFunction serviceErrorHandleFunc ServiceErrorHandleFunction router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative) contentEncodingEnabled bool// default is false }
// HandleFunc registers the handler function for the given pattern. func(mux *ServeMux)HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) }
// addHandler may set a new HandleFunc for the serveMux func(c *Container)addHandler(service *WebService, serveMux *http.ServeMux)bool { // ... if !alreadyMapped { serveMux.HandleFunc(pattern, c.dispatch) if !strings.HasSuffix(pattern, "/") { serveMux.HandleFunc(pattern+"/", c.dispatch) } } returnfalse }
那么 addHandler 是在哪里被调用的呢?这就是我们的 Add 函数
1 2 3 4 5 6 7 8 9 10 11
// Add a WebService to the Container. It will detect duplicate root paths and exit in that case. func(c *Container)Add(service *WebService) *Container { // ... // 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 }
// ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server func(c *Container)ServeHTTP(httpWriter http.ResponseWriter, httpRequest *http.Request) { // ... c.ServeMux.ServeHTTP(writer, httpRequest) }
// Dispatch the incoming Http Request to a matching WebService. func(c *Container)dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) { // so we can assign a compressing one later writer := httpWriter
// ...
// 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) }()
// This example has the same service definition as restful-user-resource // but uses a different router (CurlyRouter) that does not use regular expressions // // POST http://localhost:8080/users // <User><Id>1</Id><Name>Melissa Raspberry</Name></User> // // GET http://localhost:8080/users/1 // // PUT http://localhost:8080/users/1 // <User><Id>1</Id><Name>Melissa</Name></User> // // DELETE http://localhost:8080/users/1 //
type User struct { Id, Name string }
type UserResource struct { // normally one would use DAO (data access object) users map[string]User }
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
// GET http://localhost:8080/users/1 // func(u UserResource)findUser(request *restful.Request, response *restful.Response) { id := request.PathParameter("user-id") usr, ok := u.users[id] if !ok { response.AddHeader("Content-Type", "text/plain") response.WriteErrorString(http.StatusNotFound, "User could not be found.") } else { response.WriteEntity(usr) } }
type FilterFunction func(*Request, *Response, *FilterChain)
type FilterChain struct { Filters []FilterFunction // ordered list of FilterFunction Index int// index into filters that is currently in progress Target RouteFunction // function to call after passing all filters }
// Filter appends a container FilterFunction. These are called before dispatching // a http.Request to a WebService from the container func(c *Container)Filter(filter FilterFunction) { c.containerFilters = append(c.containerFilters, filter) }
// Filter adds a filter function to the chain of filters applicable to all its Routes func(w *WebService)Filter(filter FilterFunction) *WebService { w.filters = append(w.filters, filter) return w }
// Filter appends a FilterFunction to the end of filters for this Route to build. func(b *RouteBuilder)Filter(filter FilterFunction) *RouteBuilder { b.filters = append(b.filters, filter) return b }