java code

public static void main(String[] args) {
        String content = "xxxxx";
        String accessToken = "75_0jAs7cd0fjJJFvYMaIdY4P9RqMdHYpCGnFGItX0RFT_i1HGDl7GMCoN3eyvkCKBCjp4uJHFKBNfrW-gulrAmllT7hH58yysL1LyyFll4E8MSJbk6BWp-41AujnEKLFdAIABCW";
        String xin = "ofvdc5su2aEAuf4zjKC5A1tmCgrw";
        Arrays.asList(xin).forEach(user -> {
            Map<String, Object> request = new HashMap<>();
            request.put("touser", user);
            request.put("template_id", "fFNqKSC1M6y2k_iG-Koytbyk4kTaLJK3LiCn4cGkehQ");
            request.put("url", "https://wechat.gyfyy.com/gy1yqlc/user/h5/index.html");
            request.put("client_msg_id", System.currentTimeMillis() / 1000 + "");
            request.put("data", new HashMap<String, Object>() {{
                put("content", new HashMap<String, String>() {{
                    put("value", content);
                    put("color", "#173177");
                }});
            }});
            String s = Runner.restTemplate.postForObject("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken,
                    new HttpEntity<>(request, new LinkedMultiValueMap<String, String>() {{
                        add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
                    }}), String.class);
            System.out.println("s = " + s);
        });
    }

spring-web:6.1.1

Send a request. Result is:

Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 412 Precondition Failed: [no body]
    at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:136)
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:183)
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:137)
    at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:932)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:881)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:781)
    at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:498)
    at org.exam.Runner.lambda$main$1(Runner.java:144)
    at java.base/java.util.Arrays$ArrayList.forEach(Arrays.java:4204)
    at org.exam.Runner.main(Runner.java:132)
Disconnected from the target VM, address: '127.0.0.1:59642', transport: 'socket'

spring-web:6.0.14

Send a request. Response is:

{"errcode":0,"errmsg":"ok","msgid":3229660303395831817}

Comment From: quaff

Here is a minimal reproducer:

import java.util.Collections;

import org.springframework.http.HttpEntity;
import org.springframework.web.client.RestTemplate;

public class SimpleTests {

    public static void main(String[] args) {
        System.out.println(new RestTemplate().postForObject("https://api.weixin.qq.com/cgi-bin/message/template/send",
                new HttpEntity<>(Collections.emptyMap()), String.class));
    }

}

6.1 will report error 412 Precondition Failed: [no body] 6.0 will print {"errcode":41001,"errmsg":"access_token missing rid: 65767ca2-541efdfa-204cc1a9"}

Comment From: poutsma

This is due to the fact that as of Spring Framework 6.1, most ClientHttpRequestFactory implementations no longer buffer request bodies before sending them to the server. As a result, for certain content types such as JSON, the contents size is no longer known, and a Content-Length header is no longer set. If you would like to buffer request bodies like before, simply wrap the ClientHttpRequestFactory you are using in a BufferingClientHttpRequestFactory.

This gets the expected "access_token missing" error:

RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory()));
System.out.println(restTemplate.postForObject("https://api.weixin.qq.com/cgi-bin/message/template/send",
        new HttpEntity<>(Collections.emptyMap()), String.class));

Comment From: aliciz

I'm having the same problem now, I've changed it to define resttemplate this way, and that's fine, but I'm not sure if it's elegant, there's a better way?

    @Bean
    public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
        RestTemplateBuilder builder = new RestTemplateBuilder();

        return restTemplateBuilderConfigurer.configure(builder.requestFactory(new Function<ClientHttpRequestFactorySettings, ClientHttpRequestFactory>() {
            @Override
            public ClientHttpRequestFactory apply(ClientHttpRequestFactorySettings clientHttpRequestFactorySettings) {
                return new BufferingClientHttpRequestFactory(new SkipSslVerificationHttpRequestFactory());
            }
        }));
    }

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        RestTemplate restTemplate = restTemplateBuilder.build();
        List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                try {
                    List<MediaType> mediaTypeList = new ArrayList<>(converter.getSupportedMediaTypes());
                    mediaTypeList.add(MediaType.TEXT_PLAIN);
                    ((MappingJackson2HttpMessageConverter) converter).setSupportedMediaTypes(mediaTypeList);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return restTemplate;
    }

Comment From: bclozel

@aliciz it depends if you are using Spring Boot or not. I would ask the question on StackOverflow anyway.

Comment From: aliciz

@aliciz it depends if you are using Spring Boot or not. I would ask the question on StackOverflow anyway.

I using spring boot 3.2.1