[ 
https://issues.apache.org/jira/browse/CXF-9235?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18102228#comment-18102228
 ] 

Freeman Yue Fang edited comment on CXF-9235 at 8/5/26 9:55 PM:
---------------------------------------------------------------

Hi Neena,

Thanks for the detailed report and for tracking down the root cause — your 
diagnosis is correct IMO. I traced it through the code and confirmed:

InvocationBuilderImpl.property(name, value) 
(rt/rs/client/.../spec/InvocationBuilderImpl.java) nests the property under a 
private key, "jaxrs.filter.properties", instead of writing it flat into the 
request context:

Map<String, Object> filterProps = CastUtils.cast((Map<?, ?>) 
contextProps.get(PROPERTY_KEY));
...
filterProps.put(name, value);

Compare that to Client.property() / WebTarget.property(), which go through 
ConfigurableImpl and end up flattened directly into 
ClientConfiguration.getRequestContext() (ClientImpl.java, around line 285). 
That flat write is what lets Message.getContextualProperty() — used by both 
Headers.setProtocolHeadersInConnection() and HttpClientHTTPConduit — see the 
property at all, since it does a flat merge and never unwraps a value that's 
itself a Map.

So I'd suggest fixing this at the source rather than the transport side: your 
workaround (teaching the transport to also check jaxrs.filter.properties when a 
contextual lookup misses) does resolve your case, but it only fixes this one 
property. The nesting bug in InvocationBuilderImpl actually swallows any 
property set via Invocation.Builder.property(), and your workaround also pulls 
a JAX-RS-client-internal key (jaxrs.filter.properties) into the generic HTTP 
transport layer, which we'd rather avoid introducing upstream.

The fix I'd propose instead: make InvocationBuilderImpl.property() write 
directly into contextProps (flat), matching how Client/WebTarget already 
behave. That's a smaller change, fixes the bug for every property (not just 
this one), and needs no changes outside rt/rs/client.

Something like this in my mind for now
{code}
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/InvocationBuilderImpl.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/InvocationBuilderImpl.java
@@ -288,8 +288,13 @@ public class InvocationBuilderImpl implements 
Invocation.Builder {
         }
         if (value == null) {
             filterProps.remove(name);
+            // CXF-9235: properties set here also need to be visible as 
top-level
+            // context properties, otherwise code that reads them via
+            // Message.getContextualProperty() (e.g. the HTTP transport) never 
sees them.
+            contextProps.remove(name);
         } else {
             filterProps.put(name, value);
+            contextProps.put(name, value);
         }
         return this;
{code}

Would you be willing to rework your patch along those lines and open the PR? 
Also we should add a regression test exercising 
Invocation.Builder.property("set.content.type.for.empty.request", ...) 
directly, since that path currently has no coverage.

Thanks again for digging into this!

Freeman


was (Author: ffang):
Hi Neena,

Thanks for the detailed report and for tracking down the root cause — your 
diagnosis is correct IMO. I traced it through the code and confirmed:

InvocationBuilderImpl.property(name, value) 
(rt/rs/client/.../spec/InvocationBuilderImpl.java) nests the property under a 
private key, "jaxrs.filter.properties", instead of writing it flat into the 
request context:

Map<String, Object> filterProps = CastUtils.cast((Map<?, ?>) 
contextProps.get(PROPERTY_KEY));
...
filterProps.put(name, value);

Compare that to Client.property() / WebTarget.property(), which go through 
ConfigurableImpl and end up flattened directly into 
ClientConfiguration.getRequestContext() (ClientImpl.java, around line 285). 
That flat write is what lets Message.getContextualProperty() — used by both 
Headers.setProtocolHeadersInConnection() and HttpClientHTTPConduit — see the 
property at all, since it does a flat merge and never unwraps a value that's 
itself a Map.

So I'd suggest fixing this at the source rather than the transport side: your 
workaround (teaching the transport to also check jaxrs.filter.properties when a 
contextual lookup misses) does resolve your case, but it only fixes this one 
property. The nesting bug in InvocationBuilderImpl actually swallows any 
property set via Invocation.Builder.property(), and your workaround also pulls 
a JAX-RS-client-internal key (jaxrs.filter.properties) into the generic HTTP 
transport layer, which we'd rather avoid introducing upstream.

The fix I'd propose instead: make InvocationBuilderImpl.property() write 
directly into contextProps (flat), matching how Client/WebTarget already 
behave. That's a smaller change, fixes the bug for every property (not just 
this one), and needs no changes outside rt/rs/client.

Would you be willing to rework your patch along those lines and open the PR? 
Also we should add a regression test exercising 
Invocation.Builder.property("set.content.type.for.empty.request", ...) 
directly, since that path currently has no coverage.

Thanks again for digging into this!

Freeman

> Invocation.Builder.property() settings ignored by HTTP transport for 
> set.content.type.for.empty.request
> -------------------------------------------------------------------------------------------------------
>
>                 Key: CXF-9235
>                 URL: https://issues.apache.org/jira/browse/CXF-9235
>             Project: CXF
>          Issue Type: Bug
>            Reporter: Neena Jacob
>            Priority: Minor
>
> {{InvocationBuilderImpl.property()}} stores request properties in a nested 
> map under the key {{jaxrs.filter.properties}} inside the WebClient request 
> context. When a client calls:
>  
> {{builder.property("set.content.type.for.empty.request", "FALSE")}}
>  
> the value is stored inside that nested map, not at the top level of the 
> request context.
> However, {{Headers.setProtocolHeadersInConnection()}} retrieves the property 
> via 
> {{{}message.getContextualProperty("set.content.type.for.empty.request"){}}}, 
> which builds a merged context cache from Bus → Service → Endpoint → Exchange 
> → Message using shallow {{putAll()}} calls. The nested 
> {{jaxrs.filter.properties}} map is present in the cache as an opaque {{Map}} 
> value under that key, but its contents are never unpacked. Consequently, 
> {{getContextualProperty()}} returns {{{}null{}}}, the 
> {{set.content.type.for.empty.request}} configuration is silently ignored, and 
> the {{Content-Type}} header is always added to non-GET requests with an empty 
> entity body.
> *Working scenario (for contrast):*
> Setting the property at the {{Client}} or {{WebTarget}} level works correctly:
>  
> {{client.property("set.content.type.for.empty.request", "FALSE")// 
> orwebTarget.property("set.content.type.for.empty.request", "FALSE")}}
>  
> This goes through {{ClientImpl.WebTargetImpl.request()}} which calls 
> {{{}clientCfg.getRequestContext().putAll(configProps){}}}, placing the 
> property *directly at the top level* of the request context. 
> {{getContextualProperty()}} then finds it correctly.
> *Expected Behaviour:*
> When {{set.content.type.for.empty.request}} is set to {{FALSE}} via 
> {{{}Invocation.Builder.property(){}}}, the HTTP transport should honour the 
> setting and omit the {{Content-Type}} header for non-GET requests with an 
> empty entity body, consistent with the behaviour when the same property is 
> set via {{Client.property()}} or {{{}WebTarget.property(){}}}.
> *Affected versions:* Verified with CXF 3.1.18 and 3.5.5. Likely affects other 
> maintained branches.
> *Steps to reproduce:*
>  
> {{Client client = ClientBuilder.newClient();Invocation.Builder builder = 
> client.target("http://example.com/api";).request();}}
> {{builder.property("set.content.type.for.empty.request", "FALSE");// Issue a 
> POST/DELETE with no entity body}}
> {{// Observed: Content-Type header is still sent// Expected: Content-Type 
> header is omitted}}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to