Merge pull request #24129 from uruddarraju/gorestgodep

Updating go-restful dependency to fix thirdparty issues
This commit is contained in:
Daniel Smith 2016-04-14 11:25:22 -07:00
commit 84e49854e1
14 changed files with 180 additions and 45 deletions

4
Godeps/Godeps.json generated
View File

@ -492,8 +492,8 @@
}, },
{ {
"ImportPath": "github.com/emicklei/go-restful", "ImportPath": "github.com/emicklei/go-restful",
"Comment": "v1.2", "Comment": "v1.2-34-g496d495",
"Rev": "777bb3f19bcafe2575ffb2a3e46af92509ae9594" "Rev": "496d495156da218b9912f03dfa7df7f80fbd8cc3"
}, },
{ {
"ImportPath": "github.com/evanphx/json-patch", "ImportPath": "github.com/evanphx/json-patch",

View File

@ -1,5 +1,9 @@
Change history of go-restful Change history of go-restful
= =
2016-02-14
- take the qualify factor of the Accept header mediatype into account when deciding the contentype of the response
- add constructors for custom entity accessors for xml and json
2015-09-27 2015-09-27
- rename new WriteStatusAnd... to WriteHeaderAnd... for consistency - rename new WriteStatusAnd... to WriteHeaderAnd... for consistency

View File

@ -5,10 +5,12 @@ package restful
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
import ( import (
"bufio"
"compress/gzip" "compress/gzip"
"compress/zlib" "compress/zlib"
"errors" "errors"
"io" "io"
"net"
"net/http" "net/http"
"strings" "strings"
) )
@ -69,6 +71,17 @@ func (c *CompressingResponseWriter) isCompressorClosed() bool {
return nil == c.compressor return nil == c.compressor
} }
// Hijack implements the Hijacker interface
// This is especially useful when combining Container.EnabledContentEncoding
// in combination with websockets (for instance gorilla/websocket)
func (c *CompressingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker, ok := c.writer.(http.Hijacker)
if !ok {
return nil, nil, errors.New("ResponseWriter doesn't support Hijacker interface")
}
return hijacker.Hijack()
}
// WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested. // WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested.
func wantsCompressedResponse(httpRequest *http.Request) (bool, string) { func wantsCompressedResponse(httpRequest *http.Request) (bool, string) {
header := httpRequest.Header.Get(HEADER_AcceptEncoding) header := httpRequest.Header.Get(HEADER_AcceptEncoding)

View File

@ -74,7 +74,11 @@ func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response
func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) { func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) {
if len(c.AllowedMethods) == 0 { if len(c.AllowedMethods) == 0 {
c.AllowedMethods = c.Container.computeAllowedMethods(req) if c.Container == nil {
c.AllowedMethods = DefaultContainer.computeAllowedMethods(req)
} else {
c.AllowedMethods = c.Container.computeAllowedMethods(req)
}
} }
acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod) acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod)

View File

@ -36,8 +36,8 @@ type entityReaderWriters struct {
} }
func init() { func init() {
RegisterEntityAccessor(MIME_JSON, entityJSONAccess{ContentType: MIME_JSON}) RegisterEntityAccessor(MIME_JSON, NewEntityAccessorJSON(MIME_JSON))
RegisterEntityAccessor(MIME_XML, entityXMLAccess{ContentType: MIME_XML}) RegisterEntityAccessor(MIME_XML, NewEntityAccessorXML(MIME_XML))
} }
// RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type. // RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type.
@ -47,8 +47,20 @@ func RegisterEntityAccessor(mime string, erw EntityReaderWriter) {
entityAccessRegistry.accessors[mime] = erw entityAccessRegistry.accessors[mime] = erw
} }
// AccessorAt returns the registered ReaderWriter for this MIME type. // NewEntityAccessorJSON returns a new EntityReaderWriter for accessing JSON content.
func (r *entityReaderWriters) AccessorAt(mime string) (EntityReaderWriter, bool) { // This package is already initialized with such an accessor using the MIME_JSON contentType.
func NewEntityAccessorJSON(contentType string) EntityReaderWriter {
return entityJSONAccess{ContentType: contentType}
}
// NewEntityAccessorXML returns a new EntityReaderWriter for accessing XML content.
// This package is already initialized with such an accessor using the MIME_XML contentType.
func NewEntityAccessorXML(contentType string) EntityReaderWriter {
return entityXMLAccess{ContentType: contentType}
}
// accessorAt returns the registered ReaderWriter for this MIME type.
func (r *entityReaderWriters) accessorAt(mime string) (EntityReaderWriter, bool) {
r.protection.RLock() r.protection.RLock()
defer r.protection.RUnlock() defer r.protection.RUnlock()
er, ok := r.accessors[mime] er, ok := r.accessors[mime]

View File

@ -3,9 +3,9 @@ package main
import ( import (
"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.org/appengine"
"google.golang.com/appengine/datastore" "google.golang.org/appengine/datastore"
"google.golang.com/appengine/user" "google.golang.org/appengine/user"
"net/http" "net/http"
"time" "time"
) )

View File

@ -3,8 +3,8 @@ package main
import ( import (
"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.org/appengine"
"google.golang.com/appengine/memcache" "google.golang.org/appengine/memcache"
"net/http" "net/http"
) )

View File

@ -54,6 +54,7 @@ func main() {
cors := restful.CrossOriginResourceSharing{ cors := restful.CrossOriginResourceSharing{
ExposeHeaders: []string{"X-My-Header"}, ExposeHeaders: []string{"X-My-Header"},
AllowedHeaders: []string{"Content-Type", "Accept"}, AllowedHeaders: []string{"Content-Type", "Accept"},
AllowedMethods: []string{"GET", "POST"},
CookiesAllowed: false, CookiesAllowed: false,
Container: wsContainer} Container: wsContainer}
wsContainer.Filter(cors.Filter) wsContainer.Filter(cors.Filter)

View File

@ -0,0 +1,31 @@
package main
import (
"io"
"net/http"
"github.com/emicklei/go-restful"
)
func NoBrowserCacheFilter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
resp.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") // HTTP 1.1.
resp.Header().Set("Pragma", "no-cache") // HTTP 1.0.
resp.Header().Set("Expires", "0") // Proxies.
chain.ProcessFilter(req, resp)
}
// This example shows how to use a WebService filter that passed the Http headers to disable browser cacheing.
//
// GET http://localhost:8080/hello
func main() {
ws := new(restful.WebService)
ws.Filter(NoBrowserCacheFilter)
ws.Route(ws.GET("/hello").To(hello))
restful.Add(ws)
http.ListenAndServe(":8080", nil)
}
func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "world")
}

View File

@ -0,0 +1,45 @@
package restful
import (
"strconv"
"strings"
)
type mime struct {
media string
quality float64
}
// insertMime adds a mime to a list and keeps it sorted by quality.
func insertMime(l []mime, e mime) []mime {
for i, each := range l {
// if current mime has lower quality then insert before
if e.quality > each.quality {
left := append([]mime{}, l[0:i]...)
return append(append(left, e), l[i:]...)
}
}
return append(l, e)
}
// sortedMimes returns a list of mime sorted (desc) by its specified quality.
func sortedMimes(accept string) (sorted []mime) {
for _, each := range strings.Split(accept, ",") {
typeAndQuality := strings.Split(strings.Trim(each, " "), ";")
if len(typeAndQuality) == 1 {
sorted = insertMime(sorted, mime{typeAndQuality[0], 1.0})
} else {
// take factor
parts := strings.Split(typeAndQuality[1], "=")
if len(parts) == 2 {
f, err := strconv.ParseFloat(parts[1], 64)
if err != nil {
traceLogger.Printf("unable to parse quality in %s, %v", each, err)
} else {
sorted = insertMime(sorted, mime{typeAndQuality[0], f})
}
}
}
}
return
}

View File

@ -108,7 +108,7 @@ func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
} }
// lookup the EntityReader // lookup the EntityReader
entityReader, ok := entityAccessRegistry.AccessorAt(contentType) entityReader, ok := entityAccessRegistry.accessorAt(contentType)
if !ok { if !ok {
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType) return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
} }

View File

@ -7,7 +7,6 @@ package restful
import ( import (
"errors" "errors"
"net/http" "net/http"
"strings"
) )
// DEPRECATED, use DefaultResponseContentType(mime) // DEPRECATED, use DefaultResponseContentType(mime)
@ -68,38 +67,39 @@ func (r *Response) SetRequestAccepts(mime string) {
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say. // can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
// If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable. // If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
func (r *Response) EntityWriter() (EntityReaderWriter, bool) { func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
for _, qualifiedMime := range strings.Split(r.requestAccept, ",") { sorted := sortedMimes(r.requestAccept)
mime := strings.Trim(strings.Split(qualifiedMime, ";")[0], " ") for _, eachAccept := range sorted {
if 0 == len(mime) || mime == "*/*" { for _, eachProduce := range r.routeProduces {
for _, each := range r.routeProduces { if eachProduce == eachAccept.media {
if MIME_JSON == each { if w, ok := entityAccessRegistry.accessorAt(eachAccept.media); ok {
return entityAccessRegistry.AccessorAt(MIME_JSON) return w, true
}
if MIME_XML == each {
return entityAccessRegistry.AccessorAt(MIME_XML)
} }
} }
} else { // mime is not blank; see if we have a match in Produces }
if eachAccept.media == "*/*" {
for _, each := range r.routeProduces { for _, each := range r.routeProduces {
if mime == each { if w, ok := entityAccessRegistry.accessorAt(each); ok {
if MIME_JSON == each { return w, true
return entityAccessRegistry.AccessorAt(MIME_JSON)
}
if MIME_XML == each {
return entityAccessRegistry.AccessorAt(MIME_XML)
}
} }
} }
} }
} }
writer, ok := entityAccessRegistry.AccessorAt(r.requestAccept) // if requestAccept is empty
writer, ok := entityAccessRegistry.accessorAt(r.requestAccept)
if !ok { if !ok {
// if not registered then fallback to the defaults (if set) // if not registered then fallback to the defaults (if set)
if DefaultResponseMimeType == MIME_JSON { if DefaultResponseMimeType == MIME_JSON {
return entityAccessRegistry.AccessorAt(MIME_JSON) return entityAccessRegistry.accessorAt(MIME_JSON)
} }
if DefaultResponseMimeType == MIME_XML { if DefaultResponseMimeType == MIME_XML {
return entityAccessRegistry.AccessorAt(MIME_XML) return entityAccessRegistry.accessorAt(MIME_XML)
}
// Fallback to whatever the route says it can produce.
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
for _, each := range r.routeProduces {
if w, ok := entityAccessRegistry.accessorAt(each); ok {
return w, true
}
} }
if trace { if trace {
traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept) traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
@ -184,6 +184,15 @@ func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
return nil return nil
} }
// Flush implements http.Flusher interface, which sends any buffered data to the client.
func (r *Response) Flush() {
if f, ok := r.ResponseWriter.(http.Flusher); ok {
f.Flush()
} else if trace {
traceLogger.Printf("ResponseWriter %v doesn't support Flush", r)
}
}
// WriteHeader is overridden to remember the Status Code that has been written. // WriteHeader is overridden to remember the Status Code that has been written.
// Changes to the Header of the response have no effect after this. // Changes to the Header of the response have no effect after this.
func (r *Response) WriteHeader(httpStatus int) { func (r *Response) WriteHeader(httpStatus int) {

View File

@ -178,8 +178,8 @@ func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, mod
return jsonName, modelDescription, prop return jsonName, modelDescription, prop
case fieldKind == reflect.Map: case fieldKind == reflect.Map:
// if it's a map, it's unstructured, and swagger 1.2 can't handle it // if it's a map, it's unstructured, and swagger 1.2 can't handle it
anyt := "any" objectType := "object"
prop.Type = &anyt prop.Type = &objectType
return jsonName, modelDescription, prop return jsonName, modelDescription, prop
} }
@ -277,9 +277,10 @@ func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName
fieldType := field.Type fieldType := field.Type
var pType = "array" var pType = "array"
prop.Type = &pType prop.Type = &pType
isPrimitive := b.isPrimitiveType(fieldType.Elem().Name())
elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem()) elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
prop.Items = new(Item) prop.Items = new(Item)
if b.isPrimitiveType(elemTypeName) { if isPrimitive {
mapped := b.jsonSchemaType(elemTypeName) mapped := b.jsonSchemaType(elemTypeName)
prop.Items.Type = &mapped prop.Items.Type = &mapped
} else { } else {
@ -289,7 +290,9 @@ func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName
if fieldType.Elem().Kind() == reflect.Ptr { if fieldType.Elem().Kind() == reflect.Ptr {
fieldType = fieldType.Elem() fieldType = fieldType.Elem()
} }
b.addModel(fieldType.Elem(), elemTypeName) if !isPrimitive {
b.addModel(fieldType.Elem(), elemTypeName)
}
return jsonName, prop return jsonName, prop
} }
@ -305,10 +308,18 @@ func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonNa
if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array { if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array {
var pType = "array" var pType = "array"
prop.Type = &pType prop.Type = &pType
isPrimitive := b.isPrimitiveType(fieldType.Elem().Elem().Name())
elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem()) elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem())
prop.Items = &Item{Ref: &elemName} if isPrimitive {
// add|overwrite model for element type primName := b.jsonSchemaType(elemName)
b.addModel(fieldType.Elem().Elem(), elemName) prop.Items = &Item{Ref: &primName}
} else {
prop.Items = &Item{Ref: &elemName}
}
if !isPrimitive {
// add|overwrite model for element type
b.addModel(fieldType.Elem().Elem(), elemName)
}
} else { } else {
// non-array, pointer type // non-array, pointer type
var pType = b.jsonSchemaType(fieldType.String()[1:]) // no star, include pkg path var pType = b.jsonSchemaType(fieldType.String()[1:]) // no star, include pkg path
@ -335,9 +346,6 @@ func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.T
if t.Name() == "" { if t.Name() == "" {
return modelName + "." + jsonName return modelName + "." + jsonName
} }
if b.isPrimitiveType(t.Name()) {
return b.jsonSchemaType(t.Name())
}
return b.keyFrom(t) return b.keyFrom(t)
} }
@ -352,6 +360,9 @@ 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 {
if len(modelName) == 0 {
return false
}
return strings.Contains("uint 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)
} }

View File

@ -159,11 +159,16 @@ func (w *WebService) RemoveRoute(path, method string) error {
} }
w.routesLock.Lock() w.routesLock.Lock()
defer w.routesLock.Unlock() defer w.routesLock.Unlock()
newRoutes := make([]Route, (len(w.routes) - 1))
current := 0
for ix := range w.routes { for ix := range w.routes {
if w.routes[ix].Method == method && w.routes[ix].Path == path { if w.routes[ix].Method == method && w.routes[ix].Path == path {
w.routes = append(w.routes[:ix], w.routes[ix+1:]...) continue
} }
newRoutes[current] = w.routes[ix]
current = current + 1
} }
w.routes = newRoutes
return nil return nil
} }