From 535c509dd39dfbaed77d72950e8a37414edb3cf7 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Wed, 26 Aug 2015 21:20:22 -0700 Subject: [PATCH] Update go-restful --- Godeps/Godeps.json | 4 +- .../github.com/emicklei/go-restful/CHANGES.md | 5 ++ .../github.com/emicklei/go-restful/README.md | 2 +- .../emicklei/go-restful/compress.go | 14 ++-- .../emicklei/go-restful/compress_test.go | 74 +++++++++++++++++++ .../emicklei/go-restful/compressor_pools.go | 63 ++++++++++++++++ .../emicklei/go-restful/container.go | 38 +++++++++- .../emicklei/go-restful/parameter.go | 8 +- .../github.com/emicklei/go-restful/request.go | 42 ++++++++--- .../emicklei/go-restful/response.go | 30 ++++++-- .../emicklei/go-restful/response_test.go | 28 +++---- .../go-restful/swagger/swagger_webservice.go | 9 ++- 12 files changed, 268 insertions(+), 49 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_pools.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 1a0e19fb67..d967a12357 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -215,8 +215,8 @@ }, { "ImportPath": "github.com/emicklei/go-restful", - "Comment": "v1.1.3-76-gbfd6ff2", - "Rev": "bfd6ff29d2961031cec64346a92bae4cde96c868" + "Comment": "v1.1.3-98-g1f9a0ee", + "Rev": "1f9a0ee00ff93717a275e15b30cf7df356255877" }, { "ImportPath": "github.com/evanphx/json-patch", diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/CHANGES.md b/Godeps/_workspace/src/github.com/emicklei/go-restful/CHANGES.md index b34d43b1ca..1d209676d9 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/CHANGES.md +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/CHANGES.md @@ -1,5 +1,10 @@ Change history of go-restful = +2015-08-06 +- add support for reading entities from compressed request content +- use sync.Pool for compressors of http response and request body +- add Description to Parameter for documentation in Swagger UI + 2015-03-20 - add configurable logging diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/README.md b/Godeps/_workspace/src/github.com/emicklei/go-restful/README.md index 6e4dedf512..b20603fb39 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/README.md +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/README.md @@ -47,7 +47,7 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo - Filters for intercepting the request → response flow on Service or Route level - Request-scoped variables using attributes - Containers for WebServices on different HTTP endpoints -- Content encoding (gzip,deflate) of responses +- Content encoding (gzip,deflate) of request and response payloads - Automatic responses on OPTIONS (using a filter) - Automatic CORS request handling (using a filter) - API declaration for Swagger UI (see swagger package) diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/compress.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/compress.go index c4dcca000f..4493f4db2e 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/compress.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/compress.go @@ -73,15 +73,13 @@ func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding strin c.writer = httpWriter var err error if ENCODING_GZIP == encoding { - c.compressor, err = gzip.NewWriterLevel(httpWriter, gzip.BestSpeed) - if err != nil { - return nil, err - } + w := GzipWriterPool.Get().(*gzip.Writer) + w.Reset(httpWriter) + c.compressor = w } else if ENCODING_DEFLATE == encoding { - c.compressor, err = zlib.NewWriterLevel(httpWriter, zlib.BestSpeed) - if err != nil { - return nil, err - } + w := ZlibWriterPool.Get().(*zlib.Writer) + w.Reset(httpWriter) + c.compressor = w } else { return nil, errors.New("Unknown encoding:" + encoding) } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/compress_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/compress_test.go index 332fb22197..84a93c3fc9 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/compress_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/compress_test.go @@ -1,11 +1,17 @@ package restful import ( + "bytes" + "compress/gzip" + "compress/zlib" + "io" + "io/ioutil" "net/http" "net/http/httptest" "testing" ) +// go test -v -test.run TestGzip ...restful func TestGzip(t *testing.T) { EnableContentEncoding = true httpRequest, _ := http.NewRequest("GET", "/test", nil) @@ -27,6 +33,17 @@ func TestGzip(t *testing.T) { if httpWriter.Header().Get("Content-Encoding") != "gzip" { t.Fatal("Missing gzip header") } + reader, err := gzip.NewReader(httpWriter.Body) + if err != nil { + t.Fatal(err.Error()) + } + data, err := ioutil.ReadAll(reader) + if err != nil { + t.Fatal(err.Error()) + } + if got, want := string(data), "Hello World"; got != want { + t.Errorf("got %v want %v", got, want) + } } func TestDeflate(t *testing.T) { @@ -50,4 +67,61 @@ func TestDeflate(t *testing.T) { if httpWriter.Header().Get("Content-Encoding") != "deflate" { t.Fatal("Missing deflate header") } + reader, err := zlib.NewReader(httpWriter.Body) + if err != nil { + t.Fatal(err.Error()) + } + data, err := ioutil.ReadAll(reader) + if err != nil { + t.Fatal(err.Error()) + } + if got, want := string(data), "Hello World"; got != want { + t.Errorf("got %v want %v", got, want) + } +} + +func TestGzipDecompressRequestBody(t *testing.T) { + b := new(bytes.Buffer) + w := newGzipWriter() + w.Reset(b) + io.WriteString(w, `{"msg":"hi"}`) + w.Flush() + w.Close() + + req := new(Request) + httpRequest, _ := http.NewRequest("GET", "/", bytes.NewReader(b.Bytes())) + httpRequest.Header.Set("Content-Type", "application/json") + httpRequest.Header.Set("Content-Encoding", "gzip") + req.Request = httpRequest + + doCacheReadEntityBytes = false + doc := make(map[string]interface{}) + req.ReadEntity(&doc) + + if got, want := doc["msg"], "hi"; got != want { + t.Errorf("got %v want %v", got, want) + } +} + +func TestZlibDecompressRequestBody(t *testing.T) { + b := new(bytes.Buffer) + w := newZlibWriter() + w.Reset(b) + io.WriteString(w, `{"msg":"hi"}`) + w.Flush() + w.Close() + + req := new(Request) + httpRequest, _ := http.NewRequest("GET", "/", bytes.NewReader(b.Bytes())) + httpRequest.Header.Set("Content-Type", "application/json") + httpRequest.Header.Set("Content-Encoding", "deflate") + req.Request = httpRequest + + doCacheReadEntityBytes = false + doc := make(map[string]interface{}) + req.ReadEntity(&doc) + + if got, want := doc["msg"], "hi"; got != want { + t.Errorf("got %v want %v", got, want) + } } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_pools.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_pools.go new file mode 100644 index 0000000000..5ee1829605 --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/compressor_pools.go @@ -0,0 +1,63 @@ +package restful + +import ( + "bytes" + "compress/gzip" + "compress/zlib" + "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() + }, +} + +func newGzipWriter() *gzip.Writer { + // create with an empty bytes writer; it will be replaced before using the gzipWriter + writer, err := gzip.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed) + if err != nil { + panic(err.Error()) + } + 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) + b := new(bytes.Buffer) + w.Reset(b) + w.Flush() + w.Close() + reader, err := gzip.NewReader(bytes.NewReader(b.Bytes())) + if err != nil { + panic(err.Error()) + } + 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 { + panic(err.Error()) + } + return writer +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/container.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/container.go index fd2e12ca44..840d14b31e 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/container.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/container.go @@ -11,6 +11,7 @@ import ( "os" "runtime" "strings" + "sync" "github.com/emicklei/go-restful/log" ) @@ -18,6 +19,7 @@ import ( // Container holds a collection of WebServices and a http.ServeMux to dispatch http requests. // The requests are further dispatched to routes of WebServices using a RouteSelector type Container struct { + webServicesLock sync.RWMutex webServices []*WebService ServeMux *http.ServeMux isRegisteredOnRoot bool @@ -83,6 +85,8 @@ func (c *Container) EnableContentEncoding(enabled bool) { // Add a WebService to the Container. It will detect duplicate root paths and panic in that case. func (c *Container) Add(service *WebService) *Container { + c.webServicesLock.Lock() + defer c.webServicesLock.Unlock() // If registered on root then no additional specific mapping is needed if !c.isRegisteredOnRoot { pattern := c.fixedPrefixPath(service.RootPath()) @@ -122,6 +126,19 @@ func (c *Container) Add(service *WebService) *Container { return c } +func (c *Container) Remove(ws *WebService) error { + c.webServicesLock.Lock() + defer c.webServicesLock.Unlock() + newServices := []*WebService{} + for ix := range c.webServices { + if c.webServices[ix].rootPath != ws.rootPath { + newServices = append(newServices, c.webServices[ix]) + } + } + c.webServices = newServices + return nil +} + // logStackOnRecover is the default RecoverHandleFunction and is called // when DoNotRecover is false and the recoverHandleFunc is not set for the container. // Default implementation logs the stacktrace and writes the stacktrace on the response. @@ -190,9 +207,16 @@ func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.R } } // Find best match Route ; err is non nil if no match was found - webService, route, err := c.router.SelectRoute( - c.webServices, - httpRequest) + var webService *WebService + var route *Route + var err error + func() { + c.webServicesLock.RLock() + defer c.webServicesLock.RUnlock() + webService, route, err = c.router.SelectRoute( + c.webServices, + httpRequest) + }() if err != nil { // a non-200 response has already been written // run container filters anyway ; they should not touch the response... @@ -272,7 +296,13 @@ func (c *Container) Filter(filter FilterFunction) { // RegisteredWebServices returns the collections of added WebServices func (c Container) RegisteredWebServices() []*WebService { - return c.webServices + c.webServicesLock.RLock() + defer c.webServicesLock.RUnlock() + result := make([]*WebService, len(c.webServices)) + for ix := range c.webServices { + result[ix] = c.webServices[ix] + } + return result } // computeAllowedMethods returns a list of HTTP methods that are valid for a Request diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go index 05a9987600..a836120b5d 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go @@ -95,8 +95,14 @@ func (p *Parameter) DataType(typeName string) *Parameter { return p } -// DefaultValue sets the default value field and returnw the receiver +// DefaultValue sets the default value field and returns the receiver func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter { p.data.DefaultValue = stringRepresentation return p } + +// Description sets the description value field and returns the receiver +func (p *Parameter) Description(doc string) *Parameter { + p.data.Description = doc + return p +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/request.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/request.go index 00a069f094..e944b8d000 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/request.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/request.go @@ -6,6 +6,8 @@ package restful import ( "bytes" + "compress/gzip" + "compress/zlib" "encoding/json" "encoding/xml" "io" @@ -82,15 +84,17 @@ func (r *Request) HeaderParameter(name string) string { // 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) { + defer r.Request.Body.Close() contentType := r.Request.Header.Get(HEADER_ContentType) + contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding) if doCacheReadEntityBytes { - return r.cachingReadEntity(contentType, entityPointer) + return r.cachingReadEntity(contentType, contentEncoding, entityPointer) } // unmarshall directly from request Body - return r.decodeEntity(r.Request.Body, contentType, entityPointer) + return r.decodeEntity(r.Request.Body, contentType, contentEncoding, entityPointer) } -func (r *Request) cachingReadEntity(contentType string, entityPointer interface{}) (err error) { +func (r *Request) cachingReadEntity(contentType string, contentEncoding string, entityPointer interface{}) (err error) { var buffer []byte if r.bodyContent != nil { buffer = *r.bodyContent @@ -101,22 +105,38 @@ func (r *Request) cachingReadEntity(contentType string, entityPointer interface{ } r.bodyContent = &buffer } - return r.decodeEntity(bytes.NewReader(buffer), contentType, entityPointer) + return r.decodeEntity(bytes.NewReader(buffer), contentType, contentEncoding, entityPointer) } -func (r *Request) decodeEntity(reader io.Reader, contentType string, entityPointer interface{}) (err error) { - if strings.Contains(contentType, MIME_XML) { - return xml.NewDecoder(reader).Decode(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 + } else if ENCODING_DEFLATE == contentEncoding { + zlibReader, err := zlib.NewReader(reader) + if err != nil { + return err + } + entityReader = zlibReader } + + // decode JSON if strings.Contains(contentType, MIME_JSON) || MIME_JSON == defaultRequestContentType { - decoder := json.NewDecoder(reader) + decoder := json.NewDecoder(entityReader) decoder.UseNumber() return decoder.Decode(entityPointer) } - if MIME_XML == defaultRequestContentType { - return xml.NewDecoder(reader).Decode(entityPointer) + + // decode XML + if strings.Contains(contentType, MIME_XML) || MIME_XML == defaultRequestContentType { + return xml.NewDecoder(entityReader).Decode(entityPointer) } - return NewError(400, "Unable to unmarshal content of type:"+contentType) + + return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType) } // SetAttribute adds or replaces the attribute with the given value. diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/response.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/response.go index a33f14248c..eb5a023563 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/response.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/response.go @@ -28,11 +28,12 @@ type Response struct { statusCode int // HTTP status code that has been written explicity (if zero then net/http has written 200) contentLength int // number of bytes written for the response body prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses. + err error // err property is kept when WriteError is called } // Creates a new response based on a http ResponseWriter. func NewResponse(httpWriter http.ResponseWriter) *Response { - return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses} // 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 @@ -182,6 +183,7 @@ func (r *Response) WriteJson(value interface{}, contentType string) error { // WriteError write the http status and the error string on the response. func (r *Response) WriteError(httpStatus int, err error) error { + r.err = err return r.WriteErrorString(httpStatus, err.Error()) } @@ -203,21 +205,30 @@ 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 (i.e., -// 204 (http.StatusNoContent) or 304 (http.StatusNotModified)) +// 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) { r.statusCode = httpStatus - // if 201,204,304 then WriteEntity will not be called so we need to pass this code + // 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.StatusPartialContent == httpStatus || + http.StatusAccepted == 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 @@ -245,3 +256,8 @@ func (r Response) ContentLength() int { func (r Response) CloseNotify() <-chan bool { return r.ResponseWriter.(http.CloseNotifier).CloseNotify() } + +// Error returns the err created by WriteError +func (r Response) Error() error { + return r.err +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/response_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/response_test.go index 6caa4d5274..c66b1f3352 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/response_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/response_test.go @@ -9,7 +9,7 @@ import ( func TestWriteHeader(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} + resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} resp.WriteHeader(123) if resp.StatusCode() != 123 { t.Errorf("Unexpected status code:%d", resp.StatusCode()) @@ -18,7 +18,7 @@ func TestWriteHeader(t *testing.T) { func TestNoWriteHeader(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} + resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} if resp.StatusCode() != http.StatusOK { t.Errorf("Unexpected status code:%d", resp.StatusCode()) } @@ -31,7 +31,7 @@ type food struct { // go test -v -test.run TestMeasureContentLengthXml ...restful func TestMeasureContentLengthXml(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} + resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} resp.WriteAsXml(food{"apple"}) if resp.ContentLength() != 76 { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) @@ -41,7 +41,7 @@ func TestMeasureContentLengthXml(t *testing.T) { // go test -v -test.run TestMeasureContentLengthJson ...restful func TestMeasureContentLengthJson(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} + resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} resp.WriteAsJson(food{"apple"}) if resp.ContentLength() != 22 { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) @@ -51,7 +51,7 @@ func TestMeasureContentLengthJson(t *testing.T) { // go test -v -test.run TestMeasureContentLengthJsonNotPretty ...restful func TestMeasureContentLengthJsonNotPretty(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false} + resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false, nil} resp.WriteAsJson(food{"apple"}) if resp.ContentLength() != 16 { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) @@ -61,7 +61,7 @@ func TestMeasureContentLengthJsonNotPretty(t *testing.T) { // go test -v -test.run TestMeasureContentLengthWriteErrorString ...restful func TestMeasureContentLengthWriteErrorString(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} + resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} resp.WriteErrorString(404, "Invalid") if resp.ContentLength() != len("Invalid") { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) @@ -79,7 +79,7 @@ func TestStatusIsPassedToResponse(t *testing.T) { {write: 400, read: 200}, } { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} + resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil} resp.WriteHeader(each.write) if got, want := httpWriter.Code, each.read; got != want { t.Error("got %v want %v", got, want) @@ -90,7 +90,7 @@ func TestStatusIsPassedToResponse(t *testing.T) { // go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue54 ...restful func TestStatusCreatedAndContentTypeJson_Issue54(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true} + resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil} resp.WriteHeader(201) resp.WriteAsJson(food{"Juicy"}) if httpWriter.HeaderMap.Get("Content-Type") != "application/json" { @@ -112,7 +112,7 @@ func (e errorOnWriteRecorder) Write(bytes []byte) (int, error) { // go test -v -test.run TestLastWriteErrorCaught ...restful func TestLastWriteErrorCaught(t *testing.T) { httpWriter := errorOnWriteRecorder{httptest.NewRecorder()} - resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true} + resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil} err := resp.WriteAsJson(food{"Juicy"}) if err.Error() != "fail" { t.Errorf("Unexpected error message:%v", err) @@ -123,7 +123,7 @@ func TestLastWriteErrorCaught(t *testing.T) { func TestAcceptStarStar_Issue83(t *testing.T) { httpWriter := httptest.NewRecorder() // Accept Produces - resp := Response{httpWriter, "application/bogus,*/*;q=0.8", []string{"application/json"}, 0, 0, true} + resp := Response{httpWriter, "application/bogus,*/*;q=0.8", []string{"application/json"}, 0, 0, true, nil} resp.WriteEntity(food{"Juicy"}) ct := httpWriter.Header().Get("Content-Type") if "application/json" != ct { @@ -135,7 +135,7 @@ func TestAcceptStarStar_Issue83(t *testing.T) { func TestAcceptSkipStarStar_Issue83(t *testing.T) { httpWriter := httptest.NewRecorder() // Accept Produces - resp := Response{httpWriter, " application/xml ,*/* ; q=0.8", []string{"application/json", "application/xml"}, 0, 0, true} + resp := Response{httpWriter, " application/xml ,*/* ; q=0.8", []string{"application/json", "application/xml"}, 0, 0, true, nil} resp.WriteEntity(food{"Juicy"}) ct := httpWriter.Header().Get("Content-Type") if "application/xml" != ct { @@ -147,7 +147,7 @@ func TestAcceptSkipStarStar_Issue83(t *testing.T) { func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) { httpWriter := httptest.NewRecorder() // Accept Produces - resp := Response{httpWriter, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", []string{"application/json"}, 0, 0, true} + resp := Response{httpWriter, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", []string{"application/json"}, 0, 0, true, nil} resp.WriteEntity(food{"Juicy"}) ct := httpWriter.Header().Get("Content-Type") if "application/json" != ct { @@ -158,7 +158,7 @@ func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) { // go test -v -test.run TestWriteHeaderNoContent_Issue124 ...restful func TestWriteHeaderNoContent_Issue124(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "text/plain", []string{"text/plain"}, 0, 0, true} + resp := Response{httpWriter, "text/plain", []string{"text/plain"}, 0, 0, true, nil} resp.WriteHeader(http.StatusNoContent) if httpWriter.Code != http.StatusNoContent { t.Errorf("got %d want %d", httpWriter.Code, http.StatusNoContent) @@ -168,7 +168,7 @@ func TestWriteHeaderNoContent_Issue124(t *testing.T) { // go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue163 ...restful func TestStatusCreatedAndContentTypeJson_Issue163(t *testing.T) { httpWriter := httptest.NewRecorder() - resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true} + resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil} resp.WriteHeader(http.StatusNotModified) if httpWriter.Code != http.StatusNotModified { t.Errorf("Got %d want %d", httpWriter.Code, http.StatusNotModified) diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go index d8585b4f4e..7237253bd1 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go @@ -173,7 +173,14 @@ func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Re } else { host = hostvalues[0] } - (&decl).BasePath = fmt.Sprintf("http://%s", host) + // inspect Referer for the scheme (http vs https) + scheme := "http" + if referer := req.Request.Header["Referer"]; len(referer) > 0 { + if strings.HasPrefix(referer[0], "https") { + scheme = "https" + } + } + (&decl).BasePath = fmt.Sprintf("%s://%s", scheme, host) } resp.WriteAsJson(decl) }