سؤال

I try to do a simple rest call with springs resttemplate:

private void doLogout(String endpointUrl, String sessionId) {
    template.getForObject("http://{enpointUrl}?method=logout&session={sessionId}", Object.class,
            endpointUrl, sessionId);
}

Where the endpointUrl variable contains something like service.host.com/api/service.php

Unfortunately, my call results in a org.springframework.web.client.ResourceAccessException: I/O error: service.host.com%2Fapi%2Fservice.php

So spring seems to encode my endpointUrl string before during the creation of the url. Is there a simple way to prevent spring from doing this?

Regards

هل كانت مفيدة؟

المحلول

There is no easy way to do this. URI template variables are usually meant for path elements or a query string parameters. You're trying to pass a host. Ideally, you'd find a better solution for constructing the URI. I suggest Yuci's solution.

If you still want to work with Spring utilities and template expansion, one workaround is to use UriTemplate to produce the URL with the URI variables as you have them, then URL-decode it and pass that to your RestTemplate.

String url = "http://{enpointUrl}?method=logout&session={sessionId}";
URI expanded = new UriTemplate(url).expand(endpointUrl, sessionId); // this is what RestTemplate uses 
url = URLDecoder.decode(expanded.toString(), "UTF-8"); // java.net class
template.getForObject(url, Object.class);

نصائح أخرى

Depends on which version of Spring you're using. If your version is too old, for example, version 3.0.6.RELEASE, you'll not have such facility as UriComponentsBuilder with your spring-web jar.

What you need is to prevent Spring RestTemplate from encoding the URL. What you could do is:

import java.net.URI;

StringBuilder builder = new StringBuilder("http://");
builder.append(endpointUrl);
builder.append("?method=logout&session=");
builder.append(sessionId);

URI uri = URI.create(builder.toString());
restTemplate.getForObject(uri, Object.class);

I tested it with Spring version 3.0.6.RELEASE, and it works.

In a word, instead of using restTemplate.getForObject(String url, Object.class), use restTemplate.getForObject(java.net.URI uri, Object.class)

See the rest-resttemplate-uri section of the Spring document

Looks like I found best native way (up-to-date) solution:

  1. Do not pass encoded url string as parameter to RestTemplate.exchange()
  2. Use URI object instead. Use UriComponentsBuilder to construct URI.

See (simplified) example below:

    String instanceUrl = "https://abc.my.salesforce.com"
    HttpEntity<String> entity = new HttpEntity<>(headers);
    UriComponents uriComponents =
            UriComponentsBuilder.fromHttpUrl(instanceUrl)
                    .path("/services/data/v45.0/query/")
                    .queryParam("q", String.format(sqlSelect, id))
                    .build();

    ResponseEntity<OpportunityLineItem> responseEntity =
            restTemplate.exchange(
                    uriComponents.toUri(), HttpMethod.GET,
                    entity, OpportunityLineItem.class);

// Wrong! URI string will be double encoded
/*
ResponseEntity<OpportunityLineItem> responseEntity =
            restTemplate.exchange(
                    uriComponents.toUriString(), HttpMethod.GET,
                    entity, OpportunityLineItem.class);
*/

This way you will not get issue with double encoding.

Solution was found while debugging SalesForce REST client, based on Spring RestTemplate client (including SOQL queries).

You can use the overloaded variant that takes a java.net.URI instead public T getForObject(URI url, Class responseType) throws RestClientException

From Spring's own documentation

UriComponents uriComponents =
    UriComponentsBuilder.fromUriString("http://example.com/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

URI uri = uriComponents.toUri();

Apparently there is a better way to do this by calling build(true) of class UriComponentsBuilder:

private void doLogout(String endpointUrl, String sessionId) {
    String url = "http://" + endpointUrl +"?method=logout&session=" + + URLEncoder.encode(sessionId, "UTF-8");
    URI uri = UriComponentsBuilder.fromUriString(url.toString()).build(true).toUri();
    template.getForObject(uri, Object.class,
            endpointUrl, sessionId);
}

This method tells URIComponentsBuilder not to encode while creating URI.

Full example with headers, body, for any HttpMethod and ResponseType could look like:

String url = "http://google.com/{path}?param1={param1Value}&param2={param2Value}";
Object body = null;
HttpEntity request = new HttpEntity(body, new HttpHeaders());

Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("path", "search");
uriVariables.put("param1Value", "value1");
uriVariables.put("param2Value", "value2");

ResponseEntity<Void> responseEntity = restTemplate.exchange(url, HttpMethod.POST, request, Void.class, uriVariables)
//responseEntity.getBody()

Actually, it will use the same UriTemplate and expand method

Best way to do is with UriComponentsBuilder:

UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
builder.queryParam("some_param", "param with space for encoding");
template.getForObject(builder.encode().build().toUri(), Object.class, headers);
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top