上一篇分析了go 原生的http服务。下面开始介绍go restful源码分析,有了上一篇的铺垫这篇文章讲解起来就很简单了。和go 的http一样启动监听:
wsContainer := restful.NewContainer() server := &http.Server{Addr: ":8080",Handler: wsContainer}
不用多说这个wsContainer肯定也是实现了Handler接口的。看代码:
func NewContainer() *Container {
return &Container{
webServices: []*WebService{},ServeMux: http.NewServeMux(),isRegisteredOnRoot: false,containerFilters: []FilterFunction{},doNotRecover: true,recoverHandleFunc: logStackOnRecover,serviceErrorHandleFunc: writeServiceError,router: CurlyRouter{},contentEncodingEnabled: false}
}
func (c *Container) ServeHTTP(httpwriter http.ResponseWriter,httpRequest *http.Request) {
c.ServeMux.ServeHTTP(httpwriter,httpRequest)
}
上面有两个方式分别是定义container和实现Handler接口的的ServeHTTP()方法。那么当服务请求过来后就可以调用这个方法了。这个方法又调用c.ServeMux.ServeHTTP,是不是回到上一篇了,对,就练到一起了。
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)
}
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)
先看看Route方法,这里面记录所以路径和路径对应的方法,全部放到[]Route这个切片当中。
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
}
一个container里面多个webservice。可以通过Container的Add注册加入到webServices这个切片中。
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.webServices = append(c.webServices,service)
return c
}
注册webservice的本质是注册各种handler看addHandler这个方法。
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
}
通过serveMux.HandleFunc注册,这也和上一篇的文章对应。不过这次注册的方法c.dispatch有点特别,具体看下面的方法,
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
}
}()
}
// Install closing the request body (if any)
defer func() {
if nil != httpRequest.Body {
httpRequest.Body.Close()
}
}()
// 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,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)
}
}
这个里面方法里面通过SelectRoute选取对应的webservice和route,具体实现在:
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,"404: Page Not Found")
}
selectedRoute,err := c.detectRoute(candidateRoutes,httpRequest)
if selectedRoute == nil {
return detectedService,err
}
return detectedService,selectedRoute,nil
}