Feign remote call lost request header

Posted by receiver on Fri, 25 Feb 2022 15:22:46 +0100

In business, you need to use modules A and B, which use spring Session to share Session data. The business in module B can only be operated after the user logs in. When A calls B's service, it cannot obtain the user's Session information in module B, resulting in module B determining that the requesting user has not logged in, resulting in module A not getting the required data. The problem is that module A can get the login information of the user, and has used spring Session to share Session data.

Find out the cause of the problem

Sending remote calls using Feign

When the request enters the B service, it is intercepted by its login verification interceptor. When trying to get the login information from the Session, the result is null (login confirmed)

As we all know, the principle of session is to determine a session object through a value in the cookie (jesessionid). The user data cannot be obtained in module B because the session object cannot be obtained by specifying a cookie.

To solve this problem, you need to Debug Feign's process.

Feign process

Query the sending request, go to the remote call code break point, and set into to check

[the external chain image transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-mafi4pb9-1645798237261) (/ users / clover / library / Application Support / typora user images / screenshot 2022-02-25, 8.27.51.png)]

When it is judged that it is not equal, hashCode, toString and other methods, execute the invoke method to make a remote call, and enter setup into

In the invoke method, first create a new request template, which contains our first-class request information

With theout any other special processing, you can directly call Client to send request

From feign's process, we can see that it directly creates A new request for our door, and does not encapsulate the first-class information of the request carried by the browser when sending the request to service A.

solve

During the debug ging process, it is found that there is a this in the executeanddecade method when calling the Client to send the request targetRequest(template); Operation, it returns a request, which is the request object when the Client sends the request.

It is found in the targetRequest method that he will get an interceptor RequestInterceptors, and then it is convenient to call its apply method and pass the request template it creates to the RequestTemplate, and the RequestInterceptors are taken in the container, so we only need to add a RequestInterceptors component to the container.

For example:

@Component
public class FeignFillContent implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        // Sync cookie
        requestTemplate.header("Cookie", "xxx");
    }
}

So the question is, how should we get cookies? In fact, this problem is also very simple. We can build an interceptor and save HttpServletRequest in ThreadLocal

@Component
public class LoginInterceptor implements HandlerInterceptor {

  public final static ThreadLocal<HttpServletRequest> THREAD_LOCAL_REQUEST = new ThreadLocal<>();

  @Override
  public boolean preHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler) throws Exception {
    THREAD_LOCAL_REQUEST.set(request);
    ...
      return HandlerInterceptor.super.preHandle(request, response, handler);
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    ...
      THREAD_LOCAL_REQUEST.remove();
    HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
  }
}

SpringBoot is also provided with this tool. We don't need to write extra class RequestContextHolder. The principle of this class is to use ThreadLocal

@Component
public class FeignFillContent implements RequestInterceptor {
  @Override
  public void apply(RequestTemplate requestTemplate) {
    // Get request context
    ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = requestAttributes.getRequest();

    // Sync cookie
    String myCookies = request.getHeader("Cookie");
    requestTemplate.header("Cookie", myCookies);
  }
}

Test it. Service B successfully gets the user login information

Problem recurrence in asynchronous environment

There is no problem in the single thread environment, but there will still be problems in multi threads, such as completable future. This time, I asked the interceptor to throw a null pointer exception.

The problem is that ThreadLocal is the underlying Map, and the key uses the current thread object. Therefore, there is no problem in the single thread environment. There is a problem when asynchrony is used. Because there is a new thread after asynchrony, which is no longer the thread we used to process requests, we can't get the data in ThreadLocal through the current thread object.

// Get shopping items
CompletableFuture<Void> cartItemFuture = CompletableFuture.runAsync(() -> {
  R cartItem = cartFeignService.getCurrentUserCartItem();
  ...
}, executor);

solve

This problem is also very simple, that is, sharing ThreadLocal, that is, copying the specified ThreadLocal to the ThreadLocal of the specified thread

@Override
public OrderConfirmVo confirmOrder() {
  ...
    RequestAttributes myReqContext = RequestContextHolder.currentRequestAttributes();
  // Query all receiving addresses of members
  CompletableFuture<Void> memberFuture = CompletableFuture.runAsync(() -> {
    // Copy a copy of ThreadLocal
    RequestContextHolder.setRequestAttributes(myReqContext);
    R memberReceiveAddress = memberFeignService.getMemberReceiveAddress(mrv.getId());
    ...
  }, executor);

  // Get shopping items
  CompletableFuture<Void> cartItemFuture = CompletableFuture.runAsync(() -> {
    // Copy a copy of ThreadLocal
    RequestContextHolder.setRequestAttributes(myReqContext);
    R cartItem = cartFeignService.getCurrentUserCartItem();
    ...
  }, executor);

  CompletableFuture.allOf(memberFuture, cartItemFuture).join();
  return vo;
}

Welcome to my blog: https://www.ctong.top
Blog address: https://www.ctong.top/2022/02/25/Feign%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%E4%B8%A2%E5%A4%B1%E8%AF%B7%E6%B1%82%E5%A4%B4%E9%97%AE%E9%A2%98/

Topics: Java Spring Boot Back-end Spring Cloud Microservices