aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/emicklei
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/emicklei')
-rw-r--r--vendor/github.com/emicklei/go-restful/.travis.yml6
-rw-r--r--vendor/github.com/emicklei/go-restful/CHANGES.md125
-rw-r--r--vendor/github.com/emicklei/go-restful/Makefile7
-rw-r--r--vendor/github.com/emicklei/go-restful/README.md29
-rw-r--r--vendor/github.com/emicklei/go-restful/compress.go13
-rw-r--r--vendor/github.com/emicklei/go-restful/compress_test.go2
-rw-r--r--vendor/github.com/emicklei/go-restful/compressors.go9
-rw-r--r--vendor/github.com/emicklei/go-restful/container.go126
-rw-r--r--vendor/github.com/emicklei/go-restful/container_test.go22
-rw-r--r--vendor/github.com/emicklei/go-restful/cors_filter.go66
-rw-r--r--vendor/github.com/emicklei/go-restful/cors_filter_test.go20
-rw-r--r--vendor/github.com/emicklei/go-restful/curly.go14
-rw-r--r--vendor/github.com/emicklei/go-restful/curly_route.go26
-rw-r--r--vendor/github.com/emicklei/go-restful/curly_test.go16
-rw-r--r--vendor/github.com/emicklei/go-restful/doc.go19
-rw-r--r--vendor/github.com/emicklei/go-restful/entity_accessors.go20
-rw-r--r--vendor/github.com/emicklei/go-restful/entity_accessors_test.go2
-rw-r--r--vendor/github.com/emicklei/go-restful/filter.go9
-rw-r--r--vendor/github.com/emicklei/go-restful/install.sh9
-rw-r--r--vendor/github.com/emicklei/go-restful/jsr311.go27
-rw-r--r--vendor/github.com/emicklei/go-restful/jsr311_test.go39
-rw-r--r--vendor/github.com/emicklei/go-restful/log/log.go5
-rw-r--r--vendor/github.com/emicklei/go-restful/logger.go2
-rw-r--r--vendor/github.com/emicklei/go-restful/mime.go45
-rw-r--r--vendor/github.com/emicklei/go-restful/mime_test.go17
-rw-r--r--vendor/github.com/emicklei/go-restful/options_filter.go10
-rw-r--r--vendor/github.com/emicklei/go-restful/request.go34
-rw-r--r--vendor/github.com/emicklei/go-restful/request_test.go63
-rw-r--r--vendor/github.com/emicklei/go-restful/response.go92
-rw-r--r--vendor/github.com/emicklei/go-restful/response_test.go45
-rw-r--r--vendor/github.com/emicklei/go-restful/route.go11
-rw-r--r--vendor/github.com/emicklei/go-restful/route_builder.go90
-rw-r--r--vendor/github.com/emicklei/go-restful/route_builder_test.go20
-rw-r--r--vendor/github.com/emicklei/go-restful/web_service.go65
-rw-r--r--vendor/github.com/emicklei/go-restful/web_service_test.go89
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
+[![Build Status](https://travis-ci.org/emicklei/go-restful.png)](https://travis-ci.org/emicklei/go-restful)
+[![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/go-restful)](https://goreportcard.com/report/github.com/emicklei/go-restful)
+[![GoDoc](https://godoc.org/github.com/emicklei/go-restful?status.svg)](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)
-[![Build Status](https://drone.io/github.com/emicklei/go-restful/status.png)](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)
+}