Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / github.com / emicklei / go-restful / container.go
1 package restful
2
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.
6
7 import (
8         "bytes"
9         "errors"
10         "fmt"
11         "net/http"
12         "os"
13         "runtime"
14         "strings"
15         "sync"
16
17         "github.com/emicklei/go-restful/log"
18 )
19
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
33 }
34
35 // NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
36 func NewContainer() *Container {
37         return &Container{
38                 webServices:            []*WebService{},
39                 ServeMux:               http.NewServeMux(),
40                 isRegisteredOnRoot:     false,
41                 containerFilters:       []FilterFunction{},
42                 doNotRecover:           true,
43                 recoverHandleFunc:      logStackOnRecover,
44                 serviceErrorHandleFunc: writeServiceError,
45                 router:                 CurlyRouter{},
46                 contentEncodingEnabled: false}
47 }
48
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)
52
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
57 }
58
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)
63
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
68 }
69
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
75 }
76
77 // Router changes the default Router (currently CurlyRouter)
78 func (c *Container) Router(aRouter RouteSelector) {
79         c.router = aRouter
80 }
81
82 // EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses.
83 func (c *Container) EnableContentEncoding(enabled bool) {
84         c.contentEncodingEnabled = enabled
85 }
86
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()
91
92         // if rootPath was not set then lazy initialize it
93         if len(service.rootPath) == 0 {
94                 service.Path("/")
95         }
96
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)
101                         os.Exit(1)
102                 }
103         }
104
105         // If not registered on root then add specific mapping
106         if !c.isRegisteredOnRoot {
107                 c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
108         }
109         c.webServices = append(c.webServices, service)
110         return c
111 }
112
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)
121                 return true
122         }
123         // detect if registration already exists
124         alreadyMapped := false
125         for _, each := range c.webServices {
126                 if each.RootPath() == service.RootPath() {
127                         alreadyMapped = true
128                         break
129                 }
130         }
131         if !alreadyMapped {
132                 serveMux.HandleFunc(pattern, c.dispatch)
133                 if !strings.HasSuffix(pattern, "/") {
134                         serveMux.HandleFunc(pattern+"/", c.dispatch)
135                 }
136         }
137         return false
138 }
139
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)
143                 log.Print(errMsg)
144                 return errors.New(errMsg)
145         }
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)
157                         }
158                         newServices = append(newServices, each)
159                 }
160         }
161         c.webServices, c.ServeMux, c.isRegisteredOnRoot = newServices, newServeMux, newIsRegisteredOnRoot
162         return nil
163 }
164
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)
174                 if !ok {
175                         break
176                 }
177                 buffer.WriteString(fmt.Sprintf("    %s:%d\r\n", file, line))
178         }
179         log.Print(buffer.String())
180         httpWriter.WriteHeader(http.StatusInternalServerError)
181         httpWriter.Write(buffer.Bytes())
182 }
183
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)
189 }
190
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")
195         }
196         if httpRequest == nil {
197                 panic("httpRequest cannot be nil")
198         }
199         c.dispatch(httpWriter, httpRequest)
200 }
201
202 // Dispatch the incoming Http Request to a matching WebService.
203 func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
204         writer := httpWriter
205
206         // CompressingResponseWriter should be closed after all operations are done
207         defer func() {
208                 if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
209                         compressWriter.Close()
210                 }
211         }()
212
213         // Instal panic recovery unless told otherwise
214         if !c.doNotRecover { // catch all for 500 response
215                 defer func() {
216                         if r := recover(); r != nil {
217                                 c.recoverHandleFunc(r, writer)
218                                 return
219                         }
220                 }()
221         }
222
223         // Find best match Route ; err is non nil if no match was found
224         var webService *WebService
225         var route *Route
226         var err error
227         func() {
228                 c.webServicesLock.RLock()
229                 defer c.webServicesLock.RUnlock()
230                 webService, route, err = c.router.SelectRoute(
231                         c.webServices,
232                         httpRequest)
233         }()
234
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
240         }
241         if contentEncodingEnabled {
242                 doCompress, encoding := wantsCompressedResponse(httpRequest)
243                 if doCompress {
244                         var err error
245                         writer, err = NewCompressingResponseWriter(httpWriter, encoding)
246                         if err != nil {
247                                 log.Print("unable to install compressor: ", err)
248                                 httpWriter.WriteHeader(http.StatusInternalServerError)
249                                 return
250                         }
251                 }
252         }
253
254         if err != nil {
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) {
258                         switch err.(type) {
259                         case ServiceError:
260                                 ser := err.(ServiceError)
261                                 c.serviceErrorHandleFunc(ser, req, resp)
262                         }
263                         // TODO
264                 }}
265                 chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
266                 return
267         }
268         pathProcessor, routerProcessesPath := c.router.(PathProcessor)
269         if !routerProcessesPath {
270                 pathProcessor = defaultPathProcessor{}
271         }
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)
284                 }}
285                 chain.ProcessFilter(wrappedRequest, wrappedResponse)
286         } else {
287                 // no filters, handle request by route
288                 route.Function(wrappedRequest, wrappedResponse)
289         }
290 }
291
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, "{")
295         if -1 == varBegin {
296                 return pathspec
297         }
298         return pathspec[:varBegin]
299 }
300
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)
304 }
305
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)
309 }
310
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)
318                         return
319                 }
320
321                 chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
322                         handler.ServeHTTP(httpResponse, httpRequest)
323                 }}
324                 chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse))
325         }
326
327         c.Handle(pattern, http.HandlerFunc(f))
328 }
329
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)
334 }
335
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]
343         }
344         return result
345 }
346
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)
354                 if matches != nil {
355                         finalMatch := matches[len(matches)-1]
356                         for _, rt := range ws.Routes() {
357                                 matches := rt.pathExpr.Matcher.FindStringSubmatch(finalMatch)
358                                 if matches != nil {
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)
362                                         }
363                                 }
364                         }
365                 }
366         }
367         // methods = append(methods, "OPTIONS")  not sure about this
368         return methods
369 }
370
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
377 }