mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
Updating go-restful
This commit is contained in:
parent
e7a2d3090f
commit
d25ec60a7f
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@ -378,8 +378,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/emicklei/go-restful",
|
"ImportPath": "github.com/emicklei/go-restful",
|
||||||
"Comment": "v1.1.3-98-g1f9a0ee",
|
"Comment": "v1.2",
|
||||||
"Rev": "1f9a0ee00ff93717a275e15b30cf7df356255877"
|
"Rev": "777bb3f19bcafe2575ffb2a3e46af92509ae9594"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/evanphx/json-patch",
|
"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
|
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
|
2015-08-06
|
||||||
- add support for reading entities from compressed request content
|
- add support for reading entities from compressed request content
|
||||||
- use sync.Pool for compressors of http response and request body
|
- 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(...)
|
- Panic recovery to produce HTTP 500, customizable using RecoverHandler(...)
|
||||||
- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...)
|
- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...)
|
||||||
- Configurable (trace) logging
|
- Configurable (trace) logging
|
||||||
|
- Customizable encoding using EntityReaderWriter registration
|
||||||
|
- Customizable gzip/deflate readers and writers using CompressorProvider registration
|
||||||
|
|
||||||
### Resources
|
### 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 {
|
type CompressingResponseWriter struct {
|
||||||
writer http.ResponseWriter
|
writer http.ResponseWriter
|
||||||
compressor io.WriteCloser
|
compressor io.WriteCloser
|
||||||
|
encoding string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header is part of http.ResponseWriter interface
|
// Header is part of http.ResponseWriter interface
|
||||||
@ -35,6 +36,9 @@ func (c *CompressingResponseWriter) WriteHeader(status int) {
|
|||||||
// Write is part of http.ResponseWriter interface
|
// Write is part of http.ResponseWriter interface
|
||||||
// It is passed through the compressor
|
// It is passed through the compressor
|
||||||
func (c *CompressingResponseWriter) Write(bytes []byte) (int, error) {
|
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)
|
return c.compressor.Write(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +48,25 @@ func (c *CompressingResponseWriter) CloseNotify() <-chan bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Close the underlying compressor
|
// 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()
|
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.
|
// 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
|
c.writer = httpWriter
|
||||||
var err error
|
var err error
|
||||||
if ENCODING_GZIP == encoding {
|
if ENCODING_GZIP == encoding {
|
||||||
w := GzipWriterPool.Get().(*gzip.Writer)
|
w := currentCompressorProvider.AcquireGzipWriter()
|
||||||
w.Reset(httpWriter)
|
w.Reset(httpWriter)
|
||||||
c.compressor = w
|
c.compressor = w
|
||||||
|
c.encoding = ENCODING_GZIP
|
||||||
} else if ENCODING_DEFLATE == encoding {
|
} else if ENCODING_DEFLATE == encoding {
|
||||||
w := ZlibWriterPool.Get().(*zlib.Writer)
|
w := currentCompressorProvider.AcquireZlibWriter()
|
||||||
w.Reset(httpWriter)
|
w.Reset(httpWriter)
|
||||||
c.compressor = w
|
c.compressor = w
|
||||||
|
c.encoding = ENCODING_DEFLATE
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("Unknown encoding:" + encoding)
|
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
|
||||||
|
}
|
||||||
|
}
|
72
Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_pools.go
generated
vendored
72
Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_pools.go
generated
vendored
@ -1,5 +1,9 @@
|
|||||||
package restful
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
@ -7,12 +11,50 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GzipWriterPool is used to get reusable zippers.
|
// SyncPoolCompessors is a CompressorProvider that use the standard sync.Pool.
|
||||||
// The Get() result must be type asserted to *gzip.Writer.
|
type SyncPoolCompessors struct {
|
||||||
var GzipWriterPool = &sync.Pool{
|
GzipWriterPool *sync.Pool
|
||||||
New: func() interface{} {
|
GzipReaderPool *sync.Pool
|
||||||
return newGzipWriter()
|
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 {
|
func newGzipWriter() *gzip.Writer {
|
||||||
@ -24,17 +66,11 @@ func newGzipWriter() *gzip.Writer {
|
|||||||
return 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 {
|
func newGzipReader() *gzip.Reader {
|
||||||
// create with an empty reader (but with GZIP header); it will be replaced before using the gzipReader
|
// 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)
|
b := new(bytes.Buffer)
|
||||||
w.Reset(b)
|
w.Reset(b)
|
||||||
w.Flush()
|
w.Flush()
|
||||||
@ -46,14 +82,6 @@ func newGzipReader() *gzip.Reader {
|
|||||||
return 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 {
|
func newZlibWriter() *zlib.Writer {
|
||||||
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
||||||
if err != nil {
|
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.
|
// HandleWithFilter registers the handler for the given pattern.
|
||||||
// Container's filter chain is applied for handler.
|
// Container's filter chain is applied for handler.
|
||||||
// If a handler already exists for pattern, HandleWithFilter panics.
|
// 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) {
|
f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
|
||||||
if len(c.containerFilters) == 0 {
|
if len(c.containerFilters) == 0 {
|
||||||
handler.ServeHTTP(httpResponse, httpRequest)
|
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.
|
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 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
|
Trouble shooting
|
||||||
|
|
||||||
This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
|
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
|
version: 1
|
||||||
runtime: go
|
runtime: go
|
||||||
api_version: go1
|
api_version: go1
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"appengine"
|
|
||||||
"appengine/datastore"
|
|
||||||
"appengine/user"
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
"github.com/emicklei/go-restful/swagger"
|
"github.com/emicklei/go-restful/swagger"
|
||||||
|
"google.golang.com/appengine"
|
||||||
|
"google.golang.com/appengine/datastore"
|
||||||
|
"google.golang.com/appengine/user"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"appengine"
|
|
||||||
"appengine/memcache"
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
"github.com/emicklei/go-restful/swagger"
|
"github.com/emicklei/go-restful/swagger"
|
||||||
|
"google.golang.com/appengine"
|
||||||
|
"google.golang.com/appengine/memcache"
|
||||||
"net/http"
|
"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
|
// Add container filter to enable CORS
|
||||||
cors := restful.CrossOriginResourceSharing{
|
cors := restful.CrossOriginResourceSharing{
|
||||||
ExposeHeaders: []string{"X-My-Header"},
|
ExposeHeaders: []string{"X-My-Header"},
|
||||||
AllowedHeaders: []string{"Content-Type"},
|
AllowedHeaders: []string{"Content-Type", "Accept"},
|
||||||
CookiesAllowed: false,
|
CookiesAllowed: false,
|
||||||
Container: wsContainer}
|
Container: wsContainer}
|
||||||
wsContainer.Filter(cors.Filter)
|
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
|
usr.Id = strconv.Itoa(len(u.users) + 1) // simple id generation
|
||||||
u.users[usr.Id] = *usr
|
u.users[usr.Id] = *usr
|
||||||
response.WriteHeader(http.StatusCreated)
|
response.WriteHeaderAndEntity(http.StatusCreated, usr)
|
||||||
response.WriteEntity(usr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT http://localhost:8080/users/1
|
// PUT http://localhost:8080/users/1
|
||||||
|
@ -102,8 +102,7 @@ func (u *UserService) createUser(request *restful.Request, response *restful.Res
|
|||||||
err := request.ReadEntity(&usr)
|
err := request.ReadEntity(&usr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
u.users[usr.Id] = usr
|
u.users[usr.Id] = usr
|
||||||
response.WriteHeader(http.StatusCreated)
|
response.WriteHeaderAndEntity(http.StatusCreated, usr)
|
||||||
response.WriteEntity(usr)
|
|
||||||
} else {
|
} else {
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
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() {
|
func init() {
|
||||||
// default Logger
|
// 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) {
|
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
|
// 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.
|
// 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) {
|
func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) {
|
||||||
if "OPTIONS" != req.Request.Method {
|
if "OPTIONS" != req.Request.Method {
|
||||||
chain.ProcessFilter(req, resp)
|
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
|
// 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.
|
// 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 {
|
func OPTIONSFilter() FilterFunction {
|
||||||
return DefaultContainer.OPTIONSFilter
|
return DefaultContainer.OPTIONSFilter
|
||||||
}
|
}
|
||||||
|
8
Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go
generated
vendored
8
Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go
generated
vendored
@ -30,7 +30,7 @@ type Parameter struct {
|
|||||||
// ParameterData represents the state of a Parameter.
|
// ParameterData represents the state of a Parameter.
|
||||||
// It is made public to make it accessible to e.g. the Swagger package.
|
// It is made public to make it accessible to e.g. the Swagger package.
|
||||||
type ParameterData struct {
|
type ParameterData struct {
|
||||||
Name, Description, DataType string
|
Name, Description, DataType, DataFormat string
|
||||||
Kind int
|
Kind int
|
||||||
Required bool
|
Required bool
|
||||||
AllowableValues map[string]string
|
AllowableValues map[string]string
|
||||||
@ -95,6 +95,12 @@ func (p *Parameter) DataType(typeName string) *Parameter {
|
|||||||
return p
|
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
|
// DefaultValue sets the default value field and returns the receiver
|
||||||
func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
|
func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
|
||||||
p.data.DefaultValue = stringRepresentation
|
p.data.DefaultValue = stringRepresentation
|
||||||
|
62
Godeps/_workspace/src/github.com/emicklei/go-restful/request.go
generated
vendored
62
Godeps/_workspace/src/github.com/emicklei/go-restful/request.go
generated
vendored
@ -6,14 +6,9 @@ package restful
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultRequestContentType string
|
var defaultRequestContentType string
|
||||||
@ -81,62 +76,43 @@ func (r *Request) HeaderParameter(name string) string {
|
|||||||
return r.Request.Header.Get(name)
|
return r.Request.Header.Get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadEntity checks the Accept header and reads the content into the entityPointer
|
// ReadEntity checks the Accept header and reads the content into the entityPointer.
|
||||||
// May be called multiple times in the request-response flow
|
|
||||||
func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
|
func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
|
||||||
defer r.Request.Body.Close()
|
|
||||||
contentType := r.Request.Header.Get(HEADER_ContentType)
|
contentType := r.Request.Header.Get(HEADER_ContentType)
|
||||||
contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding)
|
contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding)
|
||||||
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) {
|
// OLD feature, cache the body for reads
|
||||||
var buffer []byte
|
if doCacheReadEntityBytes {
|
||||||
if r.bodyContent != nil {
|
if r.bodyContent == nil {
|
||||||
buffer = *r.bodyContent
|
data, err := ioutil.ReadAll(r.Request.Body)
|
||||||
} else {
|
|
||||||
buffer, err = ioutil.ReadAll(r.Request.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.bodyContent = &buffer
|
r.bodyContent = &data
|
||||||
|
}
|
||||||
|
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
|
// check if the request body needs decompression
|
||||||
if ENCODING_GZIP == contentEncoding {
|
if ENCODING_GZIP == contentEncoding {
|
||||||
gzipReader := GzipReaderPool.Get().(*gzip.Reader)
|
gzipReader := currentCompressorProvider.AcquireGzipReader()
|
||||||
gzipReader.Reset(reader)
|
defer currentCompressorProvider.ReleaseGzipReader(gzipReader)
|
||||||
entityReader = gzipReader
|
gzipReader.Reset(r.Request.Body)
|
||||||
|
r.Request.Body = gzipReader
|
||||||
} else if ENCODING_DEFLATE == contentEncoding {
|
} else if ENCODING_DEFLATE == contentEncoding {
|
||||||
zlibReader, err := zlib.NewReader(reader)
|
zlibReader, err := zlib.NewReader(r.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode XML
|
|
||||||
if strings.Contains(contentType, MIME_XML) || MIME_XML == defaultRequestContentType {
|
|
||||||
return xml.NewDecoder(entityReader).Decode(entityPointer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lookup the EntityReader
|
||||||
|
entityReader, ok := entityAccessRegistry.AccessorAt(contentType)
|
||||||
|
if !ok {
|
||||||
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
|
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.
|
// SetAttribute adds or replaces the attribute with the given value.
|
||||||
|
177
Godeps/_workspace/src/github.com/emicklei/go-restful/response.go
generated
vendored
177
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.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"errors"
|
||||||
"encoding/xml"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -14,9 +13,7 @@ import (
|
|||||||
// DEPRECATED, use DefaultResponseContentType(mime)
|
// DEPRECATED, use DefaultResponseContentType(mime)
|
||||||
var DefaultResponseMimeType string
|
var DefaultResponseMimeType string
|
||||||
|
|
||||||
//PrettyPrintResponses controls the indentation feature of XML and JSON
|
//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
|
||||||
//serialization in the response methods WriteEntity, WriteAsJson, and
|
|
||||||
//WriteAsXml.
|
|
||||||
var PrettyPrintResponses = true
|
var PrettyPrintResponses = true
|
||||||
|
|
||||||
// Response is a wrapper on the actual http ResponseWriter
|
// 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
|
return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses, nil} // empty content-types
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Accept header matching fails, fall back to this type, otherwise
|
// If Accept header matching fails, fall back to this type.
|
||||||
// a "406: Not Acceptable" response is returned.
|
|
||||||
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
||||||
// Example:
|
// Example:
|
||||||
// restful.DefaultResponseContentType(restful.MIME_JSON)
|
// restful.DefaultResponseContentType(restful.MIME_JSON)
|
||||||
@ -68,117 +64,99 @@ func (r *Response) SetRequestAccepts(mime string) {
|
|||||||
r.requestAccept = mime
|
r.requestAccept = mime
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteEntity marshals the value using the representation denoted by the Accept Header (XML or JSON)
|
// EntityWriter returns the registered EntityWriter that the entity (requested resource)
|
||||||
// If no Accept header is specified (or */*) then return the Content-Type as specified by the first in the Route.Produces.
|
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
|
||||||
// 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 called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
|
||||||
// If the value is nil then nothing is written. You may want to call WriteHeader(http.StatusNotFound) instead.
|
func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
|
||||||
// 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
|
|
||||||
}
|
|
||||||
for _, qualifiedMime := range strings.Split(r.requestAccept, ",") {
|
for _, qualifiedMime := range strings.Split(r.requestAccept, ",") {
|
||||||
mime := strings.Trim(strings.Split(qualifiedMime, ";")[0], " ")
|
mime := strings.Trim(strings.Split(qualifiedMime, ";")[0], " ")
|
||||||
if 0 == len(mime) || mime == "*/*" {
|
if 0 == len(mime) || mime == "*/*" {
|
||||||
for _, each := range r.routeProduces {
|
for _, each := range r.routeProduces {
|
||||||
if MIME_JSON == each {
|
if MIME_JSON == each {
|
||||||
return r.WriteAsJson(value)
|
return entityAccessRegistry.AccessorAt(MIME_JSON)
|
||||||
}
|
}
|
||||||
if MIME_XML == each {
|
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
|
} else { // mime is not blank; see if we have a match in Produces
|
||||||
for _, each := range r.routeProduces {
|
for _, each := range r.routeProduces {
|
||||||
if mime == each {
|
if mime == each {
|
||||||
if MIME_JSON == each {
|
if MIME_JSON == each {
|
||||||
return r.WriteAsJson(value)
|
return entityAccessRegistry.AccessorAt(MIME_JSON)
|
||||||
}
|
}
|
||||||
if MIME_XML == each {
|
if MIME_XML == each {
|
||||||
return r.WriteAsXml(value)
|
return entityAccessRegistry.AccessorAt(MIME_XML)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
writer, ok := entityAccessRegistry.AccessorAt(r.requestAccept)
|
||||||
|
if !ok {
|
||||||
|
// if not registered then fallback to the defaults (if set)
|
||||||
if DefaultResponseMimeType == MIME_JSON {
|
if DefaultResponseMimeType == MIME_JSON {
|
||||||
return r.WriteAsJson(value)
|
return entityAccessRegistry.AccessorAt(MIME_JSON)
|
||||||
} else if DefaultResponseMimeType == MIME_XML {
|
}
|
||||||
return r.WriteAsXml(value)
|
if DefaultResponseMimeType == MIME_XML {
|
||||||
} else {
|
return entityAccessRegistry.AccessorAt(MIME_XML)
|
||||||
|
}
|
||||||
if trace {
|
if trace {
|
||||||
traceLogger.Printf("mismatch in mime-types and no defaults; (http)Accept=%v,(route)Produces=%v\n", r.requestAccept, r.routeProduces)
|
traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
|
||||||
}
|
|
||||||
r.WriteHeader(http.StatusNotAcceptable) // for recording only
|
|
||||||
r.ResponseWriter.WriteHeader(http.StatusNotAcceptable)
|
|
||||||
if _, err := r.Write([]byte("406: Not Acceptable")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 nil
|
||||||
|
}
|
||||||
|
return writer.Write(r, status, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the 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 {
|
func (r *Response) WriteAsXml(value interface{}) error {
|
||||||
var output []byte
|
return writeXML(r, http.StatusOK, MIME_XML, value)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
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 {
|
func (r *Response) WriteJson(value interface{}, contentType string) error {
|
||||||
var output []byte
|
return writeJSON(r, http.StatusOK, contentType, value)
|
||||||
var err error
|
}
|
||||||
|
|
||||||
if value == nil { // do not write a nil representation
|
// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
|
||||||
return nil
|
// 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 {
|
||||||
if r.prettyPrint {
|
return writeJSON(r, status, contentType, value)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteError write the http status and the error string on the response.
|
// 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())
|
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 {
|
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
|
||||||
r.WriteHeader(httpStatus) // for recording only
|
r.err = err
|
||||||
return r.WriteEntity(err)
|
return r.WriteHeaderAndEntity(httpStatus, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteErrorString is a convenience method for an error status with the actual error
|
// WriteErrorString is a convenience method for an error status with the actual error
|
||||||
func (r *Response) WriteErrorString(status int, errorReason string) error {
|
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
|
||||||
r.statusCode = status // for recording only
|
if r.err == nil {
|
||||||
r.ResponseWriter.WriteHeader(status)
|
// if not called from WriteError
|
||||||
|
r.err = errors.New(errorReason)
|
||||||
|
}
|
||||||
|
r.WriteHeader(httpStatus)
|
||||||
if _, err := r.Write([]byte(errorReason)); err != nil {
|
if _, err := r.Write([]byte(errorReason)); err != nil {
|
||||||
return err
|
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.
|
// WriteHeader is overridden to remember the Status Code that has been written.
|
||||||
// Note that using this method, the status value is only written when
|
// Changes to the Header of the response have no effect after this.
|
||||||
// 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)
|
|
||||||
func (r *Response) WriteHeader(httpStatus int) {
|
func (r *Response) WriteHeader(httpStatus int) {
|
||||||
r.statusCode = httpStatus
|
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.
|
// 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 {
|
func (r Response) StatusCode() int {
|
||||||
if 0 == r.statusCode {
|
if 0 == r.statusCode {
|
||||||
// no status code has been written yet; assume OK
|
// 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
|
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
|
2015-05-25
|
||||||
- (api break) changed the type of Properties in Model
|
- (api break) changed the type of Properties in Model
|
||||||
- (api break) changed the type of Models in ApiDeclaration
|
- (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
|
ApiVersion string
|
||||||
// If set then call this handler after building the complete ApiDeclaration Map
|
// If set then call this handler after building the complete ApiDeclaration Map
|
||||||
PostBuildHandler PostBuildDeclarationMapFunc
|
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
|
modelDescription = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldType := field.Type
|
|
||||||
|
|
||||||
prop.setPropertyMetadata(field)
|
prop.setPropertyMetadata(field)
|
||||||
|
if prop.Type != nil {
|
||||||
|
return jsonName, modelDescription, prop
|
||||||
|
}
|
||||||
|
fieldType := field.Type
|
||||||
|
|
||||||
// check if type is doing its own marshalling
|
// check if type is doing its own marshalling
|
||||||
marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
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) {
|
func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) {
|
||||||
fieldType := field.Type
|
|
||||||
prop.setPropertyMetadata(field)
|
prop.setPropertyMetadata(field)
|
||||||
|
// Check for type override in tag
|
||||||
|
if prop.Type != nil {
|
||||||
|
return jsonName, prop
|
||||||
|
}
|
||||||
|
fieldType := field.Type
|
||||||
// check for anonymous
|
// check for anonymous
|
||||||
if len(fieldType.Name()) == 0 {
|
if len(fieldType.Name()) == 0 {
|
||||||
// anonymous
|
// 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) {
|
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)
|
prop.setPropertyMetadata(field)
|
||||||
|
if prop.Type != nil {
|
||||||
|
return jsonName, prop
|
||||||
|
}
|
||||||
|
fieldType := field.Type
|
||||||
var pType = "array"
|
var pType = "array"
|
||||||
prop.Type = &pType
|
prop.Type = &pType
|
||||||
elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
|
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) {
|
func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
|
||||||
fieldType := field.Type
|
|
||||||
prop.setPropertyMetadata(field)
|
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
|
// override type of pointer to list-likes
|
||||||
if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array {
|
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
|
// see also https://golang.org/ref/spec#Numeric_types
|
||||||
func (b modelBuilder) isPrimitiveType(modelName string) bool {
|
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
|
// 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
|
// see also http://json-schema.org/latest/json-schema-core.html#anchor8
|
||||||
func (b modelBuilder) jsonSchemaType(modelName string) string {
|
func (b modelBuilder) jsonSchemaType(modelName string) string {
|
||||||
schemaMap := map[string]string{
|
schemaMap := map[string]string{
|
||||||
|
"uint": "integer",
|
||||||
"uint8": "integer",
|
"uint8": "integer",
|
||||||
"uint16": "integer",
|
"uint16": "integer",
|
||||||
"uint32": "integer",
|
"uint32": "integer",
|
||||||
@ -389,6 +404,7 @@ func (b modelBuilder) jsonSchemaFormat(modelName string) string {
|
|||||||
"int32": "int32",
|
"int32": "int32",
|
||||||
"int64": "int64",
|
"int64": "int64",
|
||||||
"byte": "byte",
|
"byte": "byte",
|
||||||
|
"uint": "integer",
|
||||||
"uint8": "byte",
|
"uint8": "byte",
|
||||||
"float64": "double",
|
"float64": "double",
|
||||||
"float32": "float",
|
"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) {
|
func (prop *ModelProperty) setMinimum(field reflect.StructField) {
|
||||||
if tag := field.Tag.Get("minimum"); tag != "" {
|
if tag := field.Tag.Get("minimum"); tag != "" {
|
||||||
prop.Minimum = tag
|
prop.Minimum = tag
|
||||||
@ -56,4 +62,5 @@ func (prop *ModelProperty) setPropertyMetadata(field reflect.StructField) {
|
|||||||
prop.setMaximum(field)
|
prop.setMaximum(field)
|
||||||
prop.setUniqueItems(field)
|
prop.setUniqueItems(field)
|
||||||
prop.setDefaultValue(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"`
|
TermsOfServiceUrl string `json:"termsOfServiceUrl,omitempty"`
|
||||||
Contact string `json:"contact,omitempty"`
|
Contact string `json:"contact,omitempty"`
|
||||||
License string `json:"license,omitempty"`
|
License string `json:"license,omitempty"`
|
||||||
LicensUrl string `json:"licensUrl,omitempty"`
|
LicenseUrl string `json:"licenseUrl,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.1.5
|
// 5.1.5
|
||||||
@ -134,7 +134,7 @@ type Api struct {
|
|||||||
|
|
||||||
// 5.2.3 Operation Object
|
// 5.2.3 Operation Object
|
||||||
type Operation struct {
|
type Operation struct {
|
||||||
Type string `json:"type"`
|
DataTypeFields
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitempty"`
|
||||||
Notes string `json:"notes,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)
|
||||||
|
}
|
142
Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go
generated
vendored
142
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 {
|
func newSwaggerService(config Config) *SwaggerService {
|
||||||
return &SwaggerService{
|
sws := &SwaggerService{
|
||||||
config: config,
|
config: config,
|
||||||
apiDeclarationMap: new(ApiDeclarationList)}
|
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
|
// 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)
|
LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
|
||||||
wsContainer.Add(ws)
|
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
|
// Check paths for UI serving
|
||||||
if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
|
if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
|
||||||
swaggerPathSlash := 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) {
|
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) {
|
sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
|
||||||
ref := Resource{Path: k}
|
ref := Resource{Path: k}
|
||||||
if len(v.Apis) > 0 { // use description of first (could still be empty)
|
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)
|
listing.Apis = append(listing.Apis, ref)
|
||||||
})
|
})
|
||||||
resp.WriteAsJson(listing)
|
return listing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) {
|
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 {
|
if !ok {
|
||||||
resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
|
resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
|
||||||
return
|
return
|
||||||
@ -180,11 +186,28 @@ func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Re
|
|||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(&decl).BasePath = fmt.Sprintf("%s://%s", scheme, host)
|
decl.BasePath = fmt.Sprintf("%s://%s", scheme, host)
|
||||||
}
|
}
|
||||||
resp.WriteAsJson(decl)
|
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
|
// composeDeclaration uses all routes and parameters to create a ApiDeclaration
|
||||||
func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
|
func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
|
||||||
decl := ApiDeclaration{
|
decl := ApiDeclaration{
|
||||||
@ -207,13 +230,15 @@ func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pathToRoutes.Do(func(path string, routes []restful.Route) {
|
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 {
|
for _, route := range routes {
|
||||||
operation := Operation{
|
operation := Operation{
|
||||||
Method: route.Method,
|
Method: route.Method,
|
||||||
Summary: route.Doc,
|
Summary: route.Doc,
|
||||||
Notes: route.Notes,
|
Notes: route.Notes,
|
||||||
Type: asDataType(route.WriteSample),
|
// Type gets overwritten if there is a write sample
|
||||||
|
DataTypeFields: DataTypeFields{Type: &voidString},
|
||||||
Parameters: []Parameter{},
|
Parameters: []Parameter{},
|
||||||
Nickname: route.Operation,
|
Nickname: route.Operation,
|
||||||
ResponseMessages: composeResponseMessages(route, &decl)}
|
ResponseMessages: composeResponseMessages(route, &decl)}
|
||||||
@ -238,6 +263,13 @@ func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix
|
|||||||
return decl
|
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.
|
// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them.
|
||||||
func composeResponseMessages(route restful.Route, decl *ApiDeclaration) (messages []ResponseMessage) {
|
func composeResponseMessages(route restful.Route, decl *ApiDeclaration) (messages []ResponseMessage) {
|
||||||
if route.ResponseErrors == nil {
|
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
|
// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
|
||||||
func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) {
|
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 isResponse {
|
||||||
if isCollection {
|
type_, items := asDataType(sample)
|
||||||
modelName = "array[" + modelName + "]"
|
operation.Type = type_
|
||||||
}
|
operation.Items = items
|
||||||
operation.Type = modelName
|
|
||||||
}
|
}
|
||||||
modelBuilder{models}.addModelFrom(sample)
|
modelBuilder{models}.addModelFrom(sample)
|
||||||
}
|
}
|
||||||
@ -315,7 +343,7 @@ func asSwaggerParameter(param restful.ParameterData) Parameter {
|
|||||||
return Parameter{
|
return Parameter{
|
||||||
DataTypeFields: DataTypeFields{
|
DataTypeFields: DataTypeFields{
|
||||||
Type: ¶m.DataType,
|
Type: ¶m.DataType,
|
||||||
Format: asFormat(param.DataType),
|
Format: asFormat(param.DataType, param.DataFormat),
|
||||||
DefaultValue: Special(param.DefaultValue),
|
DefaultValue: Special(param.DefaultValue),
|
||||||
},
|
},
|
||||||
Name: param.Name,
|
Name: param.Name,
|
||||||
@ -360,7 +388,10 @@ func composeRootPath(req *restful.Request) string {
|
|||||||
return path + "/" + g
|
return path + "/" + g
|
||||||
}
|
}
|
||||||
|
|
||||||
func asFormat(name string) string {
|
func asFormat(dataType string, dataFormat string) string {
|
||||||
|
if dataFormat != "" {
|
||||||
|
return dataFormat
|
||||||
|
}
|
||||||
return "" // TODO
|
return "" // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,9 +411,30 @@ func asParamType(kind int) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func asDataType(any interface{}) string {
|
func asDataType(any interface{}) (*string, *Item) {
|
||||||
if any == nil {
|
// If it's not a collection, return the suggested model name
|
||||||
return "void"
|
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
|
||||||
|
}
|
38
Godeps/_workspace/src/github.com/emicklei/go-restful/web_service.go
generated
vendored
38
Godeps/_workspace/src/github.com/emicklei/go-restful/web_service.go
generated
vendored
@ -1,7 +1,9 @@
|
|||||||
package restful
|
package restful
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/log"
|
"github.com/emicklei/go-restful/log"
|
||||||
)
|
)
|
||||||
@ -21,6 +23,15 @@ type WebService struct {
|
|||||||
filters []FilterFunction
|
filters []FilterFunction
|
||||||
documentation string
|
documentation string
|
||||||
apiVersion 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.
|
// 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.
|
// Route creates a new Route using the RouteBuilder and add to the ordered list of Routes.
|
||||||
func (w *WebService) Route(builder *RouteBuilder) *WebService {
|
func (w *WebService) Route(builder *RouteBuilder) *WebService {
|
||||||
|
w.routesLock.Lock()
|
||||||
|
defer w.routesLock.Unlock()
|
||||||
builder.copyDefaults(w.produces, w.consumes)
|
builder.copyDefaults(w.produces, w.consumes)
|
||||||
w.routes = append(w.routes, builder.Build())
|
w.routes = append(w.routes, builder.Build())
|
||||||
return w
|
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
|
// Method creates a new RouteBuilder and initialize its http method
|
||||||
func (w *WebService) Method(httpMethod string) *RouteBuilder {
|
func (w *WebService) Method(httpMethod string) *RouteBuilder {
|
||||||
return new(RouteBuilder).servicePath(w.rootPath).Method(httpMethod)
|
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
|
// Routes returns the Routes associated with this WebService
|
||||||
func (w WebService) Routes() []Route {
|
func (w WebService) Routes() []Route {
|
||||||
|
if !w.dynamicRoutes {
|
||||||
return w.routes
|
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 "/"
|
// RootPath returns the RootPath associated with this WebService. Default "/"
|
||||||
|
Loading…
Reference in New Issue
Block a user