解决使用RestTemplate时报错RestClientException的问题

目录
  • 使用RestTemplate时报错RestClientException
    • 这是自己封装的一个发送请求的方法
    • 这是自定义的一个http信息Converter
  • RestTemplate的错误处理
    • 问题描述
    • ErrorHandler
    • 解决办法

使用RestTemplate时报错RestClientException

这是自己封装的一个发送请求的方法

public  Map<String, Object> sendRequest(Map<String, Object> body,String sessionId,String url) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add(new BdMappingJackson2HttpMessageConverter());
        Map<String, Object> map = new HashMap<>();
        try {
            ParameterizedTypeReference<Map<String, Object>> typeRef = new ParameterizedTypeReference<Map<String, Object>>() {
            };
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.set("cookie", "SESSION="+sessionId);
            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(body,headers);
            ResponseEntity<Map<String, Object>> responseEntity = restTemplate.exchange(url, HttpMethod.POST,
                    requestEntity,typeRef);
            map = responseEntity.getBody();
            log.info(map.toString());
        } catch (HttpStatusCodeException e) {
            log.error(e.getResponseBodyAsString(), e);
            map = JsonUtil.toMap(e.getResponseBodyAsString());
        }
        return map;
    }

这是自定义的一个http信息Converter

public class BdMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
    public BdMappingJackson2HttpMessageConverter(){
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.TEXT_HTML);
        mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        setSupportedMediaTypes(mediaTypes);
    }
}

我遇到的第一个问题是这样的

Could not write request: no suitable HttpMessageConverter found for request type [java.util.HashMap] and content type [application/octet-stream]

意思大概是无法写入请求:找不到适用于请求类型[java.util.HashMap]和内容类型[application/octet stream]的HttpMessageConverter

它默认只处理application/json:JSON数据格式,这个二进制数据流格式不支持,所以我在那个自定义的Converter加上去了。

紧接着又遇到第二个问题

Could not extract response: no suitable HttpMessageConverter found for response type [java.util.Map<java.lang.String, java.lang.Object>] and content type [text/xml;charset=UTF-8]

无法提取响应:找不到适合于响应类型[java.util.Map<java.lang.String,java.lang.Object>]和内容类型[textml/charset=UTF-8]的HttpMessageConverter

这是那边回调过来的Content-Type类型是text/xml它同样解析不了,所以我把这个也加上去了,至此就ok了。

常见的Content-Type类型:

  • text/html:HTML格式
  • text/plain:纯文本格式
  • image/png:png图片格式
  • application/json:JSON数据格式
  • application/octet-stream:二进制流数据
  • application/x-www-form-urlencoded:表单中默认的encType,表单数据被编码为key/value格式发送到服务器
  • multipart/form-data:需要在表单中进行文件上传时,就需要使用该格式

RestTemplate的错误处理

我们的项目属于微服务架构,两个基础的服务分别是网关和认证鉴权服务。

在前端访问后台服务的时候,都经过网关转发,转发之前会进行鉴权认证,根据鉴权结果判断是否可以进行相应的请求转发。

问题描述

认证鉴权服务中,判断Token对应的人员是否有相应的权限,如果没有权限,返回401状态码并在响应体中传回错误信息。

网关与鉴权服务之间的服务调用通过RestTemplate进行(可以考虑转为Feign做声明式的服务调用),然而如果鉴权服务返回401的情况下,网关服务直接报出HttpClientErrorException,让人一头雾水。

实际上,答案都在源码中,看一下RestTemplate的源码就知晓了。

ErrorHandler

在RestTemplate中,有一个成员变量ResponseErrorHandler。

ResponseErrorHandler是一个接口,包括两个方法:

public interface ResponseErrorHandler {
    boolean hasError(ClientHttpResponse var1) throws IOException;
    void handleError(ClientHttpResponse var1) throws IOException;
}

这个接口有一个默认实现DefaultResponseErrorHandler。该方法中,判断是否发生error的方法hasError最终调用的方法如下:

protected boolean hasError(HttpStatus statusCode) {
        return statusCode.series() == Series.CLIENT_ERROR || statusCode.series() == Series.SERVER_ERROR;
    }

很明显,根据响应状态吗为4xx或者5xx来认定发生了错误。而错误处理在handleError中:

public void handleError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = this.getHttpStatusCode(response);
        switch(null.$SwitchMap$org$springframework$http$HttpStatus$Series[statusCode.series().ordinal()]) {
        case 1:
            throw new HttpClientErrorException(statusCode, response.getStatusText(), response.getHeaders(), this.getResponseBody(response), this.getCharset(response));
        case 2:
            throw new HttpServerErrorException(statusCode, response.getStatusText(), response.getHeaders(), this.getResponseBody(response), this.getCharset(response));
        default:
            throw new RestClientException("Unknown status code [" + statusCode + "]");
        }
    }

可见,4xx的状态吗会抛出HttpClientErrorException;5xx的状态码会抛出HttpServerErrorException。这也就是我们一开始遇到的问题的原因所在了。而在handleError中,执行了response.getBody(),这就导致我们后续获取不到响应体了,如果要获取的话,需要进行自定义相关处理。

解决办法

如果RestTemplate的应用场景比较统一,可以自定义ResponseErorHandler(派生自DefaultResponseErrorHandler)来接管错误处理,进行自己想要的处理。

而我们的网关中,对于页面跳转类的请求和Rest API类的请求,处理办法显然是不一样的。所以最终处理是catch异常,然后进行重定向的处理操作。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring学习笔记之RestTemplate使用小结

    前言 作为一个Java后端,需要通过HTTP请求其他的网络资源可以说是一个比较常见的case了:一般怎么做呢? 可能大部分的小伙伴直接捞起Apache的HttpClient开始做,或者用其他的一些知名的开源库如OkHttp, 当然原生的HttpURLConnection也是没问题的 本篇博文则主要关注点放在Sprig的生态下,利用RestTemplate来发起Http请求的使用姿势 I. RestTempalate 基本使用 0. 目标 在介绍如何使用RestTemplate之前,我们先抛出一些

  • Spring RestTemplate具体使用详解

    1.什么是REST? REST(RepresentationalState Transfer)是Roy Fielding 提出的一个描述互联系统架构风格的名词.REST定义了一组体系架构原则,您可以根据这些原则设计以系统资源为中心的Web 服务,包括使用不同语言编写的客户端如何通过 HTTP处理和传输资源状态. 为什么称为 REST?Web本质上由各种各样的资源组成,资源由URI 唯一标识.浏览器(或者任何其它类似于浏览器的应用程序)将展示出该资源的一种表现方式,或者一种表现状态.如果用户在该页

  • Spring Boot使用RestTemplate消费REST服务的几个问题记录

    我们可以通过Spring Boot快速开发REST接口,同时也可能需要在实现接口的过程中,通过Spring Boot调用内外部REST接口完成业务逻辑. 在Spring Boot中,调用REST Api常见的一般主要有两种方式,通过自带的RestTemplate或者自己开发http客户端工具实现服务调用. RestTemplate基本功能非常强大,不过某些特殊场景,我们可能还是更习惯用自己封装的工具类,比如上传文件至分布式文件系统.处理带证书的https请求等. 本文以RestTemplate来

  • 基于RestTemplate的使用方法(详解)

    1.postForObject :传入一个业务对象,返回是一个String 调用方: BaseUser baseUser=new BaseUser(); baseUser.setUserid(userid); baseUser.setPass(pass); String postForObject = restTemplate.postForObject(this.getURL()+"/user/login", baseUser, String.class); return postF

  • 解决使用RestTemplate时报错RestClientException的问题

    目录 使用RestTemplate时报错RestClientException 这是自己封装的一个发送请求的方法 这是自定义的一个http信息Converter RestTemplate的错误处理 问题描述 ErrorHandler 解决办法 使用RestTemplate时报错RestClientException 这是自己封装的一个发送请求的方法 public Map<String, Object> sendRequest(Map<String, Object> body,Str

  • 解决eclipse启动时报错Failed to create the Java Virtural Machine.问题的方法

    最近在学习android的开发搭建环境时,在网上下载了一个最新的eclpse,在启动的时候报错:Failed to create the Java Virtural Machine. 翻译一下,其意思就是说打开eclipse的失败原因是创建java虚拟机失败! 一般在启动的时候,出错,基本上除了虚拟机出错,就是jdk配置出错. 问题截图: 看原来的配置文件eclipse.ini的内容: 看最后一行"-XX:MaxPermSize=512M"改为256即可. 在看看找不到jdk启动报错的

  • 解决Pytorch dataloader时报错每个tensor维度不一样的问题

    使用pytorch的dataloader报错: RuntimeError: stack expects each tensor to be equal size, but got [2] at entry 0 and [1] at entry 1 1. 问题描述 报错定位:位于定义dataset的代码中 def __getitem__(self, index): ... return y #此处报错 报错内容 File "D:\python\lib\site-packages\torch\uti

  • 解决 Spring RestTemplate post传递参数时报错问题

    今天跟同事接口联调,使用RestTemplate请求服务端的post接口(使用python开发).诡异的是,post请求,返回500 Internal Server Error,而使用get请求,返回正常.代码如下: HashMap<String, Object> hashMap = Maps.newHashMap(); hashMap.put("data", JSONObject.toJSONString(params)); url = "http://mydo

  • innodb_index_stats导入备份数据时报错表主键冲突的解决方法

    故障描述 percona5.6,mysqldump全备份,导入备份数据时报错Duplicate entry 'hoc_log99-item_log_27-PRIMARY-n_diff_pfx01' for key 'PRIMARY' 故障原因 查看了下这个主键应该是MySQL系统库下的系统表innodb_index_stats mysql> show create table innodb_index_stats\G *************************** 1. row ****

  • Oracle环境通过SQL*PLUS本地登录时报错的解决过程

    一. 问题描述 今天在新机子(WINDOWS)上搭建Oracle环境,完了之后通过SQL*PLUS本地登录时报错: ora-01031 insufficient privileges ---权限不足 二. 解决过程 错误排除 1. 当时首先想到的是oracle不允许用sqlplus工具登录,但随即想法打消 sqlplus sys/admin as sysdba –成功登录! sqlplus sys/admin as sysdba --成功登录! 2. 带详细信息指定本机IP和实例登录 sqlpl

  • Python 使用os.remove删除文件夹时报错的解决方法

    os.remove不能用来删除文件夹,否则拒绝访问. # -*- coding:utf-8 -*-import osif __name__ == "__main__": os.remove('D:\\test') 运行结果: 删除空目录: # -*- coding:utf-8 -*-import osif __name__ == "__main__": os.rmdir('D:\\test') 如果目录不为空会报错,如下: 删除目录(不论目录是否为空): # -*-

  • NodeJS连接MongoDB数据库时报错的快速解决方法

    今天第一次尝试连接MongoDB数据库,具体步骤也很简单. 首先,通过NodeJS运行环境安装MongoDB包,进入要安装的目录,执行语句 npm install mongodb安装成功后,通过如下语句测试与数据库建立连接几关闭数据库 var mongo = require('mongodb'); var host = "localhost"; var port = mongo.Connection.DEFAULT_PORT; //创建MongoDB数据库所在服务器的Server对象

  • 解决vue2.0动态绑定图片src属性值初始化时报错的问题

    在vue2.0中,经常会使用类似这样的语法 v-bind:src = " imgUrl "(缩写 :src = " imgUrl "),看一个案例 <template> <div> <img :src="imgUrl"> </div> </template> <script> export default { data(){ return { captcha_id: &quo

  • MySQL存储表情时报错:java.sql.SQLException: Incorrect string value:‘\xF0\x9F\x92\xA9\x0D\x0A...’的解决方法

    前言 本文主要介绍了关于MySQL存储表情报错:java.sql.SQLException: Incorrect string value: '\xF0\x9F\x92\xA9\x0D\x0A...'的相关解决方法,分享出供大家参考学习,下面话不多说了,来一起看看详细的介绍: 本项目是基于Spring MVC + MySQL + Druid DataSource.在往数据库插入表情Emoji字符时报错: java.sql.SQLException: Incorrect string value:

随机推荐