how to get returned value of my controllers from HandlerInterceptor - spring

I'm creating a log manager for my controllers that logs every action in it and returned values
My controllers are defined in this way:
#Controller
#RequestMapping(value="/ajax/user")
public class UserController extends AbstractController{
#RequestMapping(value="/signup")
public #ResponseBody ActionResponse signup(#Valid SignupModel sign) {
ActionResponse response=new ActionRespone();
response.setMessage("This is a test message");
return response;
}
}
and I defined a HandlerInterceptor to log output of each handler:
#Component
public class ControllerInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
LogManager log=new LogManager();
log.setMessage();//I need returned ActionResponse here
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
where I use log.setMessage(); I need my ActionResponse's message (This is a test message) which is returned from signup method
How can I do this?

An interceptor is not the right place to do what you want since it's not capable of getting the return value of the handler.
You can achieve what you wan't without changing any existing code using aspect oriented programming (AOP). For this to work in spring you'll need to include the jars for spring-aop and AspectJ.
Creating the aspect and advice
#Aspect
#Component
public class ActionResponseLoggerAspect {
private static final Logger logger = LoggerFactory.getLogger(ActionResponseLoggerAspect.class);
#AfterReturning(pointcut="execution(* your.package.UserController.*(..)))", returning="result")
public void afterReturning(JoinPoint joinPoint , Object result) {
if (result instanceof ActionResponse) {
ActionResponse m = (ActionResponse) result;
logger.info("ActionResponse returned with message [{}]", m.getMessage());
}
}
}
The afterReturning method will be executed every time a controller method returns.
Enabling #AspectJ Support
Enable AspectJ support by adding this to your XML configuration.
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
For more info see the spring docs.

Related

Get Request/Response Body&Header in Spring AOP

I want to get request/response body and header within my aspect before and after if it's available or how to get those .
I mean i think with before annotation should be work for request,
with after annotation should be work for response. Can be ?
What I've tried so far :
I tried logbook library it's very complicated for me i could'nt figured it out how to work with that.So i gave up.
The actuator can do trick but I am doing extra work like how many times the endpoints called etc.So therefore i can't use actuator.
Also i tried to get request headers like below at least but i think this headers coming same all the time.I couldn't get httpservletresponse like how httpservetrequest does.
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
then
request.getHeader("date") but what about requestbody ?
how to get requestbody ? responsebody ? repsonseheader ?
My aspect file :
#Aspect
#Component
public class AppAspect implements ResponseInfo{
#Before("execution(#(#org.springframework.web.bind.annotation.RequestMapping *) * *(..))")
public void loggingStartPointRequests(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
}
#After("execution(#(#org.springframework.web.bind.annotation.RequestMapping *) * *(..))")
public void loggingEndPointRequests(JoinPoint joinPoint) throws IOException {
}
}
My Controller Class:
#RestController
public class MainController {
#GetMapping("/people") //
public ResponseEntity<Poeple> getAllPeople(#RequestParam(name = "page", required = false) Integer page,
#RequestParam(name = "size", required = false) Integer size,
#RequestParam(name = "sortBy", required = false) Boolean sortByNameOrEpCount) {
doSomething();
}
}
I had the same problem and if you have your #Aspect annotated with #Component (or any #Autowired candidate) you can simply get the HttpServletRequest like this:
#Aspect
#Component
public class SomeAspect {
#Autowired
HttpServletRequest request;
#Before("...")
public void beforeAdvice(JoinPoint jp){
/* You will have the current request on the request property */
System.out.println(request.getRequestURL());
}
}
I know this is an old question but I hope it'll be helpful.
I think what you need is to implement the interface HandlerInterceptor, it would help you being able to inspect the request and the response. For example:
public class ApiMonitor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// when the client access to your endpoint
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// when you finished your process
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// after you already returned an answer to the client
}
}
If you want to operate with the object that you're returning just before you send it to the client, then you need AOP, yes. That's an example of how I do it to modify an object on certain endpoints just before it's parsed to json.
#Component
#Aspect
public class MyCustomAOPInterceptor {
/**
* These poincuts check the execution of a method in any (*)
* class of my.package.controller and that start with
* get/list/find plus any other word (*) . For example
* my.package.controller.UserController.getUserById()
*/
#Pointcut("execution(* my.package.controller.*.get*(..))")
public void petitionsStartWithGet() { }
#Pointcut("execution(* my.package.controller.*.list*(..))")
public void petitionsStartWithList() { }
#Pointcut("execution(* my.package.controller.*.find*(..))")
public void petitionsStartWithFind() { }
#AfterReturning(pointcut = "petitionsStartWithGet() || petitionsStartWithList() || petitionsStartWithFind()", returning = "result")
public void translateEntities(JoinPoint joinPoint, Object result) {
// do your stuff; result is the object that you need
}
}

How to not use aspect in spring to write the request param and response param to the console

I found the class InvocableHandlerMethod.invokeForRequest will get the request param from request and invoke the controller class to get the return value.
What should I modify the method to write the params to the console?
I want to extends ServletInvocableHandlerMethod and override the method invokeForRequest but I can't call getMethodArgumentValues because it is private.should I copy the class of ServletInvocableHandlerMethod and InvocableHandlerMethod to modify the private method? Or is there have another way to log the request and response params without aspect?
Just create interceptor
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoggingInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// log here
return true; // let go further
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// log here
}
}
and register it
// example for Spring MVC annotation-based configuration
public class YourWebConfig extends WebMvcConfigurer {
...
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor());
}
...
}

Pre-conditions within #RequestMapping method?

I don't know how to redirect user if they do not meet certain preconditions for a #RequestMapping.
I have a simple form that after completion sends the user to "/secondForm" which is unrelated to "/firstForm", how can I restrict access to "/secondForm", if first form has not been completed?
What makes this more difficult for me there is a controller in the middle.
firstForm --- (submit)---> emailController ----(redirect)----> secondForm
If you want to redirect a user to another page when certain conditions are met, you can use an interceptor. Example interceptor class:
#Component
public class RedirectInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) {
if (request.getRequestURI().contains("secondForm") && modelAndView.getModel().get("someBoolean") == false {
try {
response.sendRedirect("/firstForm");
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
}
}
And register it in configuration class:
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
RedirectInterceptor redirectInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(redirectInterceptor);
}
}
If you prefer xml configuration over java then you can alternatively use this in your spring config xml:
<mvc:interceptors>
<bean id="redirectInterceptor" class="path.to.your.interceptor.RedirectInterceptor"/>
</mvc:interceptors>
Found an additional way:
Using FlashAttribute, by assigning before the redirect in emailController that sets a value.
In applicationController, by using an if statement if there isn't a FlashAttribute then redirect is called to the root of the application.

OncePerRequestFilter - handling exceptions annotated with #ResponseStatus

I'm looking for a way to log all my requests and responses in the database (1 record = 1 request + 1 response).
My use case in details:
Log record in database with request URL, params, IP, start date etc.
Update database record (when request finish) and save response,
exceptions, end date etc.
I'm trying to do with custom OncePerRequestFilter and it work's almost OK. But I have problem with handling exceptions annotated with annotation #ResponseStatus. This kind of exceptions (thrown in controllers) I can't catch in my custom doFilter method. Do you know any way to capture these exceptions in filter? Unless I should do this in some other way?
AuditFilter:
#Component
public class AuditFilter extends OncePerRequestFilter {
private Logger logger = Logger.getLogger(AuditFilter.class.getName());
private RequestAuditRepository repository;
AuditFilter(RequestAuditRepository repository) {
this.repository = repository;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
private void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain)
throws ServletException, IOException {
RequestAuditLog requestAuditLog = new RequestAuditLog();
String catchedExceptionMsg = null;
try {
beforeRequest(requestAuditLog, request);
filterChain.doFilter(request, response);
}
catch (Exception e) {
// Not called when exception with #ResponStatus annotation throwed
catchedExceptionMsg = e.getMessage();
throw e;
}
finally {
afterRequest(requestAuditLog, catchedExceptionMsg, request, response);
response.copyBodyToResponse();
}
}
...
}
BadRequestException:
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
I think the BadRequestException is handled even before your custom filter gets triggered and therefore you can't catch this exception in your filter.
What you could do is that you write your own ExceptionHandler additionally to your filter and log your stuff there.
#ControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(BadRequestException.class)
public void handleError(BadRequestException ex) {
// do your stuff here
}
}

How to change the posted values with a spring mvc interceptor

Does anyone know how to change the posted values with a spring mvc interceptor ? I have seen some examples but none about this subject. I know how to get them but i don't know how to modify them.
#Component
public class CultureInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
}
#Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
// we get the posted values
String culture = request.getParameter("culture");
String a = request.getParameter("a");
String b = request.getParameter("b");
System.out.println(String.format("[CultureInterceptor culture=%s, a=%s, b=%s]", culture, a, b));
if (culture != null && a != null && b != null && "fr-FR".equals(culture)) {
a = a.replace(",", ".");
b = b.replace(",", ".");
}
System.out.println(String.format("[CultureInterceptor culture=%s, a=%s, b=%s]", culture, a, b));
return true;
}
Above, I have created a copy of posted values [a] and [b] but i haven't modified them in the request. Any idea to do that ?
I answer my own question. In fact it is rather complex and it took me some time to find a working solution.
First, I created a filter in a Spring configuration class (Spring Boot environment exactly) :
#Configuration
#ComponentScan({ "istia.st.springmvc.config", "istia.st.springmvc.controllers", "istia.st.springmvc.models" })
#EnableAutoConfiguration
public class Config extends WebMvcConfigurerAdapter {
#Bean
public Filter cultureFilter() {
return new CultureFilter();
}
}
Here we declare a filter that will (by default) filter every request before it attains the final handler. Then I created the filter :
public class CultureFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// next handler
filterChain.doFilter(new CultureRequestWrapper(request), response);
}
}
[OncePerRequestFilter] is a Spring class. The trick is to replace the actual request with a new one [CultureRequestWrapper(request)]. Then I created the CultureRequestWrapper :
public class CultureRequestWrapper extends HttpServletRequestWrapper {
public CultureRequestWrapper(HttpServletRequest request) {
super(request);
}
#Override
public String[] getParameterValues(String name) {
// posted values a et b
if (name != null && (name.equals("a") || name.equals("b"))) {
String[] values = super.getParameterValues(name);
String[] newValues = values.clone();
newValues[0] = newValues[0].replace(",", ".");
return newValues;
}
// other cases
return super.getParameterValues(name);
}
}
I redefined the [getParameterValues] of [HttpServletRequest] but it depends on the final servlet that will manage the request. We have to redefine the
[HttpServletRequest] methods used by this servlet.
You shouldn't be changing anything in the HttpServletRequest as it should represent the request as it came from the client. The construct that is used for scenarios such as yours, is HttpServletRequestWrapper.
What you should do is extend the HttpServletRequestWrapper, override the getParameter method where you can apply your param change logic, and forward your wrapped request further down the forwarding chain.
This link can be of help to you, note that I don't think that this will work in an interceptor, and a filter is a right place to handle it, but you might try
If what you want is modify one modelAttribute that you render in the modelAndAttribute you can use his own annotation.
#ModelAttribute("here you put your entity name")
public Entity bindModel(final HttpServletRequest request) throws CandidacyException {
String foo = request.getParameter("foo");
foo = foo.concat("add new data");
Entity entity = new Entity();
entity.setFoo(foo);
return entity;
}

Resources