[Problema de pérdida de encabezado de solicitud fingido] no se encontró un HttpMessageConverter adecuado para el tipo de respuesta

Asistente de ahorro de flujo

El motivo del fallo de HttpMessageConverter es que se utiliza un interceptor en el proyecto para interceptar la solicitud. Algunas interfaces requieren iniciar sesión para acceder; de lo contrario, se devolverá una respuesta en formato texto/html, lo que provocará que el servicio remoto no pueda analizar la respuesta. .
El motivo del error de inicio de sesión es que cuando Feign inicia una llamada remota, regenerará una nueva solicitud. El problema es que la cookie de la solicitud original no se transportará, lo que provocará que falle la llamada a la interfaz remota que requiere iniciar sesión. La solución es configurar un interceptor Feign y traer la cookie de la solicitud original al enviar la solicitud.
El contenido principal de este artículo es una serie de puntos de conocimiento sobre este tema, que incluyen, entre otros:

  • tipo de contenido http
  • Depuración conjunta de microservicios
  • Ver registro de fingir
  • Bloqueador de inicio de sesión
  • Fegin pierde su problema de cabeza

Análisis y posicionamiento de problemas.

Hoy, al depurar dos microservicios conjuntamente, descubrí que la interfaz remota siempre devolvía el siguiente 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]

Esto significa que no existe ningún HttpMessageConverter que pueda convertir [text/html;charset=UTF-8] a [class top.dumbzarro.greensource.common.utils.R].
Entre ellos, R es un objeto de retorno común definido en el proyecto y todas las interfaces devuelven este objeto.

La interfaz remota es atendida por ware, los detalles son los siguientes:

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

La interfaz llamada está en el servicio para miembros, los detalles son los siguientes:

@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);
    }
}

Lo que es más confuso es que antes de depurar conjuntamente estos dos servicios, se ajustaron el servicio de autenticación y el servicio para miembros, el servicio de autenticación y el servicio de terceros, y no hay ningún problema con las llamadas remotas de Feign entre los dos servicios.

La solución a la falta de un HttpMessageConverter adecuado en Internet es agregar un convertidor personalizado, etc. Pero siento vagamente que esto no es un problema de conversión de tipos; de lo contrario, el servicio anterior no podría ejecutarse sin una configuración adicional.

Tipo de contenido HTTP

El tipo de contenido es un campo en el protocolo HTTP. El encabezado Content-Type le dice al cliente el tipo de contenido real devuelto.
Los más comunes son:

  • text/html : Formato HTML. Cuando el navegador obtiene este tipo de archivo, llamará automáticamente al analizador HTML para representar el archivo.
  • text/plain : establece el archivo en texto sin formato. El navegador no procesará este archivo cuando lo obtenga.
  • application/json : formato de datos JSON, que no será procesado por el navegador.

TODO Tipo de contenido tipo de contenido predeterminado de springmvc fegin

En mi impresión, todas las interfaces devuelven datos json y el tipo de contenido es aplicación/json.¿Cómo es que de repente aparece texto/html? Así que utilicé la búsqueda global para comprobarlo.
Insertar descripción de la imagen aquí
De repente recordé que se ha agregado un interceptor a algunas empresas que requieren iniciar sesión para determinar si el usuario ha iniciado sesión. Cuando se determina que el usuario no ha iniciado sesión, se devolverá una respuesta de texto/html. El código detallado es el siguiente.

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

Durante la solicitud de fingir, se determinó que los datos no estaban conectados, por lo que se devolvieron los datos en el formato "texto/html". Sin embargo, en la interfaz remota, usamos R para aceptarlos. Naturalmente, no serían analizados. con éxito y se informará un error.
Normalmente, lo que se debe devolver aquí es un objeto de aplicación. Dado que este proyecto se modifica en función de Guli Mall, los extremos frontal y posterior de Guli Mall no están separados, y el proyecto posterior utiliza una estructura que separa los extremos frontal y posterior, por lo que aquí lo haremos. Este error se puede solucionar modificando el valor de retorno.
Puede consultar las siguientes modificaciones de código

@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 {
    
    

    }
}

El usuario no ha iniciado sesión

Aunque no se informará ninguna excepción de conversión, se devolverá "El usuario no ha iniciado sesión".
Insertar descripción de la imagen aquí
Lo que se puede asegurar es que inicié sesión en swagger y traje la cookie al realizar la solicitud, sin embargo, después de fegin, muestra que no inicié sesión, pero solo la interfaz del servicio de software informa un error y ni la autenticación ni terceros informan de un error.

Depuración conjunta de microservicios

Debido a que no hubo ningún problema al probar el servicio para miembros solo, quería ver la diferencia entre solicitar el servicio para miembros directamente y solicitar el miembro desde el servidor ware, por lo que planeé interrumpir ambos servicios para verlo. Tenga en cuenta que si tiene varias instancias del mismo servicio registradas en nacos, debe agregar parámetros de URL a @FeignClient para especificar el servicio local. De lo contrario, la solicitud puede enviarse a otras máquinas, lo que imposibilita la depuración de la actual. la máquina. Por supuesto, si solo hay una instancia, no es necesario agregarla. Los ejemplos son los siguientes:

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

En este momento, inicie el servicio y comience a depurar. Se descubre que el programa no pasa por el punto de llamada de la interfaz, sino que se considera que no ha iniciado sesión en el interceptor de inicio de sesión del miembro y regresa directamente al servicio de software.
Verifique la solicitud y descubra que no hay sesión en este momento y que el inicio de sesión no se realizó correctamente.
Insertar descripción de la imagen aquí

Abrir registro de inicio

Configuramos un FeginConfig para ver la respuesta a la solicitud de fegin

@Configuration
public class FeignConfig {
    
    

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

Configurar el registro de impresión en application.yml

logging:
  level:
    feign.Logger: debug

log4j define 8 niveles de registro, las prioridades de mayor a menor son: APAGADO, FATAL, ERROR, ADVERTENCIA, INFORMACIÓN, DEBUG, TRACE, TODOS. La prioridad predeterminada de log4j es ERROR. Log4j recomienda utilizar solo los cuatro niveles de ERROR, WARN, INFO y DEBUG (prioridad de mayor a menor). Si el nivel de registro se establece en un nivel determinado, se pueden imprimir registros con una prioridad mayor que este nivel.

  1. TODOS : El nivel más bajo, utilizado para activar todos los registros.
  2. TRACE : Nivel de registro muy bajo, generalmente no se utiliza.
  3. DEBUG : Señala que los eventos de información detallada son muy útiles para depurar aplicaciones y se utilizan principalmente para imprimir información en ejecución durante el proceso de desarrollo.
  4. INFORMACIÓN : El mensaje resalta el proceso en ejecución de la aplicación en un nivel aproximado. Imprima alguna información interesante o importante que le interese. Esto se puede utilizar en un entorno de producción para generar información importante sobre la ejecución del programa, pero no se puede abusar de él para evitar imprimir demasiados registros.
  5. ADVERTENCIA : Indica que se producirá una posible situación de error. Parte de la información no es información de error, pero también proporciona algunos consejos al programador.
  6. ERROR : Imprime información de error y excepción, indicando que aunque ocurre un evento de error, todavía no afecta el funcionamiento continuo del sistema.
  7. FATAL : Indica que cada evento de error grave provocará el cierre de la aplicación. Errores importantes, este nivel puede detener directamente el programa.
  8. APAGADO : El nivel más alto, utilizado para desactivar todos los registros.

Puede ver que nuestra solicitud no establece una cookie,
Insertar descripción de la imagen aquí
esta es la razón fundamental por la que falla la solicitud de fegin, por lo que configuramos fegin in ware para enviar la solicitud con cookies.

Fingir problema de cookies perdidas

Dado que fegin enviará una nueva solicitud cada vez que realice una solicitud sin traer la cookie de nuestra solicitud anterior, debemos configurarla manualmente en este momento. Continúe agregando la configuración donde se configuró la depuración antes, inyecte un interceptor en el contenedor Spring y configure una cookie antes de la solicitud de Feign.

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

Al revisar el registro, se encontró que la solicitud había traído la cookie con éxito.
Insertar descripción de la imagen aquí
Lógicamente hablando, las dos solicitudes deberían ser una cookie y una sesión, pero aquí se encontró que las dos sesiones eran inconsistentes.
Probablemente el tiempo de inicio de sesión expiró y expiró. Simplemente inicie sesión nuevamente y todo estará bien.
Insertar descripción de la imagen aquí
El mensaje fue devuelto exitosamente.

¿Por qué los microservicios anteriores no tenían problemas?

Anteriormente ajusté el servidor de autenticación y el tercero, así como el servidor de autenticación y el miembro, y no ocurrieron problemas similares.
La razón de lo primero es que el tercero no tiene un interceptor de inicio de sesión, por lo que el servidor de autenticación no devolverá el contenido de texto/html cuando llame al tercero, por lo que se puede analizar normalmente. Dado que no existe un interceptor de inicio de sesión, la presencia o ausencia de cookies no afecta las llamadas remotas.
La razón de esto último es que, aunque el miembro tiene un interceptor de inicio de sesión, debido a que la interfaz solicitada por el servidor de autenticación está permitida (consulte el código anterior para obtener más detalles), el valor de retorno de texto/html no se devolverá, por lo que puede ser analizado normalmente. Al mismo tiempo, debido a que la interfaz no requiere cookies para la autenticación de inicio de sesión, no se verá afectada si se pierde la cookie en el encabezado de la solicitud de inicio.

Supongo que te gusta

Origin blog.csdn.net/weixin_45654405/article/details/126768380
Recomendado
Clasificación