[Problema de perda de cabeçalho de solicitação fingida] nenhum HttpMessageConverter adequado encontrado para o tipo de resposta

Assistente de economia de fluxo

O motivo da falha do HttpMessageConverter é: um interceptor é usado no projeto para interceptar a solicitação, e algumas interfaces precisam estar logadas para acessar, caso contrário, uma resposta no formato texto/html é retornada, o que faz com que o serviço remoto falhe ao analisar a resposta.
O motivo da falha de login é: quando Feign inicia uma chamada remota, ele irá regenerar uma nova solicitação. O problema é que ele não carregará o cookie da solicitação original, resultando em falha ao chamar a interface remota que precisa fazer login . A solução é configurar um interceptador Feign e trazer o cookie da requisição original no momento do envio da requisição.
O conteúdo principal deste artigo é uma série de pontos de conhecimento sobre este assunto, incluindo, mas não se limitando a:

  • tipo de conteúdo http
  • Depuração conjunta de microsserviços
  • Ver registros do Feign
  • interceptador de login
  • Fegin perdeu problema de cabeça

Análise e Posicionamento de Problemas

Hoje, ao depurar conjuntamente dois microsserviços, descobri que a interface remota sempre retornava o seguinte erro:

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]

Significa que não há HttpMessageConverter que possa converter [text/html;charset=UTF-8] em [class top.dumbzarro.greensource.common.utils.R].
Entre eles, R é um objeto de retorno comum definido no projeto, e todas as interfaces retornam esse objeto.

A interface remota é servida em software, os detalhes são os seguintes:

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

A interface chamada está no serviço membro, os detalhes são os seguintes:

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

O que é confuso é que antes da depuração conjunta desses dois serviços, o serviço de autenticação e o serviço de membro, o serviço de autenticação e o serviço de terceiros foram ajustados, e não há problema com a chamada remota Feign entre os dois serviços.

A solução online para nenhum HttpMessageConverter adequado é adicionar um conversor personalizado e assim por diante. Mas sinto vagamente que este não é um problema de conversão de tipo, caso contrário o serviço anterior não seria capaz de funcionar sem configuração adicional.

Tipo de conteúdo HTTP

Content-type é um campo no protocolo HTTP. O cabeçalho Content-Type informa ao cliente o tipo de conteúdo real retornado.
Os mais comuns são:

  • text/html : Formato HTML. Quando o navegador obtiver este tipo de arquivo, ele chamará automaticamente o analisador HTML para renderizar o arquivo.
  • text/plain : Defina o arquivo como texto simples e o navegador não o processará quando obtiver esse arquivo.
  • application/json : formato de dados JSON, que não será processado pelo navegador.

TODO Tipo de conteúdo springmvc tipo de conteúdo padrão do fegin

Na minha impressão, todas as interfaces retornam dados json, e o tipo de conteúdo é application/json. Como é que text/html aparece de repente? Então usei a pesquisa global para verificar.
Insira a descrição da imagem aqui
De repente, ocorreu-me que um interceptador foi adicionado a alguns serviços que exigem login para determinar se o usuário está logado. Quando for determinado que o usuário não está logado, uma resposta de texto/html será retornada. O código detalhado é o seguinte.

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

No momento da solicitação de Feign, foi julgado que ele não estava logado, então os dados no formato "text/html" foram retornados, e usamos R para aceitá-los na interface remota, então naturalmente não puderam ser analisados ​​com sucesso e um erro apareceria.
Normalmente, o que deve ser retornado aqui é um objeto de aplicação. Como este projeto é modificado com base no Guli Mall, os front-ends e back-ends do Guli Mall não são separados, e o projeto subsequente utiliza uma estrutura que separa os front-ends e back-ends , então aqui está este erro que pode ser resolvido modificando o valor de retorno.
Você pode consultar as seguintes modificações 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 {
    
    

    }
}

usuário não logado

Embora nenhuma exceção de conversão seja informada, "o usuário não está logado" será retornado.
Insira a descrição da imagem aqui
O que pode ser garantido é que já loguei no swagger, e trouxe um cookie ao solicitar, mas depois do fegin, mostra que não loguei, mas apenas a interface do serviço ware reporta um erro, e nem auth nem terceiros reportarão um erro.

Depuração conjunta de microsserviços

Como não há problema ao testar apenas o serviço de membro, quero ver a diferença entre solicitar diretamente o serviço de membro e solicitar o membro do servidor ware, então pretendo interromper ambos os serviços. Observe que se você tiver várias instâncias do mesmo serviço registradas no nacos, você deve adicionar o parâmetro url a @FeignClient para especificar o serviço local, caso contrário a solicitação poderá ser enviada para outras máquinas, resultando em nenhuma maneira de depurar para o atual em a máquina. Claro, se houver apenas uma instância, você não precisará adicioná-la. Os exemplos são os seguintes:

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

Neste momento, inicie o serviço, inicie a depuração e descubra que o programa não passa pela chamada da interface, mas é considerado como não logado no interceptador de login do membro e retorna diretamente ao serviço de ware.
Verifique a solicitação e descubra que não há sessão no momento e o login não foi bem-sucedido.
Insira a descrição da imagem aqui

abrir log fegin

Configuramos um FeginConfig para visualizar a resposta da solicitação do fegin

@Configuration
public class FeignConfig {
    
    

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

Configure o log de impressão em application.yml

logging:
  level:
    feign.Logger: debug

log4j define 8 níveis de log, a prioridade de alta para baixa é: OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL. A prioridade padrão do log4j é ERRO. Log4j recomenda usar apenas os quatro níveis de ERROR, WARN, INFO e DEBUG (prioridade de alta para baixa). Se o nível de registro estiver definido para um determinado nível, os registros com prioridade mais alta que esse nível poderão ser impressos.

  1. ALL : O nível mais baixo, usado para ativar todos os registros.
  2. TRACE : Nível de log muito baixo, geralmente não usado.
  3. DEBUG : Ressalta que eventos de informações refinadas são muito úteis para depuração de aplicativos e são usados ​​​​principalmente para imprimir algumas informações em execução durante o processo de desenvolvimento.
  4. INFO : As mensagens destacam o progresso do aplicativo em um nível granular. Imprima algumas informações de seu interesse ou importantes.Isso pode ser usado para gerar algumas informações importantes sobre a execução do programa no ambiente de produção, mas não pode ser abusado para evitar a impressão de muitos logs.
  5. AVISO : Indica que uma possível situação de erro ocorrerá. Algumas informações não são informações de erro, mas também fornecem algumas dicas ao programador.
  6. ERRO : Imprime informações de erros e exceções, indicando que embora ocorra um evento de erro, ele ainda não afeta a operação contínua do sistema.
  7. FATAL : Indica que cada evento de erro grave fará com que o aplicativo seja encerrado. Erros graves, este nível pode interromper diretamente o programa.
  8. OFF : O nível mais alto, usado para desligar todos os registros.

Pode-se observar que nossa solicitação não configura um cookie,
Insira a descrição da imagem aqui
que é a causa raiz da falha da solicitação fegin, portanto configuramos o fegin para enviar a solicitação com um cookie no software.

Fingir problema de cookie perdido

Como o fegin enviará uma nova solicitação toda vez que solicitar, ao invés de trazer o cookie da nossa solicitação anterior, teremos que configurá-lo manualmente neste momento. Continue adicionando a configuração onde a depuração foi configurada antes, injete um interceptador no contêiner Spring e defina um cookie antes da solicitação 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;
    }
}

Verifique o log e descubra que a solicitação trouxe um cookie com sucesso.
Insira a descrição da imagem aqui
Logicamente falando, as duas solicitações deveriam ser um cookie e uma sessão, mas aqui descobrimos que as duas sessões são inconsistentes.
Provavelmente o tempo de login expirou. Basta fazer login novamente e tudo ficará bem.
Insira a descrição da imagem aqui
A mensagem foi retornada com sucesso.

Por que os microsserviços anteriores não tiveram problemas?

Eu ajustei o servidor de autenticação e terceiros e o servidor de autenticação e membro antes, mas nenhum problema semelhante ocorreu.
A razão para o primeiro é que terceiros não possuem um interceptador de login, portanto, quando o servidor de autenticação chama terceiros, ele não retornará conteúdo de texto/html, para que possa ser analisado normalmente. Como não existe interceptador de login, a presença ou ausência de cookies não afeta as chamadas remotas.
A razão para este último é que embora o membro tenha um interceptador de login, porque a interface solicitada pelo servidor de autenticação é liberada (veja o código acima para detalhes), o valor de retorno de text/html não será retornado, então pode ser analisado normalmente. Ao mesmo tempo, como a interface não requer cookies de autenticação de login, isso não afetará a perda do cookie do cabeçalho da solicitação fegin.

Acho que você gosta

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