Skip to content

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

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

Closed
spring-projects-issues opened this issue Jul 16, 2018 · 3 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: invalid An issue that we don't feel is valid

Comments

@spring-projects-issues
Copy link
Collaborator

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-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Jul 16, 2018

Etienne Dysli Metref commented

probably related to #21577

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues 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-projects-issues
Copy link
Collaborator Author

Etienne Dysli Metref commented

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

@spring-projects-issues spring-projects-issues added type: bug A general bug status: invalid An issue that we don't feel is valid type: documentation A documentation task in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues removed type: bug A general bug type: documentation A documentation task labels Jan 12, 2019
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) status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

2 participants