Search code examples
javaspringspring-mvcspring-aop

How to add my custom header to a HttpServletRequest with aspect?


I tried to create an aspect to override the HttpServletRequest parameter before the controller handler method actually gets executed. But it turns out the HttpServletRequest is always a RequestFacade instance even though I replace it with a Custom HttpServletRequestWrapper in my aspect method.

Here is the code:

// the annotation:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
}

// the aspect:
@Component
@Aspect
@Slf4j
public class MyAspect {
    @Pointcut("@annotation(com.example.springtest.annotation.MyAnnotation)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object setHeader(ProceedingJoinPoint pjp) throws Throwable {
        var args = pjp.getArgs();
        var signature = (MethodSignature) pjp.getSignature();
        var method = signature.getMethod();
        var annotations = method.getDeclaredAnnotations();
        var need = Arrays.stream(annotations).anyMatch(a -> a.annotationType().equals(MyAnnotation.class));
        if (!need) {
            return pjp.proceed();
        }

        for (var i = 0; i < args.length; i++) {
            var arg = args[i];
            if (arg instanceof HttpServletRequest request) {
                args[i] = new HttpServletRequestWrapper(request) {
                    @Override
                    public String getHeader(String name) {
                        if (name.equalsIgnoreCase("my-header")) {
                            return "my-header";
                        } else {
                            return super.getHeader(name);
                        }
                    }
                };
            }
        }
        return pjp.proceed();
    }

}

// the bootstrap class:
@Configurable
@EnableAspectJAutoProxy
@SpringBootApplication
public class SpringtestApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringtestApplication.class, args);
    }

}

// and the controller
@RestController
public class TestController {

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

    @MyAnnotation
    @GetMapping("/get-my-header")
    public String getMyHeader(HttpServletRequest request) {
        if (Objects.requireNonNull(request) instanceof HttpServletRequestWrapper) {
            log.info("is wrapper");
        } else {
            log.info("request:{}", request.getClass());
        }
        return request.getHeader("my-header");
    }
}

So what happend there? Is there any other proxy class instance change my HttpServletRequest parameter after I replaced it?

the logs show that the request parameter in both the aspect and the controller is a RequestFacade instance.

I also tried to create a service method and directly send my custom HttpServletRequestWrapper instance to it. This time the parameter is not changed.


Solution

  • I am afraid fiddling with request headers' modification is not that easy. I am also afraid that aspect is not a way to go.

    Here is a minimal and reproducible sample for Spring Boot 2 to achieve what you want by setting a named attribute to the request in a custom interceptor that is captured in a method of the @RestController.

    Interceptor

    @Component
    public class CustomInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            request.setAttribute("fooAttribute", "fooValue");
            return true;
        }
    }
    

    Configuration

    @Configuration
    public class WebMvcConfiguration implements WebMvcConfigurer {
    
        @Autowired
        private CustomInterceptor customInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(customInterceptor);
        }
    }
    

    Controller

    I let the attribute be returned in the body.

    @GetMapping("hello")
    public ResponseEntity<String> hello(@RequestAttribute String fooAttribute) {
        return ResponseEntity.ok(fooAttribute);
    }
    

    Test it out:

    curl http://localhost:8080/hello
    
    fooValue