blob: b4ad153e8db585ae31f43dd41eef80fcbd3ba0d0 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001package 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
7import (
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
22type 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)
36func 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.
51type 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).
55func (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.
62type ServiceErrorHandleFunction func(ServiceError, *Request, *Response)
63
64// ServiceErrorHandler changes the default function (writeServiceError) to be called
65// when a ServiceError is detected.
66func (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.
73func (c *Container) DoNotRecover(doNot bool) {
74 c.doNotRecover = doNot
75}
76
77// Router changes the default Router (currently CurlyRouter)
78func (c *Container) Router(aRouter RouteSelector) {
79 c.router = aRouter
80}
81
82// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses.
83func (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.
88func (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("[restful] 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 ("/")
116func (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
140func (c *Container) Remove(ws *WebService) error {
141 if c.ServeMux == http.DefaultServeMux {
142 errMsg := fmt.Sprintf("[restful] 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.
169func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) {
170 var buffer bytes.Buffer
171 buffer.WriteString(fmt.Sprintf("[restful] 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)
187func 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.
192func (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.
203func (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 // Detect if compression is needed
224 // assume without compression, test for override
225 if c.contentEncodingEnabled {
226 doCompress, encoding := wantsCompressedResponse(httpRequest)
227 if doCompress {
228 var err error
229 writer, err = NewCompressingResponseWriter(httpWriter, encoding)
230 if err != nil {
231 log.Print("[restful] unable to install compressor: ", err)
232 httpWriter.WriteHeader(http.StatusInternalServerError)
233 return
234 }
235 }
236 }
237 // Find best match Route ; err is non nil if no match was found
238 var webService *WebService
239 var route *Route
240 var err error
241 func() {
242 c.webServicesLock.RLock()
243 defer c.webServicesLock.RUnlock()
244 webService, route, err = c.router.SelectRoute(
245 c.webServices,
246 httpRequest)
247 }()
248 if err != nil {
249 // a non-200 response has already been written
250 // run container filters anyway ; they should not touch the response...
251 chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
252 switch err.(type) {
253 case ServiceError:
254 ser := err.(ServiceError)
255 c.serviceErrorHandleFunc(ser, req, resp)
256 }
257 // TODO
258 }}
259 chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
260 return
261 }
262 pathProcessor, routerProcessesPath := c.router.(PathProcessor)
263 if !routerProcessesPath {
264 pathProcessor = defaultPathProcessor{}
265 }
266 pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
267 wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
268 // pass through filters (if any)
269 if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 {
270 // compose filter chain
271 allFilters := []FilterFunction{}
272 allFilters = append(allFilters, c.containerFilters...)
273 allFilters = append(allFilters, webService.filters...)
274 allFilters = append(allFilters, route.Filters...)
275 chain := FilterChain{Filters: allFilters, Target: func(req *Request, resp *Response) {
276 // handle request by route after passing all filters
277 route.Function(wrappedRequest, wrappedResponse)
278 }}
279 chain.ProcessFilter(wrappedRequest, wrappedResponse)
280 } else {
281 // no filters, handle request by route
282 route.Function(wrappedRequest, wrappedResponse)
283 }
284}
285
286// fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {}
287func fixedPrefixPath(pathspec string) string {
288 varBegin := strings.Index(pathspec, "{")
289 if -1 == varBegin {
290 return pathspec
291 }
292 return pathspec[:varBegin]
293}
294
295// ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server
296func (c *Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) {
297 c.ServeMux.ServeHTTP(httpwriter, httpRequest)
298}
299
300// Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
301func (c *Container) Handle(pattern string, handler http.Handler) {
302 c.ServeMux.Handle(pattern, handler)
303}
304
305// HandleWithFilter registers the handler for the given pattern.
306// Container's filter chain is applied for handler.
307// If a handler already exists for pattern, HandleWithFilter panics.
308func (c *Container) HandleWithFilter(pattern string, handler http.Handler) {
309 f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
310 if len(c.containerFilters) == 0 {
311 handler.ServeHTTP(httpResponse, httpRequest)
312 return
313 }
314
315 chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
316 handler.ServeHTTP(httpResponse, httpRequest)
317 }}
318 chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse))
319 }
320
321 c.Handle(pattern, http.HandlerFunc(f))
322}
323
324// Filter appends a container FilterFunction. These are called before dispatching
325// a http.Request to a WebService from the container
326func (c *Container) Filter(filter FilterFunction) {
327 c.containerFilters = append(c.containerFilters, filter)
328}
329
330// RegisteredWebServices returns the collections of added WebServices
331func (c *Container) RegisteredWebServices() []*WebService {
332 c.webServicesLock.RLock()
333 defer c.webServicesLock.RUnlock()
334 result := make([]*WebService, len(c.webServices))
335 for ix := range c.webServices {
336 result[ix] = c.webServices[ix]
337 }
338 return result
339}
340
341// computeAllowedMethods returns a list of HTTP methods that are valid for a Request
342func (c *Container) computeAllowedMethods(req *Request) []string {
343 // Go through all RegisteredWebServices() and all its Routes to collect the options
344 methods := []string{}
345 requestPath := req.Request.URL.Path
346 for _, ws := range c.RegisteredWebServices() {
347 matches := ws.pathExpr.Matcher.FindStringSubmatch(requestPath)
348 if matches != nil {
349 finalMatch := matches[len(matches)-1]
350 for _, rt := range ws.Routes() {
351 matches := rt.pathExpr.Matcher.FindStringSubmatch(finalMatch)
352 if matches != nil {
353 lastMatch := matches[len(matches)-1]
354 if lastMatch == "" || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
355 methods = append(methods, rt.Method)
356 }
357 }
358 }
359 }
360 }
361 // methods = append(methods, "OPTIONS") not sure about this
362 return methods
363}
364
365// newBasicRequestResponse creates a pair of Request,Response from its http versions.
366// It is basic because no parameter or (produces) content-type information is given.
367func newBasicRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
368 resp := NewResponse(httpWriter)
369 resp.requestAccept = httpRequest.Header.Get(HEADER_Accept)
370 return NewRequest(httpRequest), resp
371}