省流助手

HttpMessageConverter 失败的原因是:在项目中使用了一个拦截器拦截请求,部分接口需要登陆才能访问,否则返回一个text/html格式的响应,导致远程服务解析响应失败。
登陆失败的原因是:Feign发起远程调用的时候会重新生成一个新的请求,带来的问题就是不会携带原来请求的cookie,导致调用需要登陆的远程接口时会失败。解决方法是配置一个Feign的拦截器,在发送请求的时候带上原请求的cookie。
本文主要内容是围绕这个问题展开的一系列知识点,包括但不限于:

  • http的content type
  • 微服务联调debug
  • 查看Feign日志
  • 登陆拦截器
  • Fegin丢头问题

问题分析定位

今天在联调两个微服务的时候发现远程接口总是返回以下报错:

Could not extract response: no suitable HttpMessageConverter found for response type [class top.dumbzarro.greensource.common.utils.R] and content type [text/html;charset=UTF-8]

意思是没有一个HttpMessageConverter 可以将 [text/html;charset=UTF-8]转化为[class top.dumbzarro.greensource.common.utils.R] 。
其中,R是项目中定义的一个通用的返回对象,所有接口都返回这个对象。

远程接口在ware服务,详细如下:

@FeignClient("greensource-member")
public interface MemberFeignService {@GetMapping("/memberreceiveaddress/info/{id}")R info(@PathVariable("id") Long id);
}

被调用接口在member服务,详细如下:

@RestController
@RequestMapping("memberreceiveaddress")
public class MemberReceiveAddressController {@Autowiredprivate MemberReceiveAddressService memberReceiveAddressService;@GetMapping("/info/{id}")//@RequiresPermissions("member:memberreceiveaddress:info")public R info(@PathVariable("id") Long id){MemberReceiveAddressEntity memberReceiveAddress = memberReceiveAddressService.getById(id);return R.ok().setData(memberReceiveAddress);}
}

比较疑惑的是,在联调这两个服务之前,已经调通了auth服务和member服务、auth服务和third-party服务,两个服务之间的Feign远程调用就没有问题。

网上对于no suitable HttpMessageConverter的解决方案就是添加一个自定义的转换器等等。但是隐约感觉这不是类型转换的问题,不然在没有额外配置的情况下,之前的服务不可能跑的通。

HTTP Content-type

Content-type是HTTP协议中的一个字段,Content-Type 标头告诉客户端实际返回的内容的内容类型。
常见的有:

  • text/html: HTML格式,浏览器在获取到这种文件时会自动调用html的解析器对文件进行渲染的处理。
  • text/plain:将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理。
  • application/json: JSON数据格式,浏览器不会对其进行处理。

TODO Content-type springmvc fegin的默认content-type

印象里接口都是返回json数据,content-type是application/json,怎么会突然冒出个text/html呢。于是使用全局搜索查了一下。

突然想起在部分需要登陆的业务中都增加了一个拦截器,用于判断用户是否登陆,在判断用户没有登陆的时候会返回一个text/html的响应。详细代码如下。

@Component
public class LoginUserInterceptor implements HandlerInterceptor {public static ThreadLocal<MemberResponseVo> loginUser = new ThreadLocal<>();@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String uri = request.getRequestURI();if(uri.equals("/error")){response.setContentType("text/html;charset=UTF-8");PrintWriter out = response.getWriter();out.println("<script>alert('uri为 /error, 可能原因为:1.请求方法错误 2.参数格式解析错误');</script>");return false;}boolean match = new AntPathMatcher().match("/member/**", uri);if (match) { // member接口(登陆,注册)可以不用登陆就使用,否则需要登陆return true;}HttpSession session = request.getSession();//获取登录的用户信息MemberResponseVo attribute = (MemberResponseVo) session.getAttribute(LOGIN_USER);if (attribute != null) {//把登录后用户的信息放在ThreadLocal里面进行保存loginUser.set(attribute);return true;} else {//未登录,返回登录页面response.setContentType("text/html;charset=UTF-8");PrintWriter out = response.getWriter();out.println("<script>alert('请先进行登录,再进行后续操作!');location.href='http://auth.dumbzarro.top/login.html'</script>");return false;}}

在feign的请求的时候,被判定为没有登陆,所以返回了这个“text/html”格式的数据,而在远程接口处我们使用的是R进行接受,自然就无法成功解析然后就会出现报错。
正常来说这里应该返回的是一个application对象,由于这个项目是基于谷粒商城修改的,谷粒商城是前后端不分离了,而后续这个项目使用的是前后端分离的结构,所以这里将这个返回值做一个修改,即可解决这个报错了。
可参考如下代码修改

@Component
public class LoginUserInterceptor implements HandlerInterceptor {public static ThreadLocal<MemberResponseVo> loginUser = new ThreadLocal<>();@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String uri = request.getRequestURI();if(uri.equals("/error")){response.setContentType("application/json; charset=utf-8");PrintWriter out = response.getWriter();out.println(JSONObject.toJSONString(R.error().put("error","uri为 /error, 可能原因为:1.请求方法错误 2.参数格式解析错误"),SerializerFeature.WriteMapNullValue,SerializerFeature.WriteDateUseDateFormat));return false;}boolean match = new AntPathMatcher().match("/member/**", uri);if (match) { // member接口(登陆,注册)可以不用登陆就使用,否则需要登陆return true;}HttpSession session = request.getSession();//获取登录的用户信息MemberResponseVo attribute = (MemberResponseVo) session.getAttribute(LOGIN_USER);if (attribute != null) {//把登录后用户的信息放在ThreadLocal里面进行保存loginUser.set(attribute);return true;} else {//未登录response.setContentType("application/json; charset=utf-8");PrintWriter out = response.getWriter();out.println(JSONObject.toJSONString(R.error().put("error","用户未登录"),SerializerFeature.WriteMapNullValue,SerializerFeature.WriteDateUseDateFormat));return false;}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

用户未登录

虽然不会报转换异常,但是还会返回“用户未登录”。

可以确保的是我在swagger已经登陆了,请求的时候带上了cookie了的,但是经过fegin之后就显示没有登陆,而仅仅是ware服务的这个接口报错,而auth和third-party都不会报错。

微服务联调

因为单独去测试member服务的时候都没有问题,于是就想看直接请求member服务和从ware服务器请求member的请求有什么不同,于是打算在两个服务都打断点看看。注意,如果你同一个服务有多个实例注册在nacos上,那么要在@FeignClient加入url的参数,去指定到本地的服务,否则请求可能会打到其他的机器上,导致没办法debug到当前的机器上。当然,如果只有一个实例,其实不用加也可以。示例如下:

//@FeignClient(value="greensource-member")
@FeignClient(value="greensource-member",url="localhost:7000")// 指定某台机器
public interface MemberFeignService {@GetMapping("/memberreceiveaddress/info/{id}")R info(@PathVariable("id") Long id);
}

这时候启动服务,开始debug,发现程序不会经过接口调用处经过,而是在member的登陆拦截器处被判定为没有登陆,直接返回到ware服务。
查看请求,发现此时没有session,没有登陆成功。

打开fegin 日志

我们配置一个FeginConfig,查看fegin的请求响应情况

@Configuration
public class FeignConfig {@Beanpublic feign.Logger logger() {return new Slf4jLogger();}@Beanpublic Logger.Level level() {return Logger.Level.FULL;}
}

在application.yml配置打印日志

logging:level:feign.Logger: debug

log4j定义了8个级别的log,优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。log4j默认的优先级为ERROR。Log4j建议只使用ERROR、WARN、INFO、DEBUG这四个级别(优先级从高到低)。如果将log level设置在某一个级别上,那么比此级别优先级高的log都能打印出来。

  1. ALL:最低等级的,用于打开所有日志记录。
  2. TRACE:很低的日志级别,一般不会使用。
  3. DEBUG:指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。
  4. INFO:消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
  5. WARN:表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。
  6. ERROR:打印错误和异常信息,指出虽然发生错误事件,但仍然不影响系统的继续运行。
  7. FATAL:指出每个严重的错误事件将会导致应用程序的退出。重大错误,这种级别可以直接停止程序了。
  8. OFF:最高等级的,用于关闭所有日志记录。

可以看到我们的请求是没有设置cookie的

这就是fegin请求失败的根本原因,所以我们在ware配置fegin发送请求是带上cookie。

Feign丢失cookie问题

由于fegin每次请求都会自己发一个新的请求,而不会带上我们之前的请求的cookie,这时候我们就要手动配置一下。在之前设置debug的地方继续添加配置,注入一个拦截器到spring容器中,在Feign请求之前我们设置一下cookie

@Configuration
public class FeignConfig {@Beanpublic feign.Logger logger() {return new Slf4jLogger();}@Beanpublic Logger.Level level() {return Logger.Level.FULL;}@Bean("requestInterceptor")public RequestInterceptor requestInterceptor() {RequestInterceptor requestInterceptor = new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {//1、使用RequestContextHolder拿到刚进来的请求数据ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (requestAttributes != null) {//老请求HttpServletRequest request = requestAttributes.getRequest();//2、同步请求头的数据(主要是cookie)//把老请求的cookie值放到新请求上String cookie = request.getHeader("Cookie");template.header("Cookie", cookie);}}};return requestInterceptor;}
}

查看日志,发现请求已经成功带上了cookie

按道理来说两个请求应该是一个cookie和session的,但是这里却发现两个session不一致。
大概是登陆超时了,过期了,从新登陆一下就好了。

成功返回了消息。

为什么之前的微服务不会出现问题?

之前调通了auth-server和third-party 以及 auth-server 和 member,都没有出现类似的问题。
前者的原因是third-party没有登陆拦截器,因此auth-server 调用third-party的时候不会返回text/html的内容,因此能正常解析。既然没有登陆拦截器,那么有无cookie也不影响远程调用。
后者的原因是虽然member有登陆拦截器,但是因为auth-server请求的接口是放行的(详细见上面的代码),所以也不会返回text/html的返回值,因此也能正常解析。同时有因为接口不需要登陆认证的cookie,fegin请求头的cookie丢失了也不影响。

【Feign请求头丢失问题】no suitable HttpMessageConverter found for response type相关推荐

  1. restTemplate http请求报错:no suitable HttpMessageConverter found for response type and content type

    报错信息: org.springframework.web.client.UnknownContentTypeException: Could not extract response: no sui ...

  2. RestTemplate请求Could not extract response: no suitable HttpMessageConverter found for response type..

    使用 Spring Boot 写项目,需要用到微信接口获取用户信息. 在 Jessey 和 Spring RestTemplate 两个 Rest 客户端中,想到尽量不引入更多的东西,然后就选择了 S ...

  3. 小小涉及OpenFeign原理:Could not extract response: no suitable HttpMessageConverter found for response type

    一.问题解释(想看总结的去最下面) org.springframework.web.client.UnknownContentTypeException: Could not extract resp ...

  4. Could not extract response: no suitable HttpMessageConverter found for response type [class java.lan

    解决 Could not extract response: no suitable HttpMessageConverter found for response type [class java. ...

  5. Could not extract response: no suitable HttpMessageConverter found for response type [class com.exam

    报错信息:Could not extract response: no suitable HttpMessageConverter found for response type [class com ...

  6. Could not extract response: no suitable HttpMessageConverter found for response type ***

    用RestTemplate.getForObject()获取URL的JSON时,可能会遇到如题所示的报错信息. 我的问题在于:在非MVC的Project中使用RestTemplate. 简单的说,me ...

  7. openFeig远程调用报错Could not extract response: no suitable HttpMessageConverter found for response type

    解决方案:在调用服务和被调用的服务上都加上一个配置类 @Configuration public class jsonConfig {@Beanpublic HttpMessageConverters ...

  8. Could not extract response: no suitable HttpMessageConverter found for content type [text/html]

    目录 报错信息 源码分析 解决方法 修改 mappingJackson2HttpMessageConverter 配置 继承 mappingJackson2HttpMessageConverter 实 ...

  9. 使用RestTemplate:报错Could not extract response: no suitable HttpMessageConverter found for response typ

    项目中需要调用微信接口获取access_token等一系列和微信接口相关的操作,我使用了Spring自带的RestTemplate类来发送Get或Post请求,直接在Spring配置文件中依赖注入 & ...

  10. no suitable HttpMessageConverter found for request type [java.lang.Integer]

    今天在使用Spring Template的时候遇到了这个异常: no suitable HttpMessageConverter found for request type [java.lang.I ...

最新文章

  1. 可视化生信分析利器 Galaxy 之 Docker 部署
  2. 这几本高分算法书助你稳步提升
  3. osg linux 环境配置,Linux环境下jdk1.8的下载与安装
  4. ASP.NET MVC案例教程(基于ASP.NET MVC beta)——第二篇:第一个页面
  5. 10分钟白嫖我常用的20个在线工具类网站清单。
  6. [BZOJ 5072][Lydsy1710月赛]小A的树
  7. php提交注册表单,php用户注册表单验证
  8. matlab 三维高程根据图片颜色给对应点赋予颜色
  9. wordpress数据库表详解
  10. 判断按键值_Pygame(九)按键事件(2)
  11. 生活在信息世界,人人都该懂得大数据概念
  12. 计算机数制和运算的一点总结.
  13. 微服务统一认证与授权的 Go 语言实现
  14. java gson使用_Java 如何使用Gson解析JSON数组
  15. hd Aruba wifi / honor
  16. java 汉字转拼音缩写_用JAVA实现汉字转拼音缩写
  17. 惠普服务器装centos 系统安装,hp 服务器安装linux系统安装
  18. eemd的r语言序列_EEMD详解
  19. EVE-NG模拟器教程(一)——安装包下载
  20. bootstarp怎么使盒子到最右边_8+ | 从恐龙特急克塞号到小猪佩奇,怎么都有它

热门文章

  1. FLASH PLAYER 谷歌浏览器浏览网站无法正常显示的问题
  2. ubuntu使用命令设置静态IP地址
  3. AlphaGo Zero详解
  4. 小程序源码:朋友圈集赞万能截图生成器-多玩法安装简单
  5. 【Java后端】技术文档模板
  6. ucfirst() 函数
  7. PS绘制的路径不见了
  8. git官网下载慢的问题解决方法
  9. 关于MUI一个很实用的前端框架
  10. 拼音四线三格图片_一年级语文必考拼音拼读+书写规则,孩子开学就会用到!...