Record on the loss of Feign remote call request header in learning and encouraging mall micro services

Loss of request header in Feign remote call in single thread

Code case

Order service:

Controller:

	/**
     * When the goods are selected in the shopping cart service and click "go to settlement", the order service is triggered.
     * This controller is mainly used to return order settlement details and other information
     * */
@GetMapping("/toTrade")
    public String toTrade(Model model){

         OrderConfirmVo orderConfirmVo = orderService.confirmOrder();

         model.addAttribute("orderConfirmData",orderConfirmVo);

        return "confirm";

    }

Interceptor:

	/**
     * The interceptor is used to judge whether the user logs in when jumping to the order service. If not, jump to the login page.
     * If it is used for logged in, save the user's information to ThreadLocal.
     * */
	@Component
	public class OrderLoginIntercepted implements HandlerInterceptor {

    /**
     * Local thread, data isolation between threads
     * */
    public static ThreadLocal<MemberResponseVo> threadLocal = new ThreadLocal<>();


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //Get login user information
        MemberResponseVo attribute = (MemberResponseVo)request.getSession().getAttribute(AuthConstant.LOGIN_USER);

        if(attribute!=null){
            //Save the logged in user information in ThreadLocal
            threadLocal.set(attribute);
            return true;
        }
        else{
             request.getSession().setAttribute("msg", "Please log in first");
             response.sendRedirect("http://auth.grapesmail.com/login.html");
            return false;
        }
    }

}

ServiceImpl:

	/**
     * service Implementation layer
     * */
	@Override
    public OrderConfirmVo confirmOrder(){

        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
			
		/**
    	 * When calling the order service, if you have logged in, you can get the user information from ThreadLocal of the order service
    	 * */
        MemberResponseVo memberResponseVo = OrderLoginIntercepted.threadLocal.get();

        //Remote calling member service to query user address
        List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
        orderConfirmVo.setMemberAddressVos(address);

        //Remotely call the shopping cart service to query all shopping items in the shopping cart.
        //Shopping cart service involves interceptor session to determine whether to log in. Feign remote call will construct a new request, which does not carry the request header
        //That is, the remote call through Feign will cause the request header to disappear, the request header to disappear, the session, cookie and other information to be lost, and the shopping cart will be considered as not logged in
        //Solution: add Feign's own interceptor
        List<OrderItemVo> currentUserCarItems = cartFeignService.getCurrentUserCarItems();
        orderConfirmVo.setItems(currentUserCarItems);

        //Query user points
        Integer integration = memberResponseVo.getIntegration();
        orderConfirmVo.setIntegration(integration);
        
        //TODO interface, idempotency, anti duplication token
        
        return orderConfirmVo;
    }

Shopping Cart Service:

Interceptor part code:

	@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //Once logged in, there is a user id. if not logged in, set a temporary key for the user
        UserInfoTo userInfoTo = new UserInfoTo();

        //Manage sessions between services through spring session
        HttpSession session = request.getSession();
        MemberResponseVo user = (MemberResponseVo)session.getAttribute(AuthConstant.LOGIN_USER);
        //The user has logged in and set the id. there is no need to set the key to get the id
        if(user!=null){
            userInfoTo.setUserId(user.getId());
        }
    }

To summarize the process, that is, when the user selects the goods in the shopping cart and clicks settlement, he needs to jump to the order details page. At this time, the order service is triggered. The order service "toTrade" is called remotely through Feign at the service layer to obtain the receiving address of the user and the details of the goods selected in the shopping cart. There is an interceptor in the shopping cart service, which is used to obtain information from the session domain and judge whether the user logs in. Through the remote Feign call, Feign will construct a new request by default. The request does not carry header information. Therefore, the shopping cart service cannot know whether the user logs in from the session domain through the interceptor. At this time, the default user does not log in, and the user who does not log in cannot obtain the shopping cart details.

Source code parsing lost request header reason:


Explanation of flow chart:


resolvent:

In the corresponding controller, add the request parameter. To ensure that the RequestInterceptor interceptor in the order service can get the request through the thread context. Order servicecontroller - > order serviceservice - > order servicefeign calls the order service - > order servicerequestintegrator interceptor, which is executed in one thread and thread data is shared by one ThreadLocal. Therefore, the corresponding request can be obtained through the thread context.

@GetMapping("/toTrade")
    public String toTrade(Model model){

         OrderConfirmVo orderConfirmVo = orderService.confirmOrder();

         model.addAttribute("orderConfirmData",orderConfirmVo);

        return "confirm";

    }

Add the RequestInterceptor interceptor to the container in the order service

@Configuration
public class mailFeignConfig {

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor(){
            @Override
            public void apply(RequestTemplate requestTemplate) {
                //Requestinterceptor will be performed before remote Apply method
                //Use RequestContextHolder to get the request from below. That is, get the request information from the controller parameter
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = requestAttributes.getRequest();//Old request
                if(request != null){
                    //Synchronization header information, cookie
                    String cookie = request.getHeader("Cookie");
                    //Construct a new request and get a cookie
                    requestTemplate.header("Cookie",cookie);
                }
            }
        };
    }

}

Loss of request header in Feign remote call in asynchronous call

Since asynchronous orchestration is executed by multiple threads and does not belong to the above controller - > Service - > feign - > requestintegrator thread, you can manually set the request information through the RequestContextHolder, save your own ThreadLocal variable for each thread, and let each requestintegrator go to ThreadLocal to obtain the information you need. The example code is as follows:

Code case

 @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {

        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();

        MemberResponseVo memberResponseVo = OrderLoginIntercepted.threadLocal.get();

        //Before the multithreaded asynchronous task, get the properties (lead information) of the old request through the thread context. That is, get the properties of the old request from the request of the order service parameter.
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        CompletableFuture<Void> getAddress = CompletableFuture.runAsync(() -> {
        	//Manually set the request attribute to ensure that the requestintegrator of the member service can get the request information
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //Remote query of user address
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
            orderConfirmVo.setMemberAddressVos(address);
        }, threadPoolExecutor);

        CompletableFuture<Void> getCurrentUserCartItems = CompletableFuture.runAsync(() -> {
            //Manually set the old attribute to ensure that the requestintegrator of the order service can get the request information
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //Remotely query all shopping items in the shopping cart.
            List<OrderItemVo> currentUserCarItems = cartFeignService.getCurrentUserCarItems();
            orderConfirmVo.setItems(currentUserCarItems);
        }, threadPoolExecutor);

        //Query user points
        Integer integration = memberResponseVo.getIntegration();
        orderConfirmVo.setIntegration(integration);

        //Automatic calculation of other data

        //TODO interface, idempotency, anti duplication token

        //Wait for all asynchronous tasks to complete
        CompletableFuture.allOf(getAddress,getCurrentUserCartItems).get();


        return orderConfirmVo;
    }

The difference between the two


Detailed video teaching: Feign remote call missing

Keywords: Java Microservices

Added by Teaky on Fri, 11 Feb 2022 01:34:44 +0200