如何解决Spring Boot RestTemplate设置Accept请求头无效的问题?
我完全懂你此刻的困扰——明明手动把Accept头设成了application/json,但RestTemplate死活要带上text/plain这类额外类型,导致GraphQL服务返回406错误。这背后的核心原因和RestTemplate的消息转换器(MessageConverter)机制直接相关:
当你调用exchange方法并指定响应类型为String.class时,RestTemplate会自动遍历它注册的所有HttpMessageConverter,把这些转换器支持的MediaType全部追加到Accept头里。默认情况下,StringHttpMessageConverter是最先被注册的,它默认支持text/plain,所以不管你手动设置了什么Accept值,这个类型都会被强制加上。
下面是几个可行的解决方案,按推荐程度排序:
方案1:修改StringHttpMessageConverter的支持类型
你可以自定义RestTemplate,调整StringHttpMessageConverter的支持MediaType,只保留application/json,这样就不会自动添加text/plain了:
RestTemplate restTemplate = new RestTemplate(); // 找到StringHttpMessageConverter并修改其支持的MediaType for (HttpMessageConverter<?> converter : restTemplate.getMessageConverters()) { if (converter instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter) converter).setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON)); break; } } // 后续请求代码和你原来的逻辑一致 HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "application/graphql"); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); String content = "{products(query: \"title:tow*\", first: 10) {edges {node {id title}}}}"; HttpEntity<String> requestEntity = new HttpEntity<>(content, headers); ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
方案2:添加请求拦截器强制覆盖Accept头
通过ClientHttpRequestInterceptor可以在请求发送前强制重置Accept头,彻底覆盖所有自动添加的类型:
RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add((request, body, execution) -> { // 强制将Accept头设置为application/json,覆盖所有默认值 request.getHeaders().setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); return execution.execute(request, body); }); // 原来的请求代码无需大幅修改 HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "application/graphql"); String content = "{products(query: \"title:tow*\", first: 10) {edges {node {id title}}}}"; HttpEntity<String> requestEntity = new HttpEntity<>(content, headers); ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
方案3:移除不需要的MessageConverter
如果你的场景完全不需要StringHttpMessageConverter,可以直接移除它,这样RestTemplate就不会自动添加text/plain到Accept头了:
RestTemplate restTemplate = new RestTemplate(); // 移除StringHttpMessageConverter restTemplate.getMessageConverters().removeIf(converter -> converter instanceof StringHttpMessageConverter); // 设置请求头并发送请求 HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "application/graphql"); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); String content = "{products(query: \"title:tow*\", first: 10) {edges {node {id title}}}}"; HttpEntity<String> requestEntity = new HttpEntity<>(content, headers); // 注意:如果用String.class可能会报错,可改用byte[]或自定义适配的转换器 ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
为什么手动设置setAccept不生效?
因为RestTemplate在准备请求时,会根据你指定的响应类型(这里是String.class),从MessageConverter中收集所有支持的MediaType,然后合并到你手动设置的Accept头里——你的手动设置是被追加,而不是覆盖自动添加的类型。这就是日志里会出现[text/plain, application/json, application/*+json, */*]的根本原因。
内容的提问来源于stack exchange,提问作者rram

