From 49537610e83755b9dc231ee59f5b007adeece1a2 Mon Sep 17 00:00:00 2001 From: Ben Luddy Date: Thu, 7 Nov 2024 00:05:03 -0500 Subject: [PATCH] Fix content type fallback when a client defaults to CBOR. With the ClientsAllowCBOR client-go feature gate enabled, a 415 response to a CBOR-encoded REST causes all subsequent requests from the client to fall back to a JSON request encoding. This mechanism had only worked as intended when CBOR was explicitly configured in the ClientContentConfig. When both ClientsAllowCBOR and ClientsPreferCBOR are enabled, an unconfigured (empty) content type defaults to CBOR instead of JSON. Both ways of configuring a client to use the CBOR request encoding are now subject to the same fallback mechanism. Kubernetes-commit: a77f4c7ba2e761461daaf115a38903fc91916dd6 --- rest/client.go | 33 ++++++++++++++++++++++----------- rest/request.go | 14 +++++--------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/rest/client.go b/rest/client.go index 393a4166..159caa13 100644 --- a/rest/client.go +++ b/rest/client.go @@ -238,7 +238,8 @@ func (c *RESTClient) Delete() *Request { // APIVersion returns the APIVersion this RESTClient is expected to use. func (c *RESTClient) APIVersion() schema.GroupVersion { - return c.content.GetClientContentConfig().GroupVersion + config, _ := c.content.GetClientContentConfig() + return config.GroupVersion } // requestClientContentConfigProvider observes HTTP 415 (Unsupported Media Type) responses to detect @@ -257,25 +258,35 @@ type requestClientContentConfigProvider struct { } // GetClientContentConfig returns the ClientContentConfig that should be used for new requests by -// this client. -func (p *requestClientContentConfigProvider) GetClientContentConfig() ClientContentConfig { +// this client and true if the request ContentType was selected by default. +func (p *requestClientContentConfigProvider) GetClientContentConfig() (ClientContentConfig, bool) { + config := p.base + + defaulted := config.ContentType == "" + if defaulted { + config.ContentType = "application/json" + } + if !clientfeatures.FeatureGates().Enabled(clientfeatures.ClientsAllowCBOR) { - return p.base + return config, defaulted + } + + if defaulted && clientfeatures.FeatureGates().Enabled(clientfeatures.ClientsPreferCBOR) { + config.ContentType = "application/cbor" } if sawUnsupportedMediaTypeForCBOR := p.sawUnsupportedMediaTypeForCBOR.Load(); !sawUnsupportedMediaTypeForCBOR { - return p.base + return config, defaulted } - if mediaType, _, _ := mime.ParseMediaType(p.base.ContentType); mediaType != runtime.ContentTypeCBOR { - return p.base + if mediaType, _, _ := mime.ParseMediaType(config.ContentType); mediaType != runtime.ContentTypeCBOR { + return config, defaulted } - config := p.base - // The default ClientContentConfig sets ContentType to CBOR and the client has previously - // received an HTTP 415 in response to a CBOR request. Override ContentType to JSON. + // The effective ContentType is CBOR and the client has previously received an HTTP 415 in + // response to a CBOR request. Override ContentType to JSON. config.ContentType = runtime.ContentTypeJSON - return config + return config, defaulted } // UnsupportedMediaType reports that the server has responded to a request with HTTP 415 Unsupported diff --git a/rest/request.go b/rest/request.go index 65942aa1..0ec90ad1 100644 --- a/rest/request.go +++ b/rest/request.go @@ -156,14 +156,10 @@ func NewRequest(c *RESTClient) *Request { timeout = c.Client.Timeout } - contentConfig := c.content.GetClientContentConfig() - contentTypeNotSet := len(contentConfig.ContentType) == 0 - if contentTypeNotSet { - contentConfig.ContentType = "application/json" - if clientfeatures.FeatureGates().Enabled(clientfeatures.ClientsAllowCBOR) && clientfeatures.FeatureGates().Enabled(clientfeatures.ClientsPreferCBOR) { - contentConfig.ContentType = "application/cbor" - } - } + // A request needs to know whether the content type was explicitly configured or selected by + // default in order to support the per-request Protobuf override used by clients generated + // with --prefers-protobuf. + contentConfig, contentTypeDefaulted := c.content.GetClientContentConfig() r := &Request{ c: c, @@ -176,7 +172,7 @@ func NewRequest(c *RESTClient) *Request { warningHandler: c.warningHandler, contentConfig: contentConfig, - contentTypeNotSet: contentTypeNotSet, + contentTypeNotSet: contentTypeDefaulted, } r.setAcceptHeader()