mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #17990 from nikhiljindal/updateGoRestful
Updating go restful
This commit is contained in:
commit
2fcf157512
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@ -378,8 +378,8 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/emicklei/go-restful",
|
||||
"Comment": "v1.1.3-98-g1f9a0ee",
|
||||
"Rev": "1f9a0ee00ff93717a275e15b30cf7df356255877"
|
||||
"Comment": "v1.2",
|
||||
"Rev": "777bb3f19bcafe2575ffb2a3e46af92509ae9594"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/evanphx/json-patch",
|
||||
|
10
Godeps/_workspace/src/github.com/emicklei/go-restful/CHANGES.md
generated
vendored
10
Godeps/_workspace/src/github.com/emicklei/go-restful/CHANGES.md
generated
vendored
@ -1,5 +1,15 @@
|
||||
Change history of go-restful
|
||||
=
|
||||
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
|
||||
|
2
Godeps/_workspace/src/github.com/emicklei/go-restful/README.md
generated
vendored
2
Godeps/_workspace/src/github.com/emicklei/go-restful/README.md
generated
vendored
@ -54,6 +54,8 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo
|
||||
- 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
|
||||
|
||||
|
29
Godeps/_workspace/src/github.com/emicklei/go-restful/compress.go
generated
vendored
29
Godeps/_workspace/src/github.com/emicklei/go-restful/compress.go
generated
vendored
@ -20,6 +20,7 @@ var EnableContentEncoding = false
|
||||
type CompressingResponseWriter struct {
|
||||
writer http.ResponseWriter
|
||||
compressor io.WriteCloser
|
||||
encoding string
|
||||
}
|
||||
|
||||
// Header is part of http.ResponseWriter interface
|
||||
@ -35,6 +36,9 @@ func (c *CompressingResponseWriter) WriteHeader(status int) {
|
||||
// Write is part of http.ResponseWriter interface
|
||||
// It is passed through the compressor
|
||||
func (c *CompressingResponseWriter) Write(bytes []byte) (int, error) {
|
||||
if c.isCompressorClosed() {
|
||||
return -1, errors.New("Compressing error: tried to write data using closed compressor")
|
||||
}
|
||||
return c.compressor.Write(bytes)
|
||||
}
|
||||
|
||||
@ -44,8 +48,25 @@ func (c *CompressingResponseWriter) CloseNotify() <-chan bool {
|
||||
}
|
||||
|
||||
// Close the underlying compressor
|
||||
func (c *CompressingResponseWriter) Close() {
|
||||
func (c *CompressingResponseWriter) Close() error {
|
||||
if c.isCompressorClosed() {
|
||||
return errors.New("Compressing error: tried to close already closed compressor")
|
||||
}
|
||||
|
||||
c.compressor.Close()
|
||||
if ENCODING_GZIP == c.encoding {
|
||||
currentCompressorProvider.ReleaseGzipWriter(c.compressor.(*gzip.Writer))
|
||||
}
|
||||
if ENCODING_DEFLATE == c.encoding {
|
||||
currentCompressorProvider.ReleaseZlibWriter(c.compressor.(*zlib.Writer))
|
||||
}
|
||||
// gc hint needed?
|
||||
c.compressor = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CompressingResponseWriter) isCompressorClosed() bool {
|
||||
return nil == c.compressor
|
||||
}
|
||||
|
||||
// WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested.
|
||||
@ -73,13 +94,15 @@ func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding strin
|
||||
c.writer = httpWriter
|
||||
var err error
|
||||
if ENCODING_GZIP == encoding {
|
||||
w := GzipWriterPool.Get().(*gzip.Writer)
|
||||
w := currentCompressorProvider.AcquireGzipWriter()
|
||||
w.Reset(httpWriter)
|
||||
c.compressor = w
|
||||
c.encoding = ENCODING_GZIP
|
||||
} else if ENCODING_DEFLATE == encoding {
|
||||
w := ZlibWriterPool.Get().(*zlib.Writer)
|
||||
w := currentCompressorProvider.AcquireZlibWriter()
|
||||
w.Reset(httpWriter)
|
||||
c.compressor = w
|
||||
c.encoding = ENCODING_DEFLATE
|
||||
} else {
|
||||
return nil, errors.New("Unknown encoding:" + encoding)
|
||||
}
|
||||
|
103
Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_cache.go
generated
vendored
Normal file
103
Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_cache.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
package restful
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
)
|
||||
|
||||
// BoundedCachedCompressors is a CompressorProvider that uses a cache with a fixed amount
|
||||
// of writers and readers (resources).
|
||||
// If a new resource is acquired and all are in use, it will return a new unmanaged resource.
|
||||
type BoundedCachedCompressors struct {
|
||||
gzipWriters chan *gzip.Writer
|
||||
gzipReaders chan *gzip.Reader
|
||||
zlibWriters chan *zlib.Writer
|
||||
writersCapacity int
|
||||
readersCapacity int
|
||||
}
|
||||
|
||||
// NewBoundedCachedCompressors returns a new, with filled cache, BoundedCachedCompressors.
|
||||
func NewBoundedCachedCompressors(writersCapacity, readersCapacity int) *BoundedCachedCompressors {
|
||||
b := &BoundedCachedCompressors{
|
||||
gzipWriters: make(chan *gzip.Writer, writersCapacity),
|
||||
gzipReaders: make(chan *gzip.Reader, readersCapacity),
|
||||
zlibWriters: make(chan *zlib.Writer, writersCapacity),
|
||||
writersCapacity: writersCapacity,
|
||||
readersCapacity: readersCapacity,
|
||||
}
|
||||
for ix := 0; ix < writersCapacity; ix++ {
|
||||
b.gzipWriters <- newGzipWriter()
|
||||
b.zlibWriters <- newZlibWriter()
|
||||
}
|
||||
for ix := 0; ix < readersCapacity; ix++ {
|
||||
b.gzipReaders <- newGzipReader()
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AcquireGzipWriter returns an resettable *gzip.Writer. Needs to be released.
|
||||
func (b *BoundedCachedCompressors) AcquireGzipWriter() *gzip.Writer {
|
||||
var writer *gzip.Writer
|
||||
select {
|
||||
case writer, _ = <-b.gzipWriters:
|
||||
default:
|
||||
// return a new unmanaged one
|
||||
writer = newGzipWriter()
|
||||
}
|
||||
return writer
|
||||
}
|
||||
|
||||
// ReleaseGzipWriter accepts a writer (does not have to be one that was cached)
|
||||
// only when the cache has room for it. It will ignore it otherwise.
|
||||
func (b *BoundedCachedCompressors) ReleaseGzipWriter(w *gzip.Writer) {
|
||||
// forget the unmanaged ones
|
||||
if len(b.gzipWriters) < b.writersCapacity {
|
||||
b.gzipWriters <- w
|
||||
}
|
||||
}
|
||||
|
||||
// AcquireGzipReader returns a *gzip.Reader. Needs to be released.
|
||||
func (b *BoundedCachedCompressors) AcquireGzipReader() *gzip.Reader {
|
||||
var reader *gzip.Reader
|
||||
select {
|
||||
case reader, _ = <-b.gzipReaders:
|
||||
default:
|
||||
// return a new unmanaged one
|
||||
reader = newGzipReader()
|
||||
}
|
||||
return reader
|
||||
}
|
||||
|
||||
// ReleaseGzipReader accepts a reader (does not have to be one that was cached)
|
||||
// only when the cache has room for it. It will ignore it otherwise.
|
||||
func (b *BoundedCachedCompressors) ReleaseGzipReader(r *gzip.Reader) {
|
||||
// forget the unmanaged ones
|
||||
if len(b.gzipReaders) < b.readersCapacity {
|
||||
b.gzipReaders <- r
|
||||
}
|
||||
}
|
||||
|
||||
// AcquireZlibWriter returns an resettable *zlib.Writer. Needs to be released.
|
||||
func (b *BoundedCachedCompressors) AcquireZlibWriter() *zlib.Writer {
|
||||
var writer *zlib.Writer
|
||||
select {
|
||||
case writer, _ = <-b.zlibWriters:
|
||||
default:
|
||||
// return a new unmanaged one
|
||||
writer = newZlibWriter()
|
||||
}
|
||||
return writer
|
||||
}
|
||||
|
||||
// ReleaseZlibWriter accepts a writer (does not have to be one that was cached)
|
||||
// only when the cache has room for it. It will ignore it otherwise.
|
||||
func (b *BoundedCachedCompressors) ReleaseZlibWriter(w *zlib.Writer) {
|
||||
// forget the unmanaged ones
|
||||
if len(b.zlibWriters) < b.writersCapacity {
|
||||
b.zlibWriters <- w
|
||||
}
|
||||
}
|
74
Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_pools.go
generated
vendored
74
Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_pools.go
generated
vendored
@ -1,5 +1,9 @@
|
||||
package restful
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
@ -7,12 +11,50 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// GzipWriterPool is used to get reusable zippers.
|
||||
// The Get() result must be type asserted to *gzip.Writer.
|
||||
var GzipWriterPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return newGzipWriter()
|
||||
},
|
||||
// SyncPoolCompessors is a CompressorProvider that use the standard sync.Pool.
|
||||
type SyncPoolCompessors struct {
|
||||
GzipWriterPool *sync.Pool
|
||||
GzipReaderPool *sync.Pool
|
||||
ZlibWriterPool *sync.Pool
|
||||
}
|
||||
|
||||
// NewSyncPoolCompessors returns a new ("empty") SyncPoolCompessors.
|
||||
func NewSyncPoolCompessors() *SyncPoolCompessors {
|
||||
return &SyncPoolCompessors{
|
||||
GzipWriterPool: &sync.Pool{
|
||||
New: func() interface{} { return newGzipWriter() },
|
||||
},
|
||||
GzipReaderPool: &sync.Pool{
|
||||
New: func() interface{} { return newGzipReader() },
|
||||
},
|
||||
ZlibWriterPool: &sync.Pool{
|
||||
New: func() interface{} { return newZlibWriter() },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) AcquireGzipWriter() *gzip.Writer {
|
||||
return s.GzipWriterPool.Get().(*gzip.Writer)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) ReleaseGzipWriter(w *gzip.Writer) {
|
||||
s.GzipWriterPool.Put(w)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) AcquireGzipReader() *gzip.Reader {
|
||||
return s.GzipReaderPool.Get().(*gzip.Reader)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) ReleaseGzipReader(r *gzip.Reader) {
|
||||
s.GzipReaderPool.Put(r)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) AcquireZlibWriter() *zlib.Writer {
|
||||
return s.ZlibWriterPool.Get().(*zlib.Writer)
|
||||
}
|
||||
|
||||
func (s *SyncPoolCompessors) ReleaseZlibWriter(w *zlib.Writer) {
|
||||
s.ZlibWriterPool.Put(w)
|
||||
}
|
||||
|
||||
func newGzipWriter() *gzip.Writer {
|
||||
@ -24,17 +66,11 @@ func newGzipWriter() *gzip.Writer {
|
||||
return writer
|
||||
}
|
||||
|
||||
// GzipReaderPool is used to get reusable zippers.
|
||||
// The Get() result must be type asserted to *gzip.Reader.
|
||||
var GzipReaderPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return newGzipReader()
|
||||
},
|
||||
}
|
||||
|
||||
func newGzipReader() *gzip.Reader {
|
||||
// create with an empty reader (but with GZIP header); it will be replaced before using the gzipReader
|
||||
w := GzipWriterPool.Get().(*gzip.Writer)
|
||||
// we can safely use currentCompressProvider because it is set on package initialization.
|
||||
w := currentCompressorProvider.AcquireGzipWriter()
|
||||
defer currentCompressorProvider.ReleaseGzipWriter(w)
|
||||
b := new(bytes.Buffer)
|
||||
w.Reset(b)
|
||||
w.Flush()
|
||||
@ -46,14 +82,6 @@ func newGzipReader() *gzip.Reader {
|
||||
return reader
|
||||
}
|
||||
|
||||
// ZlibWriterPool is used to get reusable zippers.
|
||||
// The Get() result must be type asserted to *zlib.Writer.
|
||||
var ZlibWriterPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return newZlibWriter()
|
||||
},
|
||||
}
|
||||
|
||||
func newZlibWriter() *zlib.Writer {
|
||||
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
||||
if err != nil {
|
||||
|
53
Godeps/_workspace/src/github.com/emicklei/go-restful/compressors.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/emicklei/go-restful/compressors.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package restful
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
)
|
||||
|
||||
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.
|
||||
ReleaseGzipWriter(w *gzip.Writer)
|
||||
|
||||
// Returns a *gzip.Reader which needs to be released later.
|
||||
AcquireGzipReader() *gzip.Reader
|
||||
|
||||
// Releases an aqcuired *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.
|
||||
ReleaseZlibWriter(w *zlib.Writer)
|
||||
}
|
||||
|
||||
// DefaultCompressorProvider is the actual provider of compressors (zlib or gzip).
|
||||
var currentCompressorProvider CompressorProvider
|
||||
|
||||
func init() {
|
||||
currentCompressorProvider = NewSyncPoolCompessors()
|
||||
}
|
||||
|
||||
// CurrentCompressorProvider returns the current CompressorProvider.
|
||||
// It is initialized using a SyncPoolCompessors.
|
||||
func CurrentCompressorProvider() CompressorProvider {
|
||||
return currentCompressorProvider
|
||||
}
|
||||
|
||||
// CompressorProvider sets the actual provider of compressors (zlib or gzip).
|
||||
func SetCompressorProvider(p CompressorProvider) {
|
||||
if p == nil {
|
||||
panic("cannot set compressor provider to nil")
|
||||
}
|
||||
currentCompressorProvider = p
|
||||
}
|
2
Godeps/_workspace/src/github.com/emicklei/go-restful/container.go
generated
vendored
2
Godeps/_workspace/src/github.com/emicklei/go-restful/container.go
generated
vendored
@ -272,7 +272,7 @@ func (c Container) Handle(pattern string, handler http.Handler) {
|
||||
// HandleWithFilter registers the handler for the given pattern.
|
||||
// Container's filter chain is applied for handler.
|
||||
// If a handler already exists for pattern, HandleWithFilter panics.
|
||||
func (c Container) HandleWithFilter(pattern string, handler http.Handler) {
|
||||
func (c *Container) HandleWithFilter(pattern string, handler http.Handler) {
|
||||
f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
|
||||
if len(c.containerFilters) == 0 {
|
||||
handler.ServeHTTP(httpResponse, httpRequest)
|
||||
|
5
Godeps/_workspace/src/github.com/emicklei/go-restful/doc.go
generated
vendored
5
Godeps/_workspace/src/github.com/emicklei/go-restful/doc.go
generated
vendored
@ -162,6 +162,11 @@ Default value is false; it will recover from panics. This has performance implic
|
||||
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.
|
||||
|
||||
restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20))
|
||||
|
||||
If content encoding is enabled then the default strategy for getting new gzip/zlib writers and readers is to use a sync.Pool.
|
||||
Because writers are expensive structures, performance is even more improved when using a preloaded cache. You can also inject your own implementation.
|
||||
|
||||
Trouble shooting
|
||||
|
||||
This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
|
||||
|
151
Godeps/_workspace/src/github.com/emicklei/go-restful/entity_accessors.go
generated
vendored
Normal file
151
Godeps/_workspace/src/github.com/emicklei/go-restful/entity_accessors.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
package restful
|
||||
|
||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
||||
// Use of this source code is governed by a license
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// EntityReaderWriter can read and write values using an encoding such as JSON,XML.
|
||||
type EntityReaderWriter interface {
|
||||
// Read a serialized version of the value from the request.
|
||||
// The Request may have a decompressing reader. Depends on Content-Encoding.
|
||||
Read(req *Request, v interface{}) error
|
||||
|
||||
// Write a serialized version of the value on the response.
|
||||
// The Response may have a compressing writer. Depends on Accept-Encoding.
|
||||
// status should be a valid Http Status code
|
||||
Write(resp *Response, status int, v interface{}) error
|
||||
}
|
||||
|
||||
// entityAccessRegistry is a singleton
|
||||
var entityAccessRegistry = &entityReaderWriters{
|
||||
protection: new(sync.RWMutex),
|
||||
accessors: map[string]EntityReaderWriter{},
|
||||
}
|
||||
|
||||
// entityReaderWriters associates MIME to an EntityReaderWriter
|
||||
type entityReaderWriters struct {
|
||||
protection *sync.RWMutex
|
||||
accessors map[string]EntityReaderWriter
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterEntityAccessor(MIME_JSON, entityJSONAccess{ContentType: MIME_JSON})
|
||||
RegisterEntityAccessor(MIME_XML, entityXMLAccess{ContentType: MIME_XML})
|
||||
}
|
||||
|
||||
// RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type.
|
||||
func RegisterEntityAccessor(mime string, erw EntityReaderWriter) {
|
||||
entityAccessRegistry.protection.Lock()
|
||||
defer entityAccessRegistry.protection.Unlock()
|
||||
entityAccessRegistry.accessors[mime] = erw
|
||||
}
|
||||
|
||||
// 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]
|
||||
if !ok {
|
||||
// retry with reverse lookup
|
||||
// more expensive but we are in an exceptional situation anyway
|
||||
for k, v := range r.accessors {
|
||||
if strings.Contains(mime, k) {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return er, ok
|
||||
}
|
||||
|
||||
// entityXMLAccess is a EntityReaderWriter for XML encoding
|
||||
type entityXMLAccess struct {
|
||||
// This is used for setting the Content-Type header when writing
|
||||
ContentType string
|
||||
}
|
||||
|
||||
// Read unmarshalls the value from XML
|
||||
func (e entityXMLAccess) Read(req *Request, v interface{}) error {
|
||||
return xml.NewDecoder(req.Request.Body).Decode(v)
|
||||
}
|
||||
|
||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
||||
func (e entityXMLAccess) Write(resp *Response, status int, v interface{}) error {
|
||||
return writeXML(resp, status, e.ContentType, v)
|
||||
}
|
||||
|
||||
// writeXML marshalls the value to JSON and set the Content-Type Header.
|
||||
func writeXML(resp *Response, status int, contentType string, v interface{}) error {
|
||||
if v == nil {
|
||||
resp.WriteHeader(status)
|
||||
// do not write a nil representation
|
||||
return nil
|
||||
}
|
||||
if resp.prettyPrint {
|
||||
// pretty output must be created and written explicitly
|
||||
output, err := xml.MarshalIndent(v, " ", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Header().Set(HEADER_ContentType, contentType)
|
||||
resp.WriteHeader(status)
|
||||
_, err = resp.Write([]byte(xml.Header))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = resp.Write(output)
|
||||
return err
|
||||
}
|
||||
// not-so-pretty
|
||||
resp.Header().Set(HEADER_ContentType, contentType)
|
||||
resp.WriteHeader(status)
|
||||
return xml.NewEncoder(resp).Encode(v)
|
||||
}
|
||||
|
||||
// entityJSONAccess is a EntityReaderWriter for JSON encoding
|
||||
type entityJSONAccess struct {
|
||||
// This is used for setting the Content-Type header when writing
|
||||
ContentType string
|
||||
}
|
||||
|
||||
// Read unmarshalls the value from JSON
|
||||
func (e entityJSONAccess) Read(req *Request, v interface{}) error {
|
||||
decoder := json.NewDecoder(req.Request.Body)
|
||||
decoder.UseNumber()
|
||||
return decoder.Decode(v)
|
||||
}
|
||||
|
||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
||||
func (e entityJSONAccess) Write(resp *Response, status int, v interface{}) error {
|
||||
return writeJSON(resp, status, e.ContentType, v)
|
||||
}
|
||||
|
||||
// write marshalls the value to JSON and set the Content-Type Header.
|
||||
func writeJSON(resp *Response, status int, contentType string, v interface{}) error {
|
||||
if v == nil {
|
||||
resp.WriteHeader(status)
|
||||
// do not write a nil representation
|
||||
return nil
|
||||
}
|
||||
if resp.prettyPrint {
|
||||
// pretty output must be created and written explicitly
|
||||
output, err := json.MarshalIndent(v, " ", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Header().Set(HEADER_ContentType, contentType)
|
||||
resp.WriteHeader(status)
|
||||
_, err = resp.Write(output)
|
||||
return err
|
||||
}
|
||||
// not-so-pretty
|
||||
resp.Header().Set(HEADER_ContentType, contentType)
|
||||
resp.WriteHeader(status)
|
||||
return json.NewEncoder(resp).Encode(v)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
application: datastore-example
|
||||
application: <your_app_id>
|
||||
version: 1
|
||||
runtime: go
|
||||
api_version: go1
|
||||
|
@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"appengine"
|
||||
"appengine/datastore"
|
||||
"appengine/user"
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/emicklei/go-restful/swagger"
|
||||
"google.golang.com/appengine"
|
||||
"google.golang.com/appengine/datastore"
|
||||
"google.golang.com/appengine/user"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"appengine"
|
||||
"appengine/memcache"
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/emicklei/go-restful/swagger"
|
||||
"google.golang.com/appengine"
|
||||
"google.golang.com/appengine/memcache"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
2
Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-CORS-filter.go
generated
vendored
2
Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-CORS-filter.go
generated
vendored
@ -53,7 +53,7 @@ func main() {
|
||||
// Add container filter to enable CORS
|
||||
cors := restful.CrossOriginResourceSharing{
|
||||
ExposeHeaders: []string{"X-My-Header"},
|
||||
AllowedHeaders: []string{"Content-Type"},
|
||||
AllowedHeaders: []string{"Content-Type", "Accept"},
|
||||
CookiesAllowed: false,
|
||||
Container: wsContainer}
|
||||
wsContainer.Filter(cors.Filter)
|
||||
|
@ -100,8 +100,7 @@ func (u *UserResource) createUser(request *restful.Request, response *restful.Re
|
||||
}
|
||||
usr.Id = strconv.Itoa(len(u.users) + 1) // simple id generation
|
||||
u.users[usr.Id] = *usr
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
response.WriteEntity(usr)
|
||||
response.WriteHeaderAndEntity(http.StatusCreated, usr)
|
||||
}
|
||||
|
||||
// PUT http://localhost:8080/users/1
|
||||
|
@ -102,8 +102,7 @@ func (u *UserService) createUser(request *restful.Request, response *restful.Res
|
||||
err := request.ReadEntity(&usr)
|
||||
if err == nil {
|
||||
u.users[usr.Id] = usr
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
response.WriteEntity(usr)
|
||||
response.WriteHeaderAndEntity(http.StatusCreated, usr)
|
||||
} else {
|
||||
response.WriteError(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
2
Godeps/_workspace/src/github.com/emicklei/go-restful/log/log.go
generated
vendored
2
Godeps/_workspace/src/github.com/emicklei/go-restful/log/log.go
generated
vendored
@ -15,7 +15,7 @@ var Logger StdLogger
|
||||
|
||||
func init() {
|
||||
// default Logger
|
||||
SetLogger(stdlog.New(os.Stdout, "[restful] ", stdlog.LstdFlags|stdlog.Lshortfile))
|
||||
SetLogger(stdlog.New(os.Stderr, "[restful] ", stdlog.LstdFlags|stdlog.Lshortfile))
|
||||
}
|
||||
|
||||
func SetLogger(customLogger StdLogger) {
|
||||
|
4
Godeps/_workspace/src/github.com/emicklei/go-restful/options_filter.go
generated
vendored
4
Godeps/_workspace/src/github.com/emicklei/go-restful/options_filter.go
generated
vendored
@ -8,7 +8,8 @@ import "strings"
|
||||
|
||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
||||
// and provides the response with a set of allowed methods for the request URL Path.
|
||||
// As for any filter, you can also install it for a particular WebService within a Container
|
||||
// As for any filter, you can also install it for a particular WebService within a Container.
|
||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
||||
func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) {
|
||||
if "OPTIONS" != req.Request.Method {
|
||||
chain.ProcessFilter(req, resp)
|
||||
@ -19,6 +20,7 @@ func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterCha
|
||||
|
||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
||||
// and provides the response with a set of allowed methods for the request URL Path.
|
||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
||||
func OPTIONSFilter() FilterFunction {
|
||||
return DefaultContainer.OPTIONSFilter
|
||||
}
|
||||
|
18
Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go
generated
vendored
18
Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go
generated
vendored
@ -30,12 +30,12 @@ type Parameter struct {
|
||||
// ParameterData represents the state of a Parameter.
|
||||
// It is made public to make it accessible to e.g. the Swagger package.
|
||||
type ParameterData struct {
|
||||
Name, Description, DataType string
|
||||
Kind int
|
||||
Required bool
|
||||
AllowableValues map[string]string
|
||||
AllowMultiple bool
|
||||
DefaultValue string
|
||||
Name, Description, DataType, DataFormat string
|
||||
Kind int
|
||||
Required bool
|
||||
AllowableValues map[string]string
|
||||
AllowMultiple bool
|
||||
DefaultValue string
|
||||
}
|
||||
|
||||
// Data returns the state of the Parameter
|
||||
@ -95,6 +95,12 @@ func (p *Parameter) DataType(typeName string) *Parameter {
|
||||
return p
|
||||
}
|
||||
|
||||
// DataFormat sets the dataFormat field for Swagger UI
|
||||
func (p *Parameter) DataFormat(formatName string) *Parameter {
|
||||
p.data.DataFormat = formatName
|
||||
return p
|
||||
}
|
||||
|
||||
// DefaultValue sets the default value field and returns the receiver
|
||||
func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
|
||||
p.data.DefaultValue = stringRepresentation
|
||||
|
66
Godeps/_workspace/src/github.com/emicklei/go-restful/request.go
generated
vendored
66
Godeps/_workspace/src/github.com/emicklei/go-restful/request.go
generated
vendored
@ -6,14 +6,9 @@ package restful
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var defaultRequestContentType string
|
||||
@ -81,62 +76,43 @@ func (r *Request) HeaderParameter(name string) string {
|
||||
return r.Request.Header.Get(name)
|
||||
}
|
||||
|
||||
// ReadEntity checks the Accept header and reads the content into the entityPointer
|
||||
// May be called multiple times in the request-response flow
|
||||
// ReadEntity checks the Accept header and reads the content into the entityPointer.
|
||||
func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
|
||||
defer r.Request.Body.Close()
|
||||
contentType := r.Request.Header.Get(HEADER_ContentType)
|
||||
contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding)
|
||||
|
||||
// OLD feature, cache the body for reads
|
||||
if doCacheReadEntityBytes {
|
||||
return r.cachingReadEntity(contentType, contentEncoding, entityPointer)
|
||||
}
|
||||
// unmarshall directly from request Body
|
||||
return r.decodeEntity(r.Request.Body, contentType, contentEncoding, entityPointer)
|
||||
}
|
||||
|
||||
func (r *Request) cachingReadEntity(contentType string, contentEncoding string, entityPointer interface{}) (err error) {
|
||||
var buffer []byte
|
||||
if r.bodyContent != nil {
|
||||
buffer = *r.bodyContent
|
||||
} else {
|
||||
buffer, err = ioutil.ReadAll(r.Request.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
if r.bodyContent == nil {
|
||||
data, err := ioutil.ReadAll(r.Request.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.bodyContent = &data
|
||||
}
|
||||
r.bodyContent = &buffer
|
||||
r.Request.Body = ioutil.NopCloser(bytes.NewReader(*r.bodyContent))
|
||||
}
|
||||
return r.decodeEntity(bytes.NewReader(buffer), contentType, contentEncoding, entityPointer)
|
||||
}
|
||||
|
||||
func (r *Request) decodeEntity(reader io.Reader, contentType string, contentEncoding string, entityPointer interface{}) (err error) {
|
||||
entityReader := reader
|
||||
|
||||
// check if the request body needs decompression
|
||||
if ENCODING_GZIP == contentEncoding {
|
||||
gzipReader := GzipReaderPool.Get().(*gzip.Reader)
|
||||
gzipReader.Reset(reader)
|
||||
entityReader = gzipReader
|
||||
gzipReader := currentCompressorProvider.AcquireGzipReader()
|
||||
defer currentCompressorProvider.ReleaseGzipReader(gzipReader)
|
||||
gzipReader.Reset(r.Request.Body)
|
||||
r.Request.Body = gzipReader
|
||||
} else if ENCODING_DEFLATE == contentEncoding {
|
||||
zlibReader, err := zlib.NewReader(reader)
|
||||
zlibReader, err := zlib.NewReader(r.Request.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entityReader = zlibReader
|
||||
r.Request.Body = zlibReader
|
||||
}
|
||||
|
||||
// decode JSON
|
||||
if strings.Contains(contentType, MIME_JSON) || MIME_JSON == defaultRequestContentType {
|
||||
decoder := json.NewDecoder(entityReader)
|
||||
decoder.UseNumber()
|
||||
return decoder.Decode(entityPointer)
|
||||
// lookup the EntityReader
|
||||
entityReader, ok := entityAccessRegistry.AccessorAt(contentType)
|
||||
if !ok {
|
||||
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
|
||||
}
|
||||
|
||||
// decode XML
|
||||
if strings.Contains(contentType, MIME_XML) || MIME_XML == defaultRequestContentType {
|
||||
return xml.NewDecoder(entityReader).Decode(entityPointer)
|
||||
}
|
||||
|
||||
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
|
||||
return entityReader.Read(r, entityPointer)
|
||||
}
|
||||
|
||||
// SetAttribute adds or replaces the attribute with the given value.
|
||||
|
183
Godeps/_workspace/src/github.com/emicklei/go-restful/response.go
generated
vendored
183
Godeps/_workspace/src/github.com/emicklei/go-restful/response.go
generated
vendored
@ -5,8 +5,7 @@ package restful
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@ -14,9 +13,7 @@ import (
|
||||
// DEPRECATED, use DefaultResponseContentType(mime)
|
||||
var DefaultResponseMimeType string
|
||||
|
||||
//PrettyPrintResponses controls the indentation feature of XML and JSON
|
||||
//serialization in the response methods WriteEntity, WriteAsJson, and
|
||||
//WriteAsXml.
|
||||
//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
|
||||
var PrettyPrintResponses = true
|
||||
|
||||
// Response is a wrapper on the actual http ResponseWriter
|
||||
@ -36,8 +33,7 @@ func NewResponse(httpWriter http.ResponseWriter) *Response {
|
||||
return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses, nil} // empty content-types
|
||||
}
|
||||
|
||||
// If Accept header matching fails, fall back to this type, otherwise
|
||||
// a "406: Not Acceptable" response is returned.
|
||||
// If Accept header matching fails, fall back to this type.
|
||||
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
||||
// Example:
|
||||
// restful.DefaultResponseContentType(restful.MIME_JSON)
|
||||
@ -68,117 +64,99 @@ func (r *Response) SetRequestAccepts(mime string) {
|
||||
r.requestAccept = mime
|
||||
}
|
||||
|
||||
// WriteEntity marshals the value using the representation denoted by the Accept Header (XML or JSON)
|
||||
// If no Accept header is specified (or */*) then return the Content-Type as specified by the first in the Route.Produces.
|
||||
// If an Accept header is specified then return the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
|
||||
// If the value is nil then nothing is written. You may want to call WriteHeader(http.StatusNotFound) instead.
|
||||
// Current implementation ignores any q-parameters in the Accept Header.
|
||||
func (r *Response) WriteEntity(value interface{}) error {
|
||||
if value == nil { // do not write a nil representation
|
||||
return nil
|
||||
}
|
||||
// EntityWriter returns the registered EntityWriter that the entity (requested resource)
|
||||
// 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 r.WriteAsJson(value)
|
||||
return entityAccessRegistry.AccessorAt(MIME_JSON)
|
||||
}
|
||||
if MIME_XML == each {
|
||||
return r.WriteAsXml(value)
|
||||
return entityAccessRegistry.AccessorAt(MIME_XML)
|
||||
}
|
||||
}
|
||||
} else { // mime is not blank; see if we have a match in Produces
|
||||
for _, each := range r.routeProduces {
|
||||
if mime == each {
|
||||
if MIME_JSON == each {
|
||||
return r.WriteAsJson(value)
|
||||
return entityAccessRegistry.AccessorAt(MIME_JSON)
|
||||
}
|
||||
if MIME_XML == each {
|
||||
return r.WriteAsXml(value)
|
||||
return entityAccessRegistry.AccessorAt(MIME_XML)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if DefaultResponseMimeType == MIME_JSON {
|
||||
return r.WriteAsJson(value)
|
||||
} else if DefaultResponseMimeType == MIME_XML {
|
||||
return r.WriteAsXml(value)
|
||||
} else {
|
||||
if trace {
|
||||
traceLogger.Printf("mismatch in mime-types and no defaults; (http)Accept=%v,(route)Produces=%v\n", r.requestAccept, r.routeProduces)
|
||||
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)
|
||||
}
|
||||
r.WriteHeader(http.StatusNotAcceptable) // for recording only
|
||||
r.ResponseWriter.WriteHeader(http.StatusNotAcceptable)
|
||||
if _, err := r.Write([]byte("406: Not Acceptable")); err != nil {
|
||||
return err
|
||||
if DefaultResponseMimeType == MIME_XML {
|
||||
return entityAccessRegistry.AccessorAt(MIME_XML)
|
||||
}
|
||||
if trace {
|
||||
traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return writer, ok
|
||||
}
|
||||
|
||||
// WriteEntity calls WriteHeaderAndEntity with Http Status OK (200)
|
||||
func (r *Response) WriteEntity(value interface{}) error {
|
||||
return r.WriteHeaderAndEntity(http.StatusOK, value)
|
||||
}
|
||||
|
||||
// WriteHeaderAndEntity marshals the value using the representation denoted by the Accept Header and the registered EntityWriters.
|
||||
// If no Accept header is specified (or */*) then respond with the Content-Type as specified by the first in the Route.Produces.
|
||||
// If an Accept header is specified then respond with the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
|
||||
// If the value is nil then no response is send except for the Http status. You may want to call WriteHeader(http.StatusNotFound) instead.
|
||||
// If there is no writer available that can represent the value in the requested MIME type then Http Status NotAcceptable is written.
|
||||
// Current implementation ignores any q-parameters in the Accept Header.
|
||||
// Returns an error if the value could not be written on the response.
|
||||
func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error {
|
||||
writer, ok := r.EntityWriter()
|
||||
if !ok {
|
||||
r.WriteHeader(http.StatusNotAcceptable)
|
||||
return nil
|
||||
}
|
||||
return writer.Write(r, status, value)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (r *Response) WriteAsXml(value interface{}) error {
|
||||
var output []byte
|
||||
var err error
|
||||
|
||||
if value == nil { // do not write a nil representation
|
||||
return nil
|
||||
}
|
||||
if r.prettyPrint {
|
||||
output, err = xml.MarshalIndent(value, " ", " ")
|
||||
} else {
|
||||
output, err = xml.Marshal(value)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return r.WriteError(http.StatusInternalServerError, err)
|
||||
}
|
||||
r.Header().Set(HEADER_ContentType, MIME_XML)
|
||||
if r.statusCode > 0 { // a WriteHeader was intercepted
|
||||
r.ResponseWriter.WriteHeader(r.statusCode)
|
||||
}
|
||||
_, err = r.Write([]byte(xml.Header))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = r.Write(output); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return writeXML(r, http.StatusOK, MIME_XML, value)
|
||||
}
|
||||
|
||||
// WriteAsJson is a convenience method for writing a value in json
|
||||
// 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.
|
||||
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.
|
||||
func (r *Response) WriteAsJson(value interface{}) error {
|
||||
return r.WriteJson(value, MIME_JSON) // no charset
|
||||
return writeJSON(r, http.StatusOK, MIME_JSON, value)
|
||||
}
|
||||
|
||||
// WriteJson is a convenience method for writing a value in Json with a given Content-Type
|
||||
// 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.
|
||||
func (r *Response) WriteJson(value interface{}, contentType string) error {
|
||||
var output []byte
|
||||
var err error
|
||||
return writeJSON(r, http.StatusOK, contentType, value)
|
||||
}
|
||||
|
||||
if value == nil { // do not write a nil representation
|
||||
return nil
|
||||
}
|
||||
if r.prettyPrint {
|
||||
output, err = json.MarshalIndent(value, " ", " ")
|
||||
} else {
|
||||
output, err = json.Marshal(value)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return r.WriteErrorString(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
r.Header().Set(HEADER_ContentType, contentType)
|
||||
if r.statusCode > 0 { // a WriteHeader was intercepted
|
||||
r.ResponseWriter.WriteHeader(r.statusCode)
|
||||
}
|
||||
if _, err = r.Write(output); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
|
||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
||||
func (r *Response) WriteHeaderAndJson(status int, value interface{}, contentType string) error {
|
||||
return writeJSON(r, status, contentType, value)
|
||||
}
|
||||
|
||||
// WriteError write the http status and the error string on the response.
|
||||
@ -187,16 +165,19 @@ func (r *Response) WriteError(httpStatus int, err error) error {
|
||||
return r.WriteErrorString(httpStatus, err.Error())
|
||||
}
|
||||
|
||||
// WriteServiceError is a convenience method for a responding with a ServiceError and a status
|
||||
// WriteServiceError is a convenience method for a responding with a status and a ServiceError
|
||||
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
|
||||
r.WriteHeader(httpStatus) // for recording only
|
||||
return r.WriteEntity(err)
|
||||
r.err = err
|
||||
return r.WriteHeaderAndEntity(httpStatus, err)
|
||||
}
|
||||
|
||||
// WriteErrorString is a convenience method for an error status with the actual error
|
||||
func (r *Response) WriteErrorString(status int, errorReason string) error {
|
||||
r.statusCode = status // for recording only
|
||||
r.ResponseWriter.WriteHeader(status)
|
||||
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
|
||||
if r.err == nil {
|
||||
// if not called from WriteError
|
||||
r.err = errors.New(errorReason)
|
||||
}
|
||||
r.WriteHeader(httpStatus)
|
||||
if _, err := r.Write([]byte(errorReason)); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -204,31 +185,13 @@ func (r *Response) WriteErrorString(status int, errorReason string) error {
|
||||
}
|
||||
|
||||
// WriteHeader is overridden to remember the Status Code that has been written.
|
||||
// Note that using this method, the status value is only written when
|
||||
// calling WriteEntity,
|
||||
// or directly calling WriteAsXml or WriteAsJson,
|
||||
// or if the status is one for which no response is allowed:
|
||||
//
|
||||
// 202 = http.StatusAccepted
|
||||
// 204 = http.StatusNoContent
|
||||
// 206 = http.StatusPartialContent
|
||||
// 304 = http.StatusNotModified
|
||||
//
|
||||
// If this behavior does not fit your need then you can write to the underlying response, such as:
|
||||
// response.ResponseWriter.WriteHeader(http.StatusAccepted)
|
||||
// Changes to the Header of the response have no effect after this.
|
||||
func (r *Response) WriteHeader(httpStatus int) {
|
||||
r.statusCode = httpStatus
|
||||
// if 202,204,206,304 then WriteEntity will not be called so we need to pass this code
|
||||
if http.StatusNoContent == httpStatus ||
|
||||
http.StatusNotModified == httpStatus ||
|
||||
http.StatusPartialContent == httpStatus ||
|
||||
http.StatusAccepted == httpStatus {
|
||||
r.ResponseWriter.WriteHeader(httpStatus)
|
||||
}
|
||||
r.ResponseWriter.WriteHeader(httpStatus)
|
||||
}
|
||||
|
||||
// StatusCode returns the code that has been written using WriteHeader.
|
||||
// If WriteHeader, WriteEntity or WriteAsXml has not been called (yet) then return 200 OK.
|
||||
func (r Response) StatusCode() int {
|
||||
if 0 == r.statusCode {
|
||||
// no status code has been written yet; assume OK
|
||||
|
4
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/CHANGES.md
generated
vendored
4
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/CHANGES.md
generated
vendored
@ -1,5 +1,9 @@
|
||||
Change history of swagger
|
||||
=
|
||||
2015-10-16
|
||||
- add type override mechanism for swagger models (MR 254, nathanejohnson)
|
||||
- replace uses of wildcard in generated apidocs (issue 251)
|
||||
|
||||
2015-05-25
|
||||
- (api break) changed the type of Properties in Model
|
||||
- (api break) changed the type of Models in ApiDeclaration
|
||||
|
2
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/config.go
generated
vendored
2
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/config.go
generated
vendored
@ -29,4 +29,6 @@ type Config struct {
|
||||
ApiVersion string
|
||||
// If set then call this handler after building the complete ApiDeclaration Map
|
||||
PostBuildHandler PostBuildDeclarationMapFunc
|
||||
// Swagger global info struct
|
||||
Info Info
|
||||
}
|
||||
|
28
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder.go
generated
vendored
28
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder.go
generated
vendored
@ -132,9 +132,11 @@ func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, mod
|
||||
modelDescription = tag
|
||||
}
|
||||
|
||||
fieldType := field.Type
|
||||
|
||||
prop.setPropertyMetadata(field)
|
||||
if prop.Type != nil {
|
||||
return jsonName, modelDescription, prop
|
||||
}
|
||||
fieldType := field.Type
|
||||
|
||||
// check if type is doing its own marshalling
|
||||
marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
||||
@ -212,8 +214,12 @@ func hasNamedJSONTag(field reflect.StructField) bool {
|
||||
}
|
||||
|
||||
func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) {
|
||||
fieldType := field.Type
|
||||
prop.setPropertyMetadata(field)
|
||||
// Check for type override in tag
|
||||
if prop.Type != nil {
|
||||
return jsonName, prop
|
||||
}
|
||||
fieldType := field.Type
|
||||
// check for anonymous
|
||||
if len(fieldType.Name()) == 0 {
|
||||
// anonymous
|
||||
@ -263,8 +269,12 @@ func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonNam
|
||||
}
|
||||
|
||||
func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
|
||||
fieldType := field.Type
|
||||
// check for type override in tags
|
||||
prop.setPropertyMetadata(field)
|
||||
if prop.Type != nil {
|
||||
return jsonName, prop
|
||||
}
|
||||
fieldType := field.Type
|
||||
var pType = "array"
|
||||
prop.Type = &pType
|
||||
elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
|
||||
@ -284,8 +294,12 @@ func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName
|
||||
}
|
||||
|
||||
func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
|
||||
fieldType := field.Type
|
||||
prop.setPropertyMetadata(field)
|
||||
// Check for type override in tags
|
||||
if prop.Type != nil {
|
||||
return jsonName, prop
|
||||
}
|
||||
fieldType := field.Type
|
||||
|
||||
// override type of pointer to list-likes
|
||||
if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array {
|
||||
@ -338,7 +352,7 @@ func (b modelBuilder) keyFrom(st reflect.Type) string {
|
||||
|
||||
// see also https://golang.org/ref/spec#Numeric_types
|
||||
func (b modelBuilder) isPrimitiveType(modelName string) bool {
|
||||
return strings.Contains("uint8 uint16 uint32 uint64 int int8 int16 int32 int64 float32 float64 bool string byte rune time.Time", modelName)
|
||||
return strings.Contains("uint uint8 uint16 uint32 uint64 int int8 int16 int32 int64 float32 float64 bool string byte rune time.Time", modelName)
|
||||
}
|
||||
|
||||
// jsonNameOfField returns the name of the field as it should appear in JSON format
|
||||
@ -359,6 +373,7 @@ func (b modelBuilder) jsonNameOfField(field reflect.StructField) string {
|
||||
// see also http://json-schema.org/latest/json-schema-core.html#anchor8
|
||||
func (b modelBuilder) jsonSchemaType(modelName string) string {
|
||||
schemaMap := map[string]string{
|
||||
"uint": "integer",
|
||||
"uint8": "integer",
|
||||
"uint16": "integer",
|
||||
"uint32": "integer",
|
||||
@ -389,6 +404,7 @@ func (b modelBuilder) jsonSchemaFormat(modelName string) string {
|
||||
"int32": "int32",
|
||||
"int64": "int64",
|
||||
"byte": "byte",
|
||||
"uint": "integer",
|
||||
"uint8": "byte",
|
||||
"float64": "double",
|
||||
"float32": "float",
|
||||
|
7
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_property_ext.go
generated
vendored
7
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_property_ext.go
generated
vendored
@ -31,6 +31,12 @@ func (prop *ModelProperty) setMaximum(field reflect.StructField) {
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setType(field reflect.StructField) {
|
||||
if tag := field.Tag.Get("type"); tag != "" {
|
||||
prop.Type = &tag
|
||||
}
|
||||
}
|
||||
|
||||
func (prop *ModelProperty) setMinimum(field reflect.StructField) {
|
||||
if tag := field.Tag.Get("minimum"); tag != "" {
|
||||
prop.Minimum = tag
|
||||
@ -56,4 +62,5 @@ func (prop *ModelProperty) setPropertyMetadata(field reflect.StructField) {
|
||||
prop.setMaximum(field)
|
||||
prop.setUniqueItems(field)
|
||||
prop.setDefaultValue(field)
|
||||
prop.setType(field)
|
||||
}
|
||||
|
4
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger.go
generated
vendored
4
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger.go
generated
vendored
@ -48,7 +48,7 @@ type Info struct {
|
||||
TermsOfServiceUrl string `json:"termsOfServiceUrl,omitempty"`
|
||||
Contact string `json:"contact,omitempty"`
|
||||
License string `json:"license,omitempty"`
|
||||
LicensUrl string `json:"licensUrl,omitempty"`
|
||||
LicenseUrl string `json:"licenseUrl,omitempty"`
|
||||
}
|
||||
|
||||
// 5.1.5
|
||||
@ -134,7 +134,7 @@ type Api struct {
|
||||
|
||||
// 5.2.3 Operation Object
|
||||
type Operation struct {
|
||||
Type string `json:"type"`
|
||||
DataTypeFields
|
||||
Method string `json:"method"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
|
21
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_builder.go
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_builder.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package swagger
|
||||
|
||||
type SwaggerBuilder struct {
|
||||
SwaggerService
|
||||
}
|
||||
|
||||
func NewSwaggerBuilder(config Config) *SwaggerBuilder {
|
||||
return &SwaggerBuilder{*newSwaggerService(config)}
|
||||
}
|
||||
|
||||
func (sb SwaggerBuilder) ProduceListing() ResourceListing {
|
||||
return sb.SwaggerService.produceListing()
|
||||
}
|
||||
|
||||
func (sb SwaggerBuilder) ProduceAllDeclarations() map[string]ApiDeclaration {
|
||||
return sb.SwaggerService.produceAllDeclarations()
|
||||
}
|
||||
|
||||
func (sb SwaggerBuilder) ProduceDeclarations(route string) (*ApiDeclaration, bool) {
|
||||
return sb.SwaggerService.produceDeclarations(route)
|
||||
}
|
148
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go
generated
vendored
148
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go
generated
vendored
@ -19,9 +19,35 @@ type SwaggerService struct {
|
||||
}
|
||||
|
||||
func newSwaggerService(config Config) *SwaggerService {
|
||||
return &SwaggerService{
|
||||
sws := &SwaggerService{
|
||||
config: config,
|
||||
apiDeclarationMap: new(ApiDeclarationList)}
|
||||
|
||||
// Build all ApiDeclarations
|
||||
for _, each := range config.WebServices {
|
||||
rootPath := each.RootPath()
|
||||
// skip the api service itself
|
||||
if rootPath != config.ApiPath {
|
||||
if rootPath == "" || rootPath == "/" {
|
||||
// use routes
|
||||
for _, route := range each.Routes() {
|
||||
entry := staticPathFromRoute(route)
|
||||
_, exists := sws.apiDeclarationMap.At(entry)
|
||||
if !exists {
|
||||
sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry))
|
||||
}
|
||||
}
|
||||
} else { // use root path
|
||||
sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if specified then call the PostBuilderHandler
|
||||
if config.PostBuildHandler != nil {
|
||||
config.PostBuildHandler(sws.apiDeclarationMap)
|
||||
}
|
||||
return sws
|
||||
}
|
||||
|
||||
// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf
|
||||
@ -57,31 +83,6 @@ func RegisterSwaggerService(config Config, wsContainer *restful.Container) {
|
||||
LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
|
||||
wsContainer.Add(ws)
|
||||
|
||||
// Build all ApiDeclarations
|
||||
for _, each := range config.WebServices {
|
||||
rootPath := each.RootPath()
|
||||
// skip the api service itself
|
||||
if rootPath != config.ApiPath {
|
||||
if rootPath == "" || rootPath == "/" {
|
||||
// use routes
|
||||
for _, route := range each.Routes() {
|
||||
entry := staticPathFromRoute(route)
|
||||
_, exists := sws.apiDeclarationMap.At(entry)
|
||||
if !exists {
|
||||
sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry))
|
||||
}
|
||||
}
|
||||
} else { // use root path
|
||||
sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if specified then call the PostBuilderHandler
|
||||
if config.PostBuildHandler != nil {
|
||||
config.PostBuildHandler(sws.apiDeclarationMap)
|
||||
}
|
||||
|
||||
// Check paths for UI serving
|
||||
if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
|
||||
swaggerPathSlash := config.SwaggerPath
|
||||
@ -138,7 +139,12 @@ func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.Fil
|
||||
}
|
||||
|
||||
func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) {
|
||||
listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion}
|
||||
listing := sws.produceListing()
|
||||
resp.WriteAsJson(listing)
|
||||
}
|
||||
|
||||
func (sws SwaggerService) produceListing() ResourceListing {
|
||||
listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion, Info: sws.config.Info}
|
||||
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
|
||||
ref := Resource{Path: k}
|
||||
if len(v.Apis) > 0 { // use description of first (could still be empty)
|
||||
@ -146,11 +152,11 @@ func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Respons
|
||||
}
|
||||
listing.Apis = append(listing.Apis, ref)
|
||||
})
|
||||
resp.WriteAsJson(listing)
|
||||
return listing
|
||||
}
|
||||
|
||||
func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) {
|
||||
decl, ok := sws.apiDeclarationMap.At(composeRootPath(req))
|
||||
decl, ok := sws.produceDeclarations(composeRootPath(req))
|
||||
if !ok {
|
||||
resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
|
||||
return
|
||||
@ -180,11 +186,28 @@ func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Re
|
||||
scheme = "https"
|
||||
}
|
||||
}
|
||||
(&decl).BasePath = fmt.Sprintf("%s://%s", scheme, host)
|
||||
decl.BasePath = fmt.Sprintf("%s://%s", scheme, host)
|
||||
}
|
||||
resp.WriteAsJson(decl)
|
||||
}
|
||||
|
||||
func (sws SwaggerService) produceAllDeclarations() map[string]ApiDeclaration {
|
||||
decls := map[string]ApiDeclaration{}
|
||||
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
|
||||
decls[k] = v
|
||||
})
|
||||
return decls
|
||||
}
|
||||
|
||||
func (sws SwaggerService) produceDeclarations(route string) (*ApiDeclaration, bool) {
|
||||
decl, ok := sws.apiDeclarationMap.At(route)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
decl.BasePath = sws.config.WebServicesUrl
|
||||
return &decl, true
|
||||
}
|
||||
|
||||
// composeDeclaration uses all routes and parameters to create a ApiDeclaration
|
||||
func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
|
||||
decl := ApiDeclaration{
|
||||
@ -207,13 +230,15 @@ func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix
|
||||
}
|
||||
}
|
||||
pathToRoutes.Do(func(path string, routes []restful.Route) {
|
||||
api := Api{Path: strings.TrimSuffix(path, "/"), Description: ws.Documentation()}
|
||||
api := Api{Path: strings.TrimSuffix(withoutWildcard(path), "/"), Description: ws.Documentation()}
|
||||
voidString := "void"
|
||||
for _, route := range routes {
|
||||
operation := Operation{
|
||||
Method: route.Method,
|
||||
Summary: route.Doc,
|
||||
Notes: route.Notes,
|
||||
Type: asDataType(route.WriteSample),
|
||||
Method: route.Method,
|
||||
Summary: route.Doc,
|
||||
Notes: route.Notes,
|
||||
// Type gets overwritten if there is a write sample
|
||||
DataTypeFields: DataTypeFields{Type: &voidString},
|
||||
Parameters: []Parameter{},
|
||||
Nickname: route.Operation,
|
||||
ResponseMessages: composeResponseMessages(route, &decl)}
|
||||
@ -238,6 +263,13 @@ func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix
|
||||
return decl
|
||||
}
|
||||
|
||||
func withoutWildcard(path string) string {
|
||||
if strings.HasSuffix(path, ":*}") {
|
||||
return path[0:len(path)-3] + "}"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them.
|
||||
func composeResponseMessages(route restful.Route, decl *ApiDeclaration) (messages []ResponseMessage) {
|
||||
if route.ResponseErrors == nil {
|
||||
@ -299,14 +331,10 @@ func detectCollectionType(st reflect.Type) (bool, reflect.Type) {
|
||||
|
||||
// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
|
||||
func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) {
|
||||
st := reflect.TypeOf(sample)
|
||||
isCollection, st := detectCollectionType(st)
|
||||
modelName := modelBuilder{}.keyFrom(st)
|
||||
if isResponse {
|
||||
if isCollection {
|
||||
modelName = "array[" + modelName + "]"
|
||||
}
|
||||
operation.Type = modelName
|
||||
type_, items := asDataType(sample)
|
||||
operation.Type = type_
|
||||
operation.Items = items
|
||||
}
|
||||
modelBuilder{models}.addModelFrom(sample)
|
||||
}
|
||||
@ -315,7 +343,7 @@ func asSwaggerParameter(param restful.ParameterData) Parameter {
|
||||
return Parameter{
|
||||
DataTypeFields: DataTypeFields{
|
||||
Type: ¶m.DataType,
|
||||
Format: asFormat(param.DataType),
|
||||
Format: asFormat(param.DataType, param.DataFormat),
|
||||
DefaultValue: Special(param.DefaultValue),
|
||||
},
|
||||
Name: param.Name,
|
||||
@ -360,7 +388,10 @@ func composeRootPath(req *restful.Request) string {
|
||||
return path + "/" + g
|
||||
}
|
||||
|
||||
func asFormat(name string) string {
|
||||
func asFormat(dataType string, dataFormat string) string {
|
||||
if dataFormat != "" {
|
||||
return dataFormat
|
||||
}
|
||||
return "" // TODO
|
||||
}
|
||||
|
||||
@ -380,9 +411,30 @@ func asParamType(kind int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func asDataType(any interface{}) string {
|
||||
if any == nil {
|
||||
return "void"
|
||||
func asDataType(any interface{}) (*string, *Item) {
|
||||
// If it's not a collection, return the suggested model name
|
||||
st := reflect.TypeOf(any)
|
||||
isCollection, st := detectCollectionType(st)
|
||||
modelName := modelBuilder{}.keyFrom(st)
|
||||
// if it's not a collection we are done
|
||||
if !isCollection {
|
||||
return &modelName, nil
|
||||
}
|
||||
return reflect.TypeOf(any).Name()
|
||||
|
||||
// XXX: This is not very elegant
|
||||
// We create an Item object referring to the given model
|
||||
models := ModelList{}
|
||||
mb := modelBuilder{&models}
|
||||
mb.addModelFrom(any)
|
||||
|
||||
elemTypeName := mb.getElementTypeName(modelName, "", st)
|
||||
item := new(Item)
|
||||
if mb.isPrimitiveType(elemTypeName) {
|
||||
mapped := mb.jsonSchemaType(elemTypeName)
|
||||
item.Type = &mapped
|
||||
} else {
|
||||
item.Ref = &elemTypeName
|
||||
}
|
||||
tmp := "array"
|
||||
return &tmp, item
|
||||
}
|
||||
|
5
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/test_package/struct.go
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/test_package/struct.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
package test_package
|
||||
|
||||
type TestStruct struct {
|
||||
TestField string
|
||||
}
|
40
Godeps/_workspace/src/github.com/emicklei/go-restful/web_service.go
generated
vendored
40
Godeps/_workspace/src/github.com/emicklei/go-restful/web_service.go
generated
vendored
@ -1,7 +1,9 @@
|
||||
package restful
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/emicklei/go-restful/log"
|
||||
)
|
||||
@ -21,6 +23,15 @@ type WebService struct {
|
||||
filters []FilterFunction
|
||||
documentation string
|
||||
apiVersion string
|
||||
|
||||
dynamicRoutes bool
|
||||
|
||||
// protects 'routes' if dynamic routes are enabled
|
||||
routesLock sync.RWMutex
|
||||
}
|
||||
|
||||
func (w *WebService) SetDynamicRoutes(enable bool) {
|
||||
w.dynamicRoutes = enable
|
||||
}
|
||||
|
||||
// compilePathExpression ensures that the path is compiled into a RegEx for those routers that need it.
|
||||
@ -134,11 +145,28 @@ func FormParameter(name, description string) *Parameter {
|
||||
|
||||
// Route creates a new Route using the RouteBuilder and add to the ordered list of Routes.
|
||||
func (w *WebService) Route(builder *RouteBuilder) *WebService {
|
||||
w.routesLock.Lock()
|
||||
defer w.routesLock.Unlock()
|
||||
builder.copyDefaults(w.produces, w.consumes)
|
||||
w.routes = append(w.routes, builder.Build())
|
||||
return w
|
||||
}
|
||||
|
||||
// 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.")
|
||||
}
|
||||
w.routesLock.Lock()
|
||||
defer w.routesLock.Unlock()
|
||||
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:]...)
|
||||
}
|
||||
}
|
||||
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)
|
||||
@ -160,7 +188,17 @@ func (w *WebService) Consumes(accepts ...string) *WebService {
|
||||
|
||||
// Routes returns the Routes associated with this WebService
|
||||
func (w WebService) Routes() []Route {
|
||||
return w.routes
|
||||
if !w.dynamicRoutes {
|
||||
return w.routes
|
||||
}
|
||||
// Make a copy of the array to prevent concurrency problems
|
||||
w.routesLock.RLock()
|
||||
defer w.routesLock.RUnlock()
|
||||
result := make([]Route, len(w.routes))
|
||||
for ix := range w.routes {
|
||||
result[ix] = w.routes[ix]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// RootPath returns the RootPath associated with this WebService. Default "/"
|
||||
|
@ -3168,7 +3168,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/api/v1/proxy/nodes/{name}/{path:*}",
|
||||
"path": "/api/v1/proxy/nodes/{name}/{path}",
|
||||
"description": "API at /api/v1",
|
||||
"operations": [
|
||||
{
|
||||
@ -5281,7 +5281,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}",
|
||||
"path": "/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path}",
|
||||
"description": "API at /api/v1",
|
||||
"operations": [
|
||||
{
|
||||
@ -6633,7 +6633,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*}",
|
||||
"path": "/api/v1/namespaces/{namespace}/pods/{name}/proxy/{path}",
|
||||
"description": "API at /api/v1",
|
||||
"operations": [
|
||||
{
|
||||
@ -10918,7 +10918,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/api/v1/proxy/namespaces/{namespace}/services/{name}/{path:*}",
|
||||
"path": "/api/v1/proxy/namespaces/{namespace}/services/{name}/{path}",
|
||||
"description": "API at /api/v1",
|
||||
"operations": [
|
||||
{
|
||||
|
@ -6819,7 +6819,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_connect_get_requests_to_proxy_of_pod_2">connect GET requests to proxy of Pod</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>GET /api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*}</pre>
|
||||
<pre>GET /api/v1/namespaces/{namespace}/pods/{name}/proxy/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -6940,7 +6940,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_connect_put_requests_to_proxy_of_pod_2">connect PUT requests to proxy of Pod</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>PUT /api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*}</pre>
|
||||
<pre>PUT /api/v1/namespaces/{namespace}/pods/{name}/proxy/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -7061,7 +7061,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_connect_delete_requests_to_proxy_of_pod_2">connect DELETE requests to proxy of Pod</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>DELETE /api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*}</pre>
|
||||
<pre>DELETE /api/v1/namespaces/{namespace}/pods/{name}/proxy/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -7182,7 +7182,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_connect_post_requests_to_proxy_of_pod_2">connect POST requests to proxy of Pod</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>POST /api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*}</pre>
|
||||
<pre>POST /api/v1/namespaces/{namespace}/pods/{name}/proxy/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -15215,7 +15215,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_get_requests_to_pod_2">proxy GET requests to Pod</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>GET /api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}</pre>
|
||||
<pre>GET /api/v1/proxy/namespaces/{namespace}/pods/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -15328,7 +15328,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_put_requests_to_pod_2">proxy PUT requests to Pod</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>PUT /api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}</pre>
|
||||
<pre>PUT /api/v1/proxy/namespaces/{namespace}/pods/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -15441,7 +15441,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_delete_requests_to_pod_2">proxy DELETE requests to Pod</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>DELETE /api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}</pre>
|
||||
<pre>DELETE /api/v1/proxy/namespaces/{namespace}/pods/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -15554,7 +15554,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_post_requests_to_pod_2">proxy POST requests to Pod</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>POST /api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*}</pre>
|
||||
<pre>POST /api/v1/proxy/namespaces/{namespace}/pods/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -16087,7 +16087,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_get_requests_to_service_2">proxy GET requests to Service</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>GET /api/v1/proxy/namespaces/{namespace}/services/{name}/{path:*}</pre>
|
||||
<pre>GET /api/v1/proxy/namespaces/{namespace}/services/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -16200,7 +16200,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_put_requests_to_service_2">proxy PUT requests to Service</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>PUT /api/v1/proxy/namespaces/{namespace}/services/{name}/{path:*}</pre>
|
||||
<pre>PUT /api/v1/proxy/namespaces/{namespace}/services/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -16313,7 +16313,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_delete_requests_to_service_2">proxy DELETE requests to Service</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>DELETE /api/v1/proxy/namespaces/{namespace}/services/{name}/{path:*}</pre>
|
||||
<pre>DELETE /api/v1/proxy/namespaces/{namespace}/services/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -16426,7 +16426,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_post_requests_to_service_2">proxy POST requests to Service</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>POST /api/v1/proxy/namespaces/{namespace}/services/{name}/{path:*}</pre>
|
||||
<pre>POST /api/v1/proxy/namespaces/{namespace}/services/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -16927,7 +16927,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_get_requests_to_node_2">proxy GET requests to Node</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>GET /api/v1/proxy/nodes/{name}/{path:*}</pre>
|
||||
<pre>GET /api/v1/proxy/nodes/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -17032,7 +17032,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_put_requests_to_node_2">proxy PUT requests to Node</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>PUT /api/v1/proxy/nodes/{name}/{path:*}</pre>
|
||||
<pre>PUT /api/v1/proxy/nodes/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -17137,7 +17137,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_delete_requests_to_node_2">proxy DELETE requests to Node</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>DELETE /api/v1/proxy/nodes/{name}/{path:*}</pre>
|
||||
<pre>DELETE /api/v1/proxy/nodes/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -17242,7 +17242,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
<h3 id="_proxy_post_requests_to_node_2">proxy POST requests to Node</h3>
|
||||
<div class="listingblock">
|
||||
<div class="content">
|
||||
<pre>POST /api/v1/proxy/nodes/{name}/{path:*}</pre>
|
||||
<pre>POST /api/v1/proxy/nodes/{name}/{path}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sect3">
|
||||
@ -23664,7 +23664,7 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2015-11-13 20:43:21 UTC
|
||||
Last updated 2015-12-01 01:46:05 UTC
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
Loading…
Reference in New Issue
Block a user