Java SpringRESTTemplateURL编码

Java SpringRESTTemplateURL编码,java,spring,resttemplate,Java,Spring,Resttemplate,我尝试使用springs resttemplate执行一个简单的rest调用: private void doLogout(String endpointUrl, String sessionId) { template.getForObject("http://{enpointUrl}?method=logout&session={sessionId}", Object.class, endpointUrl, sessionId); } 其中endpo

我尝试使用springs resttemplate执行一个简单的rest调用:

private void doLogout(String endpointUrl, String sessionId) {
    template.getForObject("http://{enpointUrl}?method=logout&session={sessionId}", Object.class,
            endpointUrl, sessionId);
}
其中endpointUrl变量包含类似service.host.com/api/service.php的内容

不幸的是,我的调用导致org.springframework.web.client.ResourceAccessException:I/O错误:service.host.com%2Fapi%2Fservice.php

因此,spring似乎在创建url之前对endpointUrl字符串进行了编码。有没有一种简单的方法可以防止spring这样做


要做到这一点,没有简单的方法。URI模板变量通常用于路径元素或查询字符串参数。你想通过一个主机。理想情况下,您会找到构造URI的更好的解决方案。我建议

如果您仍然希望使用Spring实用程序和模板扩展,一种解决方法是使用
UriTemplate
生成带有URI变量的URL,然后URL解码并将其传递给
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);

您可以使用采用java.net.URI的重载变量 public T getForObject(URI url,类responseType)抛出RestClientException


取决于您使用的Spring版本。如果您的版本太旧,例如版本3.0.6.RELEASE,那么您的SpringWebJAR将没有像
UriComponentsBuilder
这样的功能

您需要的是防止SpringRESTTemplate对URL进行编码。你能做的是:

导入java.net.URI;
StringBuilder=新的StringBuilder(“http://”);
append(endpointUrl);
builder.append(“?method=logout&session=”);
builder.append(sessionId);
URI=URI.create(builder.toString());
getForObject(uri,Object.class);
我用SpringVersion3.0.6.RELEASE对它进行了测试,它可以正常工作

总之,不要使用
restemplate.getForObject(字符串url,Object.class)
,而是使用
restemplate.getForObject(java.net.URI,Object.class)


请参阅标题、正文的完整示例,以了解任何HttpMethod和ResponseType的外观:

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()
stringurl=”http://google.com/{path}?param1={param1Value}¶m2={param2 value}”;
objectbody=null;
HttpEntity请求=新的HttpEntity(主体,新的HttpHeaders());
Map uriVariables=newhashmap();
uriVariables.put(“路径”、“搜索”);
uriVariables.put(“param1Value”、“value1”);
uriVariables.put(“param2Value”、“value2”);
ResponseEntity ResponseEntity=restemplate.exchange(url、HttpMethod.POST、request、Void.class、uriVariables)
//responseEntity.getBody()

实际上,它将使用相同的UriTemplate和expand方法

看起来像是我找到的最佳原生方式(最新)解决方案:

  • 不要将编码的url字符串作为参数传递给
    restemplate.exchange()
  • 改用URI对象。使用
    UriComponentsBuilder
    构造URI
  • 见以下(简化)示例:

        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);
    */
    
    String instanceUrl=”https://abc.my.salesforce.com"
    HttpEntity=新的HttpEntity(标题);
    尿酸组分尿酸组分=
    UriComponentsBuilder.fromHttpUrl(instanceUrl)
    .path(“/services/data/v45.0/query/”)
    .queryParam(“q”,String.format(sqlSelect,id))
    .build();
    响应响应响应响应响应响应=
    restTemplate.exchange(
    uriComponents.toUri(),HttpMethod.GET,
    实体,OpportunityLineItem.class);
    //错了!URI字符串将被双重编码
    /*
    响应响应响应响应响应响应=
    restTemplate.exchange(
    uriComponents.toUriString(),HttpMethod.GET,
    实体,OpportunityLineItem.class);
    */
    
    这样,您就不会遇到双重编码的问题


    在调试SalesForce REST客户端时,基于Spring
    RestTemplate
    客户端(包括SOQL查询)找到了解决方案。

    显然,通过调用类UriComponentsBuilder的build(true)有更好的方法:

    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);
    }
    

    此方法告诉URIComponentsBuilder在创建URI时不要编码。

    这解决了我的url中转义/的问题。但不幸的是,这并不能解决我的全部问题。有些情况下,“有效负载”包含JSON字符串。这个json字符串编码在rest模板代码中的某个地方。传递给templates getForObject方法的字符串URL看起来与预期的一样。但是,当spring试图扩展我的url的json部分时,我收到一个异常…@user1145874您必须询问另一个问题,并提供所有详细信息,但我建议不要将json作为查询字符串请求参数。将其发送到帖子或帖子的正文中。您正在做不必要的工作:1)使用
    扩展的.toString()
    对uri进行编码,2)然后将其解码回
    url
    ,3)然后将其再次编码到
    模板.getForObject()
    。最好使用clear方法(使用URI对象而不是字符串)。请参阅我对这个问题的回答中的解释-@DmitryTrifonov我试图澄清这样的转换可以通过模板变量解析来生成URI(“没有简单的方法可以做到这一点”)。如果他们可以在没有URL的情况下正确构造URL,那么这是一个更好的解决方案。uri变量将被编码,这与此问题相反。如果传递给
    getForObject
    的uri字符串构造正确,他们就不会有编码问题。因此,他们问题的要点是如何构造正确的uri。他们
    endpointUrl
    sessionId
    参数中的URI的一部分,以及传递给
    getForObject
    的字符串文本中的URI的一部分。它们需要一些可以协调这些部分的内容。您跳过了所有这些,只是假设存在一个
    instanceUrl
    。在他们的问题上下文中,这是什么?我在哪里ts值来自?您是如何构建它的?这里的问题不是查询参数
    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);
    }