Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP interface client raises IllegalArgumentException if query param name contains a colon #34364

Closed
ZIRAKrezovic opened this issue Feb 4, 2025 · 0 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: regression A bug that is also a regression
Milestone

Comments

@ZIRAKrezovic
Copy link

ZIRAKrezovic commented Feb 4, 2025

I have traced the issue to the following commit: d927d64

Reproducer:

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

import reactor.core.publisher.Mono;

import java.util.Map;

@ExtendWith(MockitoExtension.class)
class HttpRequestValuesTest {
    @Mock private ExchangeFunction exchangeFunction;
    @Captor private ArgumentCaptor<ClientRequest> captor;

    @HttpExchange(
            url = "/reactive/v1",
            accept = MediaType.APPLICATION_JSON_VALUE,
            contentType = MediaType.APPLICATION_JSON_VALUE)
    interface TestClient {
        @GetExchange
        String get(@RequestParam Map<String, String> queryParams);
    }

    @Test
    void reproducer() {
        ClientResponse mockResponse = mock();
        when(mockResponse.statusCode()).thenReturn(HttpStatus.OK);
        when(mockResponse.bodyToMono(Void.class)).thenReturn(Mono.empty());
        given(exchangeFunction.exchange(captor.capture())).willReturn(Mono.just(mockResponse));

        var webClient = WebClient.builder().exchangeFunction(exchangeFunction).build();
        var adapter = WebClientAdapter.create(webClient);
        var factory = HttpServiceProxyFactory.builderFor(adapter).build();
        var client = factory.createClient(TestClient.class);

        Assertions.assertDoesNotThrow(() -> client.get(Map.of("userId:eq", "test")));
    }
}

When a URL variable name is transformed to "{userId:eq}" on line 499 of the file linked above, the UriComponentsBuilder interprets this as "variable userId with default value "eq""

This causes the following exception with Spring Framework 6.2.2

Caused by: java.lang.IllegalArgumentException: Map has no value for 'userId'
	at org.springframework.web.util.UriComponents$MapTemplateVariables.getValue(UriComponents.java:348)
	at org.springframework.web.util.HierarchicalUriComponents$QueryUriTemplateVariables.getValue(HierarchicalUriComponents.java:1099)
	at org.springframework.web.util.UriComponents.expandUriComponent(UriComponents.java:263)
	at org.springframework.web.util.HierarchicalUriComponents.lambda$expandQueryParams$5(HierarchicalUriComponents.java:453)
	at org.springframework.util.UnmodifiableMultiValueMap.lambda$forEach$0(UnmodifiableMultiValueMap.java:115)
	at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:986)
	at org.springframework.util.MultiValueMapAdapter.forEach(MultiValueMapAdapter.java:179)
	at org.springframework.util.UnmodifiableMultiValueMap.forEach(UnmodifiableMultiValueMap.java:115)
	at org.springframework.web.util.HierarchicalUriComponents.expandQueryParams(HierarchicalUriComponents.java:452)
	at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:441)
	at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:53)
	at org.springframework.web.util.UriComponents.expand(UriComponents.java:161)
	at org.springframework.web.util.DefaultUriBuilderFactory$DefaultUriBuilder.build(DefaultUriBuilderFactory.java:447)
	at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.uri(DefaultWebClient.java:240)
	at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.uri(DefaultWebClient.java:201)
	at org.springframework.web.reactive.function.client.support.WebClientAdapter.newRequest(WebClientAdapter.java:121)
	at org.springframework.web.reactive.function.client.support.WebClientAdapter.exchangeForBodyMono(WebClientAdapter.java:78)
	at org.springframework.web.service.invoker.HttpServiceMethod$ReactorExchangeResponseFunction.lambda$initBodyFunction$5(HttpServiceMethod.java:554)
	at org.springframework.web.service.invoker.HttpServiceMethod$ReactorExchangeResponseFunction.execute(HttpServiceMethod.java:449)
	at org.springframework.web.service.invoker.HttpServiceMethod.invoke(HttpServiceMethod.java:133)
	at org.springframework.web.service.invoker.HttpServiceProxyFactory$HttpServiceMethodInterceptor.invoke(HttpServiceProxyFactory.java:243)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Feb 4, 2025
@snicoll snicoll added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Feb 4, 2025
@rstoyanchev rstoyanchev self-assigned this Feb 11, 2025
@rstoyanchev rstoyanchev added type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Feb 11, 2025
@rstoyanchev rstoyanchev added this to the 6.2.3 milestone Feb 11, 2025
@rstoyanchev rstoyanchev changed the title HTTP interface client query param handling does not work when query parameter name contains a colon HTTP interface client raises IllegalArgumentException if query param name contains a colon Feb 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants