使用wireshark抓包,验证feign http请求的数据透传结果

一、背景

在灰度部署、A/B测试、单元化部署等场景下,微服务服务之间的调用,要求我们对上游服务给过来的数据进行透传至下游服务。

如果是灰度部署,需要对http请求进行染色,http header头部增加灰度标识,然后传递给下游服务。这个传递就跟击鼓传花一样,谁都不能丢弃掉这个灰度标识。
可现实是,我们的服务在执行的过程中,极容易把这个灰度标识丢掉了。(当然不是故意的)

如果程序的执行顺序都是串行的,那当然不会丢了上下文中的数据。现实中的程序执行,为了高性能和解耦,我们就会使用线程池、本地异步事件、mq异步、redis数据订阅与发布等技术,使得上下文的数据丢掉了,无法继续透传下去了。

为了做到数据透传,我们一般有两种做法,一是java agent技术,另外就是在服务中,在拦截器中增加数据的赋值。

验证数据透传的结果,一般的做法是输入日志,通过唯一标识去跟踪数据透传到哪一步。比如http feign调用框架,你就可以使用wireshark抓包来验证。(本文就使用后者来验证feign框架的数据透传)

二、目标

  • 支持feign调用的数据透传
  • 支持父子线程和线程池等的数据透传
  • 能够验证数据透传的结果

三、边界

服务之间的数据透传,不包括rabbitmq、redis等第三方中间件的透传。

四、关键思路

  • 父子线程的上下文传递,包括线程池等并发工具类,可以使用阿里云的开源框架transmittable-thread-local

  • 抓http协议的包,你可以使用charles,也可以使用强大的抓包工具:wireshark(说后者强大,是因为它还可以抓websocket等协议的包)

  • feign调用的参数传递,使用RequestInterceptor拦截器,在头部增加你的参数进行调用

五、feign拦截器


import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
 
 
public class CustomFeignInterceptor implements RequestInterceptor {

    private static final Logger log = LoggerFactory.getLogger(CustomFeignInterceptor.class);
    /**
     * FeignClient调用时传递的用户id
     */
    public static String XX_USER_ID = "XX_USER_ID";
    /**
     * FeignClient调用时传递的学校id
     */
    public static String XX_SCHOOL_ID = "XX_SCHOOL_ID";
    
    @Autowired
    XxTransmittableThreadLocal<TransmittedUserInfo> transmittableThreadLocal;

    @Override
    public void apply(RequestTemplate template) {
        TransmittedUserInfo userInfo = transmittableThreadLocal.get();
        if (userInfo != null) {
            if (StringUtils.isNotEmpty(userInfo.getUserId())){
                template.header(CustomFeignInterceptor.XX_USER_ID, userInfo.getUserId());
            }
            if (StringUtils.isNotEmpty(userInfo.getSchoolId())){
                template.header(CustomFeignInterceptor.XX_SCHOOL_ID, userInfo.getSchoolId());
            }
        }
    }
}
  • 拦截器的实例化(当然你可以额外增加开关)
    @Bean
    public RequestInterceptor customFeignInterceptor() {
        return new CustomFeignInterceptor();
    }

六、上下文类-阿里ttl框架

import com.alibaba.ttl.TransmittableThreadLocal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class XxTransmittableThreadLocal<T> {

    private static final Logger log = LoggerFactory.getLogger(XxTransmittableThreadLocal.class);


    private TransmittableThreadLocal<T> threadLocal = new TransmittableThreadLocal();

    public void set(T value) {
        threadLocal.set(value);
    }

    public T get() {
        return threadLocal.get();
    }
}

七、把要透传的数据传递至上下文

在自定义feign拦截器里,我们从上下文中取出了需透传的值。现在,我们将详细说明,在controller拦截器中,注入透传数据至上下文。

  • spring servlet 过滤器之OncePerRequestFilter

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class InfoTransmitFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(InfoTransmitFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authUserId = request.getHeader(JwtAuthHeaders.AUTH_USER_ID);
        String authSchoolId = request.getHeader(JwtAuthHeaders.AUTH_SCHOOL_ID);
        if (StringUtils.isNotBlank(authUserId)) {
            TransmittedUserInfo userInfo = new TransmittedUserInfo(authUserId, authSchoolId);
           
            XxTransmittableThreadLocal<TransmittedUserInfo> xxTransmittableThreadLocal = (XxTransmittableThreadLocal<TransmittedUserInfo>) ApplicationContextProvider
                    .getApplicationContext()
                    .getBean("xxTransmittableThreadLocal");
            // 把需透传的用户信息,存储到上下文
            xhTransmittableThreadLocal.set(userInfo);
        }

        filterChain.doFilter(request, response);
    }

}
  • 过滤器的实例化(可以额外增加开关)
    @Bean
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    public FilterRegistrationBean infoTransmitFilterRegBean() {
        InfoTransmitFilter infoTransmitFilter = new InfoTransmitFilter();
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(infoTransmitFilter);
        filterRegistrationBean.setOrder(Ordered.LOWEST_PRECEDENCE);
        return filterRegistrationBean;
    }
  • REACTIVE框架(如果你使用的非servlet框架)
    @Bean
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
    public InfoTransmitWebFilter infoTransmitWebFilter() {
        return new InfoTransmitWebFilter();
    }
  • InfoTransmitWebFilter的实现和InfoTransmitFilter类似,详情见下:
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;


public class InfoTransmitWebFilter implements WebFilter {
    public static final int ORDER = Ordered.LOWEST_PRECEDENCE;
    private static final Logger logger = LoggerFactory.getLogger(InfoTransmitWebFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        final String userId = exchange.getRequest().getHeaders().getFirst(JwtAuthHeaders.AUTH_USER_ID);
        final String authSchoolId = exchange.getRequest().getHeaders().getFirst(JwtAuthHeaders.AUTH_SCHOOL_ID);
        if (StringUtils.isNotBlank(userId)) {
            TransmittedUserInfo userInfo = new TransmittedUserInfo(userId, authSchoolId);
           
            XxTransmittableThreadLocal<TransmittedUserInfo> xxTransmittableThreadLocal = (XxTransmittableThreadLocal<TransmittedUserInfo>) ApplicationContextProvider
                    .getApplicationContext()
                    .getBean("xxTransmittableThreadLocal");
            // 把需透传的用户信息,存储到上下文
            xhTransmittableThreadLocal.set(userInfo);
        }

        return chain.filter(exchange);
    }
}

八、wireshark抓包

开启抓包

# 这里输入过滤条件
ip.addr == 192.168.5.70 and tcp.port == 7584

在这里插入图片描述

使用Postman调用接口,开始测试

在这里插入图片描述
找到http协议的报文,见"Hypertext Transfer Protocol"部分:
在这里插入图片描述
如果你想要去看那些tcp协议的报文,可以进一步帅选:

ip.addr == 192.168.5.70 and tcp.port == 7584 and http

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天草二十六_简村人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值