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

Configuring a RestTemplate with EncodingMode.VALUES_ONLY does not encode special characters [SPR-17048] #21586

Closed
spring-issuemaster opened this issue Jul 16, 2018 · 3 comments
Assignees

Comments

@spring-issuemaster
Copy link
Collaborator

@spring-issuemaster spring-issuemaster commented Jul 16, 2018

Etienne Dysli Metref opened SPR-17048 and commented

Contrary to what Spring Web's documentation states, changing the encoding method by configuring the DefaultUriBuilderFactory used by RestTemplate with setEncodingMode(EncodingMode.VALUES_ONLY) does not have the expected effect. It should "apply UriUtils.encode(String, Charset) to each URI variable value" which in turn will "encode all characters that are either illegal, or have any reserved meaning, anywhere within a URI, as defined in RFC 3986". However I'm still seeing +, : or / in query parameter values that are not %-encoded.

The following test case fails with java.lang.AssertionError: Request URI expected:<[https://host?parameter=%2B%3A%2F](https://host/?parameter=%2B%3A%2F)> but was:<[https://host?parameter=+:/](https://host/?parameter=+:/)>. (executing it with dependencies org.springframework.boot:spring-boot-starter:2.0.3.RELEASE, org.springframework:spring-web:5.0.7.RELEASE, org.springframework.boot:spring-boot-starter-test:2.0.3.RELEASE)

package com.example.demo.encoding;

import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

import java.nio.charset.StandardCharsets;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;

@RunWith(SpringRunner.class)
@RestClientTest(DemoClient.class)
public class EncodingTest {
  @Autowired private MockRestServiceServer mockServer;
  @Autowired private DemoClient client;

  @Test
  public void encodeAllCharactersInParameter() {
    mockServer.expect(requestTo(encodedQueryUrl("https://host", "+:/")))
      .andExpect(method(HttpMethod.GET))
      .andRespond(withSuccess());
    client.request("https://host", "+:/");
    mockServer.verify();
  }

  private String encodedQueryUrl(final String baseUrl, final String parameter) {
    return String.format("%s?parameter=%s", baseUrl,
      UriUtils.encode(parameter, StandardCharsets.UTF_8));
  }
}

@Component
class DemoClient {
  private final RestTemplate restTemplate;

  public DemoClient(RestTemplateBuilder restTemplateBuilder) {
    DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
    factory.setEncodingMode(EncodingMode.VALUES_ONLY);
    restTemplateBuilder.uriTemplateHandler(factory);
    this.restTemplate = restTemplateBuilder.build();
  }

  public Object request(final String url, final String parameter) {
    UriComponents queryUrl = UriComponentsBuilder.fromHttpUrl(url)
      .queryParam("parameter", parameter).build().encode();
    return restTemplate.getForObject(queryUrl.toUri(), Object.class);
  }
}

Affects: 5.0.7

Reference URL: https://stackoverflow.com/questions/51241321/how-to-have-a-resttemplate-encode-all-characters-with-uricomponents-and-encoding

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 16, 2018

Etienne Dysli Metref commented

probably related to #21577

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 16, 2018

Rossen Stoyanchev commented

This is an issue with the example.

  1. The request method prepares and encodes a java.net.URI externally, so the RestTemplate is not the one preparing it. You need to pass a URI template with a URI variable in it:
public Object request(final String url, final String parameter) {
	String urlString = UriComponentsBuilder.fromHttpUrl(url)
			.queryParam("parameter", "{param}")
			.build()
			.toUriString();
	return restTemplate.getForObject(urlString, Object.class, parameter);
}

Or simply have request take the URI template string:

public Object request(final String url) {
	return restTemplate.getForObject(url, Object.class, parameter);
}

// then invoke like this...
request("https://host?parameter={param}");
  1. RestTemplateBuilder.uriTemplateHandler returns a new instance which is being ignored, so the configuration change does not take effect. You need:
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
restTemplateBuilder = restTemplateBuilder.uriTemplateHandler(factory); // <<<< see here
this.restTemplate = restTemplateBuilder.build();

It works as expected with the above changes.

#21577 will make it easier to also achieve the same effect using UriComponentsBuilder, so check for updates there.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 17, 2018

Etienne Dysli Metref commented

Thank you very much for pointing out where I was wrong and answering on SO too! :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.