Merge pull request #38625 from smarterclayton/bump_gorestful

Automatic merge from submit-queue

Update to latest go-restful

Enables us to remap the variable names swagger uses in output so we can avoid duplicates across type names.

Part of #38071

@mbohlool
pull/6/head
Kubernetes Submit Queue 2016-12-12 22:24:37 -08:00 committed by GitHub
commit 9407bc5bbe
11 changed files with 95 additions and 41 deletions

12
Godeps/Godeps.json generated
View File

@ -897,18 +897,18 @@
}, },
{ {
"ImportPath": "github.com/emicklei/go-restful", "ImportPath": "github.com/emicklei/go-restful",
"Comment": "v1.2-79-g89ef8af", "Comment": "v1.2-96-g09691a3",
"Rev": "89ef8af493ab468a45a42bb0d89a06fccdd2fb22" "Rev": "09691a3b6378b740595c1002f40c34dd5f218a22"
}, },
{ {
"ImportPath": "github.com/emicklei/go-restful/log", "ImportPath": "github.com/emicklei/go-restful/log",
"Comment": "v1.2-79-g89ef8af", "Comment": "v1.2-96-g09691a3",
"Rev": "89ef8af493ab468a45a42bb0d89a06fccdd2fb22" "Rev": "09691a3b6378b740595c1002f40c34dd5f218a22"
}, },
{ {
"ImportPath": "github.com/emicklei/go-restful/swagger", "ImportPath": "github.com/emicklei/go-restful/swagger",
"Comment": "v1.2-79-g89ef8af", "Comment": "v1.2-96-g09691a3",
"Rev": "89ef8af493ab468a45a42bb0d89a06fccdd2fb22" "Rev": "09691a3b6378b740595c1002f40c34dd5f218a22"
}, },
{ {
"ImportPath": "github.com/evanphx/json-patch", "ImportPath": "github.com/evanphx/json-patch",

View File

@ -1,5 +1,13 @@
Change history of go-restful Change history of go-restful
= =
2016-11-26
- Default change! now use CurlyRouter (was RouterJSR311)
- Default change! no more caching of request content
- Default change! do not recover from panics
2016-09-22
- fix the DefaultRequestContentType feature
2016-02-14 2016-02-14
- take the qualify factor of the Accept header mediatype into account when deciding the contentype of the response - 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 - add constructors for custom entity accessors for xml and json

View File

@ -25,10 +25,10 @@ type Container struct {
ServeMux *http.ServeMux ServeMux *http.ServeMux
isRegisteredOnRoot bool isRegisteredOnRoot bool
containerFilters []FilterFunction containerFilters []FilterFunction
doNotRecover bool // default is false doNotRecover bool // default is true
recoverHandleFunc RecoverHandleFunction recoverHandleFunc RecoverHandleFunction
serviceErrorHandleFunc ServiceErrorHandleFunction serviceErrorHandleFunc ServiceErrorHandleFunction
router RouteSelector // default is a RouterJSR311, CurlyRouter is the faster alternative router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
contentEncodingEnabled bool // default is false contentEncodingEnabled bool // default is false
} }
@ -39,10 +39,10 @@ func NewContainer() *Container {
ServeMux: http.NewServeMux(), ServeMux: http.NewServeMux(),
isRegisteredOnRoot: false, isRegisteredOnRoot: false,
containerFilters: []FilterFunction{}, containerFilters: []FilterFunction{},
doNotRecover: false, doNotRecover: true,
recoverHandleFunc: logStackOnRecover, recoverHandleFunc: logStackOnRecover,
serviceErrorHandleFunc: writeServiceError, serviceErrorHandleFunc: writeServiceError,
router: RouterJSR311{}, router: CurlyRouter{},
contentEncodingEnabled: false} contentEncodingEnabled: false}
} }
@ -69,7 +69,7 @@ func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) {
// DoNotRecover controls whether panics will be caught to return HTTP 500. // DoNotRecover controls whether panics will be caught to return HTTP 500.
// If set to true, Route functions are responsible for handling any error situation. // If set to true, Route functions are responsible for handling any error situation.
// Default value is false = recover from panics. This has performance implications. // Default value is true.
func (c *Container) DoNotRecover(doNot bool) { func (c *Container) DoNotRecover(doNot bool) {
c.doNotRecover = doNot c.doNotRecover = doNot
} }

View File

@ -108,11 +108,13 @@ func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, reque
return (matched && err == nil), false return (matched && err == nil), false
} }
var jsr311Router = RouterJSR311{}
// detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type // detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
// headers of the Request. See also RouterJSR311 in jsr311.go // headers of the Request. See also RouterJSR311 in jsr311.go
func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) { func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
// tracing is done inside detectRoute // tracing is done inside detectRoute
return RouterJSR311{}.detectRoute(candidateRoutes.routes(), httpRequest) return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
} }
// detectWebService returns the best matching webService given the list of path tokens. // detectWebService returns the best matching webService given the list of path tokens.

View File

@ -145,22 +145,11 @@ Performance options
This package has several options that affect the performance of your service. It is important to understand them and how you can change it. This package has several options that affect the performance of your service. It is important to understand them and how you can change it.
restful.DefaultContainer.Router(CurlyRouter{}) restful.DefaultContainer.DoNotRecover(false)
The default router is the RouterJSR311 which is an implementation of its spec (http://jsr311.java.net/nonav/releases/1.1/spec/spec.html).
However, it uses regular expressions for all its routes which, depending on your usecase, may consume a significant amount of time.
The CurlyRouter implementation is more lightweight that also allows you to use wildcards and expressions, but only if needed.
restful.DefaultContainer.DoNotRecover(true)
DoNotRecover controls whether panics will be caught to return HTTP 500. DoNotRecover controls whether panics will be caught to return HTTP 500.
If set to true, Route functions are responsible for handling any error situation. If set to false, the container will recover from panics.
Default value is false; it will recover from panics. This has performance implications. Default value is true
restful.SetCacheReadEntity(false)
SetCacheReadEntity controls whether the response data ([]byte) is cached such that ReadEntity is repeatable.
If you expect to read large amounts of payload data, and you do not use this feature, you should set it to false.
restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20)) restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20))

View File

@ -24,3 +24,12 @@ func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
// FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction // FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction
type FilterFunction func(*Request, *Response, *FilterChain) type FilterFunction func(*Request, *Response, *FilterChain)
// NoBrowserCacheFilter is a filter function to set HTTP headers that disable browser caching
// See examples/restful-no-cache-filter.go for usage
func NoBrowserCacheFilter(req *Request, resp *Response, chain *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)
}

View File

@ -13,7 +13,7 @@ import (
var defaultRequestContentType string var defaultRequestContentType string
var doCacheReadEntityBytes = true var doCacheReadEntityBytes = false
// Request is a wrapper for a http Request that provides convenience methods // Request is a wrapper for a http Request that provides convenience methods
type Request struct { type Request struct {
@ -107,10 +107,15 @@ func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
r.Request.Body = zlibReader r.Request.Body = zlibReader
} }
// lookup the EntityReader // lookup the EntityReader, use defaultRequestContentType if needed and provided
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) if len(defaultRequestContentType) != 0 {
entityReader, ok = entityAccessRegistry.accessorAt(defaultRequestContentType)
}
if !ok {
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
}
} }
return entityReader.Read(r, entityPointer) return entityReader.Read(r, entityPointer)
} }

View File

@ -2,6 +2,7 @@ package swagger
import ( import (
"net/http" "net/http"
"reflect"
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
) )
@ -9,8 +10,13 @@ import (
// PostBuildDeclarationMapFunc can be used to modify the api declaration map. // PostBuildDeclarationMapFunc can be used to modify the api declaration map.
type PostBuildDeclarationMapFunc func(apiDeclarationMap *ApiDeclarationList) type PostBuildDeclarationMapFunc func(apiDeclarationMap *ApiDeclarationList)
// MapSchemaFormatFunc can be used to modify typeName at definition time.
type MapSchemaFormatFunc func(typeName string) string type MapSchemaFormatFunc func(typeName string) string
// MapModelTypeNameFunc can be used to return the desired typeName for a given
// type. It will return false if the default name should be used.
type MapModelTypeNameFunc func(t reflect.Type) (string, bool)
type Config struct { type Config struct {
// url where the services are available, e.g. http://localhost:8080 // url where the services are available, e.g. http://localhost:8080
// if left empty then the basePath of Swagger is taken from the actual request // if left empty then the basePath of Swagger is taken from the actual request
@ -33,6 +39,8 @@ type Config struct {
PostBuildHandler PostBuildDeclarationMapFunc PostBuildHandler PostBuildDeclarationMapFunc
// Swagger global info struct // Swagger global info struct
Info Info Info Info
// [optional] If set, model builder should call this handler to get addition typename-to-swagger-format-field convertion. // [optional] If set, model builder should call this handler to get addition typename-to-swagger-format-field conversion.
SchemaFormatHandler MapSchemaFormatFunc SchemaFormatHandler MapSchemaFormatFunc
// [optional] If set, model builder should call this handler to retrieve the name for a given type.
ModelTypeNameHandler MapModelTypeNameFunc
} }

View File

@ -43,6 +43,12 @@ func (b modelBuilder) addModelFrom(sample interface{}) {
} }
func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model { func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model {
// Turn pointers into simpler types so further checks are
// correct.
if st.Kind() == reflect.Ptr {
st = st.Elem()
}
modelName := b.keyFrom(st) modelName := b.keyFrom(st)
if nameOverride != "" { if nameOverride != "" {
modelName = nameOverride modelName = nameOverride
@ -137,6 +143,11 @@ func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, mod
return "", "", prop return "", "", prop
} }
if field.Name == "XMLName" && field.Type.String() == "xml.Name" {
// property is metadata for the xml.Name attribute, can be skipped
return "", "", prop
}
if tag := field.Tag.Get("modelDescription"); tag != "" { if tag := field.Tag.Get("modelDescription"); tag != "" {
modelDescription = tag modelDescription = tag
} }
@ -155,7 +166,7 @@ func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, mod
prop.Type = &pType prop.Type = &pType
} }
if prop.Format == "" { if prop.Format == "" {
prop.Format = b.jsonSchemaFormat(fieldType.String()) prop.Format = b.jsonSchemaFormat(b.keyFrom(fieldType))
} }
return jsonName, modelDescription, prop return jsonName, modelDescription, prop
} }
@ -192,13 +203,14 @@ func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, mod
return jsonName, modelDescription, prop return jsonName, modelDescription, prop
} }
if b.isPrimitiveType(fieldType.String()) { fieldTypeName := b.keyFrom(fieldType)
mapped := b.jsonSchemaType(fieldType.String()) if b.isPrimitiveType(fieldTypeName) {
mapped := b.jsonSchemaType(fieldTypeName)
prop.Type = &mapped prop.Type = &mapped
prop.Format = b.jsonSchemaFormat(fieldType.String()) prop.Format = b.jsonSchemaFormat(fieldTypeName)
return jsonName, modelDescription, prop return jsonName, modelDescription, prop
} }
modelType := fieldType.String() modelType := b.keyFrom(fieldType)
prop.Ref = &modelType prop.Ref = &modelType
if fieldType.Name() == "" { // override type of anonymous structs if fieldType.Name() == "" { // override type of anonymous structs
@ -272,7 +284,7 @@ func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonNam
} }
// simple struct // simple struct
b.addModel(fieldType, "") b.addModel(fieldType, "")
var pType = fieldType.String() var pType = b.keyFrom(fieldType)
prop.Ref = &pType prop.Ref = &pType
return jsonName, prop return jsonName, prop
} }
@ -336,10 +348,11 @@ func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonNa
} }
} else { } else {
// non-array, pointer type // non-array, pointer type
var pType = b.jsonSchemaType(fieldType.String()[1:]) // no star, include pkg path fieldTypeName := b.keyFrom(fieldType.Elem())
if b.isPrimitiveType(fieldType.String()[1:]) { var pType = b.jsonSchemaType(fieldTypeName) // no star, include pkg path
if b.isPrimitiveType(fieldTypeName) {
prop.Type = &pType prop.Type = &pType
prop.Format = b.jsonSchemaFormat(fieldType.String()[1:]) prop.Format = b.jsonSchemaFormat(fieldTypeName)
return jsonName, prop return jsonName, prop
} }
prop.Ref = &pType prop.Ref = &pType
@ -355,7 +368,7 @@ func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonNa
func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string { func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string {
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {
return t.String()[1:] t = t.Elem()
} }
if t.Name() == "" { if t.Name() == "" {
return modelName + "." + jsonName return modelName + "." + jsonName
@ -365,6 +378,11 @@ func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.T
func (b modelBuilder) keyFrom(st reflect.Type) string { func (b modelBuilder) keyFrom(st reflect.Type) string {
key := st.String() key := st.String()
if b.Config != nil && b.Config.ModelTypeNameHandler != nil {
if name, ok := b.Config.ModelTypeNameHandler(st); ok {
key = name
}
}
if len(st.Name()) == 0 { // unnamed type if len(st.Name()) == 0 { // unnamed type
// Swagger UI has special meaning for [ // Swagger UI has special meaning for [
key = strings.Replace(key, "[]", "||", -1) key = strings.Replace(key, "[]", "||", -1)

View File

@ -33,6 +33,21 @@ func (prop *ModelProperty) setMaximum(field reflect.StructField) {
func (prop *ModelProperty) setType(field reflect.StructField) { func (prop *ModelProperty) setType(field reflect.StructField) {
if tag := field.Tag.Get("type"); tag != "" { if tag := field.Tag.Get("type"); tag != "" {
// Check if the first two characters of the type tag are
// intended to emulate slice/array behaviour.
//
// If type is intended to be a slice/array then add the
// overriden type to the array item instead of the main property
if len(tag) > 2 && tag[0:2] == "[]" {
pType := "array"
prop.Type = &pType
prop.Items = new(Item)
iType := tag[2:]
prop.Items.Type = &iType
return
}
prop.Type = &tag prop.Type = &tag
} }
} }

View File

@ -277,7 +277,7 @@ func composeResponseMessages(route restful.Route, decl *ApiDeclaration, config *
} }
// sort by code // sort by code
codes := sort.IntSlice{} codes := sort.IntSlice{}
for code, _ := range route.ResponseErrors { for code := range route.ResponseErrors {
codes = append(codes, code) codes = append(codes, code)
} }
codes.Sort() codes.Sort()