使用HashMap中的参数的Spring RestTemplate post会抛出400 Bad Request

时间:2018-04-05 08:44:17

标签: spring spring-boot

当我使用Spring RestTemplate.postForObject使用HashMap发布参数时,服务器会抛出400 Bad Request:

Map<String, Object> uriVariables = new HashMap<>();
uriVariables.put("param1", "param1val");
restTemplate.postForObject(url, uriVariables, responseType);

日志:

14:51:20.102 [main] DEBUG org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/demo-1/test"
14:51:20.113 [main] DEBUG org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, application/json, application/*+json, */*]
14:51:20.161 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{param1=1}] using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@8e24743]
14:51:20.225 [main] DEBUG org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/demo-1/test" resulted in 400 (null); invoking error handler
Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 400 null
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:78)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
    at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:380)

但如果我使用MultiValueMap,它就可以了。

MultiValueMap<String, Object> uriVariables = new LinkedMultiValueMap<>();
uriVariables.add("param1", "param1val");
restTemplate.postForObject(url, uriVariables, responseType);

日志:

14:52:08.493 [main] DEBUG org.springframework.web.client.RestTemplate - Created POST request for "http://localhost:8080/demo-1/test"
14:52:08.501 [main] DEBUG org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, application/json, application/*+json, */*]
14:52:08.502 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{param1=[1]}] using [org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@4abdb505]
14:52:08.537 [main] DEBUG org.springframework.web.client.RestTemplate - POST request for "http://localhost:8080/demo-1/test" resulted in 200 (null)
14:52:08.539 [main] DEBUG org.springframework.web.client.RestTemplate - Reading [java.lang.String] as "text/plain;charset=UTF-8" using [org.springframework.http.converter.StringHttpMessageConverter@13c27452]
{"name":"1"}

这是控制器方法:

@RequestMapping(name = "/test", method = RequestMethod.POST)
@ResponseBody
public String test(@RequestParam("param1") final String param1) {
    // some class Test
    final Test test = new Test();
    test.setName(param1);
    return JsonUtils.toJson(test);
}

有人可以解释为什么使用HashMap传递请求参数不起作用吗?

4 个答案:

答案 0 :(得分:1)

当您说:it throws 400 Bad Request:时,您是否理解it引用的内容?提示:它不是Spring REST客户端代码,而是您正在与之通信的服务器,它不接受您的http请求有效。

您可以激活spring restTemplate使用的httpclient实现的日志记录,以查看从HashMapLinkedMultiValueMap的更改如何更改生成的http请求。

您还可以查看spring restTemplate源代码,了解为什么会生成不同的请求。

编辑:您发布的日志的重要部分:

  

14:51:20.161 [main] DEBUG org.springframework.web.client.RestTemplate - 使用[org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@8e24743]编写[{param1 = 1}]

这里肯定是在http请求主体

上写json 使用LinkedMultiValueMap时

  

14:52:08.502 [main] DEBUG org.springframework.web.client.RestTemplate - 使用[org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@4abdb505]编写[{param1 = [1]}]

在第一种情况下,您的请求将不会有参数param1,而是请求正文中的所有数据,而在第二种情况下,您将获得param1参数,使请求有效服务器

应该可以通过扩展/创建邮件转换器来配置RestTemplate,以便按HashMap的方式工作,但我建议您与LinkedMultiValueMap保持联系有两个原因:

  • 这是默认的弹簧设置。因此,当您需要更新到下一版本时,您将更容易迁移。
  • 使用您的代码的其他人不会对RestTemplate使用您的自定义配置的“新”方式感到惊讶

答案 1 :(得分:1)

MultiValueMap具有与HashMap不同的值类型。可以在其界面定义中看到:

interface MultiValueMap<K, V> extends Map<K, List<V>>

和HashMap:

class HashMap<K,V> extends AbstractMap<K,V>

您使用put方法填充HashMap和add方法来填充MultiValueMap,因此它看起来很相似,但实际上MultiValueMap包含列表,而不是单个值。它可以在你的日志中看到:

HashMap中:

  

14:51:20.161 [main] DEBUG org.springframework.web.client.RestTemplate    - 写[{param1 = 1}]

MultiValueMap:

  

14:52:08.502 [main] DEBUG org.springframework.web.client.RestTemplate    - 写[{param1 = 1}]

如您所见{param1=1}不起作用且{param1=[1]}有效。

此外,在RestTemplate的{​​{3}}中,建议使用MultiValueMap:

  

实体的主体或请求本身可以是MultiValueMap   创建一个多部分请求。 MultiValueMap中的值可以是任意值   表示零件主体的对象,或HttpEntity   表示身体和标题的一部分。 MultiValueMap可以   使用MultipartBodyBuilder方便地构建。

答案 2 :(得分:0)

您可以选择使用HttpHeadersHttpEntity以及MultiValueMaphashMap。你查找这个关键字和链接。可能有用。

RestTemplate

HttpHeaders

HttpEntity

MultiValueMap

这是示例代码:

HttpHeaders headers = new HttpHeaders();

headers.add("x-sample-session-id", sampleService.getServerSessionId());

MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();


map.add("file", new ByteArrayResource(model.getFile().getBytes()));
map.add("apList", model.getApList());

HttpEntity<?> request = new HttpEntity<Object>(map, headers);

String url = sampleService.getContextPath() + "/thirdParty/" + sampleService.getThirdPartyId() + "/child/"
        + childId + "/location?sessionId=" + childSessionId
        + "&op=sendOnDemandLocationResponseWithPhoto&latitude=" + latitude + "&longitude=" + longitude
        + "&datetime=" + datetime + "&provider=" + provider + "&guardianId=" + guardianId + "&cellInfos="
        + cellInfos + "&timestamp=" + timestamp + "&battery=" + battery;

RestTemplate rest = new RestTemplate();

try {
    // success
    return rest.exchange(url, HttpMethod.POST, request, String.class);
} catch (HttpStatusCodeException e) {
    // new Error Response
    return ResponseEntity.status(e.getStatusCode()).headers(e.getResponseHeaders())
            .body(e.getResponseBodyAsString());
}

答案 3 :(得分:0)

要使用HashMap使其工作,请尝试以下方法: -

List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);

<强> 解释

默认情况下,RestTemplate在构造函数

中设置一些MessageConverters,如下所示
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
this.messageConverters.add(new SourceHttpMessageConverter<>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

此外,它会自动添加MappingJackson2HttpMessageConverter,但这是在AllEncompassingFormHttpMessageConverter

之后

这不起作用(非常奇怪),因为AllEncompassingFormHttpMessageConverter接管并在MappingJackson2HttpMessageConverter实际工作之前返回

if (jackson2Present) {
    this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}

课程AllEncompassingFormHttpMessageConverter扩展FormHttpMessageConverter正在实施HttpMessageConverter<MultiValueMap<String, ?>>此处MultiValueMap需要注意

MappingJackson2HttpMessageConverter的情况下,它会延伸AbstractJackson2HttpMessageConverter,这会进一步扩展AbstractGenericHttpMessageConverter<Object>注释Object