diff options
Diffstat (limited to 'vendor/github.com/emicklei')
35 files changed, 835 insertions, 359 deletions
diff --git a/vendor/github.com/emicklei/go-restful/.travis.yml b/vendor/github.com/emicklei/go-restful/.travis.yml new file mode 100644 index 000000000..b22f8f547 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful/.travis.yml @@ -0,0 +1,6 @@ +language: go + +go: + - 1.x + +script: go test -v
\ No newline at end of file diff --git a/vendor/github.com/emicklei/go-restful/CHANGES.md b/vendor/github.com/emicklei/go-restful/CHANGES.md index 45bd20129..d90aaa22e 100644 --- a/vendor/github.com/emicklei/go-restful/CHANGES.md +++ b/vendor/github.com/emicklei/go-restful/CHANGES.md @@ -1,27 +1,74 @@ Change history of go-restful = +2017-09-13 +- added route condition functions using `.If(func)` in route building. + +2017-02-16 +- solved issue #304, make operation names unique + +2017-01-30 + + [IMPORTANT] For swagger users, change your import statement to: + swagger "github.com/emicklei/go-restful-swagger12" + +- moved swagger 1.2 code to go-restful-swagger12 +- created TAG 2.0.0 + +2017-01-27 + +- remove defer request body close +- expose Dispatch for testing filters and Routefunctions +- swagger response model cannot be array +- created TAG 1.0.0 + +2016-12-22 + +- (API change) Remove code related to caching request content. Removes SetCacheReadEntity(doCache bool) + +2016-11-26 + +- Default change! now use CurlyRouter (was RouterJSR311) +- Default change! no more caching of request content +- Default change! do not recover from panics + +2016-09-22 + +- fix the DefaultRequestContentType feature + +2016-02-14 + +- take the qualify factor of the Accept header mediatype into account when deciding the contentype of the response +- add constructors for custom entity accessors for xml and json + 2015-09-27 + - rename new WriteStatusAnd... to WriteHeaderAnd... for consistency 2015-09-25 + - fixed problem with changing Header after WriteHeader (issue 235) 2015-09-14 + - changed behavior of WriteHeader (immediate write) and WriteEntity (no status write) - added support for custom EntityReaderWriters. 2015-08-06 + - add support for reading entities from compressed request content - use sync.Pool for compressors of http response and request body - add Description to Parameter for documentation in Swagger UI 2015-03-20 + - add configurable logging 2015-03-18 + - if not specified, the Operation is derived from the Route function 2015-03-17 + - expose Parameter creation functions - make trace logger an interface - fix OPTIONSFilter @@ -30,21 +77,26 @@ Change history of go-restful - add Notes to Route 2014-11-27 + - (api add) PrettyPrint per response. (as proposed in #167) 2014-11-12 + - (api add) ApiVersion(.) for documentation in Swagger UI 2014-11-10 + - (api change) struct fields tagged with "description" show up in Swagger UI 2014-10-31 + - (api change) ReturnsError -> Returns - (api add) RouteBuilder.Do(aBuilder) for DRY use of RouteBuilder - fix swagger nested structs - sort Swagger response messages by code 2014-10-23 + - (api add) ReturnsError allows you to document Http codes in swagger - fixed problem with greedy CurlyRouter - (api add) Access-Control-Max-Age in CORS @@ -58,102 +110,117 @@ Change history of go-restful - (api add) ParameterNamed for detailed documentation 2014-04-16 + - (api add) expose constructor of Request for testing. 2014-06-27 + - (api add) ParameterNamed gives access to a Parameter definition and its data (for further specification). - (api add) SetCacheReadEntity allow scontrol over whether or not the request body is being cached (default true for compatibility reasons). 2014-07-03 + - (api add) CORS can be configured with a list of allowed domains 2014-03-12 + - (api add) Route path parameters can use wildcard or regular expressions. (requires CurlyRouter) 2014-02-26 + - (api add) Request now provides information about the matched Route, see method SelectedRoutePath 2014-02-17 + - (api change) renamed parameter constants (go-lint checks) 2014-01-10 - - (api add) support for CloseNotify, see http://golang.org/pkg/net/http/#CloseNotifier + +- (api add) support for CloseNotify, see http://golang.org/pkg/net/http/#CloseNotifier 2014-01-07 - - (api change) Write* methods in Response now return the error or nil. - - added example of serving HTML from a Go template. - - fixed comparing Allowed headers in CORS (is now case-insensitive) + +- (api change) Write* methods in Response now return the error or nil. +- added example of serving HTML from a Go template. +- fixed comparing Allowed headers in CORS (is now case-insensitive) 2013-11-13 - - (api add) Response knows how many bytes are written to the response body. + +- (api add) Response knows how many bytes are written to the response body. 2013-10-29 - - (api add) RecoverHandler(handler RecoverHandleFunction) to change how panic recovery is handled. Default behavior is to log and return a stacktrace. This may be a security issue as it exposes sourcecode information. + +- (api add) RecoverHandler(handler RecoverHandleFunction) to change how panic recovery is handled. Default behavior is to log and return a stacktrace. This may be a security issue as it exposes sourcecode information. 2013-10-04 - - (api add) Response knows what HTTP status has been written - - (api add) Request can have attributes (map of string->interface, also called request-scoped variables + +- (api add) Response knows what HTTP status has been written +- (api add) Request can have attributes (map of string->interface, also called request-scoped variables 2013-09-12 - - (api change) Router interface simplified - - Implemented CurlyRouter, a Router that does not use|allow regular expressions in paths + +- (api change) Router interface simplified +- Implemented CurlyRouter, a Router that does not use|allow regular expressions in paths 2013-08-05 - add OPTIONS support - add CORS support 2013-08-27 - - fixed some reported issues (see github) - - (api change) deprecated use of WriteError; use WriteErrorString instead + +- fixed some reported issues (see github) +- (api change) deprecated use of WriteError; use WriteErrorString instead 2014-04-15 - - (fix) v1.0.1 tag: fix Issue 111: WriteErrorString + +- (fix) v1.0.1 tag: fix Issue 111: WriteErrorString 2013-08-08 - - (api add) Added implementation Container: a WebServices collection with its own http.ServeMux allowing multiple endpoints per program. Existing uses of go-restful will register their services to the DefaultContainer. - - (api add) the swagger package has be extended to have a UI per container. - - if panic is detected then a small stack trace is printed (thanks to runner-mei) - - (api add) WriteErrorString to Response + +- (api add) Added implementation Container: a WebServices collection with its own http.ServeMux allowing multiple endpoints per program. Existing uses of go-restful will register their services to the DefaultContainer. +- (api add) the swagger package has be extended to have a UI per container. +- if panic is detected then a small stack trace is printed (thanks to runner-mei) +- (api add) WriteErrorString to Response Important API changes: - - (api remove) package variable DoNotRecover no longer works ; use restful.DefaultContainer.DoNotRecover(true) instead. - - (api remove) package variable EnableContentEncoding no longer works ; use restful.DefaultContainer.EnableContentEncoding(true) instead. +- (api remove) package variable DoNotRecover no longer works ; use restful.DefaultContainer.DoNotRecover(true) instead. +- (api remove) package variable EnableContentEncoding no longer works ; use restful.DefaultContainer.EnableContentEncoding(true) instead. 2013-07-06 - - (api add) Added support for response encoding (gzip and deflate(zlib)). This feature is disabled on default (for backwards compatibility). Use restful.EnableContentEncoding = true in your initialization to enable this feature. +- (api add) Added support for response encoding (gzip and deflate(zlib)). This feature is disabled on default (for backwards compatibility). Use restful.EnableContentEncoding = true in your initialization to enable this feature. 2013-06-19 - - (improve) DoNotRecover option, moved request body closer, improved ReadEntity +- (improve) DoNotRecover option, moved request body closer, improved ReadEntity 2013-06-03 - - (api change) removed Dispatcher interface, hide PathExpression - - changed receiver names of type functions to be more idiomatic Go +- (api change) removed Dispatcher interface, hide PathExpression +- changed receiver names of type functions to be more idiomatic Go 2013-06-02 - - (optimize) Cache the RegExp compilation of Paths. +- (optimize) Cache the RegExp compilation of Paths. 2013-05-22 - - (api add) Added support for request/response filter functions +- (api add) Added support for request/response filter functions 2013-05-18 - - (api add) Added feature to change the default Http Request Dispatch function (travis cline) - - (api change) Moved Swagger Webservice to swagger package (see example restful-user) +- (api add) Added feature to change the default Http Request Dispatch function (travis cline) +- (api change) Moved Swagger Webservice to swagger package (see example restful-user) [2012-11-14 .. 2013-05-18> - - See https://github.com/emicklei/go-restful/commits +- See https://github.com/emicklei/go-restful/commits 2012-11-14 - - Initial commit +- Initial commit diff --git a/vendor/github.com/emicklei/go-restful/Makefile b/vendor/github.com/emicklei/go-restful/Makefile new file mode 100644 index 000000000..b40081cc0 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful/Makefile @@ -0,0 +1,7 @@ +all: test + +test: + go test -v . + +ex: + cd examples && ls *.go | xargs go build -o /tmp/ignore
\ No newline at end of file diff --git a/vendor/github.com/emicklei/go-restful/README.md b/vendor/github.com/emicklei/go-restful/README.md index 8f954c016..002a08d96 100644 --- a/vendor/github.com/emicklei/go-restful/README.md +++ b/vendor/github.com/emicklei/go-restful/README.md @@ -1,8 +1,13 @@ go-restful ========== - package for building REST-style Web Services using Google Go +[](https://travis-ci.org/emicklei/go-restful) +[](https://goreportcard.com/report/github.com/emicklei/go-restful) +[](https://godoc.org/github.com/emicklei/go-restful) + +- [Code examples](https://github.com/emicklei/go-restful/tree/master/examples) + REST asks developers to use HTTP methods explicitly and in a way that's consistent with the protocol definition. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping: - GET = Retrieve a representation of a resource @@ -40,35 +45,31 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo - Routes for request → function mapping with path parameter (e.g. {id}) support - Configurable router: - - Routing algorithm after [JSR311](http://jsr311.java.net/nonav/releases/1.1/spec/spec.html) that is implemented using (but doest **not** accept) regular expressions (See RouterJSR311 which is used by default) - - Fast routing algorithm that allows static elements, regular expressions and dynamic parameters in the URL path (e.g. /meetings/{id} or /static/{subpath:*}, See CurlyRouter) + - (default) Fast routing algorithm that allows static elements, regular expressions and dynamic parameters in the URL path (e.g. /meetings/{id} or /static/{subpath:*} + - Routing algorithm after [JSR311](http://jsr311.java.net/nonav/releases/1.1/spec/spec.html) that is implemented using (but does **not** accept) regular expressions - Request API for reading structs from JSON/XML and accesing parameters (path,query,header) - Response API for writing structs to JSON/XML and setting headers +- Customizable encoding using EntityReaderWriter registration - Filters for intercepting the request → response flow on Service or Route level - Request-scoped variables using attributes - Containers for WebServices on different HTTP endpoints - Content encoding (gzip,deflate) of request and response payloads - Automatic responses on OPTIONS (using a filter) - Automatic CORS request handling (using a filter) -- API declaration for Swagger UI (see swagger package) +- API declaration for Swagger UI ([go-restful-openapi](https://github.com/emicklei/go-restful-openapi), see [go-restful-swagger12](https://github.com/emicklei/go-restful-swagger12)) - Panic recovery to produce HTTP 500, customizable using RecoverHandler(...) - Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...) - Configurable (trace) logging -- Customizable encoding using EntityReaderWriter registration - Customizable gzip/deflate readers and writers using CompressorProvider registration ### Resources -- [Documentation on godoc.org](http://godoc.org/github.com/emicklei/go-restful) -- [Code examples](https://github.com/emicklei/go-restful/tree/master/examples) -- [Example posted on blog](http://ernestmicklei.com/2012/11/24/go-restful-first-working-example/) -- [Design explained on blog](http://ernestmicklei.com/2012/11/11/go-restful-api-design/) +- [Example posted on blog](http://ernestmicklei.com/2012/11/go-restful-first-working-example/) +- [Design explained on blog](http://ernestmicklei.com/2012/11/go-restful-api-design/) - [sourcegraph](https://sourcegraph.com/github.com/emicklei/go-restful) -- [gopkg.in](https://gopkg.in/emicklei/go-restful.v1) +- [showcase: Zazkia - tcp proxy for testing resiliency](https://github.com/emicklei/zazkia) - [showcase: Mora - MongoDB REST Api server](https://github.com/emicklei/mora) -[](https://drone.io/github.com/emicklei/go-restful/latest) - -(c) 2012 - 2015, http://ernestmicklei.com. MIT License +Type ```git shortlog -s``` for a full list of contributors. -Type ```git shortlog -s``` for a full list of contributors.
\ No newline at end of file +© 2012 - 2017, http://ernestmicklei.com. MIT License. Contributions are welcome. diff --git a/vendor/github.com/emicklei/go-restful/compress.go b/vendor/github.com/emicklei/go-restful/compress.go index 66f3603e4..220b37712 100644 --- a/vendor/github.com/emicklei/go-restful/compress.go +++ b/vendor/github.com/emicklei/go-restful/compress.go @@ -5,10 +5,12 @@ package restful // that can be found in the LICENSE file. import ( + "bufio" "compress/gzip" "compress/zlib" "errors" "io" + "net" "net/http" "strings" ) @@ -69,6 +71,17 @@ func (c *CompressingResponseWriter) isCompressorClosed() bool { return nil == c.compressor } +// Hijack implements the Hijacker interface +// This is especially useful when combining Container.EnabledContentEncoding +// in combination with websockets (for instance gorilla/websocket) +func (c *CompressingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hijacker, ok := c.writer.(http.Hijacker) + if !ok { + return nil, nil, errors.New("ResponseWriter doesn't support Hijacker interface") + } + return hijacker.Hijack() +} + // WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested. func wantsCompressedResponse(httpRequest *http.Request) (bool, string) { header := httpRequest.Header.Get(HEADER_AcceptEncoding) diff --git a/vendor/github.com/emicklei/go-restful/compress_test.go b/vendor/github.com/emicklei/go-restful/compress_test.go index 84a93c3fc..cc3e93d54 100644 --- a/vendor/github.com/emicklei/go-restful/compress_test.go +++ b/vendor/github.com/emicklei/go-restful/compress_test.go @@ -94,7 +94,6 @@ func TestGzipDecompressRequestBody(t *testing.T) { httpRequest.Header.Set("Content-Encoding", "gzip") req.Request = httpRequest - doCacheReadEntityBytes = false doc := make(map[string]interface{}) req.ReadEntity(&doc) @@ -117,7 +116,6 @@ func TestZlibDecompressRequestBody(t *testing.T) { httpRequest.Header.Set("Content-Encoding", "deflate") req.Request = httpRequest - doCacheReadEntityBytes = false doc := make(map[string]interface{}) req.ReadEntity(&doc) diff --git a/vendor/github.com/emicklei/go-restful/compressors.go b/vendor/github.com/emicklei/go-restful/compressors.go index f028456e0..9db4a8c8e 100644 --- a/vendor/github.com/emicklei/go-restful/compressors.go +++ b/vendor/github.com/emicklei/go-restful/compressors.go @@ -9,25 +9,26 @@ import ( "compress/zlib" ) +// CompressorProvider describes a component that can provider compressors for the std methods. type CompressorProvider interface { // Returns a *gzip.Writer which needs to be released later. // Before using it, call Reset(). AcquireGzipWriter() *gzip.Writer - // Releases an aqcuired *gzip.Writer. + // Releases an acquired *gzip.Writer. ReleaseGzipWriter(w *gzip.Writer) // Returns a *gzip.Reader which needs to be released later. AcquireGzipReader() *gzip.Reader - // Releases an aqcuired *gzip.Reader. + // Releases an acquired *gzip.Reader. ReleaseGzipReader(w *gzip.Reader) // Returns a *zlib.Writer which needs to be released later. // Before using it, call Reset(). AcquireZlibWriter() *zlib.Writer - // Releases an aqcuired *zlib.Writer. + // Releases an acquired *zlib.Writer. ReleaseZlibWriter(w *zlib.Writer) } @@ -44,7 +45,7 @@ func CurrentCompressorProvider() CompressorProvider { return currentCompressorProvider } -// CompressorProvider sets the actual provider of compressors (zlib or gzip). +// SetCompressorProvider sets the actual provider of compressors (zlib or gzip). func SetCompressorProvider(p CompressorProvider) { if p == nil { panic("cannot set compressor provider to nil") diff --git a/vendor/github.com/emicklei/go-restful/container.go b/vendor/github.com/emicklei/go-restful/container.go index 59f34abea..4196180e5 100644 --- a/vendor/github.com/emicklei/go-restful/container.go +++ b/vendor/github.com/emicklei/go-restful/container.go @@ -6,6 +6,7 @@ package restful import ( "bytes" + "errors" "fmt" "net/http" "os" @@ -24,24 +25,24 @@ type Container struct { ServeMux *http.ServeMux isRegisteredOnRoot bool containerFilters []FilterFunction - doNotRecover bool // default is false + doNotRecover bool // default is true recoverHandleFunc RecoverHandleFunction serviceErrorHandleFunc ServiceErrorHandleFunction - router RouteSelector // default is a RouterJSR311, CurlyRouter is the faster alternative + router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative) contentEncodingEnabled bool // default is false } -// NewContainer creates a new Container using a new ServeMux and default router (RouterJSR311) +// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter) func NewContainer() *Container { return &Container{ webServices: []*WebService{}, ServeMux: http.NewServeMux(), isRegisteredOnRoot: false, containerFilters: []FilterFunction{}, - doNotRecover: false, + doNotRecover: true, recoverHandleFunc: logStackOnRecover, serviceErrorHandleFunc: writeServiceError, - router: RouterJSR311{}, + router: CurlyRouter{}, contentEncodingEnabled: false} } @@ -68,12 +69,12 @@ func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) { // DoNotRecover controls whether panics will be caught to return HTTP 500. // If set to true, Route functions are responsible for handling any error situation. -// Default value is false = recover from panics. This has performance implications. +// Default value is true. func (c *Container) DoNotRecover(doNot bool) { c.doNotRecover = doNot } -// Router changes the default Router (currently RouterJSR311) +// Router changes the default Router (currently CurlyRouter) func (c *Container) Router(aRouter RouteSelector) { c.router = aRouter } @@ -83,34 +84,16 @@ func (c *Container) EnableContentEncoding(enabled bool) { c.contentEncodingEnabled = enabled } -// Add a WebService to the Container. It will detect duplicate root paths and panic in that case. +// Add a WebService to the Container. It will detect duplicate root paths and exit in that case. func (c *Container) Add(service *WebService) *Container { c.webServicesLock.Lock() defer c.webServicesLock.Unlock() - // If registered on root then no additional specific mapping is needed - if !c.isRegisteredOnRoot { - pattern := c.fixedPrefixPath(service.RootPath()) - // check if root path registration is needed - if "/" == pattern || "" == pattern { - c.ServeMux.HandleFunc("/", c.dispatch) - c.isRegisteredOnRoot = true - } else { - // detect if registration already exists - alreadyMapped := false - for _, each := range c.webServices { - if each.RootPath() == service.RootPath() { - alreadyMapped = true - break - } - } - if !alreadyMapped { - c.ServeMux.HandleFunc(pattern, c.dispatch) - if !strings.HasSuffix(pattern, "/") { - c.ServeMux.HandleFunc(pattern+"/", c.dispatch) - } - } - } + + // 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() { @@ -118,24 +101,64 @@ func (c *Container) Add(service *WebService) *Container { os.Exit(1) } } - // if rootPath was not set then lazy initialize it - if len(service.rootPath) == 0 { - service.Path("/") + + // 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 } +// addHandler may set a new HandleFunc for the serveMux +// this function must run inside the critical region protected by the webServicesLock. +// returns true if the function was registered on root ("/") +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 +} + func (c *Container) Remove(ws *WebService) error { + if c.ServeMux == http.DefaultServeMux { + errMsg := fmt.Sprintf("[restful] cannot remove a WebService from a Container using the DefaultServeMux: ['%v']", ws) + log.Print(errMsg) + return errors.New(errMsg) + } c.webServicesLock.Lock() defer c.webServicesLock.Unlock() + // build a new ServeMux and re-register all WebServices + newServeMux := http.NewServeMux() newServices := []*WebService{} - for ix := range c.webServices { - if c.webServices[ix].rootPath != ws.rootPath { - newServices = append(newServices, c.webServices[ix]) + newIsRegisteredOnRoot := false + for _, each := range c.webServices { + if each.rootPath != ws.rootPath { + // If not registered on root then add specific mapping + if !newIsRegisteredOnRoot { + newIsRegisteredOnRoot = c.addHandler(each, newServeMux) + } + newServices = append(newServices, each) } } - c.webServices = newServices + c.webServices, c.ServeMux, c.isRegisteredOnRoot = newServices, newServeMux, newIsRegisteredOnRoot return nil } @@ -166,6 +189,17 @@ func writeServiceError(err ServiceError, req *Request, resp *Response) { } // Dispatch the incoming Http Request to a matching WebService. +func (c *Container) Dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) { + if httpWriter == nil { + panic("httpWriter cannot be nil") + } + if httpRequest == nil { + panic("httpRequest cannot be nil") + } + c.dispatch(httpWriter, httpRequest) +} + +// Dispatch the incoming Http Request to a matching WebService. func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) { writer := httpWriter @@ -185,12 +219,6 @@ func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.R } }() } - // 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 @@ -251,7 +279,7 @@ func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.R } // fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {} -func (c Container) fixedPrefixPath(pathspec string) string { +func fixedPrefixPath(pathspec string) string { varBegin := strings.Index(pathspec, "{") if -1 == varBegin { return pathspec @@ -260,12 +288,12 @@ func (c Container) fixedPrefixPath(pathspec string) string { } // 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) { +func (c *Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) { c.ServeMux.ServeHTTP(httpwriter, httpRequest) } // Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics. -func (c Container) Handle(pattern string, handler http.Handler) { +func (c *Container) Handle(pattern string, handler http.Handler) { c.ServeMux.Handle(pattern, handler) } @@ -295,7 +323,7 @@ func (c *Container) Filter(filter FilterFunction) { } // RegisteredWebServices returns the collections of added WebServices -func (c Container) RegisteredWebServices() []*WebService { +func (c *Container) RegisteredWebServices() []*WebService { c.webServicesLock.RLock() defer c.webServicesLock.RUnlock() result := make([]*WebService, len(c.webServices)) @@ -306,7 +334,7 @@ func (c Container) RegisteredWebServices() []*WebService { } // computeAllowedMethods returns a list of HTTP methods that are valid for a Request -func (c Container) computeAllowedMethods(req *Request) []string { +func (c *Container) computeAllowedMethods(req *Request) []string { // Go through all RegisteredWebServices() and all its Routes to collect the options methods := []string{} requestPath := req.Request.URL.Path diff --git a/vendor/github.com/emicklei/go-restful/container_test.go b/vendor/github.com/emicklei/go-restful/container_test.go index dd2552c37..491c793ab 100644 --- a/vendor/github.com/emicklei/go-restful/container_test.go +++ b/vendor/github.com/emicklei/go-restful/container_test.go @@ -59,3 +59,25 @@ func TestContainer_HandleWithFilter(t *testing.T) { t.Errorf("handler added by calling HandleWithFilter wasn't called") } } + +func TestContainerAddAndRemove(t *testing.T) { + ws1 := new(WebService).Path("/") + ws2 := new(WebService).Path("/users") + wc := NewContainer() + wc.Add(ws1) + wc.Add(ws2) + wc.Remove(ws2) + if len(wc.webServices) != 1 { + t.Errorf("expected one webservices") + } + if !wc.isRegisteredOnRoot { + t.Errorf("expected on root registered") + } + wc.Remove(ws1) + if len(wc.webServices) > 0 { + t.Errorf("expected zero webservices") + } + if wc.isRegisteredOnRoot { + t.Errorf("expected not on root registered") + } +} diff --git a/vendor/github.com/emicklei/go-restful/cors_filter.go b/vendor/github.com/emicklei/go-restful/cors_filter.go index cd9e7fd29..1efeef072 100644 --- a/vendor/github.com/emicklei/go-restful/cors_filter.go +++ b/vendor/github.com/emicklei/go-restful/cors_filter.go @@ -5,6 +5,7 @@ package restful // that can be found in the LICENSE file. import ( + "regexp" "strconv" "strings" ) @@ -19,11 +20,13 @@ import ( type CrossOriginResourceSharing struct { ExposeHeaders []string // list of Header names AllowedHeaders []string // list of Header names - AllowedDomains []string // list of allowed values for Http Origin. If empty all are allowed. + AllowedDomains []string // list of allowed values for Http Origin. An allowed value can be a regular expression to support subdomain matching. If empty all are allowed. AllowedMethods []string MaxAge int // number of seconds before requiring new Options request CookiesAllowed bool Container *Container + + allowedOriginPatterns []*regexp.Regexp // internal field for origin regexp check. } // Filter is a filter function that implements the CORS flow as documented on http://enable-cors.org/server.html @@ -37,21 +40,12 @@ func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain * chain.ProcessFilter(req, resp) return } - if len(c.AllowedDomains) > 0 { // if provided then origin must be included - included := false - for _, each := range c.AllowedDomains { - if each == origin { - included = true - break - } - } - if !included { - if trace { - traceLogger.Printf("HTTP Origin:%s is not part of %v", origin, c.AllowedDomains) - } - chain.ProcessFilter(req, resp) - return + if !c.isOriginAllowed(origin) { // check whether this origin is allowed + if trace { + traceLogger.Printf("HTTP Origin:%s is not part of %v, neither matches any part of %v", origin, c.AllowedDomains, c.allowedOriginPatterns) } + chain.ProcessFilter(req, resp) + return } if req.Request.Method != "OPTIONS" { c.doActualRequest(req, resp) @@ -74,7 +68,11 @@ func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) { if len(c.AllowedMethods) == 0 { - c.AllowedMethods = c.Container.computeAllowedMethods(req) + if c.Container == nil { + c.AllowedMethods = DefaultContainer.computeAllowedMethods(req) + } else { + c.AllowedMethods = c.Container.computeAllowedMethods(req) + } } acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod) @@ -124,13 +122,32 @@ func (c CrossOriginResourceSharing) isOriginAllowed(origin string) bool { if len(c.AllowedDomains) == 0 { return true } + allowed := false - for _, each := range c.AllowedDomains { - if each == origin { + for _, domain := range c.AllowedDomains { + if domain == origin { allowed = true break } } + + if !allowed { + if len(c.allowedOriginPatterns) == 0 { + // compile allowed domains to allowed origin patterns + allowedOriginRegexps, err := compileRegexps(c.AllowedDomains) + if err != nil { + return false + } + c.allowedOriginPatterns = allowedOriginRegexps + } + + for _, pattern := range c.allowedOriginPatterns { + if allowed = pattern.MatchString(origin); allowed { + break + } + } + } + return allowed } @@ -170,3 +187,16 @@ func (c CrossOriginResourceSharing) isValidAccessControlRequestHeader(header str } return false } + +// Take a list of strings and compile them into a list of regular expressions. +func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) { + regexps := []*regexp.Regexp{} + for _, regexpStr := range regexpStrings { + r, err := regexp.Compile(regexpStr) + if err != nil { + return regexps, err + } + regexps = append(regexps, r) + } + return regexps, nil +} diff --git a/vendor/github.com/emicklei/go-restful/cors_filter_test.go b/vendor/github.com/emicklei/go-restful/cors_filter_test.go index 9b4723089..09c5d3300 100644 --- a/vendor/github.com/emicklei/go-restful/cors_filter_test.go +++ b/vendor/github.com/emicklei/go-restful/cors_filter_test.go @@ -29,7 +29,7 @@ func TestCORSFilter_Preflight(t *testing.T) { httpRequest.Header.Set(HEADER_AccessControlRequestHeaders, "X-Custom-Header, X-Additional-Header") httpWriter := httptest.NewRecorder() - DefaultContainer.dispatch(httpWriter, httpRequest) + DefaultContainer.Dispatch(httpWriter, httpRequest) actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin) if "http://api.bob.com" != actual { @@ -78,7 +78,7 @@ func TestCORSFilter_Actual(t *testing.T) { httpRequest.Header.Set("X-Custom-Header", "value") httpWriter := httptest.NewRecorder() - DefaultContainer.dispatch(httpWriter, httpRequest) + DefaultContainer.Dispatch(httpWriter, httpRequest) actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin) if "http://api.bob.com" != actual { t.Fatal("expected: http://api.bob.com but got:" + actual) @@ -89,11 +89,15 @@ func TestCORSFilter_Actual(t *testing.T) { } var allowedDomainInput = []struct { - domains []string - origin string - accepted bool + domains []string + origin string + allowed bool }{ {[]string{}, "http://anything.com", true}, + {[]string{"example.com"}, "example.com", true}, + {[]string{"example.com"}, "not-allowed", false}, + {[]string{"not-matching.com", "example.com"}, "example.com", true}, + {[]string{".*"}, "example.com", true}, } // go test -v -test.run TestCORSFilter_AllowedDomains ...restful @@ -113,12 +117,12 @@ func TestCORSFilter_AllowedDomains(t *testing.T) { httpRequest, _ := http.NewRequest("PUT", "http://api.his.com/cors", nil) httpRequest.Header.Set(HEADER_Origin, each.origin) httpWriter := httptest.NewRecorder() - DefaultContainer.dispatch(httpWriter, httpRequest) + DefaultContainer.Dispatch(httpWriter, httpRequest) actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin) - if actual != each.origin && each.accepted { + if actual != each.origin && each.allowed { t.Fatal("expected to be accepted") } - if actual == each.origin && !each.accepted { + if actual == each.origin && !each.allowed { t.Fatal("did not expect to be accepted") } } diff --git a/vendor/github.com/emicklei/go-restful/curly.go b/vendor/github.com/emicklei/go-restful/curly.go index ce284f747..79f1f5aa2 100644 --- a/vendor/github.com/emicklei/go-restful/curly.go +++ b/vendor/github.com/emicklei/go-restful/curly.go @@ -44,16 +44,16 @@ func (c CurlyRouter) SelectRoute( } // selectRoutes return a collection of Route from a WebService that matches the path tokens from the request. -func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) []Route { - candidates := &sortableCurlyRoutes{[]*curlyRoute{}} +func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes { + candidates := sortableCurlyRoutes{} for _, each := range ws.routes { matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens) if matches { - candidates.add(&curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers? + candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers? } } sort.Sort(sort.Reverse(candidates)) - return candidates.routes() + return candidates } // matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are. @@ -108,11 +108,13 @@ func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, reque return (matched && err == nil), false } +var jsr311Router = RouterJSR311{} + // detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type // headers of the Request. See also RouterJSR311 in jsr311.go -func (c CurlyRouter) detectRoute(candidateRoutes []Route, httpRequest *http.Request) (*Route, error) { +func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) { // tracing is done inside detectRoute - return RouterJSR311{}.detectRoute(candidateRoutes, httpRequest) + return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest) } // detectWebService returns the best matching webService given the list of path tokens. diff --git a/vendor/github.com/emicklei/go-restful/curly_route.go b/vendor/github.com/emicklei/go-restful/curly_route.go index 3edab72fd..296f94650 100644 --- a/vendor/github.com/emicklei/go-restful/curly_route.go +++ b/vendor/github.com/emicklei/go-restful/curly_route.go @@ -11,30 +11,28 @@ type curlyRoute struct { staticCount int } -type sortableCurlyRoutes struct { - candidates []*curlyRoute -} +type sortableCurlyRoutes []curlyRoute -func (s *sortableCurlyRoutes) add(route *curlyRoute) { - s.candidates = append(s.candidates, route) +func (s *sortableCurlyRoutes) add(route curlyRoute) { + *s = append(*s, route) } -func (s *sortableCurlyRoutes) routes() (routes []Route) { - for _, each := range s.candidates { +func (s sortableCurlyRoutes) routes() (routes []Route) { + for _, each := range s { routes = append(routes, each.route) // TODO change return type } return routes } -func (s *sortableCurlyRoutes) Len() int { - return len(s.candidates) +func (s sortableCurlyRoutes) Len() int { + return len(s) } -func (s *sortableCurlyRoutes) Swap(i, j int) { - s.candidates[i], s.candidates[j] = s.candidates[j], s.candidates[i] +func (s sortableCurlyRoutes) Swap(i, j int) { + s[i], s[j] = s[j], s[i] } -func (s *sortableCurlyRoutes) Less(i, j int) bool { - ci := s.candidates[i] - cj := s.candidates[j] +func (s sortableCurlyRoutes) Less(i, j int) bool { + ci := s[i] + cj := s[j] // primary key if ci.staticCount < cj.staticCount { diff --git a/vendor/github.com/emicklei/go-restful/curly_test.go b/vendor/github.com/emicklei/go-restful/curly_test.go index 31d66dcbd..bec017ca7 100644 --- a/vendor/github.com/emicklei/go-restful/curly_test.go +++ b/vendor/github.com/emicklei/go-restful/curly_test.go @@ -163,12 +163,12 @@ func TestCurly_ISSUE_34(t *testing.T) { ws1 := new(WebService).Path("/") ws1.Route(ws1.GET("/{type}/{id}").To(curlyDummy)) ws1.Route(ws1.GET("/network/{id}").To(curlyDummy)) - routes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12")) - if len(routes) != 2 { + croutes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12")) + if len(croutes) != 2 { t.Fatal("expected 2 routes") } - if routes[0].Path != "/network/{id}" { - t.Error("first is", routes[0].Path) + if got, want := croutes[0].route.Path, "/network/{id}"; got != want { + t.Errorf("got %v want %v", got, want) } } @@ -177,12 +177,12 @@ func TestCurly_ISSUE_34_2(t *testing.T) { ws1 := new(WebService) ws1.Route(ws1.GET("/network/{id}").To(curlyDummy)) ws1.Route(ws1.GET("/{type}/{id}").To(curlyDummy)) - routes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12")) - if len(routes) != 2 { + croutes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12")) + if len(croutes) != 2 { t.Fatal("expected 2 routes") } - if routes[0].Path != "/network/{id}" { - t.Error("first is", routes[0].Path) + if got, want := croutes[0].route.Path, "/network/{id}"; got != want { + t.Errorf("got %v want %v", got, want) } } diff --git a/vendor/github.com/emicklei/go-restful/doc.go b/vendor/github.com/emicklei/go-restful/doc.go index d40405bf7..f7c16b01f 100644 --- a/vendor/github.com/emicklei/go-restful/doc.go +++ b/vendor/github.com/emicklei/go-restful/doc.go @@ -1,5 +1,5 @@ /* -Package restful, a lean package for creating REST-style WebServices without magic. +Package restful , a lean package for creating REST-style WebServices without magic. WebServices and Routes @@ -145,22 +145,11 @@ Performance options This package has several options that affect the performance of your service. It is important to understand them and how you can change it. - restful.DefaultContainer.Router(CurlyRouter{}) - -The default router is the RouterJSR311 which is an implementation of its spec (http://jsr311.java.net/nonav/releases/1.1/spec/spec.html). -However, it uses regular expressions for all its routes which, depending on your usecase, may consume a significant amount of time. -The CurlyRouter implementation is more lightweight that also allows you to use wildcards and expressions, but only if needed. - - restful.DefaultContainer.DoNotRecover(true) + restful.DefaultContainer.DoNotRecover(false) DoNotRecover controls whether panics will be caught to return HTTP 500. -If set to true, Route functions are responsible for handling any error situation. -Default value is false; it will recover from panics. This has performance implications. - - restful.SetCacheReadEntity(false) - -SetCacheReadEntity controls whether the response data ([]byte) is cached such that ReadEntity is repeatable. -If you expect to read large amounts of payload data, and you do not use this feature, you should set it to false. +If set to false, the container will recover from panics. +Default value is true restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20)) diff --git a/vendor/github.com/emicklei/go-restful/entity_accessors.go b/vendor/github.com/emicklei/go-restful/entity_accessors.go index e3ab79d9b..6ecf6c7f8 100644 --- a/vendor/github.com/emicklei/go-restful/entity_accessors.go +++ b/vendor/github.com/emicklei/go-restful/entity_accessors.go @@ -36,8 +36,8 @@ type entityReaderWriters struct { } func init() { - RegisterEntityAccessor(MIME_JSON, entityJSONAccess{ContentType: MIME_JSON}) - RegisterEntityAccessor(MIME_XML, entityXMLAccess{ContentType: MIME_XML}) + RegisterEntityAccessor(MIME_JSON, NewEntityAccessorJSON(MIME_JSON)) + RegisterEntityAccessor(MIME_XML, NewEntityAccessorXML(MIME_XML)) } // RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type. @@ -47,8 +47,20 @@ func RegisterEntityAccessor(mime string, erw EntityReaderWriter) { entityAccessRegistry.accessors[mime] = erw } -// AccessorAt returns the registered ReaderWriter for this MIME type. -func (r *entityReaderWriters) AccessorAt(mime string) (EntityReaderWriter, bool) { +// NewEntityAccessorJSON returns a new EntityReaderWriter for accessing JSON content. +// This package is already initialized with such an accessor using the MIME_JSON contentType. +func NewEntityAccessorJSON(contentType string) EntityReaderWriter { + return entityJSONAccess{ContentType: contentType} +} + +// NewEntityAccessorXML returns a new EntityReaderWriter for accessing XML content. +// This package is already initialized with such an accessor using the MIME_XML contentType. +func NewEntityAccessorXML(contentType string) EntityReaderWriter { + return entityXMLAccess{ContentType: contentType} +} + +// accessorAt returns the registered ReaderWriter for this MIME type. +func (r *entityReaderWriters) accessorAt(mime string) (EntityReaderWriter, bool) { r.protection.RLock() defer r.protection.RUnlock() er, ok := r.accessors[mime] diff --git a/vendor/github.com/emicklei/go-restful/entity_accessors_test.go b/vendor/github.com/emicklei/go-restful/entity_accessors_test.go index 943093ae0..d1c1e1585 100644 --- a/vendor/github.com/emicklei/go-restful/entity_accessors_test.go +++ b/vendor/github.com/emicklei/go-restful/entity_accessors_test.go @@ -49,7 +49,7 @@ func TestKeyValueEncoding(t *testing.T) { // Write httpWriter := httptest.NewRecorder() // Accept Produces - resp := Response{httpWriter, "application/kv,*/*;q=0.8", []string{"application/kv"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "application/kv,*/*;q=0.8", routeProduces: []string{"application/kv"}, prettyPrint: true} resp.WriteEntity(b) t.Log(string(httpWriter.Body.Bytes())) if !kv.writeCalled { diff --git a/vendor/github.com/emicklei/go-restful/filter.go b/vendor/github.com/emicklei/go-restful/filter.go index 4b86656e1..c23bfb591 100644 --- a/vendor/github.com/emicklei/go-restful/filter.go +++ b/vendor/github.com/emicklei/go-restful/filter.go @@ -24,3 +24,12 @@ func (f *FilterChain) ProcessFilter(request *Request, response *Response) { // FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction type FilterFunction func(*Request, *Response, *FilterChain) + +// NoBrowserCacheFilter is a filter function to set HTTP headers that disable browser caching +// See examples/restful-no-cache-filter.go for usage +func NoBrowserCacheFilter(req *Request, resp *Response, chain *FilterChain) { + resp.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") // HTTP 1.1. + resp.Header().Set("Pragma", "no-cache") // HTTP 1.0. + resp.Header().Set("Expires", "0") // Proxies. + chain.ProcessFilter(req, resp) +} diff --git a/vendor/github.com/emicklei/go-restful/install.sh b/vendor/github.com/emicklei/go-restful/install.sh deleted file mode 100644 index 5fe03b569..000000000 --- a/vendor/github.com/emicklei/go-restful/install.sh +++ /dev/null @@ -1,9 +0,0 @@ -cd examples - ls *.go | xargs -I {} go build -o /tmp/ignore {} - cd .. -go fmt ...swagger && \ -go test -test.v ...swagger && \ -go install ...swagger && \ -go fmt ...restful && \ -go test -test.v ...restful && \ -go install ...restful
\ No newline at end of file diff --git a/vendor/github.com/emicklei/go-restful/jsr311.go b/vendor/github.com/emicklei/go-restful/jsr311.go index b4fa9bbae..9e8122416 100644 --- a/vendor/github.com/emicklei/go-restful/jsr311.go +++ b/vendor/github.com/emicklei/go-restful/jsr311.go @@ -41,9 +41,29 @@ func (r RouterJSR311) SelectRoute( // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) { + ifOk := []Route{} + for _, each := range routes { + ok := true + for _, fn := range each.If { + if !fn(httpRequest) { + ok = false + break + } + } + if ok { + ifOk = append(ifOk, each) + } + } + if len(ifOk) == 0 { + if trace { + traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes)) + } + return nil, NewError(http.StatusNotFound, "404: Not Found") + } + // http method methodOk := []Route{} - for _, each := range routes { + for _, each := range ifOk { if httpRequest.Method == each.Method { methodOk = append(methodOk, each) } @@ -74,7 +94,7 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R // accept outputMediaOk := []Route{} accept := httpRequest.Header.Get(HEADER_Accept) - if accept == "" { + if len(accept) == 0 { accept = "*/*" } for _, each := range inputMediaOk { @@ -88,7 +108,8 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R } return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable") } - return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil + // return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil + return &outputMediaOk[0], nil } // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 diff --git a/vendor/github.com/emicklei/go-restful/jsr311_test.go b/vendor/github.com/emicklei/go-restful/jsr311_test.go index 3e79a6def..ecde60366 100644 --- a/vendor/github.com/emicklei/go-restful/jsr311_test.go +++ b/vendor/github.com/emicklei/go-restful/jsr311_test.go @@ -2,6 +2,7 @@ package restful import ( "io" + "net/http" "sort" "testing" ) @@ -209,4 +210,42 @@ func TestSortableRouteCandidates(t *testing.T) { } } +func TestDetectRouteReturns404IfNoRoutePassesConditions(t *testing.T) { + called := false + shouldNotBeCalledButWas := false + + routes := []Route{ + new(RouteBuilder).To(dummy). + If(func(req *http.Request) bool { return false }). + Build(), + + // check that condition functions are called in order + new(RouteBuilder). + To(dummy). + If(func(req *http.Request) bool { return true }). + If(func(req *http.Request) bool { called = true; return false }). + Build(), + + // check that condition functions short circuit + new(RouteBuilder). + To(dummy). + If(func(req *http.Request) bool { return false }). + If(func(req *http.Request) bool { shouldNotBeCalledButWas = true; return false }). + Build(), + } + + _, err := RouterJSR311{}.detectRoute(routes, (*http.Request)(nil)) + if se := err.(ServiceError); se.Code != 404 { + t.Fatalf("expected 404, got %d", se.Code) + } + + if !called { + t.Fatal("expected condition function to get called, but it wasn't") + } + + if shouldNotBeCalledButWas { + t.Fatal("expected condition function to not be called, but it was") + } +} + func dummy(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "dummy") } diff --git a/vendor/github.com/emicklei/go-restful/log/log.go b/vendor/github.com/emicklei/go-restful/log/log.go index f70d89524..6cd44c7a5 100644 --- a/vendor/github.com/emicklei/go-restful/log/log.go +++ b/vendor/github.com/emicklei/go-restful/log/log.go @@ -5,7 +5,7 @@ import ( "os" ) -// Logger corresponds to a minimal subset of the interface satisfied by stdlib log.Logger +// StdLogger corresponds to a minimal subset of the interface satisfied by stdlib log.Logger type StdLogger interface { Print(v ...interface{}) Printf(format string, v ...interface{}) @@ -18,14 +18,17 @@ func init() { SetLogger(stdlog.New(os.Stderr, "[restful] ", stdlog.LstdFlags|stdlog.Lshortfile)) } +// SetLogger sets the logger for this package func SetLogger(customLogger StdLogger) { Logger = customLogger } +// Print delegates to the Logger func Print(v ...interface{}) { Logger.Print(v...) } +// Printf delegates to the Logger func Printf(format string, v ...interface{}) { Logger.Printf(format, v...) } diff --git a/vendor/github.com/emicklei/go-restful/logger.go b/vendor/github.com/emicklei/go-restful/logger.go index 3f1c4db86..6595df002 100644 --- a/vendor/github.com/emicklei/go-restful/logger.go +++ b/vendor/github.com/emicklei/go-restful/logger.go @@ -21,7 +21,7 @@ func TraceLogger(logger log.StdLogger) { EnableTracing(logger != nil) } -// expose the setter for the global logger on the top-level package +// SetLogger exposes the setter for the global logger on the top-level package func SetLogger(customLogger log.StdLogger) { log.SetLogger(customLogger) } diff --git a/vendor/github.com/emicklei/go-restful/mime.go b/vendor/github.com/emicklei/go-restful/mime.go new file mode 100644 index 000000000..d7ea2b615 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful/mime.go @@ -0,0 +1,45 @@ +package restful + +import ( + "strconv" + "strings" +) + +type mime struct { + media string + quality float64 +} + +// insertMime adds a mime to a list and keeps it sorted by quality. +func insertMime(l []mime, e mime) []mime { + for i, each := range l { + // if current mime has lower quality then insert before + if e.quality > each.quality { + left := append([]mime{}, l[0:i]...) + return append(append(left, e), l[i:]...) + } + } + return append(l, e) +} + +// sortedMimes returns a list of mime sorted (desc) by its specified quality. +func sortedMimes(accept string) (sorted []mime) { + for _, each := range strings.Split(accept, ",") { + typeAndQuality := strings.Split(strings.Trim(each, " "), ";") + if len(typeAndQuality) == 1 { + sorted = insertMime(sorted, mime{typeAndQuality[0], 1.0}) + } else { + // take factor + parts := strings.Split(typeAndQuality[1], "=") + if len(parts) == 2 { + f, err := strconv.ParseFloat(parts[1], 64) + if err != nil { + traceLogger.Printf("unable to parse quality in %s, %v", each, err) + } else { + sorted = insertMime(sorted, mime{typeAndQuality[0], f}) + } + } + } + } + return +} diff --git a/vendor/github.com/emicklei/go-restful/mime_test.go b/vendor/github.com/emicklei/go-restful/mime_test.go new file mode 100644 index 000000000..a910bb100 --- /dev/null +++ b/vendor/github.com/emicklei/go-restful/mime_test.go @@ -0,0 +1,17 @@ +package restful + +import ( + "fmt" + "testing" +) + +// go test -v -test.run TestSortMimes ...restful +func TestSortMimes(t *testing.T) { + accept := "text/html; q=0.8, text/plain, image/gif, */*; q=0.01, image/jpeg" + result := sortedMimes(accept) + got := fmt.Sprintf("%v", result) + want := "[{text/plain 1} {image/gif 1} {image/jpeg 1} {text/html 0.8} {*/* 0.01}]" + if got != want { + t.Errorf("bad sort order of mime types:%s", got) + } +} diff --git a/vendor/github.com/emicklei/go-restful/options_filter.go b/vendor/github.com/emicklei/go-restful/options_filter.go index 4514eadcf..5c1b34251 100644 --- a/vendor/github.com/emicklei/go-restful/options_filter.go +++ b/vendor/github.com/emicklei/go-restful/options_filter.go @@ -15,7 +15,15 @@ func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterCha chain.ProcessFilter(req, resp) return } - resp.AddHeader(HEADER_Allow, strings.Join(c.computeAllowedMethods(req), ",")) + + archs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders) + methods := strings.Join(c.computeAllowedMethods(req), ",") + origin := req.Request.Header.Get(HEADER_Origin) + + resp.AddHeader(HEADER_Allow, methods) + resp.AddHeader(HEADER_AccessControlAllowOrigin, origin) + resp.AddHeader(HEADER_AccessControlAllowHeaders, archs) + resp.AddHeader(HEADER_AccessControlAllowMethods, methods) } // OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method diff --git a/vendor/github.com/emicklei/go-restful/request.go b/vendor/github.com/emicklei/go-restful/request.go index 988adc984..8c23af12c 100644 --- a/vendor/github.com/emicklei/go-restful/request.go +++ b/vendor/github.com/emicklei/go-restful/request.go @@ -5,20 +5,15 @@ package restful // that can be found in the LICENSE file. import ( - "bytes" "compress/zlib" - "io/ioutil" "net/http" ) var defaultRequestContentType string -var doCacheReadEntityBytes = true - // Request is a wrapper for a http Request that provides convenience methods type Request struct { Request *http.Request - bodyContent *[]byte // to cache the request body for multiple reads of ReadEntity pathParameters map[string]string attributes map[string]interface{} // for storing request-scoped values selectedRoutePath string // root path + route path that matched the request, e.g. /meetings/{id}/attendees @@ -41,12 +36,6 @@ func DefaultRequestContentType(mime string) { defaultRequestContentType = mime } -// SetCacheReadEntity controls whether the response data ([]byte) is cached such that ReadEntity is repeatable. -// Default is true (due to backwardcompatibility). For better performance, you should set it to false if you don't need it. -func SetCacheReadEntity(doCache bool) { - doCacheReadEntityBytes = doCache -} - // PathParameter accesses the Path parameter value by its name func (r *Request) PathParameter(name string) string { return r.pathParameters[name] @@ -81,18 +70,6 @@ func (r *Request) ReadEntity(entityPointer interface{}) (err error) { contentType := r.Request.Header.Get(HEADER_ContentType) contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding) - // OLD feature, cache the body for reads - if doCacheReadEntityBytes { - if r.bodyContent == nil { - data, err := ioutil.ReadAll(r.Request.Body) - if err != nil { - return err - } - r.bodyContent = &data - } - r.Request.Body = ioutil.NopCloser(bytes.NewReader(*r.bodyContent)) - } - // check if the request body needs decompression if ENCODING_GZIP == contentEncoding { gzipReader := currentCompressorProvider.AcquireGzipReader() @@ -107,10 +84,15 @@ func (r *Request) ReadEntity(entityPointer interface{}) (err error) { r.Request.Body = zlibReader } - // lookup the EntityReader - entityReader, ok := entityAccessRegistry.AccessorAt(contentType) + // lookup the EntityReader, use defaultRequestContentType if needed and provided + entityReader, ok := entityAccessRegistry.accessorAt(contentType) if !ok { - return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType) + if len(defaultRequestContentType) != 0 { + entityReader, ok = entityAccessRegistry.accessorAt(defaultRequestContentType) + } + if !ok { + return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType) + } } return entityReader.Read(r, entityPointer) } diff --git a/vendor/github.com/emicklei/go-restful/request_test.go b/vendor/github.com/emicklei/go-restful/request_test.go index 72f078f92..31f509659 100644 --- a/vendor/github.com/emicklei/go-restful/request_test.go +++ b/vendor/github.com/emicklei/go-restful/request_test.go @@ -29,38 +29,6 @@ type Sample struct { Value string } -func TestReadEntityXmlCached(t *testing.T) { - SetCacheReadEntity(true) - bodyReader := strings.NewReader("<Sample><Value>42</Value></Sample>") - httpRequest, _ := http.NewRequest("GET", "/test", bodyReader) - httpRequest.Header.Set("Content-Type", "application/xml") - request := &Request{Request: httpRequest} - sam := new(Sample) - request.ReadEntity(sam) - if sam.Value != "42" { - t.Fatal("read failed") - } - if request.bodyContent == nil { - t.Fatal("no expected cached bytes found") - } -} - -func TestReadEntityXmlNonCached(t *testing.T) { - SetCacheReadEntity(false) - bodyReader := strings.NewReader("<Sample><Value>42</Value></Sample>") - httpRequest, _ := http.NewRequest("GET", "/test", bodyReader) - httpRequest.Header.Set("Content-Type", "application/xml") - request := &Request{Request: httpRequest} - sam := new(Sample) - request.ReadEntity(sam) - if sam.Value != "42" { - t.Fatal("read failed") - } - if request.bodyContent != nil { - t.Fatal("unexpected cached bytes found") - } -} - func TestReadEntityJson(t *testing.T) { bodyReader := strings.NewReader(`{"Value" : "42"}`) httpRequest, _ := http.NewRequest("GET", "/test", bodyReader) @@ -86,37 +54,6 @@ func TestReadEntityJsonCharset(t *testing.T) { } func TestReadEntityJsonNumber(t *testing.T) { - SetCacheReadEntity(true) - bodyReader := strings.NewReader(`{"Value" : 4899710515899924123}`) - httpRequest, _ := http.NewRequest("GET", "/test", bodyReader) - httpRequest.Header.Set("Content-Type", "application/json") - request := &Request{Request: httpRequest} - any := make(Anything) - request.ReadEntity(&any) - number, ok := any["Value"].(json.Number) - if !ok { - t.Fatal("read failed") - } - vint, err := number.Int64() - if err != nil { - t.Fatal("convert failed") - } - if vint != 4899710515899924123 { - t.Fatal("read failed") - } - vfloat, err := number.Float64() - if err != nil { - t.Fatal("convert failed") - } - // match the default behaviour - vstring := strconv.FormatFloat(vfloat, 'e', 15, 64) - if vstring != "4.899710515899924e+18" { - t.Fatal("convert float64 failed") - } -} - -func TestReadEntityJsonNumberNonCached(t *testing.T) { - SetCacheReadEntity(false) bodyReader := strings.NewReader(`{"Value" : 4899710515899924123}`) httpRequest, _ := http.NewRequest("GET", "/test", bodyReader) httpRequest.Header.Set("Content-Type", "application/json") diff --git a/vendor/github.com/emicklei/go-restful/response.go b/vendor/github.com/emicklei/go-restful/response.go index 3798f18c8..4d987d130 100644 --- a/vendor/github.com/emicklei/go-restful/response.go +++ b/vendor/github.com/emicklei/go-restful/response.go @@ -5,12 +5,13 @@ package restful // that can be found in the LICENSE file. import ( + "bufio" "errors" + "net" "net/http" - "strings" ) -// DEPRECATED, use DefaultResponseContentType(mime) +// DefaultResponseMimeType is DEPRECATED, use DefaultResponseContentType(mime) var DefaultResponseMimeType string //PrettyPrintResponses controls the indentation feature of XML and JSON serialization @@ -20,19 +21,22 @@ var PrettyPrintResponses = true // It provides several convenience methods to prepare and write response content. type Response struct { http.ResponseWriter - requestAccept string // mime-type what the Http Request says it wants to receive - routeProduces []string // mime-types what the Route says it can produce - statusCode int // HTTP status code that has been written explicity (if zero then net/http has written 200) - contentLength int // number of bytes written for the response body - prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses. - err error // err property is kept when WriteError is called + requestAccept string // mime-type what the Http Request says it wants to receive + routeProduces []string // mime-types what the Route says it can produce + statusCode int // HTTP status code that has been written explicitly (if zero then net/http has written 200) + contentLength int // number of bytes written for the response body + prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses. + err error // err property is kept when WriteError is called + hijacker http.Hijacker // if underlying ResponseWriter supports it } -// Creates a new response based on a http ResponseWriter. +// NewResponse creates a new response based on a http ResponseWriter. func NewResponse(httpWriter http.ResponseWriter) *Response { - return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses, nil} // empty content-types + hijacker, _ := httpWriter.(http.Hijacker) + return &Response{ResponseWriter: httpWriter, routeProduces: []string{}, statusCode: http.StatusOK, prettyPrint: PrettyPrintResponses, hijacker: hijacker} } +// DefaultResponseContentType set a default. // If Accept header matching fails, fall back to this type. // Valid values are restful.MIME_JSON and restful.MIME_XML // Example: @@ -48,6 +52,16 @@ func (r Response) InternalServerError() Response { return r } +// Hijack implements the http.Hijacker interface. This expands +// the Response to fulfill http.Hijacker if the underlying +// http.ResponseWriter supports it. +func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if r.hijacker == nil { + return nil, nil, errors.New("http.Hijacker not implemented by underlying http.ResponseWriter") + } + return r.hijacker.Hijack() +} + // PrettyPrint changes whether this response must produce pretty (line-by-line, indented) JSON or XML output. func (r *Response) PrettyPrint(bePretty bool) { r.prettyPrint = bePretty @@ -68,38 +82,39 @@ func (r *Response) SetRequestAccepts(mime string) { // can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say. // If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable. func (r *Response) EntityWriter() (EntityReaderWriter, bool) { - for _, qualifiedMime := range strings.Split(r.requestAccept, ",") { - mime := strings.Trim(strings.Split(qualifiedMime, ";")[0], " ") - if 0 == len(mime) || mime == "*/*" { - for _, each := range r.routeProduces { - if MIME_JSON == each { - return entityAccessRegistry.AccessorAt(MIME_JSON) - } - if MIME_XML == each { - return entityAccessRegistry.AccessorAt(MIME_XML) + sorted := sortedMimes(r.requestAccept) + for _, eachAccept := range sorted { + for _, eachProduce := range r.routeProduces { + if eachProduce == eachAccept.media { + if w, ok := entityAccessRegistry.accessorAt(eachAccept.media); ok { + return w, true } } - } else { // mime is not blank; see if we have a match in Produces + } + if eachAccept.media == "*/*" { for _, each := range r.routeProduces { - if mime == each { - if MIME_JSON == each { - return entityAccessRegistry.AccessorAt(MIME_JSON) - } - if MIME_XML == each { - return entityAccessRegistry.AccessorAt(MIME_XML) - } + if w, ok := entityAccessRegistry.accessorAt(each); ok { + return w, true } } } } - writer, ok := entityAccessRegistry.AccessorAt(r.requestAccept) + // if requestAccept is empty + writer, ok := entityAccessRegistry.accessorAt(r.requestAccept) if !ok { // if not registered then fallback to the defaults (if set) if DefaultResponseMimeType == MIME_JSON { - return entityAccessRegistry.AccessorAt(MIME_JSON) + return entityAccessRegistry.accessorAt(MIME_JSON) } if DefaultResponseMimeType == MIME_XML { - return entityAccessRegistry.AccessorAt(MIME_XML) + return entityAccessRegistry.accessorAt(MIME_XML) + } + // Fallback to whatever the route says it can produce. + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + for _, each := range r.routeProduces { + if w, ok := entityAccessRegistry.accessorAt(each); ok { + return w, true + } } if trace { traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept) @@ -130,25 +145,25 @@ func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error { } // WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value) -// It uses the standard encoding/xml package for marshalling the valuel ; not using a registered EntityReaderWriter. +// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter. func (r *Response) WriteAsXml(value interface{}) error { return writeXML(r, http.StatusOK, MIME_XML, value) } // WriteHeaderAndXml is a convenience method for writing a status and value in xml (requires Xml tags on the value) -// It uses the standard encoding/xml package for marshalling the valuel ; not using a registered EntityReaderWriter. +// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter. func (r *Response) WriteHeaderAndXml(status int, value interface{}) error { return writeXML(r, status, MIME_XML, value) } // WriteAsJson is a convenience method for writing a value in json. -// It uses the standard encoding/json package for marshalling the valuel ; not using a registered EntityReaderWriter. +// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter. func (r *Response) WriteAsJson(value interface{}) error { return writeJSON(r, http.StatusOK, MIME_JSON, value) } // WriteJson is a convenience method for writing a value in Json with a given Content-Type. -// It uses the standard encoding/json package for marshalling the valuel ; not using a registered EntityReaderWriter. +// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter. func (r *Response) WriteJson(value interface{}, contentType string) error { return writeJSON(r, http.StatusOK, contentType, value) } @@ -184,6 +199,15 @@ func (r *Response) WriteErrorString(httpStatus int, errorReason string) error { return nil } +// Flush implements http.Flusher interface, which sends any buffered data to the client. +func (r *Response) Flush() { + if f, ok := r.ResponseWriter.(http.Flusher); ok { + f.Flush() + } else if trace { + traceLogger.Printf("ResponseWriter %v doesn't support Flush", r) + } +} + // WriteHeader is overridden to remember the Status Code that has been written. // Changes to the Header of the response have no effect after this. func (r *Response) WriteHeader(httpStatus int) { diff --git a/vendor/github.com/emicklei/go-restful/response_test.go b/vendor/github.com/emicklei/go-restful/response_test.go index c8354f8ae..0587c40b4 100644 --- a/vendor/github.com/emicklei/go-restful/response_test.go +++ b/vendor/github.com/emicklei/go-restful/response_test.go @@ -10,7 +10,7 @@ import ( func TestWriteHeader(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true} resp.WriteHeader(123) if resp.StatusCode() != 123 { t.Errorf("Unexpected status code:%d", resp.StatusCode()) @@ -19,7 +19,7 @@ func TestWriteHeader(t *testing.T) { func TestNoWriteHeader(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true} if resp.StatusCode() != http.StatusOK { t.Errorf("Unexpected status code:%d", resp.StatusCode()) } @@ -32,7 +32,7 @@ type food struct { // go test -v -test.run TestMeasureContentLengthXml ...restful func TestMeasureContentLengthXml(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true} resp.WriteAsXml(food{"apple"}) if resp.ContentLength() != 76 { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) @@ -42,7 +42,7 @@ func TestMeasureContentLengthXml(t *testing.T) { // go test -v -test.run TestMeasureContentLengthJson ...restful func TestMeasureContentLengthJson(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true} resp.WriteAsJson(food{"apple"}) if resp.ContentLength() != 22 { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) @@ -52,7 +52,7 @@ func TestMeasureContentLengthJson(t *testing.T) { // go test -v -test.run TestMeasureContentLengthJsonNotPretty ...restful func TestMeasureContentLengthJsonNotPretty(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}} resp.WriteAsJson(food{"apple"}) if resp.ContentLength() != 17 { // 16+1 using the Encoder directly yields another /n t.Errorf("Incorrect measured length:%d", resp.ContentLength()) @@ -62,7 +62,7 @@ func TestMeasureContentLengthJsonNotPretty(t *testing.T) { // go test -v -test.run TestMeasureContentLengthWriteErrorString ...restful func TestMeasureContentLengthWriteErrorString(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true} resp.WriteErrorString(404, "Invalid") if resp.ContentLength() != len("Invalid") { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) @@ -80,7 +80,7 @@ func TestStatusIsPassedToResponse(t *testing.T) { {write: 400, read: 400}, } { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true} resp.WriteHeader(each.write) if got, want := httpWriter.Code, each.read; got != want { t.Errorf("got %v want %v", got, want) @@ -91,11 +91,11 @@ func TestStatusIsPassedToResponse(t *testing.T) { // go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue54 ...restful func TestStatusCreatedAndContentTypeJson_Issue54(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "application/json", routeProduces: []string{"application/json"}, prettyPrint: true} resp.WriteHeader(201) resp.WriteAsJson(food{"Juicy"}) if httpWriter.HeaderMap.Get("Content-Type") != "application/json" { - t.Errorf("Expected content type json but got:%d", httpWriter.HeaderMap.Get("Content-Type")) + t.Errorf("Expected content type json but got:%s", httpWriter.HeaderMap.Get("Content-Type")) } if httpWriter.Code != 201 { t.Errorf("Expected status 201 but got:%d", httpWriter.Code) @@ -113,7 +113,7 @@ func (e errorOnWriteRecorder) Write(bytes []byte) (int, error) { // go test -v -test.run TestLastWriteErrorCaught ...restful func TestLastWriteErrorCaught(t *testing.T) { httpWriter := errorOnWriteRecorder{httptest.NewRecorder()} - resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "application/json", routeProduces: []string{"application/json"}, prettyPrint: true} err := resp.WriteAsJson(food{"Juicy"}) if err.Error() != "fail" { t.Errorf("Unexpected error message:%v", err) @@ -124,7 +124,7 @@ func TestLastWriteErrorCaught(t *testing.T) { func TestAcceptStarStar_Issue83(t *testing.T) { httpWriter := httptest.NewRecorder() // Accept Produces - resp := Response{httpWriter, "application/bogus,*/*;q=0.8", []string{"application/json"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "application/bogus,*/*;q=0.8", routeProduces: []string{"application/json"}, prettyPrint: true} resp.WriteEntity(food{"Juicy"}) ct := httpWriter.Header().Get("Content-Type") if "application/json" != ct { @@ -136,7 +136,7 @@ func TestAcceptStarStar_Issue83(t *testing.T) { func TestAcceptSkipStarStar_Issue83(t *testing.T) { httpWriter := httptest.NewRecorder() // Accept Produces - resp := Response{httpWriter, " application/xml ,*/* ; q=0.8", []string{"application/json", "application/xml"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: " application/xml ,*/* ; q=0.8", routeProduces: []string{"application/json", "application/xml"}, prettyPrint: true} resp.WriteEntity(food{"Juicy"}) ct := httpWriter.Header().Get("Content-Type") if "application/xml" != ct { @@ -148,7 +148,7 @@ func TestAcceptSkipStarStar_Issue83(t *testing.T) { func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) { httpWriter := httptest.NewRecorder() // Accept Produces - resp := Response{httpWriter, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", []string{"application/json"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", routeProduces: []string{"application/json"}, prettyPrint: true} resp.WriteEntity(food{"Juicy"}) ct := httpWriter.Header().Get("Content-Type") if "application/json" != ct { @@ -159,7 +159,7 @@ func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) { // go test -v -test.run TestWriteHeaderNoContent_Issue124 ...restful func TestWriteHeaderNoContent_Issue124(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "text/plain", []string{"text/plain"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "text/plain", routeProduces: []string{"text/plain"}, prettyPrint: true} resp.WriteHeader(http.StatusNoContent) if httpWriter.Code != http.StatusNoContent { t.Errorf("got %d want %d", httpWriter.Code, http.StatusNoContent) @@ -169,7 +169,7 @@ func TestWriteHeaderNoContent_Issue124(t *testing.T) { // go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue163 ...restful func TestStatusCreatedAndContentTypeJson_Issue163(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "application/json", routeProduces: []string{"application/json"}, prettyPrint: true} resp.WriteHeader(http.StatusNotModified) if httpWriter.Code != http.StatusNotModified { t.Errorf("Got %d want %d", httpWriter.Code, http.StatusNotModified) @@ -178,7 +178,7 @@ func TestStatusCreatedAndContentTypeJson_Issue163(t *testing.T) { func TestWriteHeaderAndEntity_Issue235(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "application/json", routeProduces: []string{"application/json"}, prettyPrint: true} var pong = struct { Foo string `json:"foo"` }{Foo: "123"} @@ -194,9 +194,18 @@ func TestWriteHeaderAndEntity_Issue235(t *testing.T) { } } -func TestWriteEntityNotAcceptable(t *testing.T) { +func TestWriteEntityNoAcceptMatchWithProduces(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "application/bogus", []string{"application/json"}, 0, 0, true, nil} + resp := Response{ResponseWriter: httpWriter, requestAccept: "application/bogus", routeProduces: []string{"application/json"}, prettyPrint: true} + resp.WriteEntity("done") + if httpWriter.Code != http.StatusOK { + t.Errorf("got %d want %d", httpWriter.Code, http.StatusOK) + } +} + +func TestWriteEntityNoAcceptMatchNoProduces(t *testing.T) { + httpWriter := httptest.NewRecorder() + resp := Response{ResponseWriter: httpWriter, requestAccept: "application/bogus", routeProduces: []string{}, prettyPrint: true} resp.WriteEntity("done") if httpWriter.Code != http.StatusNotAcceptable { t.Errorf("got %d want %d", httpWriter.Code, http.StatusNotAcceptable) diff --git a/vendor/github.com/emicklei/go-restful/route.go b/vendor/github.com/emicklei/go-restful/route.go index f54e8622e..9d5b156e0 100644 --- a/vendor/github.com/emicklei/go-restful/route.go +++ b/vendor/github.com/emicklei/go-restful/route.go @@ -13,6 +13,11 @@ import ( // RouteFunction declares the signature of a function that can be bound to a Route. type RouteFunction func(*Request, *Response) +// RouteSelectionConditionFunction declares the signature of a function that +// can be used to add extra conditional logic when selecting whether the route +// matches the HTTP request. +type RouteSelectionConditionFunction func(httpRequest *http.Request) bool + // Route binds a HTTP Method,Path,Consumes combination to a RouteFunction. type Route struct { Method string @@ -21,6 +26,7 @@ type Route struct { Path string // webservice root path + described path Function RouteFunction Filters []FilterFunction + If []RouteSelectionConditionFunction // cached values for dispatching relativePath string @@ -34,6 +40,9 @@ type Route struct { ParameterDocs []*Parameter ResponseErrors map[int]ResponseError ReadSample, WriteSample interface{} // structs that model an example request or response payload + + // Extra information used to store custom information about the route. + Metadata map[string]interface{} } // Initialize for Route @@ -97,7 +106,7 @@ func (r Route) matchesContentType(mimeTypes string) bool { } if len(mimeTypes) == 0 { - // idempotent methods with (most-likely or garanteed) empty content match missing Content-Type + // idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type m := r.Method if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" { return true diff --git a/vendor/github.com/emicklei/go-restful/route_builder.go b/vendor/github.com/emicklei/go-restful/route_builder.go index b49b7c74d..83db02b7c 100644 --- a/vendor/github.com/emicklei/go-restful/route_builder.go +++ b/vendor/github.com/emicklei/go-restful/route_builder.go @@ -5,10 +5,12 @@ package restful // that can be found in the LICENSE file. import ( + "fmt" "os" "reflect" "runtime" "strings" + "sync/atomic" "github.com/emicklei/go-restful/log" ) @@ -22,6 +24,10 @@ type RouteBuilder struct { httpMethod string // required function RouteFunction // required filters []FilterFunction + conditions []RouteSelectionConditionFunction + + typeNameHandleFunc TypeNameHandleFunction // required + // documentation doc string notes string @@ -29,6 +35,7 @@ type RouteBuilder struct { readSample, writeSample interface{} parameters []*Parameter errorMap map[int]ResponseError + metadata map[string]interface{} } // Do evaluates each argument with the RouteBuilder itself. @@ -83,7 +90,7 @@ func (b *RouteBuilder) Doc(documentation string) *RouteBuilder { return b } -// A verbose explanation of the operation behavior. Optional. +// Notes is a verbose explanation of the operation behavior. Optional. func (b *RouteBuilder) Notes(notes string) *RouteBuilder { b.notes = notes return b @@ -92,8 +99,13 @@ func (b *RouteBuilder) Notes(notes string) *RouteBuilder { // Reads tells what resource type will be read from the request payload. Optional. // A parameter of type "body" is added ,required is set to true and the dataType is set to the qualified name of the sample's type. func (b *RouteBuilder) Reads(sample interface{}) *RouteBuilder { + fn := b.typeNameHandleFunc + if fn == nil { + fn = reflectTypeName + } + typeAsName := fn(sample) + b.readSample = sample - typeAsName := reflect.TypeOf(sample).String() bodyParameter := &Parameter{&ParameterData{Name: "body"}} bodyParameter.beBody() bodyParameter.Required(true) @@ -128,7 +140,7 @@ func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder { return b } -// Operation allows you to document what the acutal method/function call is of the Route. +// Operation allows you to document what the actual method/function call is of the Route. // Unless called, the operation name is derived from the RouteFunction set using To(..). func (b *RouteBuilder) Operation(name string) *RouteBuilder { b.operation = name @@ -145,9 +157,10 @@ func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) // The model parameter is optional ; either pass a struct instance or use nil if not applicable. func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder { err := ResponseError{ - Code: code, - Message: message, - Model: model, + Code: code, + Message: message, + Model: model, + IsDefault: false, } // lazy init because there is no NewRouteBuilder (yet) if b.errorMap == nil { @@ -157,10 +170,36 @@ func (b *RouteBuilder) Returns(code int, message string, model interface{}) *Rou return b } +// DefaultReturns is a special Returns call that sets the default of the response ; the code is zero. +func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder { + b.Returns(0, message, model) + // Modify the ResponseError just added/updated + re := b.errorMap[0] + // errorMap is initialized + b.errorMap[0] = ResponseError{ + Code: re.Code, + Message: re.Message, + Model: re.Model, + IsDefault: true, + } + return b +} + +// Metadata adds or updates a key=value pair to the metadata map. +func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder { + if b.metadata == nil { + b.metadata = map[string]interface{}{} + } + b.metadata[key] = value + return b +} + +// ResponseError represents a response; not necessarily an error. type ResponseError struct { - Code int - Message string - Model interface{} + Code int + Message string + Model interface{} + IsDefault bool } func (b *RouteBuilder) servicePath(path string) *RouteBuilder { @@ -174,6 +213,21 @@ func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder { return b } +// If sets a condition function that controls matching the Route based on custom logic. +// The condition function is provided the HTTP request and should return true if the route +// should be considered. +// +// Efficiency note: the condition function is called before checking the method, produces, and +// consumes criteria, so that the correct HTTP status code can be returned. +// +// Lifecycle note: no filter functions have been called prior to calling the condition function, +// so the condition function should not depend on any context that might be set up by container +// or route filters. +func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder { + b.conditions = append(b.conditions, condition) + return b +} + // If no specific Route path then set to rootPath // If no specific Produces then set to rootProduces // If no specific Consumes then set to rootConsumes @@ -186,6 +240,13 @@ func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) { } } +// typeNameHandler sets the function that will convert types to strings in the parameter +// and model definitions. +func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder { + b.typeNameHandleFunc = handler + return b +} + // Build creates a new Route using the specification details collected by the RouteBuilder func (b *RouteBuilder) Build() Route { pathExpr, err := newPathExpression(b.currentPath) @@ -209,6 +270,7 @@ func (b *RouteBuilder) Build() Route { Consumes: b.consumes, Function: b.function, Filters: b.filters, + If: b.conditions, relativePath: b.currentPath, pathExpr: pathExpr, Doc: b.doc, @@ -217,7 +279,8 @@ func (b *RouteBuilder) Build() Route { ParameterDocs: b.parameters, ResponseErrors: b.errorMap, ReadSample: b.readSample, - WriteSample: b.writeSample} + WriteSample: b.writeSample, + Metadata: b.metadata} route.postBuild() return route } @@ -226,6 +289,8 @@ func concatPath(path1, path2 string) string { return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/") } +var anonymousFuncCount int32 + // nameOfFunction returns the short name of the function f for documentation. // It uses a runtime feature for debugging ; its value may change for later Go versions. func nameOfFunction(f interface{}) string { @@ -236,5 +301,10 @@ func nameOfFunction(f interface{}) string { last = strings.TrimSuffix(last, ")-fm") // Go 1.5 last = strings.TrimSuffix(last, "·fm") // < Go 1.5 last = strings.TrimSuffix(last, "-fm") // Go 1.5 + if last == "func1" { // this could mean conflicts in API docs + val := atomic.AddInt32(&anonymousFuncCount, 1) + last = "func" + fmt.Sprintf("%d", val) + atomic.StoreInt32(&anonymousFuncCount, val) + } return last } diff --git a/vendor/github.com/emicklei/go-restful/route_builder_test.go b/vendor/github.com/emicklei/go-restful/route_builder_test.go index 56dbe02e4..25881d5eb 100644 --- a/vendor/github.com/emicklei/go-restful/route_builder_test.go +++ b/vendor/github.com/emicklei/go-restful/route_builder_test.go @@ -2,6 +2,7 @@ package restful import ( "testing" + "time" ) func TestRouteBuilder_PathParameter(t *testing.T) { @@ -41,7 +42,7 @@ func TestRouteBuilder(t *testing.T) { json := "application/json" b := new(RouteBuilder) b.To(dummy) - b.Path("/routes").Method("HEAD").Consumes(json).Produces(json) + b.Path("/routes").Method("HEAD").Consumes(json).Produces(json).Metadata("test", "test-value").DefaultReturns("default", time.Now()) r := b.Build() if r.Path != "/routes" { t.Error("path invalid") @@ -55,4 +56,21 @@ func TestRouteBuilder(t *testing.T) { if r.Operation != "dummy" { t.Error("Operation not set") } + if r.Metadata["test"] != "test-value" { + t.Errorf("Metadata not set") + } + if _, ok := r.ResponseErrors[0]; !ok { + t.Fatal("expected default response") + } +} + +func TestAnonymousFuncNaming(t *testing.T) { + f1 := func() {} + f2 := func() {} + if got, want := nameOfFunction(f1), "func1"; got != want { + t.Errorf("got %v want %v", got, want) + } + if got, want := nameOfFunction(f2), "func2"; got != want { + t.Errorf("got %v want %v", got, want) + } } diff --git a/vendor/github.com/emicklei/go-restful/web_service.go b/vendor/github.com/emicklei/go-restful/web_service.go index e89be7009..094c0a02a 100644 --- a/vendor/github.com/emicklei/go-restful/web_service.go +++ b/vendor/github.com/emicklei/go-restful/web_service.go @@ -1,8 +1,9 @@ package restful import ( - "fmt" + "errors" "os" + "reflect" "sync" "github.com/emicklei/go-restful/log" @@ -24,6 +25,8 @@ type WebService struct { documentation string apiVersion string + typeNameHandleFunc TypeNameHandleFunction + dynamicRoutes bool // protects 'routes' if dynamic routes are enabled @@ -34,11 +37,27 @@ func (w *WebService) SetDynamicRoutes(enable bool) { w.dynamicRoutes = enable } +// TypeNameHandleFunction declares functions that can handle translating the name of a sample object +// into the restful documentation for the service. +type TypeNameHandleFunction func(sample interface{}) string + +// TypeNameHandler sets the function that will convert types to strings in the parameter +// and model definitions. If not set, the web service will invoke +// reflect.TypeOf(object).String(). +func (w *WebService) TypeNameHandler(handler TypeNameHandleFunction) *WebService { + w.typeNameHandleFunc = handler + return w +} + +// reflectTypeName is the default TypeNameHandleFunction and for a given object +// returns the name that Go identifies it with (e.g. "string" or "v1.Object") via +// the reflection API. +func reflectTypeName(sample interface{}) string { + return reflect.TypeOf(sample).String() +} + // compilePathExpression ensures that the path is compiled into a RegEx for those routers that need it. func (w *WebService) compilePathExpression() { - if len(w.rootPath) == 0 { - w.Path("/") // lazy initialize path - } compiled, err := newPathExpression(w.rootPath) if err != nil { log.Printf("[restful] invalid path:%s because:%v", w.rootPath, err) @@ -54,12 +73,15 @@ func (w *WebService) ApiVersion(apiVersion string) *WebService { } // Version returns the API version for documentation purposes. -func (w WebService) Version() string { return w.apiVersion } +func (w *WebService) Version() string { return w.apiVersion } // Path specifies the root URL template path of the WebService. // All Routes will be relative to this path. func (w *WebService) Path(root string) *WebService { w.rootPath = root + if len(w.rootPath) == 0 { + w.rootPath = "/" + } w.compilePathExpression() return w } @@ -155,21 +177,26 @@ func (w *WebService) Route(builder *RouteBuilder) *WebService { // RemoveRoute removes the specified route, looks for something that matches 'path' and 'method' func (w *WebService) RemoveRoute(path, method string) error { if !w.dynamicRoutes { - return fmt.Errorf("dynamic routes are not enabled.") + return errors.New("dynamic routes are not enabled.") } w.routesLock.Lock() defer w.routesLock.Unlock() + newRoutes := make([]Route, (len(w.routes) - 1)) + current := 0 for ix := range w.routes { if w.routes[ix].Method == method && w.routes[ix].Path == path { - w.routes = append(w.routes[:ix], w.routes[ix+1:]...) + continue } + newRoutes[current] = w.routes[ix] + current = current + 1 } + w.routes = newRoutes return nil } // Method creates a new RouteBuilder and initialize its http method func (w *WebService) Method(httpMethod string) *RouteBuilder { - return new(RouteBuilder).servicePath(w.rootPath).Method(httpMethod) + return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method(httpMethod) } // Produces specifies that this WebService can produce one or more MIME types. @@ -187,7 +214,7 @@ func (w *WebService) Consumes(accepts ...string) *WebService { } // Routes returns the Routes associated with this WebService -func (w WebService) Routes() []Route { +func (w *WebService) Routes() []Route { if !w.dynamicRoutes { return w.routes } @@ -202,12 +229,12 @@ func (w WebService) Routes() []Route { } // RootPath returns the RootPath associated with this WebService. Default "/" -func (w WebService) RootPath() string { +func (w *WebService) RootPath() string { return w.rootPath } -// PathParameters return the path parameter names for (shared amoung its Routes) -func (w WebService) PathParameters() []*Parameter { +// PathParameters return the path parameter names for (shared among its Routes) +func (w *WebService) PathParameters() []*Parameter { return w.pathParameters } @@ -224,7 +251,7 @@ func (w *WebService) Doc(plainText string) *WebService { } // Documentation returns it. -func (w WebService) Documentation() string { +func (w *WebService) Documentation() string { return w.documentation } @@ -234,30 +261,30 @@ func (w WebService) Documentation() string { // HEAD is a shortcut for .Method("HEAD").Path(subPath) func (w *WebService) HEAD(subPath string) *RouteBuilder { - return new(RouteBuilder).servicePath(w.rootPath).Method("HEAD").Path(subPath) + return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("HEAD").Path(subPath) } // GET is a shortcut for .Method("GET").Path(subPath) func (w *WebService) GET(subPath string) *RouteBuilder { - return new(RouteBuilder).servicePath(w.rootPath).Method("GET").Path(subPath) + return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath) } // POST is a shortcut for .Method("POST").Path(subPath) func (w *WebService) POST(subPath string) *RouteBuilder { - return new(RouteBuilder).servicePath(w.rootPath).Method("POST").Path(subPath) + return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("POST").Path(subPath) } // PUT is a shortcut for .Method("PUT").Path(subPath) func (w *WebService) PUT(subPath string) *RouteBuilder { - return new(RouteBuilder).servicePath(w.rootPath).Method("PUT").Path(subPath) + return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PUT").Path(subPath) } // PATCH is a shortcut for .Method("PATCH").Path(subPath) func (w *WebService) PATCH(subPath string) *RouteBuilder { - return new(RouteBuilder).servicePath(w.rootPath).Method("PATCH").Path(subPath) + return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PATCH").Path(subPath) } // DELETE is a shortcut for .Method("DELETE").Path(subPath) func (w *WebService) DELETE(subPath string) *RouteBuilder { - return new(RouteBuilder).servicePath(w.rootPath).Method("DELETE").Path(subPath) + return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("DELETE").Path(subPath) } diff --git a/vendor/github.com/emicklei/go-restful/web_service_test.go b/vendor/github.com/emicklei/go-restful/web_service_test.go index 469890434..734938134 100644 --- a/vendor/github.com/emicklei/go-restful/web_service_test.go +++ b/vendor/github.com/emicklei/go-restful/web_service_test.go @@ -44,6 +44,8 @@ func TestCapturePanic(t *testing.T) { httpRequest, _ := http.NewRequest("GET", "http://here.com/fire", nil) httpRequest.Header.Set("Accept", "*/*") httpWriter := httptest.NewRecorder() + // override the default here + DefaultContainer.DoNotRecover(false) DefaultContainer.dispatch(httpWriter, httpRequest) if 500 != httpWriter.Code { t.Error("500 expected on fire") @@ -110,6 +112,17 @@ func TestContentType415_Issue170(t *testing.T) { } } +func TestNoContentTypePOST(t *testing.T) { + tearDown() + Add(newPostNoConsumesService()) + httpRequest, _ := http.NewRequest("POST", "http://here.com/post", nil) + httpWriter := httptest.NewRecorder() + DefaultContainer.dispatch(httpWriter, httpRequest) + if 204 != httpWriter.Code { + t.Errorf("Expected 204, got %d", httpWriter.Code) + } +} + func TestContentType415_POST_Issue170(t *testing.T) { tearDown() Add(newPostOnlyJsonOnlyService()) @@ -171,6 +184,41 @@ func TestRemoveRoute(t *testing.T) { t.Errorf("got %v, want %v", got, want) } } +func TestRemoveLastRoute(t *testing.T) { + tearDown() + TraceLogger(testLogger{t}) + ws := newGetPlainTextOrJsonServiceMultiRoute() + Add(ws) + httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil) + httpRequest.Header.Set("Accept", "text/plain") + httpWriter := httptest.NewRecorder() + DefaultContainer.dispatch(httpWriter, httpRequest) + if got, want := httpWriter.Code, 200; got != want { + t.Errorf("got %v, want %v", got, want) + } + + // dynamic apis are disabled, should error and do nothing + if err := ws.RemoveRoute("/get", "GET"); err == nil { + t.Error("unexpected non-error") + } + + httpWriter = httptest.NewRecorder() + DefaultContainer.dispatch(httpWriter, httpRequest) + if got, want := httpWriter.Code, 200; got != want { + t.Errorf("got %v, want %v", got, want) + } + + ws.SetDynamicRoutes(true) + if err := ws.RemoveRoute("/get", "GET"); err != nil { + t.Errorf("unexpected error %v", err) + } + + httpWriter = httptest.NewRecorder() + DefaultContainer.dispatch(httpWriter, httpRequest) + if got, want := httpWriter.Code, 404; got != want { + t.Errorf("got %v, want %v", got, want) + } +} // go test -v -test.run TestContentTypeOctet_Issue170 ...restful func TestContentTypeOctet_Issue170(t *testing.T) { @@ -193,6 +241,29 @@ func TestContentTypeOctet_Issue170(t *testing.T) { } } +type exampleBody struct{} + +func TestParameterDataTypeDefaults(t *testing.T) { + tearDown() + ws := new(WebService) + route := ws.POST("/post").Reads(&exampleBody{}) + if route.parameters[0].data.DataType != "*restful.exampleBody" { + t.Errorf("body parameter incorrect name: %#v", route.parameters[0].data) + } +} + +func TestParameterDataTypeCustomization(t *testing.T) { + tearDown() + ws := new(WebService) + ws.TypeNameHandler(func(sample interface{}) string { + return "my.custom.type.name" + }) + route := ws.POST("/post").Reads(&exampleBody{}) + if route.parameters[0].data.DataType != "my.custom.type.name" { + t.Errorf("body parameter incorrect name: %#v", route.parameters[0].data) + } +} + func newPanicingService() *WebService { ws := new(WebService).Path("") ws.Route(ws.GET("/fire").To(doPanic)) @@ -226,6 +297,14 @@ func newGetPlainTextOrJsonService() *WebService { return ws } +func newGetPlainTextOrJsonServiceMultiRoute() *WebService { + ws := new(WebService).Path("") + ws.Produces("text/plain", "application/json") + ws.Route(ws.GET("/get").To(doNothing)) + ws.Route(ws.GET("/status").To(doNothing)) + return ws +} + func newGetConsumingOctetStreamService() *WebService { ws := new(WebService).Path("") ws.Consumes("application/octet-stream") @@ -233,6 +312,12 @@ func newGetConsumingOctetStreamService() *WebService { return ws } +func newPostNoConsumesService() *WebService { + ws := new(WebService).Path("") + ws.Route(ws.POST("/post").To(return204)) + return ws +} + func newSelectedRouteTestingService() *WebService { ws := new(WebService).Path("") ws.Route(ws.GET(pathGetFriends).To(selectedRouteChecker)) @@ -252,3 +337,7 @@ func doPanic(req *Request, resp *Response) { func doNothing(req *Request, resp *Response) { } + +func return204(req *Request, resp *Response) { + resp.WriteHeader(204) +} |