简体   繁体   English

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

[英]Spring RestTemplate post using parameters in a HashMap throws 400 Bad Request

When I use Spring RestTemplate.postForObject to post parameters using HashMap , server throws 400 Bad Request:当我使用 Spring RestTemplate.postForObject使用HashMap发布参数时,服务器抛出 400 Bad Request:

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

Log:日志:

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)

But if I use MultiValueMap , it works.但是如果我使用MultiValueMap ,它就可以工作。

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

Log:日志:

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"}

Here is controller method:这是控制器方法:

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

Can somebody explain why passing request parameters using a HashMap doesn't work?有人可以解释为什么使用HashMap传递请求参数不起作用吗?

When you say : it throws 400 Bad Request: do you understand what is referred by it ?当你说: it throws 400 Bad Request:你明白它指的是it吗? hint : it is not Spring REST client code, but the server you are talking to, which do not accept your http request as valid.提示:它不是 Spring REST 客户端代码,而是您正在与之交谈的服务器,它不接受您的 http 请求为有效。

You could activate logging of the httpclient implementation used by spring restTemplate to see how going from HashMap to LinkedMultiValueMap change the generated http request.您可以激活 spring restTemplate 使用的 httpclient 实现的日志记录,以查看从HashMapLinkedMultiValueMap如何更改生成的 http 请求。

You also could look at spring restTemplate source code to see why generate different requests.您还可以查看 spring restTemplate 源代码以了解为什么会生成不同的请求。

EDIT: the important part of the log you've posted :编辑:您发布的日志的重要部分:

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.161 [main] 调试 org.springframework.web.client.RestTemplate - 使用 [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@8e24743] 编写 [{param1=1}]

here it is writing json most certainly on the http request body这里肯定是在 http 请求正文上写 json

while with LinkedMultiValueMap :使用 LinkedMultiValueMap 时:

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.502 [main] DEBUG org.springframework.web.client.RestTemplate - 使用 [org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@4abdb505] 编写 [{param1=[1]}]

in the first case, your request won't have a parameter param1 but all data in the request body, while in the second case, you get a param1 parameter, making the request valid for the server在第一种情况下,您的请求不会有参数param1而是请求正文中的所有数据,而在第二种情况下,您会得到一个param1参数,使请求对服务器有效

It should be possible to configure RestTemplate by extending / creating a message converter to work the way you want with a HashMap , but I advise you to stay with LinkedMultiValueMap for 2 reasons :应该可以通过扩展/创建消息转换器来配置RestTemplate以使用HashMap以您想要的方式工作,但我建议您使用LinkedMultiValueMap有两个原因:

  • this is the default spring settings.这是默认的弹簧设置。 So you will have an easier migration when you'll need to update to next versions.因此,当您需要更新到下一个版本时,您将可以更轻松地进行迁移。
  • other people using your code won't be surprised by the "new" way RestTemplate is working with your custom configuration使用您的代码的其他人不会对 RestTemplate 使用您的自定义配置的“新”方式感到惊讶

MultiValueMap has different value type than HashMap. MultiValueMap 与 HashMap 具有不同的值类型。 It can be seen in its interface definition:在其接口定义中可以看出:

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

and HashMap:和哈希映射:

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

You use put method to fill HashMap and add method to fill MultiValueMap, so it looks similar, but in fact MultiValueMap holds lists , not single values.你使用put方法填充HashMap, add方法填充MultiValueMap,看起来很相似,但实际上MultiValueMap保存的是lists ,而不是单个值。 It can be seen in your logs:它可以在您的日志中看到:

HashMap:哈希映射:

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

MultiValueMap:多值映射:

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

As you see {param1=1} does not work and {param1=[1]} works.如您所见, {param1=1}不起作用,而{param1=[1]}起作用。

Additionally, in the documentation of RestTemplate it is recommended to use MultiValueMap:此外,在RestTemplate文档中建议使用 MultiValueMap:

The body of the entity, or request itself, can be a MultiValueMap to create a multipart request.实体的主体或请求本身可以是 MultiValueMap 以创建多部分请求。 The values in the MultiValueMap can be any Object representing the body of the part, or an HttpEntity representing a part with body and headers. MultiValueMap 中的值可以是表示部件主体的任何对象,也可以是表示具有主体和标头的部件的 HttpEntity。 The MultiValueMap can be built conveniently using MultipartBodyBuilder.可以使用 MultipartBodyBuilder 方便地构建 MultiValueMap。

You choice to use HttpHeaders and HttpEntity and MultiValueMap or hashMap .您可以选择使用HttpHeadersHttpEntity以及MultiValueMaphashMap you look up this keyword and link.你查找这个关键字和链接。 may be useful.可能有用。

RestTemplate 休息模板

HttpHeaders 头文件

HttpEntity 实体

MultiValueMap 多值映射

This is sample code :这是示例代码:

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

For making it to work using HashMap try this:-要使用HashMap使其工作,请尝试以下操作:-

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

Explanation说明

By default RestTemplate sets a number of MessageConverters like below in the constructor默认情况下,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());

And also it adds automatically MappingJackson2HttpMessageConverter but that is after AllEncompassingFormHttpMessageConverter它还自动添加了MappingJackson2HttpMessageConverter但那是在AllEncompassingFormHttpMessageConverter之后

which doesn't work(very strange) because AllEncompassingFormHttpMessageConverter takes over and returns before MappingJackson2HttpMessageConverter could have actually worked这不起作用(非常奇怪),因为AllEncompassingFormHttpMessageConverterMappingJackson2HttpMessageConverter实际工作之前接管并返回

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

And the class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter which is implementing the HttpMessageConverter<MultiValueMap<String, ?>> , here MultiValueMap is something to note .AllEncompassingFormHttpMessageConverter扩展了FormHttpMessageConverter ,它实现了HttpMessageConverter<MultiValueMap<String, ?>>这里MultiValueMap是需要注意的

While in the case of MappingJackson2HttpMessageConverter it extends AbstractJackson2HttpMessageConverter which is further extending the AbstractGenericHttpMessageConverter<Object> and note Object here而在MappingJackson2HttpMessageConverter的情况下,它扩展了AbstractJackson2HttpMessageConverter ,它进一步扩展了AbstractGenericHttpMessageConverter<Object>在此处注意Object

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM