How can I log the JSON response of Spring 3 controllers with #ResponseBody in a HandlerInterceptorAdapter? - spring

I have controllers that return JSON to the client. The controllers methods are marked using mvc annotation such as:
#RequestMapping("/delete.me")
public #ResponseBody Map<String, Object> delete(HttpServletRequest request, #RequestParam("ids[]") Integer[] ids) {
Spring knows to return JSON since Jackson is on the class path and the client is requesting a JSON response. I would like to log the response of these requests and all other controllers. In the past I have used an interceptor to do this. However, I got the response body from the ModelAndView. How can I get the response body in the inteceptor now that I'm using #ResponseBody? Specifically, how can I get the response body in this method?
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

You can log everything by using CustomizableTraceInterceptor
you can either set it in your application context xml config and use AOP: (log level Trace)
<bean id="customizableTraceInterceptor"
class="org.springframework.aop.interceptor.CustomizableTraceInterceptor">
<property name="exitMessage" value="Leaving $[methodName](): $[returnValue]" />
</bean>
or you can completly customize it by implementing it in Java and use the method setExitMessage():
public class TraceInterceptor extends CustomizableTraceInterceptor {
private Logger log = LoggerFactory.getLogger("blabla");
#Override
protected void writeToLog(Log logger, String message, Throwable ex) {
//Write debug info when exception is thrown
if (ex != null) {
log.debug(message, ex);
}
....
}
#Override
protected boolean isInterceptorEnabled(MethodInvocation invocation, Log logger) {
return true;
}
#Override
public void setExitMessage(String exitMessage) {
.... //Use PlaceHolders
}
}
and use the placeholders such as '$[returnValue]'. You can find the complete list in the spring api documentation.
EDIT: Also, if you want to get the value of your #ResponseBody in another interceptor, I think it's not possible until version > 3.1.1. Check this issue: https://jira.springsource.org/browse/SPR-9226

Related

How to get the updated/modified HttpServletRequest object from AOP #Before advice to Spring controller method

I used Spring AOP #Before advice in Spring boot application, and it should execute before hitting any api's.
My task/requirement :- If in the request header application-name is not passed then we should modify the header and add to 'unknown' value to the application-name for every API.
I am modifying the header in the AOP #before advice using HttpServletWrapper class as shown below.
Problem is - the AOP should return updated HttpServletrequest to a controller method but it's not working, not returning the updated one in controller.
Controller:-
#GetMapping
#RequestMapping("/demo")
public ResponseEntity<String> getEmployee(HttpServletRequest request) {
System.out.println("Header, application-name"+request.getHeader("application-name"));
return new ResponseEntity<>("Success", HttpStatus.OK);
}
Spring AOP code,
#Aspect
#Component
public class AOPExample {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping) ||"
+ "#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void controllerRequestMapping() {}
#Before("controllerRequestMapping()")
public HttpServletRequest advice(JoinPoint jp) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String header = request.getHeader("application-name");
if (header == null) {
HttpServletRequestWrapperClass wrappedRequest = new HttpServletRequestWrapperClass(request);
wrappedRequest.putHeader("application-name", "Unknown");
request = wrappedRequest;
} else {
//validate application name
//400 - throw bad request exception
}
System.out.println("After setting---"+request.getHeader("application-name"));
return request;
}
}
Finally I resolved the issue,
Instead of using #Before advice used #Around advice, Around advice should return the object using proceed method.
#Aspect
#Component
public class AOPExample {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping) ||"
+ "#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void controllerRequestMapping() {}
#Around("controllerRequestMapping()")
public Object advice(ProceedingJoinPoint jp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String header = request.getHeader("application-name");
System.out.println("Header in AOP"+header);
if (header == null) {
HttpServletRequestWrapperClass wrappedRequest = new HttpServletRequestWrapperClass(request);
wrappedRequest.putHeader("application-name", "Unknown");
request = wrappedRequest;
} else {
//validate application name
//400 - throw bad request exception
//throw new BadRequestException("Invalid application name");
}
System.out.println("After setting---"+request.getHeader("application-name"));
return jp.proceed(new Object[] {request});
}
}
Updated httpservlet request is getting in controller method. Thanks

Controller interceptor that process endpoint annotation in WebFlux

My team is in the middle of migrating our Spring MVC extensions to WebFlux.
We've got a feature that lets our clients customize metric of controller method. To do that we've created our annotation that is processed by HandlerInterceptorAdapter.
The problem is that I can't see any equivalent of this in Spring WebFlux. I can't use WebFilter because Spring does not know yet which endpoint will be called. How can I implement that?
The closest workaround I found is to use RequestMappingHandlerMapping and somehow build a map of Map<String(path), HandlerMethod>, but this is cumbersome and error prone in my opinion.
Is there any better way to solve this?
Edit:
It goes like this
public class MeteredHandlerInterceptor extends HandlerInterceptorAdapter {
public MeteredHandlerInterceptor() {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// I save start time of method
return true;
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// I read endpoint method from the HandlerMethod, I apply any customisation by our custom #MeteredEndpoint annotation (for example custom name) and I save it in MeterRegistry
}
}
I haven't coded workaround yet because I didn't want to invest time in it, but I see that I could obtain HandlerMethod for path, but I'm not sure I will receive same HandlerMethod as I normally would when the controller is called.
Maybe little bit late, but it can still be useful for someone...
I have not found an easy way to do that, the best I was able to create is a HandlerAdapter bean that intercepts handling in the following way:
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public HandlerAdapter handlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
return new HandlerAdapter() {
#Override
public boolean supports(Object handler) {
return handler instanceof HandlerMethod;
}
#Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
// your stuff here...
// e.g. ((HandlerMethod) handler).getMethod().getAnnotations()...
return requestMappingHandlerAdapter.handle(exchange, handler);
}
};
}
The idea is that this adapter is used for all HandlerMethod handlers (those are the ones created by collecting annotated methods from #Controllers) and delegates the handling to the RequestMappingHandlerAdapter (that would be used directly for HandlerMethod handlers in normal case, notice the #Order annotation here).
The point is you can put your code before/after the invocation of the handle method and you are aware of the method being invoked at this point.
Solution:
#Component
class AuditWebFilter(
private val requestMapping: RequestMappingHandlerMapping
): WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
// if not to call - then exchange.attributes will be empty
// so little early initialize exchange.attributes by calling next line
requestMapping.getHandler(exchange)
val handlerFunction = exchange.attributes.get(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE) as HandlerMethod
val annotationMethod = handlerFunction.method.getAnnotation(MyAnnotation::class.java)
// annotationMethod proccesing here
}
}

How to add CORS headers to the Spring error page rendered by BasicErrorController?

I have a single page client being served by a Spring Boot REST MVC API application (spring boot version 1.5.2).
My app is secured via Auth0 JWT tokens. When things are working, the CORS headers for responses are provided by a ServletFilter that gets configured as part of setting up the security:
protected void configure(HttpSecurity http) throws Exception {
...
http.addFilterBefore(simpleCORSFilter(), Auth0AuthenticationFilter.class);
...
}
This seems to work everywhere I've tested it so far - but one place where it's not working is with the default Spring error page (path "/error", rendered by default by the BasicErrorController class).
When there's an exception thrown in my service methods, the error page works and renders the content I want as JSON in the response body, but the client app can't access the http response body because the response lacks CORS headers.
So the question: "how do I add CORS headers to the error page"?
Should I be removing the CORS filter from my security setup and applying the CORS filter more globally? Where would that be done - I can't find anything relevant in the Spring doccumentation.
Or should I be writing a custom Error controller? The only example of a custom error controller in the documentation just seems to allow you to return a string.
You can define a separate Controller for Error and allow cross origin to it using
#CrossOrigin("*")
Combining Poorvi's answer with Joni Karppinen's custom error controller code gives:
#RestController
public class ErrorController
implements org.springframework.boot.autoconfigure.web.ErrorController
{
private static final String PATH = "/error";
#Autowired private ErrorAttributes errorAttributes;
#Override
public String getErrorPath(){
return PATH;
}
// I guess when time comes to lock down cors header, we could use a spring
// value configuration here to share with corsfilter.
#CrossOrigin("*")
#RequestMapping(value = PATH, produces = "application/json")
public #ResponseBody
ErrorJson error(HttpServletRequest request, HttpServletResponse response){
return new ErrorJson(
response.getStatus(),
getErrorAttributes(request, false) );
}
private Map<String, Object> getErrorAttributes(
HttpServletRequest request,
boolean includeStackTrace
){
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return errorAttributes.getErrorAttributes(
requestAttributes,
includeStackTrace);
}
}
class ErrorJson {
public Integer status;
public String error;
public String message;
public String timeStamp;
public String trace;
public ErrorJson(int status, Map<String, Object> errorAttributes){
this.status = status;
this.error = (String) errorAttributes.get("error");
this.message = (String) errorAttributes.get("message");
this.timeStamp = errorAttributes.get("timestamp").toString();
this.trace = (String) errorAttributes.get("trace");
}
}
Which seems to do the job for me.

How to checkin interceptor whether a controller triggered a redirect

In my Spring MVC project I added an interceptor class, to check, whether a redirect has been triggered.
Here is my controller-class:
#Controller
public class RedirectTesterController {
#RequestMapping (value="/page1")
public String showPage1(){
return "page1";
}
#RequestMapping (value="/submit1")
public String submitPage1(){
return "redirect:/page2";
}
#RequestMapping (value="/page2")
public String showPage2(){
return "page2";
}
}
So if I call e.g.
localhost:8080/MyContext/submit1
the method "submitPage1" is executed.
Now - the server tells the client, to call
localhost:8080/MyContext/page2
which is also working.
So - I want to step into that process, after method "submitPage1"has been executed.
In my mind there should be some order/command in the httpResponse, which I could ask.
To check that, I made a breakpoint in my interceptor class in the method: "postHandle" - bit since then, I have no idea how to continue.
I tried to read the outputStream - but doing so crashes my application. (leads to an exception --> outputStream has already been called..).
Isn't there an easy solution for that ?
Following example shows how to test if a view is a redirect:
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptorAdapter() {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
if (modelAndView != null && StringUtils.startsWithIgnoreCase(modelAndView.getViewName(), "redirect:")) {
// handle redirect...
}
}
});
}
}
See: HandlerInterceptorAdapter, StringUtils
Spring MVC Documentation: Intercepting requests with a HandlerInterceptor

Stacktrace of exceptions in Spring Rest responses

I have few Rest web services implemented through Spring. The problem is that if any exception is thrown the webservice returns json object with formatted error message that contains stacktrace. Can I have a single point of handling exceptions, and return my custom json objects with messages that wouldn't contain stacktrace?
I see descriptions for spring mvc but im not really using that for building my views etc.
I know it's too late, but just pointing out some solutions that may help others!
case 1: if you're using application.properties file, add following line to your properties file.
server.error.include-stacktrace=on_trace_param
case 2: if you're using application.yml file, add following line to your yml file.
server:
error:
include-stacktrace: on_trace_param
case 3: In case, none of them works, try following changes:
Try to suppress the stack trace by overriding fillInStackTrace method in your exception class as below.
public class DuplicateFoundException extends RuntimeException {
#Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
ps1: I referred this article.
Spring provides an out of the box solution to handle all your custom exceptions from a single point. What you need is #ControllerAdvice annotation in your exception controller:
#ControllerAdvice
public class GlobalDefaultExceptionHandler {
#ExceptionHandler(Exception.class)
public String exception(Exception e) {
return "error";
}
}
If you want to go deep into Springs #ExceptionHandler at individual controller level or #ControllerAdvice at global application level here is a good blog.
To handle exceptions thrown from a spring application at a single point, this is the best way to do it. #ControllerAdvice will create an aspect join-point which will intercept all the exceptions with required matching types bound to the corresponding public method.Here, public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException dataIntegrityViolationException,
WebRequest request) is handling DataIntegrityViolationException thrown out of the system at one place.
#ControllerAdvice
public class GlobalControllerExceptionHandler {
private Logger logger = Logger.getLogger(this.getClass());
private HttpHeaders header = new HttpHeaders();
#Autowired
private MessageSource messageSource;
public GlobalControllerExceptionHandler() {
header.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
}
/**
* #param dataIntegrityViolationException
* #param request
* #return
*/
#ExceptionHandler({ DataIntegrityViolationException.class })
public ResponseEntity<?> handleDataIntegrityViolationException(DataIntegrityViolationException dataIntegrityViolationException,
WebRequest request) {
String message = ExceptionUtils.getMessage(dataIntegrityViolationException);
logger.error("*********BEGIN**************DataIntegrityViolationException******************BEGIN*******************\n");
logger.error(message, dataIntegrityViolationException.fillInStackTrace());
logger.error("*********ENDS**************DataIntegrityViolationException*******************ENDS*****************************\n");
return ResponseEntity.status(HttpStatus.CONFLICT).headers(header).body(dataIntegrityViolationException);
}
}

Resources