3 // Copyright 2013 Ernest Micklei. All rights reserved.
4 // Use of this source code is governed by a license
5 // that can be found in the LICENSE file.
17 "github.com/emicklei/go-restful/log"
20 // Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
21 // The requests are further dispatched to routes of WebServices using a RouteSelector
22 type Container struct {
23 webServicesLock sync.RWMutex
24 webServices []*WebService
25 ServeMux *http.ServeMux
26 isRegisteredOnRoot bool
27 containerFilters []FilterFunction
28 doNotRecover bool // default is true
29 recoverHandleFunc RecoverHandleFunction
30 serviceErrorHandleFunc ServiceErrorHandleFunction
31 router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
32 contentEncodingEnabled bool // default is false
35 // NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
36 func NewContainer() *Container {
38 webServices: []*WebService{},
39 ServeMux: http.NewServeMux(),
40 isRegisteredOnRoot: false,
41 containerFilters: []FilterFunction{},
43 recoverHandleFunc: logStackOnRecover,
44 serviceErrorHandleFunc: writeServiceError,
45 router: CurlyRouter{},
46 contentEncodingEnabled: false}
49 // RecoverHandleFunction declares functions that can be used to handle a panic situation.
50 // The first argument is what recover() returns. The second must be used to communicate an error response.
51 type RecoverHandleFunction func(interface{}, http.ResponseWriter)
53 // RecoverHandler changes the default function (logStackOnRecover) to be called
54 // when a panic is detected. DoNotRecover must be have its default value (=false).
55 func (c *Container) RecoverHandler(handler RecoverHandleFunction) {
56 c.recoverHandleFunc = handler
59 // ServiceErrorHandleFunction declares functions that can be used to handle a service error situation.
60 // The first argument is the service error, the second is the request that resulted in the error and
61 // the third must be used to communicate an error response.
62 type ServiceErrorHandleFunction func(ServiceError, *Request, *Response)
64 // ServiceErrorHandler changes the default function (writeServiceError) to be called
65 // when a ServiceError is detected.
66 func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) {
67 c.serviceErrorHandleFunc = handler
70 // DoNotRecover controls whether panics will be caught to return HTTP 500.
71 // If set to true, Route functions are responsible for handling any error situation.
72 // Default value is true.
73 func (c *Container) DoNotRecover(doNot bool) {
74 c.doNotRecover = doNot
77 // Router changes the default Router (currently CurlyRouter)
78 func (c *Container) Router(aRouter RouteSelector) {
82 // EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses.
83 func (c *Container) EnableContentEncoding(enabled bool) {
84 c.contentEncodingEnabled = enabled
87 // Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
88 func (c *Container) Add(service *WebService) *Container {
89 c.webServicesLock.Lock()
90 defer c.webServicesLock.Unlock()
92 // if rootPath was not set then lazy initialize it
93 if len(service.rootPath) == 0 {
97 // cannot have duplicate root paths
98 for _, each := range c.webServices {
99 if each.RootPath() == service.RootPath() {
100 log.Printf("WebService with duplicate root path detected:['%v']", each)
105 // If not registered on root then add specific mapping
106 if !c.isRegisteredOnRoot {
107 c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
109 c.webServices = append(c.webServices, service)
113 // addHandler may set a new HandleFunc for the serveMux
114 // this function must run inside the critical region protected by the webServicesLock.
115 // returns true if the function was registered on root ("/")
116 func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
117 pattern := fixedPrefixPath(service.RootPath())
118 // check if root path registration is needed
119 if "/" == pattern || "" == pattern {
120 serveMux.HandleFunc("/", c.dispatch)
123 // detect if registration already exists
124 alreadyMapped := false
125 for _, each := range c.webServices {
126 if each.RootPath() == service.RootPath() {
132 serveMux.HandleFunc(pattern, c.dispatch)
133 if !strings.HasSuffix(pattern, "/") {
134 serveMux.HandleFunc(pattern+"/", c.dispatch)
140 func (c *Container) Remove(ws *WebService) error {
141 if c.ServeMux == http.DefaultServeMux {
142 errMsg := fmt.Sprintf("cannot remove a WebService from a Container using the DefaultServeMux: ['%v']", ws)
144 return errors.New(errMsg)
146 c.webServicesLock.Lock()
147 defer c.webServicesLock.Unlock()
148 // build a new ServeMux and re-register all WebServices
149 newServeMux := http.NewServeMux()
150 newServices := []*WebService{}
151 newIsRegisteredOnRoot := false
152 for _, each := range c.webServices {
153 if each.rootPath != ws.rootPath {
154 // If not registered on root then add specific mapping
155 if !newIsRegisteredOnRoot {
156 newIsRegisteredOnRoot = c.addHandler(each, newServeMux)
158 newServices = append(newServices, each)
161 c.webServices, c.ServeMux, c.isRegisteredOnRoot = newServices, newServeMux, newIsRegisteredOnRoot
165 // logStackOnRecover is the default RecoverHandleFunction and is called
166 // when DoNotRecover is false and the recoverHandleFunc is not set for the container.
167 // Default implementation logs the stacktrace and writes the stacktrace on the response.
168 // This may be a security issue as it exposes sourcecode information.
169 func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) {
170 var buffer bytes.Buffer
171 buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
172 for i := 2; ; i += 1 {
173 _, file, line, ok := runtime.Caller(i)
177 buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
179 log.Print(buffer.String())
180 httpWriter.WriteHeader(http.StatusInternalServerError)
181 httpWriter.Write(buffer.Bytes())
184 // writeServiceError is the default ServiceErrorHandleFunction and is called
185 // when a ServiceError is returned during route selection. Default implementation
186 // calls resp.WriteErrorString(err.Code, err.Message)
187 func writeServiceError(err ServiceError, req *Request, resp *Response) {
188 resp.WriteErrorString(err.Code, err.Message)
191 // Dispatch the incoming Http Request to a matching WebService.
192 func (c *Container) Dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
193 if httpWriter == nil {
194 panic("httpWriter cannot be nil")
196 if httpRequest == nil {
197 panic("httpRequest cannot be nil")
199 c.dispatch(httpWriter, httpRequest)
202 // Dispatch the incoming Http Request to a matching WebService.
203 func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
206 // CompressingResponseWriter should be closed after all operations are done
208 if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
209 compressWriter.Close()
213 // Instal panic recovery unless told otherwise
214 if !c.doNotRecover { // catch all for 500 response
216 if r := recover(); r != nil {
217 c.recoverHandleFunc(r, writer)
223 // Find best match Route ; err is non nil if no match was found
224 var webService *WebService
228 c.webServicesLock.RLock()
229 defer c.webServicesLock.RUnlock()
230 webService, route, err = c.router.SelectRoute(
235 // Detect if compression is needed
236 // assume without compression, test for override
237 contentEncodingEnabled := c.contentEncodingEnabled
238 if route != nil && route.contentEncodingEnabled != nil {
239 contentEncodingEnabled = *route.contentEncodingEnabled
241 if contentEncodingEnabled {
242 doCompress, encoding := wantsCompressedResponse(httpRequest)
245 writer, err = NewCompressingResponseWriter(httpWriter, encoding)
247 log.Print("unable to install compressor: ", err)
248 httpWriter.WriteHeader(http.StatusInternalServerError)
255 // a non-200 response has already been written
256 // run container filters anyway ; they should not touch the response...
257 chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
260 ser := err.(ServiceError)
261 c.serviceErrorHandleFunc(ser, req, resp)
265 chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
268 pathProcessor, routerProcessesPath := c.router.(PathProcessor)
269 if !routerProcessesPath {
270 pathProcessor = defaultPathProcessor{}
272 pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
273 wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
274 // pass through filters (if any)
275 if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 {
276 // compose filter chain
277 allFilters := []FilterFunction{}
278 allFilters = append(allFilters, c.containerFilters...)
279 allFilters = append(allFilters, webService.filters...)
280 allFilters = append(allFilters, route.Filters...)
281 chain := FilterChain{Filters: allFilters, Target: func(req *Request, resp *Response) {
282 // handle request by route after passing all filters
283 route.Function(wrappedRequest, wrappedResponse)
285 chain.ProcessFilter(wrappedRequest, wrappedResponse)
287 // no filters, handle request by route
288 route.Function(wrappedRequest, wrappedResponse)
292 // fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {}
293 func fixedPrefixPath(pathspec string) string {
294 varBegin := strings.Index(pathspec, "{")
298 return pathspec[:varBegin]
301 // ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server
302 func (c *Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) {
303 c.ServeMux.ServeHTTP(httpwriter, httpRequest)
306 // Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
307 func (c *Container) Handle(pattern string, handler http.Handler) {
308 c.ServeMux.Handle(pattern, handler)
311 // HandleWithFilter registers the handler for the given pattern.
312 // Container's filter chain is applied for handler.
313 // If a handler already exists for pattern, HandleWithFilter panics.
314 func (c *Container) HandleWithFilter(pattern string, handler http.Handler) {
315 f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
316 if len(c.containerFilters) == 0 {
317 handler.ServeHTTP(httpResponse, httpRequest)
321 chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
322 handler.ServeHTTP(httpResponse, httpRequest)
324 chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse))
327 c.Handle(pattern, http.HandlerFunc(f))
330 // Filter appends a container FilterFunction. These are called before dispatching
331 // a http.Request to a WebService from the container
332 func (c *Container) Filter(filter FilterFunction) {
333 c.containerFilters = append(c.containerFilters, filter)
336 // RegisteredWebServices returns the collections of added WebServices
337 func (c *Container) RegisteredWebServices() []*WebService {
338 c.webServicesLock.RLock()
339 defer c.webServicesLock.RUnlock()
340 result := make([]*WebService, len(c.webServices))
341 for ix := range c.webServices {
342 result[ix] = c.webServices[ix]
347 // computeAllowedMethods returns a list of HTTP methods that are valid for a Request
348 func (c *Container) computeAllowedMethods(req *Request) []string {
349 // Go through all RegisteredWebServices() and all its Routes to collect the options
350 methods := []string{}
351 requestPath := req.Request.URL.Path
352 for _, ws := range c.RegisteredWebServices() {
353 matches := ws.pathExpr.Matcher.FindStringSubmatch(requestPath)
355 finalMatch := matches[len(matches)-1]
356 for _, rt := range ws.Routes() {
357 matches := rt.pathExpr.Matcher.FindStringSubmatch(finalMatch)
359 lastMatch := matches[len(matches)-1]
360 if lastMatch == "" || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
361 methods = append(methods, rt.Method)
367 // methods = append(methods, "OPTIONS") not sure about this
371 // newBasicRequestResponse creates a pair of Request,Response from its http versions.
372 // It is basic because no parameter or (produces) content-type information is given.
373 func newBasicRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
374 resp := NewResponse(httpWriter)
375 resp.requestAccept = httpRequest.Header.Get(HEADER_Accept)
376 return NewRequest(httpRequest), resp