[Feindre un problème de perte d'en-tête de requête] aucun HttpMessageConverter approprié n'a été trouvé pour le type de réponse

Assistant d'économie de débit

La raison de l'échec de HttpMessageConverter est qu'un intercepteur est utilisé dans le projet pour intercepter la demande. Certaines interfaces nécessitent une connexion pour y accéder, sinon une réponse au format texte/html sera renvoyée, ce qui empêchera le service distant d'analyser la réponse. .
La raison de l'échec de la connexion est que lorsque Feign lance un appel à distance, il régénérera une nouvelle demande. Le problème est que le cookie de la demande d'origine ne sera pas transporté, provoquant l'échec de l'appel à l'interface distante qui nécessite la connexion. La solution est de configurer un intercepteur Feign et d'apporter le cookie de la requête d'origine lors de l'envoi de la requête.
Le contenu principal de cet article est une série de points de connaissances autour de cette question, y compris, mais sans s'y limiter :

  • type de contenu http
  • Débogage conjoint des microservices
  • Afficher le journal de Feign
  • Bloqueur de connexion
  • Fegin perd son problème de tête

Analyse du problème et positionnement

Aujourd'hui, lors du débogage conjoint de deux microservices, j'ai constaté que l'interface distante renvoyait toujours l'erreur suivante :

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]

Cela signifie qu'il n'existe aucun HttpMessageConverter capable de convertir [text/html;charset=UTF-8] en [class top.dumbzarro.greensource.common.utils.R].
Parmi eux, R est un objet de retour commun défini dans le projet, et toutes les interfaces renvoient cet objet.

L'interface distante est servie sous forme de logiciel, les détails sont les suivants :

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

L'interface appelée est dans le service membre, les détails sont les suivants :

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

Ce qui est plus déroutant, c'est qu'avant de déboguer conjointement ces deux services, le service d'authentification et le service membre, le service d'authentification et le service tiers ont été ajustés, et il n'y a aucun problème avec les appels à distance Feign entre les deux services.

La solution à l'absence de HttpMessageConverter approprié sur Internet consiste à ajouter un convertisseur personnalisé, etc. Mais j'ai vaguement l'impression qu'il ne s'agit pas d'un problème de conversion de type, sinon le service précédent ne pourrait pas fonctionner sans configuration supplémentaire.

Type de contenu HTTP

Content-type est un champ du protocole HTTP. L'en-tête Content-Type indique au client le type de contenu du contenu réel renvoyé.
Les plus courants sont :

  • text/html : Format HTML. Lorsque le navigateur obtient ce type de fichier, il appellera automatiquement l'analyseur HTML pour restituer le fichier.
  • text/plain : Définissez le fichier en texte brut. Le navigateur ne traitera pas ce fichier lorsqu'il l'obtiendra.
  • application/json : format de données JSON, qui ne seront pas traitées par le navigateur.

TODO Type de contenu type de contenu par défaut de springmvc fegin

À mon avis, les interfaces renvoient toutes des données json et le type de contenu est application/json. Comment se fait-il que text/html apparaisse soudainement ? Je l'ai donc vérifié avec une recherche globale.
insérer la description de l'image ici
Soudain, je me suis souvenu qu'un intercepteur avait été ajouté à certaines entreprises qui nécessitent une connexion pour déterminer si l'utilisateur est connecté. Lorsqu'il est déterminé que l'utilisateur n'est pas connecté, une réponse texte/html sera renvoyée. Le code détaillé est le suivant.

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

Lors de la demande de feign, il a été déterminé que les données n'étaient pas connectées, donc les données au format "text/html" ont été renvoyées. Cependant, sur l'interface distante, nous avons utilisé R pour les accepter. Naturellement, elles ne seraient pas analysées. avec succès et une erreur serait signalée.
Normalement, ce qui doit être renvoyé ici est un objet d'application. Étant donné que ce projet est modifié en fonction de Guli Mall, les extrémités avant et arrière de Guli Mall ne sont pas séparées et le projet suivant utilise une structure qui sépare les extrémités avant et arrière, donc ici nous allons Cette erreur peut être résolue en modifiant la valeur de retour.
Vous pouvez vous référer à la modification de code suivante

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

    }
}

L'utilisateur n'est pas connecté

Bien qu'aucune exception de conversion ne soit signalée, « L'utilisateur n'est pas connecté » sera quand même renvoyé.
insérer la description de l'image ici
Ce qui peut être assuré, c'est que je me suis connecté à swagger et que j'ai apporté le cookie lors de la demande. Cependant, après fegin, cela montre que je ne me suis pas connecté, mais seule l'interface du service ware signale une erreur, et ni l'authentification. ni un tiers ne signale une erreur. .

Débogage conjoint des microservices

Comme il n'y avait aucun problème lors du test du service membre seul, je voulais voir la différence entre demander directement le service membre et demander le membre au serveur Ware, j'ai donc prévu d'interrompre les deux services pour voir. Notez que si vous avez plusieurs instances du même service enregistrées sur nacos, vous devez ajouter des paramètres url à @FeignClient pour spécifier le service local. Sinon, la requête risque d'être envoyée à d'autres machines, rendant impossible le débogage de la machine actuelle. la machine. Bien entendu, s’il n’y a qu’une seule instance, vous n’avez pas besoin de l’ajouter. Les exemples sont les suivants :

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

À ce stade, démarrez le service et démarrez le débogage. Il s'avère que le programme ne passe pas par le point d'appel de l'interface. Au lieu de cela, il est jugé comme non connecté à l'intercepteur de connexion du membre et retourne directement au service Ware.
Vérifiez la demande et constatez qu'il n'y a pas de session pour le moment et que la connexion n'a pas réussi.
insérer la description de l'image ici

Ouvrir le journal Fegin

Nous configurons un FeginConfig pour afficher la réponse à la demande de fegin

@Configuration
public class FeignConfig {
    
    

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

Configurer le journal d'impression dans application.yml

logging:
  level:
    feign.Logger: debug

log4j définit 8 niveaux de log, les priorités de haut en bas sont : OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL. La priorité par défaut de log4j est ERREUR. Log4j recommande d'utiliser uniquement les quatre niveaux d'ERREUR, WARN, INFO et DEBUG (priorité de haut en bas). Si le niveau de journalisation est défini sur un certain niveau, les journaux ayant une priorité plus élevée que ce niveau peuvent être imprimés.

  1. ALL : Le niveau le plus bas, utilisé pour activer toute la journalisation.
  2. TRACE : Niveau de log très faible, généralement non utilisé.
  3. DEBUG : souligne que les événements d'informations à granularité fine sont très utiles pour le débogage des applications et sont principalement utilisés pour imprimer certaines informations en cours d'exécution pendant le processus de développement.
  4. INFO : Le message met en évidence le processus en cours d'exécution de l'application à un niveau grossier. Imprimez des informations intéressantes ou importantes qui vous intéressent. Cela peut être utilisé dans un environnement de production pour générer des informations importantes sur le fonctionnement du programme, mais il ne faut pas en abuser pour éviter d'imprimer trop de journaux.
  5. WARN : Indique qu'une situation d'erreur potentielle va se produire. Certaines informations ne sont pas des informations d'erreur, mais elles fournissent également quelques conseils au programmeur.
  6. ERREUR : Imprime les informations sur les erreurs et les exceptions, indiquant que même si un événement d'erreur se produit, il n'affecte toujours pas le fonctionnement continu du système.
  7. FATAL : Indique que chaque événement d'erreur grave entraînera la fermeture de l'application. Erreurs majeures, ce niveau peut arrêter directement le programme.
  8. OFF : Le niveau le plus élevé, utilisé pour désactiver toute journalisation.

Vous pouvez voir que notre demande ne définit pas de cookie.
insérer la description de l'image ici
C'est la raison fondamentale pour laquelle la demande fegin échoue, nous configurons donc fegin in ware pour envoyer la demande avec des cookies.

Feindre un problème de cookie perdu

Puisque fegin enverra une nouvelle requête à chaque fois qu'il fera une requête sans apporter le cookie de notre requête précédente, nous devons la configurer manuellement à ce moment. Continuez à ajouter la configuration là où le débogage a été configuré auparavant, injectez un intercepteur dans le conteneur Spring et définissez un cookie avant la demande 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;
    }
}

En vérifiant le journal, il a été constaté que la requête avait réussi à amener le cookie.
insérer la description de l'image ici
Logiquement parlant, les deux requêtes devraient être un cookie et une session, mais ici, il a été constaté que les deux sessions étaient incohérentes.
Le délai de connexion a probablement expiré. Connectez-vous simplement à nouveau et tout ira bien.
insérer la description de l'image ici
Le message a été renvoyé avec succès.

Pourquoi les microservices précédents n’ont-ils pas rencontré de problèmes ?

J'ai déjà ajusté le serveur d'authentification et le tiers ainsi que le serveur d'authentification et le membre, et aucun problème similaire ne s'est produit.
La première raison est que le tiers n'a pas d'intercepteur de connexion, donc le serveur d'authentification ne renverra pas le contenu texte/html lors de l'appel du tiers, il peut donc être analysé normalement. Puisqu’il n’y a pas d’intercepteur de connexion, la présence ou l’absence de cookies n’affecte pas les appels à distance.
La raison en est que bien que le membre dispose d'un intercepteur de connexion, parce que l'interface demandée par le serveur d'authentification est autorisée (voir le code ci-dessus pour plus de détails), la valeur de retour de text/html ne sera pas renvoyée, elle peut donc être analysé normalement. Dans le même temps, étant donné que l'interface ne nécessite pas de cookies pour l'authentification de connexion, elle ne sera pas affectée si le cookie dans l'en-tête de la requête fegin est perdu.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_45654405/article/details/126768380
conseillé
Classement