Unify runtime.SerializerInfo with negotiate.AcceptedMediaTypes

There was no reason to have two types and this avoids ~10% of allocations
on the GET code path.

```
BenchmarkGet-12          	  100000	    109045 ns/op	   17608 B/op	     146 allocs/op

BenchmarkGet-12          	  100000	    108850 ns/op	   15942 B/op	     132 allocs/op
```
This commit is contained in:
Clayton Coleman 2019-03-21 21:00:55 -04:00
parent 59b4f47b22
commit 0489d0b1cf
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
9 changed files with 100 additions and 57 deletions

View File

@ -644,6 +644,8 @@ func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.Serial
return []runtime.SerializerInfo{ return []runtime.SerializerInfo{
{ {
MediaType: "application/json", MediaType: "application/json",
MediaTypeType: "application",
MediaTypeSubType: "json",
EncodesAsText: true, EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false), Serializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false),
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, true), PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, true),
@ -655,6 +657,8 @@ func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.Serial
}, },
{ {
MediaType: "application/yaml", MediaType: "application/yaml",
MediaTypeType: "application",
MediaTypeSubType: "yaml",
EncodesAsText: true, EncodesAsText: true,
Serializer: json.NewYAMLSerializer(json.DefaultMetaFactory, s.creator, s.typer), Serializer: json.NewYAMLSerializer(json.DefaultMetaFactory, s.creator, s.typer),
}, },

View File

@ -51,6 +51,8 @@ func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.Serial
return []runtime.SerializerInfo{ return []runtime.SerializerInfo{
{ {
MediaType: "application/json", MediaType: "application/json",
MediaTypeType: "application",
MediaTypeSubType: "json",
EncodesAsText: true, EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false), Serializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, false),
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, true), PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, s.creator, s.typer, true),
@ -62,6 +64,8 @@ func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.Serial
}, },
{ {
MediaType: "application/yaml", MediaType: "application/yaml",
MediaTypeType: "application",
MediaTypeSubType: "yaml",
EncodesAsText: true, EncodesAsText: true,
Serializer: json.NewYAMLSerializer(json.DefaultMetaFactory, s.creator, s.typer), Serializer: json.NewYAMLSerializer(json.DefaultMetaFactory, s.creator, s.typer),
}, },

View File

@ -91,6 +91,10 @@ type Framer interface {
type SerializerInfo struct { type SerializerInfo struct {
// MediaType is the value that represents this serializer over the wire. // MediaType is the value that represents this serializer over the wire.
MediaType string MediaType string
// MediaTypeType is the first part of the MediaType ("application" in "application/json").
MediaTypeType string
// MediaTypeSubType is the second part of the MediaType ("json" in "application/json").
MediaTypeSubType string
// EncodesAsText indicates this serializer can be encoded to UTF-8 safely. // EncodesAsText indicates this serializer can be encoded to UTF-8 safely.
EncodesAsText bool EncodesAsText bool
// Serializer is the individual object serializer for this media type. // Serializer is the individual object serializer for this media type.

View File

@ -17,6 +17,9 @@ limitations under the License.
package serializer package serializer
import ( import (
"mime"
"strings"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/json"
@ -120,6 +123,15 @@ func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) Codec
Serializer: d.Serializer, Serializer: d.Serializer,
PrettySerializer: d.PrettySerializer, PrettySerializer: d.PrettySerializer,
} }
mediaType, _, err := mime.ParseMediaType(info.MediaType)
if err != nil {
panic(err)
}
parts := strings.SplitN(mediaType, "/", 2)
info.MediaTypeType = parts[0]
info.MediaTypeSubType = parts[1]
if d.StreamSerializer != nil { if d.StreamSerializer != nil {
info.StreamSerializer = &runtime.StreamSerializerInfo{ info.StreamSerializer = &runtime.StreamSerializerInfo{
Serializer: d.StreamSerializer, Serializer: d.StreamSerializer,

View File

@ -43,13 +43,13 @@ func MediaTypesForSerializer(ns runtime.NegotiatedSerializer) (mediaTypes, strea
// NegotiateOutputMediaType negotiates the output structured media type and a serializer, or // NegotiateOutputMediaType negotiates the output structured media type and a serializer, or
// returns an error. // returns an error.
func NegotiateOutputMediaType(req *http.Request, ns runtime.NegotiatedSerializer, restrictions EndpointRestrictions) (MediaTypeOptions, runtime.SerializerInfo, error) { func NegotiateOutputMediaType(req *http.Request, ns runtime.NegotiatedSerializer, restrictions EndpointRestrictions) (MediaTypeOptions, runtime.SerializerInfo, error) {
mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), AcceptedMediaTypesForEndpoint(ns), restrictions) mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), ns.SupportedMediaTypes(), restrictions)
if !ok { if !ok {
supported, _ := MediaTypesForSerializer(ns) supported, _ := MediaTypesForSerializer(ns)
return mediaType, runtime.SerializerInfo{}, NewNotAcceptableError(supported) return mediaType, runtime.SerializerInfo{}, NewNotAcceptableError(supported)
} }
// TODO: move into resthandler // TODO: move into resthandler
info := mediaType.Accepted.Serializer info := mediaType.Accepted
if (mediaType.Pretty || isPrettyPrint(req)) && info.PrettySerializer != nil { if (mediaType.Pretty || isPrettyPrint(req)) && info.PrettySerializer != nil {
info.Serializer = info.PrettySerializer info.Serializer = info.PrettySerializer
} }
@ -58,12 +58,12 @@ func NegotiateOutputMediaType(req *http.Request, ns runtime.NegotiatedSerializer
// NegotiateOutputMediaTypeStream returns a stream serializer for the given request. // NegotiateOutputMediaTypeStream returns a stream serializer for the given request.
func NegotiateOutputMediaTypeStream(req *http.Request, ns runtime.NegotiatedSerializer, restrictions EndpointRestrictions) (runtime.SerializerInfo, error) { func NegotiateOutputMediaTypeStream(req *http.Request, ns runtime.NegotiatedSerializer, restrictions EndpointRestrictions) (runtime.SerializerInfo, error) {
mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), AcceptedMediaTypesForEndpoint(ns), restrictions) mediaType, ok := NegotiateMediaTypeOptions(req.Header.Get("Accept"), ns.SupportedMediaTypes(), restrictions)
if !ok || mediaType.Accepted.Serializer.StreamSerializer == nil { if !ok || mediaType.Accepted.StreamSerializer == nil {
_, supported := MediaTypesForSerializer(ns) _, supported := MediaTypesForSerializer(ns)
return runtime.SerializerInfo{}, NewNotAcceptableError(supported) return runtime.SerializerInfo{}, NewNotAcceptableError(supported)
} }
return mediaType.Accepted.Serializer, nil return mediaType.Accepted, nil
} }
// NegotiateInputSerializer returns the input serializer for the provided request. // NegotiateInputSerializer returns the input serializer for the provided request.
@ -99,12 +99,15 @@ func NegotiateInputSerializerForMediaType(mediaType string, streaming bool, ns r
func isPrettyPrint(req *http.Request) bool { func isPrettyPrint(req *http.Request) bool {
// DEPRECATED: should be part of the content type // DEPRECATED: should be part of the content type
if req.URL != nil { if req.URL != nil {
// avoid an allocation caused by parsing the URL query
if strings.Contains(req.URL.RawQuery, "pretty") {
pp := req.URL.Query().Get("pretty") pp := req.URL.Query().Get("pretty")
if len(pp) > 0 { if len(pp) > 0 {
pretty, _ := strconv.ParseBool(pp) pretty, _ := strconv.ParseBool(pp)
return pretty return pretty
} }
} }
}
userAgent := req.UserAgent() userAgent := req.UserAgent()
// This covers basic all browsers and cli http tools // This covers basic all browsers and cli http tools
if strings.HasPrefix(userAgent, "curl") || strings.HasPrefix(userAgent, "Wget") || strings.HasPrefix(userAgent, "Mozilla/5.0") { if strings.HasPrefix(userAgent, "curl") || strings.HasPrefix(userAgent, "Wget") || strings.HasPrefix(userAgent, "Mozilla/5.0") {
@ -139,17 +142,6 @@ func (emptyEndpointRestrictions) AllowsConversion(schema.GroupVersionKind, strin
func (emptyEndpointRestrictions) AllowsServerVersion(string) bool { return false } func (emptyEndpointRestrictions) AllowsServerVersion(string) bool { return false }
func (emptyEndpointRestrictions) AllowsStreamSchema(s string) bool { return s == "watch" } func (emptyEndpointRestrictions) AllowsStreamSchema(s string) bool { return s == "watch" }
// AcceptedMediaType contains information about a valid media type that the
// server can serialize.
type AcceptedMediaType struct {
// Type is the first part of the media type ("application")
Type string
// SubType is the second part of the media type ("json")
SubType string
// Serializer is the serialization info this object accepts
Serializer runtime.SerializerInfo
}
// MediaTypeOptions describes information for a given media type that may alter // MediaTypeOptions describes information for a given media type that may alter
// the server response // the server response
type MediaTypeOptions struct { type MediaTypeOptions struct {
@ -176,13 +168,13 @@ type MediaTypeOptions struct {
Unrecognized []string Unrecognized []string
// the accepted media type from the client // the accepted media type from the client
Accepted *AcceptedMediaType Accepted runtime.SerializerInfo
} }
// acceptMediaTypeOptions returns an options object that matches the provided media type params. If // acceptMediaTypeOptions returns an options object that matches the provided media type params. If
// it returns false, the provided options are not allowed and the media type must be skipped. These // it returns false, the provided options are not allowed and the media type must be skipped. These
// parameters are unversioned and may not be changed. // parameters are unversioned and may not be changed.
func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType, endpoint EndpointRestrictions) (MediaTypeOptions, bool) { func acceptMediaTypeOptions(params map[string]string, accepts *runtime.SerializerInfo, endpoint EndpointRestrictions) (MediaTypeOptions, bool) {
var options MediaTypeOptions var options MediaTypeOptions
// extract all known parameters // extract all known parameters
@ -208,7 +200,7 @@ func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType
// controls the streaming schema // controls the streaming schema
case "stream": case "stream":
if len(v) > 0 && (accepts.Serializer.StreamSerializer == nil || !endpoint.AllowsStreamSchema(v)) { if len(v) > 0 && (accepts.StreamSerializer == nil || !endpoint.AllowsStreamSchema(v)) {
return MediaTypeOptions{}, false return MediaTypeOptions{}, false
} }
options.Stream = v options.Stream = v
@ -236,16 +228,16 @@ func acceptMediaTypeOptions(params map[string]string, accepts *AcceptedMediaType
} }
} }
if options.Convert != nil && !endpoint.AllowsConversion(*options.Convert, accepts.Type, accepts.SubType) { if options.Convert != nil && !endpoint.AllowsConversion(*options.Convert, accepts.MediaTypeType, accepts.MediaTypeSubType) {
return MediaTypeOptions{}, false return MediaTypeOptions{}, false
} }
options.Accepted = accepts options.Accepted = *accepts
return options, true return options, true
} }
type candidateMediaType struct { type candidateMediaType struct {
accepted *AcceptedMediaType accepted *runtime.SerializerInfo
clauses goautoneg.Accept clauses goautoneg.Accept
} }
@ -253,10 +245,10 @@ type candidateMediaTypeSlice []candidateMediaType
// NegotiateMediaTypeOptions returns the most appropriate content type given the accept header and // NegotiateMediaTypeOptions returns the most appropriate content type given the accept header and
// a list of alternatives along with the accepted media type parameters. // a list of alternatives along with the accepted media type parameters.
func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endpoint EndpointRestrictions) (MediaTypeOptions, bool) { func NegotiateMediaTypeOptions(header string, accepted []runtime.SerializerInfo, endpoint EndpointRestrictions) (MediaTypeOptions, bool) {
if len(header) == 0 && len(accepted) > 0 { if len(header) == 0 && len(accepted) > 0 {
return MediaTypeOptions{ return MediaTypeOptions{
Accepted: &accepted[0], Accepted: accepted[0],
}, true }, true
} }
@ -266,8 +258,8 @@ func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endp
for i := range accepted { for i := range accepted {
accepts := &accepted[i] accepts := &accepted[i]
switch { switch {
case clause.Type == accepts.Type && clause.SubType == accepts.SubType, case clause.Type == accepts.MediaTypeType && clause.SubType == accepts.MediaTypeSubType,
clause.Type == accepts.Type && clause.SubType == "*", clause.Type == accepts.MediaTypeType && clause.SubType == "*",
clause.Type == "*" && clause.SubType == "*": clause.Type == "*" && clause.SubType == "*":
candidates = append(candidates, candidateMediaType{accepted: accepts, clauses: clause}) candidates = append(candidates, candidateMediaType{accepted: accepts, clauses: clause})
} }
@ -282,22 +274,3 @@ func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endp
return MediaTypeOptions{}, false return MediaTypeOptions{}, false
} }
// AcceptedMediaTypesForEndpoint returns an array of structs that are used to efficiently check which
// allowed media types the server exposes.
func AcceptedMediaTypesForEndpoint(ns runtime.NegotiatedSerializer) []AcceptedMediaType {
var acceptedMediaTypes []AcceptedMediaType
for _, info := range ns.SupportedMediaTypes() {
segments := strings.SplitN(info.MediaType, "/", 2)
if len(segments) == 1 {
segments = append(segments, "*")
}
t := AcceptedMediaType{
Type: segments[0],
SubType: segments[1],
Serializer: info,
}
acceptedMediaTypes = append(acceptedMediaTypes, t)
}
return acceptedMediaTypes
}

View File

@ -17,8 +17,10 @@ limitations under the License.
package negotiation package negotiation
import ( import (
"mime"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"testing" "testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -39,7 +41,23 @@ type fakeNegotiater struct {
func (n *fakeNegotiater) SupportedMediaTypes() []runtime.SerializerInfo { func (n *fakeNegotiater) SupportedMediaTypes() []runtime.SerializerInfo {
var out []runtime.SerializerInfo var out []runtime.SerializerInfo
for _, s := range n.types { for _, s := range n.types {
info := runtime.SerializerInfo{Serializer: n.serializer, MediaType: s, EncodesAsText: true} mediaType, _, err := mime.ParseMediaType(s)
if err != nil {
panic(err)
}
parts := strings.SplitN(mediaType, "/", 2)
if len(parts) == 1 {
// this is an error on the server side
parts = append(parts, "")
}
info := runtime.SerializerInfo{
Serializer: n.serializer,
MediaType: s,
MediaTypeType: parts[0],
MediaTypeSubType: parts[1],
EncodesAsText: true,
}
for _, t := range n.streamTypes { for _, t := range n.streamTypes {
if t == s { if t == s {
info.StreamSerializer = &runtime.StreamSerializerInfo{ info.StreamSerializer = &runtime.StreamSerializerInfo{

View File

@ -512,6 +512,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
// //
// test/integration/auth_test.go is currently the most comprehensive status code test // test/integration/auth_test.go is currently the most comprehensive status code test
for _, s := range a.group.Serializer.SupportedMediaTypes() {
if len(s.MediaTypeSubType) == 0 || len(s.MediaTypeType) == 0 {
return nil, fmt.Errorf("all serializers in the group Serializer must have MediaTypeType and MediaTypeSubType set: %s", s.MediaType)
}
}
mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer) mediaTypes, streamMediaTypes := negotiation.MediaTypesForSerializer(a.group.Serializer)
allMediaTypes := append(mediaTypes, streamMediaTypes...) allMediaTypes := append(mediaTypes, streamMediaTypes...)
ws.Produces(allMediaTypes...) ws.Produces(allMediaTypes...)

View File

@ -17,7 +17,9 @@ limitations under the License.
package storage package storage
import ( import (
"mime"
"reflect" "reflect"
"strings"
"testing" "testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -60,7 +62,24 @@ type fakeNegotiater struct {
func (n *fakeNegotiater) SupportedMediaTypes() []runtime.SerializerInfo { func (n *fakeNegotiater) SupportedMediaTypes() []runtime.SerializerInfo {
var out []runtime.SerializerInfo var out []runtime.SerializerInfo
for _, s := range n.types { for _, s := range n.types {
info := runtime.SerializerInfo{Serializer: n.serializer, MediaType: s, EncodesAsText: true} mediaType, _, err := mime.ParseMediaType(s)
if err != nil {
panic(err)
}
parts := strings.SplitN(mediaType, "/", 2)
if len(parts) == 1 {
// this is an error on the server side
parts = append(parts, "")
}
info := runtime.SerializerInfo{
Serializer: n.serializer,
MediaType: s,
MediaTypeType: parts[0],
MediaTypeSubType: parts[1],
EncodesAsText: true,
}
for _, t := range n.streamTypes { for _, t := range n.streamTypes {
if t == s { if t == s {
info.StreamSerializer = &runtime.StreamSerializerInfo{ info.StreamSerializer = &runtime.StreamSerializerInfo{

View File

@ -43,6 +43,8 @@ func init() {
var watchJsonSerializerInfo = runtime.SerializerInfo{ var watchJsonSerializerInfo = runtime.SerializerInfo{
MediaType: "application/json", MediaType: "application/json",
MediaTypeType: "application",
MediaTypeSubType: "json",
EncodesAsText: true, EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false), Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false),
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, true), PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, true),
@ -77,6 +79,8 @@ func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInf
return []runtime.SerializerInfo{ return []runtime.SerializerInfo{
{ {
MediaType: "application/json", MediaType: "application/json",
MediaTypeType: "application",
MediaTypeSubType: "json",
EncodesAsText: true, EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false), Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false),
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, true), PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, true),