[Feign request header loss problem] no suitable HttpMessageConverter found for response type

Stream Saver

The reason for the failure of HttpMessageConverter is that an interceptor is used in the project to intercept the request. Some interfaces require login to access, otherwise a response in text/html format will be returned, causing the remote service to fail to parse the response.
The reason for the login failure is that when Feign initiates a remote call, it will regenerate a new request. The problem is that the cookie of the original request will not be carried, causing the call to the remote interface that requires login to fail. The solution is to configure a Feign interceptor and bring the cookie of the original request when sending the request.
The main content of this article is a series of knowledge points around this issue, including but not limited to:

  • http的content type
  • Microservice joint debugging
  • View Feign log
  • Login blocker
  • Fegin loses its head problem

Problem analysis and positioning

Today, when jointly debugging two microservices, I found that the remote interface always returned the following error:

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]

This means that there is no HttpMessageConverter that can convert [text/html;charset=UTF-8] to [class top.dumbzarro.greensource.common.utils.R].
Among them, R is a common return object defined in the project, and all interfaces return this object.

The remote interface is served by ware, the details are as follows:

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

The called interface is in the member service, the details are as follows:

@RestController
@RequestMapping("memberreceiveaddress")
public class MemberReceiveAddressController {
    
    
	@Autowired
    private 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);
    }
}

What is more confusing is that before jointly debugging these two services, the auth service and member service, auth service and third-party service have been adjusted, and there is no problem with Feign remote calls between the two services.

The solution to no suitable HttpMessageConverter on the Internet is to add a custom converter and so on. But I vaguely feel that this is not a type conversion problem, otherwise the previous service would not be able to run without additional configuration.

HTTP Content-type

Content-type is a field in the HTTP protocol. The Content-Type header tells the client the content type of the actual content returned.
Common ones are:

  • text/html : HTML format. When the browser obtains this kind of file, it will automatically call the HTML parser to render the file.
  • text/plain : Set the file to plain text. The browser will not process this file when it obtains it.
  • application/json : JSON data format, which will not be processed by the browser.

TODO Content-type springmvc fegin's default content-type

In my impression, the interfaces all return json data, and the content-type is application/json, so how could text/html pop up suddenly. So I used global search to check it out.
Insert image description here
Suddenly I remembered that an interceptor has been added to some businesses that require login to determine whether the user is logged in. When it is determined that the user is not logged in, a text/html response will be returned. The detailed code is as follows.

@Component
public class LoginUserInterceptor implements HandlerInterceptor {
    
    

    public static ThreadLocal<MemberResponseVo> loginUser = new ThreadLocal<>();

    @Override
    public 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;
        }
    }

During feign's request, it was determined that the data was not logged in, so the data in the "text/html" format was returned. However, at the remote interface, we used R to accept it. Naturally, it would not be parsed successfully and an error would be reported.
Normally, what should be returned here is an application object. Since this project is modified based on Guli Mall, the front and back ends of Guli Mall are not separated, and the subsequent project uses a structure that separates the front and back ends, so here we will This error can be solved by modifying the return value.
You can refer to the following code modifications

@Component
public class LoginUserInterceptor implements HandlerInterceptor {
    
    

    public static ThreadLocal<MemberResponseVo> loginUser = new ThreadLocal<>();

    @Override
    public 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;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    

    }
}

User is not logged in

Although no conversion exception will be reported, "user is not logged in" will be returned.
Insert image description here
What can be ensured is that I have logged in to swagger and brought the cookie when making the request. However, after fegin, it shows that I have not logged in, but only the interface of the ware service reports an error, and neither auth nor third-party reports an error. .

Microservice joint debugging

Because there was no problem when testing the member service alone, I wanted to see the difference between directly requesting the member service and requesting the member from the ware server, so I planned to interrupt both services to see. Note that if you have multiple instances of the same service registered on nacos, you must add url parameters to @FeignClient to specify the local service. Otherwise, the request may be sent to other machines, making it impossible to debug the current one. on the machine. Of course, if there is only one instance, you don’t need to add it. Examples are as follows:

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

At this time, start the service and start debugging. It is found that the program does not pass through the interface call point. Instead, it is judged as not logged in at the member's login interceptor and returns directly to the ware service.
Check the request and find that there is no session at this time, and the login is not successful.
Insert image description here

Open fegin log

We configure a FeginConfig to view fegin's request response

@Configuration
public class FeignConfig {
    
    

    @Bean
    public feign.Logger logger() {
    
    
        return new Slf4jLogger();
    }
    @Bean
    public Logger.Level level() {
    
    
        return Logger.Level.FULL;
    }
}

Configure print log in application.yml

logging:
  level:
    feign.Logger: debug

log4j defines 8 levels of log, the priorities from high to low are: OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL. The default priority of log4j is ERROR. Log4j recommends using only the four levels of ERROR, WARN, INFO, and DEBUG (priority from high to low). If the log level is set to a certain level, logs with a higher priority than this level can be printed.

  1. ALL : The lowest level, used to turn on all logging.
  2. TRACE : Very low log level, generally not used.
  3. DEBUG : Points out that fine-grained information events are very helpful for debugging applications, and are mainly used to print some running information during the development process.
  4. INFO : The message highlights the running process of the application at a coarse-grained level. Print some interesting or important information that you are interested in. This can be used in a production environment to output some important information about the running of the program, but it cannot be abused to avoid printing too many logs.
  5. WARN : Indicates that a potential error situation will occur. Some information is not error information, but it also provides some tips to the programmer.
  6. ERROR : Prints error and exception information, indicating that although an error event occurs, it still does not affect the continued operation of the system.
  7. FATAL : Indicates that each serious error event will cause the application to exit. Major errors, this level can directly stop the program.
  8. OFF : The highest level, used to turn off all logging.

You can see that our request does not set a cookie.
Insert image description here
This is the fundamental reason why the fegin request fails, so we configure fegin in ware to send the request with cookies.

Feign lost cookie problem

Since fegin will send a new request every time it makes a request without bringing the cookie of our previous request, we need to configure it manually at this time. Continue to add configuration where debug was set up before, inject an interceptor into the spring container, and set a cookie before Feign request

@Configuration
public class FeignConfig {
    
    

    @Bean
    public feign.Logger logger() {
    
    
        return new Slf4jLogger();
    }
    @Bean
    public Logger.Level level() {
    
    
        return Logger.Level.FULL;
    }

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor() {
    
    

        RequestInterceptor requestInterceptor = new RequestInterceptor() {
    
    
            @Override
            public 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;
    }
}

Checking the log, it was found that the request had successfully brought the cookie.
Insert image description here
Logically speaking, the two requests should be one cookie and session, but here it was found that the two sessions were inconsistent.
Probably the login timed out and expired. Just log in again and it will be fine.
Insert image description here
The message was returned successfully.

Why did the previous microservices not have problems?

I have previously adjusted auth-server and third-party as well as auth-server and member, and no similar problems occurred.
The reason for the former is that third-party does not have a login interceptor, so auth-server will not return the text/html content when calling third-party, so it can be parsed normally. Since there is no login interceptor, the presence or absence of cookies does not affect remote calls.
The reason for the latter is that although member has a login interceptor, because the interface requested by the auth-server is allowed (see the code above for details), the return value of text/html will not be returned, so it can be parsed normally. At the same time, because the interface does not require cookies for login authentication, it will not be affected if the cookie in the fegin request header is lost.

Guess you like

Origin blog.csdn.net/weixin_45654405/article/details/126768380