Update go-restful

pull/6/head
Brendan Burns 2015-08-26 21:20:22 -07:00
parent b6f2f396ba
commit 535c509dd3
12 changed files with 268 additions and 49 deletions

4
Godeps/Godeps.json generated
View File

@ -215,8 +215,8 @@
}, },
{ {
"ImportPath": "github.com/emicklei/go-restful", "ImportPath": "github.com/emicklei/go-restful",
"Comment": "v1.1.3-76-gbfd6ff2", "Comment": "v1.1.3-98-g1f9a0ee",
"Rev": "bfd6ff29d2961031cec64346a92bae4cde96c868" "Rev": "1f9a0ee00ff93717a275e15b30cf7df356255877"
}, },
{ {
"ImportPath": "github.com/evanphx/json-patch", "ImportPath": "github.com/evanphx/json-patch",

View File

@ -1,5 +1,10 @@
Change history of go-restful 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 2015-03-20
- add configurable logging - add configurable logging

View File

@ -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 - Filters for intercepting the request → response flow on Service or Route level
- Request-scoped variables using attributes - Request-scoped variables using attributes
- Containers for WebServices on different HTTP endpoints - 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 responses on OPTIONS (using a filter)
- Automatic CORS request handling (using a filter) - Automatic CORS request handling (using a filter)
- API declaration for Swagger UI (see swagger package) - API declaration for Swagger UI (see swagger package)

View File

@ -73,15 +73,13 @@ 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 {
c.compressor, err = gzip.NewWriterLevel(httpWriter, gzip.BestSpeed) w := GzipWriterPool.Get().(*gzip.Writer)
if err != nil { w.Reset(httpWriter)
return nil, err c.compressor = w
}
} else if ENCODING_DEFLATE == encoding { } else if ENCODING_DEFLATE == encoding {
c.compressor, err = zlib.NewWriterLevel(httpWriter, zlib.BestSpeed) w := ZlibWriterPool.Get().(*zlib.Writer)
if err != nil { w.Reset(httpWriter)
return nil, err c.compressor = w
}
} else { } else {
return nil, errors.New("Unknown encoding:" + encoding) return nil, errors.New("Unknown encoding:" + encoding)
} }

View File

@ -1,11 +1,17 @@
package restful package restful
import ( import (
"bytes"
"compress/gzip"
"compress/zlib"
"io"
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
) )
// go test -v -test.run TestGzip ...restful
func TestGzip(t *testing.T) { func TestGzip(t *testing.T) {
EnableContentEncoding = true EnableContentEncoding = true
httpRequest, _ := http.NewRequest("GET", "/test", nil) httpRequest, _ := http.NewRequest("GET", "/test", nil)
@ -27,6 +33,17 @@ func TestGzip(t *testing.T) {
if httpWriter.Header().Get("Content-Encoding") != "gzip" { if httpWriter.Header().Get("Content-Encoding") != "gzip" {
t.Fatal("Missing gzip header") 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) { func TestDeflate(t *testing.T) {
@ -50,4 +67,61 @@ func TestDeflate(t *testing.T) {
if httpWriter.Header().Get("Content-Encoding") != "deflate" { if httpWriter.Header().Get("Content-Encoding") != "deflate" {
t.Fatal("Missing deflate header") 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)
}
} }

View File

@ -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
}

View File

@ -11,6 +11,7 @@ import (
"os" "os"
"runtime" "runtime"
"strings" "strings"
"sync"
"github.com/emicklei/go-restful/log" "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. // 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 // The requests are further dispatched to routes of WebServices using a RouteSelector
type Container struct { type Container struct {
webServicesLock sync.RWMutex
webServices []*WebService webServices []*WebService
ServeMux *http.ServeMux ServeMux *http.ServeMux
isRegisteredOnRoot bool 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. // Add a WebService to the Container. It will detect duplicate root paths and panic in that case.
func (c *Container) Add(service *WebService) *Container { 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 registered on root then no additional specific mapping is needed
if !c.isRegisteredOnRoot { if !c.isRegisteredOnRoot {
pattern := c.fixedPrefixPath(service.RootPath()) pattern := c.fixedPrefixPath(service.RootPath())
@ -122,6 +126,19 @@ func (c *Container) Add(service *WebService) *Container {
return c 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 // logStackOnRecover is the default RecoverHandleFunction and is called
// when DoNotRecover is false and the recoverHandleFunc is not set for the container. // 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. // 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 // Find best match Route ; err is non nil if no match was found
webService, route, err := c.router.SelectRoute( 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, c.webServices,
httpRequest) httpRequest)
}()
if err != nil { if err != nil {
// a non-200 response has already been written // a non-200 response has already been written
// run container filters anyway ; they should not touch the response... // 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 // RegisteredWebServices returns the collections of added WebServices
func (c Container) RegisteredWebServices() []*WebService { 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 // computeAllowedMethods returns a list of HTTP methods that are valid for a Request

View File

@ -95,8 +95,14 @@ func (p *Parameter) DataType(typeName string) *Parameter {
return p 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 { func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
p.data.DefaultValue = stringRepresentation p.data.DefaultValue = stringRepresentation
return p 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
}

View File

@ -6,6 +6,8 @@ package restful
import ( import (
"bytes" "bytes"
"compress/gzip"
"compress/zlib"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"io" "io"
@ -82,15 +84,17 @@ func (r *Request) HeaderParameter(name string) string {
// 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 // 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)
if doCacheReadEntityBytes { if doCacheReadEntityBytes {
return r.cachingReadEntity(contentType, entityPointer) return r.cachingReadEntity(contentType, contentEncoding, entityPointer)
} }
// unmarshall directly from request Body // 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 var buffer []byte
if r.bodyContent != nil { if r.bodyContent != nil {
buffer = *r.bodyContent buffer = *r.bodyContent
@ -101,22 +105,38 @@ func (r *Request) cachingReadEntity(contentType string, entityPointer interface{
} }
r.bodyContent = &buffer 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) { func (r *Request) decodeEntity(reader io.Reader, contentType string, contentEncoding string, entityPointer interface{}) (err error) {
if strings.Contains(contentType, MIME_XML) { entityReader := reader
return xml.NewDecoder(reader).Decode(entityPointer)
// 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 { if strings.Contains(contentType, MIME_JSON) || MIME_JSON == defaultRequestContentType {
decoder := json.NewDecoder(reader) decoder := json.NewDecoder(entityReader)
decoder.UseNumber() decoder.UseNumber()
return decoder.Decode(entityPointer) 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. // SetAttribute adds or replaces the attribute with the given value.

View File

@ -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) 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 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. 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. // Creates a new response based on a http ResponseWriter.
func NewResponse(httpWriter http.ResponseWriter) *Response { 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 // 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. // WriteError write the http status and the error string on the response.
func (r *Response) WriteError(httpStatus int, err error) error { func (r *Response) WriteError(httpStatus int, err error) error {
r.err = err
return r.WriteErrorString(httpStatus, err.Error()) 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. // WriteHeader is overridden to remember the Status Code that has been written.
// Note that using this method, the status value is only written when // Note that using this method, the status value is only written when
// - calling WriteEntity, // calling WriteEntity,
// - or directly calling WriteAsXml or WriteAsJson, // or directly calling WriteAsXml or WriteAsJson,
// - or if the status is one for which no response is allowed (i.e., // or if the status is one for which no response is allowed:
// 204 (http.StatusNoContent) or 304 (http.StatusNotModified)) //
// 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 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 || if http.StatusNoContent == httpStatus ||
http.StatusNotModified == httpStatus || http.StatusNotModified == httpStatus ||
http.StatusPartialContent == 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
@ -245,3 +256,8 @@ func (r Response) ContentLength() int {
func (r Response) CloseNotify() <-chan bool { func (r Response) CloseNotify() <-chan bool {
return r.ResponseWriter.(http.CloseNotifier).CloseNotify() return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
// Error returns the err created by WriteError
func (r Response) Error() error {
return r.err
}

View File

@ -9,7 +9,7 @@ import (
func TestWriteHeader(t *testing.T) { func TestWriteHeader(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteHeader(123) resp.WriteHeader(123)
if resp.StatusCode() != 123 { if resp.StatusCode() != 123 {
t.Errorf("Unexpected status code:%d", resp.StatusCode()) t.Errorf("Unexpected status code:%d", resp.StatusCode())
@ -18,7 +18,7 @@ func TestWriteHeader(t *testing.T) {
func TestNoWriteHeader(t *testing.T) { func TestNoWriteHeader(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
if resp.StatusCode() != http.StatusOK { if resp.StatusCode() != http.StatusOK {
t.Errorf("Unexpected status code:%d", resp.StatusCode()) t.Errorf("Unexpected status code:%d", resp.StatusCode())
} }
@ -31,7 +31,7 @@ type food struct {
// go test -v -test.run TestMeasureContentLengthXml ...restful // go test -v -test.run TestMeasureContentLengthXml ...restful
func TestMeasureContentLengthXml(t *testing.T) { func TestMeasureContentLengthXml(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteAsXml(food{"apple"}) resp.WriteAsXml(food{"apple"})
if resp.ContentLength() != 76 { if resp.ContentLength() != 76 {
t.Errorf("Incorrect measured length:%d", resp.ContentLength()) t.Errorf("Incorrect measured length:%d", resp.ContentLength())
@ -41,7 +41,7 @@ func TestMeasureContentLengthXml(t *testing.T) {
// go test -v -test.run TestMeasureContentLengthJson ...restful // go test -v -test.run TestMeasureContentLengthJson ...restful
func TestMeasureContentLengthJson(t *testing.T) { func TestMeasureContentLengthJson(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteAsJson(food{"apple"}) resp.WriteAsJson(food{"apple"})
if resp.ContentLength() != 22 { if resp.ContentLength() != 22 {
t.Errorf("Incorrect measured length:%d", resp.ContentLength()) t.Errorf("Incorrect measured length:%d", resp.ContentLength())
@ -51,7 +51,7 @@ func TestMeasureContentLengthJson(t *testing.T) {
// go test -v -test.run TestMeasureContentLengthJsonNotPretty ...restful // go test -v -test.run TestMeasureContentLengthJsonNotPretty ...restful
func TestMeasureContentLengthJsonNotPretty(t *testing.T) { func TestMeasureContentLengthJsonNotPretty(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false} resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false, nil}
resp.WriteAsJson(food{"apple"}) resp.WriteAsJson(food{"apple"})
if resp.ContentLength() != 16 { if resp.ContentLength() != 16 {
t.Errorf("Incorrect measured length:%d", resp.ContentLength()) t.Errorf("Incorrect measured length:%d", resp.ContentLength())
@ -61,7 +61,7 @@ func TestMeasureContentLengthJsonNotPretty(t *testing.T) {
// go test -v -test.run TestMeasureContentLengthWriteErrorString ...restful // go test -v -test.run TestMeasureContentLengthWriteErrorString ...restful
func TestMeasureContentLengthWriteErrorString(t *testing.T) { func TestMeasureContentLengthWriteErrorString(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteErrorString(404, "Invalid") resp.WriteErrorString(404, "Invalid")
if resp.ContentLength() != len("Invalid") { if resp.ContentLength() != len("Invalid") {
t.Errorf("Incorrect measured length:%d", resp.ContentLength()) t.Errorf("Incorrect measured length:%d", resp.ContentLength())
@ -79,7 +79,7 @@ func TestStatusIsPassedToResponse(t *testing.T) {
{write: 400, read: 200}, {write: 400, read: 200},
} { } {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteHeader(each.write) resp.WriteHeader(each.write)
if got, want := httpWriter.Code, each.read; got != want { if got, want := httpWriter.Code, each.read; got != want {
t.Error("got %v want %v", 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 // go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue54 ...restful
func TestStatusCreatedAndContentTypeJson_Issue54(t *testing.T) { func TestStatusCreatedAndContentTypeJson_Issue54(t *testing.T) {
httpWriter := httptest.NewRecorder() 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.WriteHeader(201)
resp.WriteAsJson(food{"Juicy"}) resp.WriteAsJson(food{"Juicy"})
if httpWriter.HeaderMap.Get("Content-Type") != "application/json" { 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 // go test -v -test.run TestLastWriteErrorCaught ...restful
func TestLastWriteErrorCaught(t *testing.T) { func TestLastWriteErrorCaught(t *testing.T) {
httpWriter := errorOnWriteRecorder{httptest.NewRecorder()} 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"}) err := resp.WriteAsJson(food{"Juicy"})
if err.Error() != "fail" { if err.Error() != "fail" {
t.Errorf("Unexpected error message:%v", err) t.Errorf("Unexpected error message:%v", err)
@ -123,7 +123,7 @@ func TestLastWriteErrorCaught(t *testing.T) {
func TestAcceptStarStar_Issue83(t *testing.T) { func TestAcceptStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
// Accept Produces // 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"}) resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type") ct := httpWriter.Header().Get("Content-Type")
if "application/json" != ct { if "application/json" != ct {
@ -135,7 +135,7 @@ func TestAcceptStarStar_Issue83(t *testing.T) {
func TestAcceptSkipStarStar_Issue83(t *testing.T) { func TestAcceptSkipStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
// Accept Produces // 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"}) resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type") ct := httpWriter.Header().Get("Content-Type")
if "application/xml" != ct { if "application/xml" != ct {
@ -147,7 +147,7 @@ func TestAcceptSkipStarStar_Issue83(t *testing.T) {
func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) { func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder() httpWriter := httptest.NewRecorder()
// Accept Produces // 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"}) resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type") ct := httpWriter.Header().Get("Content-Type")
if "application/json" != ct { if "application/json" != ct {
@ -158,7 +158,7 @@ func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) {
// go test -v -test.run TestWriteHeaderNoContent_Issue124 ...restful // go test -v -test.run TestWriteHeaderNoContent_Issue124 ...restful
func TestWriteHeaderNoContent_Issue124(t *testing.T) { func TestWriteHeaderNoContent_Issue124(t *testing.T) {
httpWriter := httptest.NewRecorder() 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) resp.WriteHeader(http.StatusNoContent)
if httpWriter.Code != http.StatusNoContent { if httpWriter.Code != http.StatusNoContent {
t.Errorf("got %d want %d", 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 // go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue163 ...restful
func TestStatusCreatedAndContentTypeJson_Issue163(t *testing.T) { func TestStatusCreatedAndContentTypeJson_Issue163(t *testing.T) {
httpWriter := httptest.NewRecorder() 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) resp.WriteHeader(http.StatusNotModified)
if httpWriter.Code != http.StatusNotModified { if httpWriter.Code != http.StatusNotModified {
t.Errorf("Got %d want %d", httpWriter.Code, http.StatusNotModified) t.Errorf("Got %d want %d", httpWriter.Code, http.StatusNotModified)

View File

@ -173,7 +173,14 @@ func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Re
} else { } else {
host = hostvalues[0] 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) resp.WriteAsJson(decl)
} }