[Solved] The RequestInterceptor in Open Feign cannot get the solution to the HttpServletRequest problem

Problem reproduced:

In the case of Hystrix using open fegin, when the current thread request is passed to the downstream, it is found that the HttpServletRequest obtained from the RequestContextHolder is empty.

@Override
public void apply(RequestTemplate requestTemplate) {
   logger.info("Execute Feign interceptor, process request header...");
   ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
   if (Objects.nonNull(requestAttributes)) {
       HttpServletRequest request = requestAttributes.getRequest();
       Enumeration<String> headerNames = request.getHeaderNames();
       while (headerNames.hasMoreElements()){
           String nextElement = headerNames.nextElement();
           logger.info("Header[{}={}]", nextElement, request.getHeader(nextElement));
           requestTemplate.header(nextElement, request.getHeader(nextElement));
       }
   }
}

Cause Analysis

RequestContextHolder request context holder, we can get the RequestAttributes of the current request through this class at any position of the current thread, but there is a problem, the request object is Stored in ThreadLocal, our Hystrix requests another service interface by reopening the sub-thread, so we naturally cannot obtain RequestAttributes in the sub-thread.

public abstract class RequestContextHolder {

   private static final boolean jsfPresent =
         ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());

   private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
         new NamedThreadLocal<>("Request attributes");

   private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
         new NamedInheritableThreadLocal<>("Request context");
   //...omit...
}

As can be seen in the HystrixInvocationHandler processor, it is requested by a child thread.

@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
    throws Throwable {
  // early exit if the invoked method is from java.lang.Object
  // code is the same as ReflectiveFeign.FeignInvocationHandler
  if ("equals".equals(method.getName())) {
    try {
      Object otherHandler =
          args.length > 0 & amp; & amp; args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
      return equals(otherHandler);
    } catch (IllegalArgumentException e) {
      return false;
    }
  } else if ("hashCode".equals(method.getName())) {
    return hashCode();
  } else if ("toString".equals(method.getName())) {
    return toString();
  }

  HystrixCommand<Object> hystrixCommand =
      new HystrixCommand<Object>(setterMethodMap.get(method)) {
        @Override
        protected Object run() throws Exception {
          try {
            return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
          } catch (Exception e) {
            throw e;
          } catch (Throwable t) {
            throw (Error) t;
          }
        }

        @Override
        protected Object getFallback() {
          if (fallbackFactory == null) {
            return super.getFallback();
          }
          try {
            Object fallback = fallbackFactory.create(getExecutionException());
            Object result = fallbackMethodMap.get(method).invoke(fallback, args);
            if (isReturnsHystrixCommand(method)) {
              return ((HystrixCommand) result).execute();
            } else if (isReturnsObservable(method)) {
              // Create a cold Observable
              return ((Observable) result).toBlocking().first();
            } else if (isReturnsSingle(method)) {
              // Create a cold Observable as a Single
              return ((Single) result).toObservable().toBlocking().first();
            } else if (isReturnsCompletable(method)) {
              ((Completable) result).await();
              return null;
            } else if (isReturnsCompletableFuture(method)) {
              return ((Future) result).get();
            } else {
              return result;
            }
          } catch (IllegalAccessException e) {
            // shouldn't happen as method is public due to being an interface
            throw new AssertionError(e);
          } catch (InvocationTargetException | ExecutionException e) {
            // Exceptions on fallback are tossed by Hystrix
            throw new AssertionError(e.getCause());
          } catch (InterruptedException e) {
            // Exceptions on fallback are tossed by Hystrix
            Thread.currentThread().interrupt();
            throw new AssertionError(e.getCause());
          }
        }
      };

  if (Util.isDefault(method)) {
    return hystrixCommand.execute();
  } else if (isReturnsHystrixCommand(method)) {
    return hystrixCommand;
  } else if (isReturnsObservable(method)) {
    // Create a cold Observable
    return hystrixCommand.toObservable();
  } else if (isReturnsSingle(method)) {
    // Create a cold Observable as a Single
    return hystrixCommand.toObservable().toSingle();
  } else if (isReturnsCompletable(method)) {
    return hystrixCommand.toObservable().toCompletable();
  } else if (isReturnsCompletableFuture(method)) {
    return new ObservableCompletableFuture<>(hystrixCommand);
  }
  return hystrixCommand.execute();
}

Solution

1. Modify Hystrix isolation policy (not recommended)

Hystrix provides two isolation strategies: THREAD, SEMAPHORE. Its default strategy is THREAD (thread pool).
Modify the isolation strategy to SEMAPHORE, this scheme will not be mentioned, after all, the official website does not recommend using this mode!

2. Custom isolation policy (recommended)

The default strategy implementation class is HystrixConcurrencyStrategyDefault, you can look at its source code, which is basically an empty object. We don’t need its default implementation class here, we implement the HystrixConcurrencyStrategy class and rewrite its #wrapCallable method. The logic in the method is to set the request of the main thread to be inherited by the child thread before restarting the child thread* *(Note: The inherited request is still the same. If the main thread and the child thread are asynchronous, the request will be destroyed after the main thread ends, and the child thread will not be able to obtain it)**. Above code:

package com.pcliu.platform.setting.openfeign;

import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.concurrent.Callable;

/**
 * @author pcliu
 * @version 1.0
 * @date 2022/6/30 17:56
 */
@Component
public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
    public static final Logger logger = LoggerFactory.getLogger(RequestAttributeHystrixConcurrencyStrategy.class);

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        RequestAttributes currentRequestAttributes = RequestContextHolder.currentRequestAttributes();
        RequestContextHolder.setRequestAttributes(currentRequestAttributes, Boolean.TRUE);
        logger.info("Sub thread inherits parent thread request");
        return callable;
    }
}

It’s not over yet, it’s not enough just to implement it, it has to be used, it’s very simple, add a configuration item to bootstrap.yml:

hystrix:
  plugin:
    HystrixConcurrencyStrategy:
      implementation: com.pcliu.platform.setting.openfeign.RequestAttributeHystrixConcurrencyStrategy

OK~ here it is

Ideas of custom isolation policy

When the HystrixPlugins plugin obtains the current policy, it will first load the policy implementation class configured by the programmer, and will load the default policy implementation when it cannot be found.

public HystrixConcurrencyStrategy getConcurrencyStrategy() {
    if (concurrencyStrategy.get() == null) {
        // check for an implementation from Archaius first
        Object impl = getPluginImplementation(HystrixConcurrencyStrategy.class);
        if (impl == null) {
            // nothing set via Archaius so initialize with default
            concurrencyStrategy.compareAndSet(null, HystrixConcurrencyStrategyDefault.getInstance());
            // we don't return from here but call get() again in case of thread-race so the winner will always get returned
        } else {
            // we received an implementation from Archaius so use it
            concurrencyStrategy.compareAndSet(null, (HystrixConcurrencyStrategy) impl);
        }
    }
    return concurrencyStrategy.get();
}

Look at this method==Object impl = getPluginImplementation(HystrixConcurrencyStrategy.class);, and then enter this method T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties);, here is the programmer The configured strategy implementation class is then loaded: String propertyName = “hystrix.plugin.” + classSimpleName + “.implementation”;. In addition to defining policy plugins, various other plugins can also be defined.

private <T> T getPluginImplementation(Class<T> pluginClass) {
    T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties);
    if (p != null) return p;
    return findService(pluginClass, classLoader);
}

private static <T> T getPluginImplementationViaProperties(Class<T> pluginClass, HystrixDynamicProperties dynamicProperties) {
    String classSimpleName = pluginClass.getSimpleName();
    // Check Archaius for plugin class.
    String propertyName = "hystrix.plugin." + classSimpleName + ".implementation";
    String implementingClass = dynamicProperties.getString(propertyName, null).get();
    if (implementingClass != null) {
        try {
            Class<?> cls = Class.forName(implementingClass);
            // narrow the scope (cast) to the type we're expecting
            cls = cls.asSubclass(pluginClass);
            return (T) cls.newInstance();
        } catch (ClassCastException e) {
            throw new RuntimeException(classSimpleName + " implementation is not an instance of " + classSimpleName + ": " + implementingClass);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(classSimpleName + " implementation class not found: " + implementingClass, e);
        } catch (InstantiationException e) {
            throw new RuntimeException(classSimpleName + " implementation not able to be instantiated: " + implementingClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(classSimpleName + " implementation not able to be accessed: " + implementingClass, e);
        }
    } else {
        return null;
    }
}

Extended

In fact, analyzing this method==Object impl = getPluginImplementation(HystrixConcurrencyStrategy.class);, if the programmer does not configure the plug-in implementation class, it will finally return return findService(pluginClass, classLoader);< /em>, look through the code and find that it also provides an SPI method to load custom plug-ins.

private static <T> T findService(
        Class<T>spi,
        ClassLoader classLoader) throws ServiceConfigurationError {
    
    ServiceLoader<T> sl = ServiceLoader.load(spi,
            classLoader);
    for (T s : sl) {
        if (s != null)
            return s;
    }
    return null;
}

This is the end~

syntaxbug.com © 2021 All Rights Reserved.