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

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

Related

Adding Global Error Handling for Spring Rest Controllers

I have many controllers like this one
#RestController
#RequestMapping("/contracts")
public class ContractsController {
#Autowired
ContractsService service;
#PostMapping("/selectAll")
public WebMessageModel selectAll(#RequestBody ContractFiltersInputModel inputModel) {
return new WebMessageModel(true, service.selectAll(inputModel));
}
}
And I have another controller
#Controller
public class BaseController {
private static Logger logger = LoggerFactory.getLogger(IndexController.class);
#RequestMapping
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String requestURI = request.getRequestURI();
StringBuffer requestURL = request.getRequestURL();
logger.info("-----requestURI => " + requestURI + ", requestURL => " + requestURL);
request.getRequestDispatcher(requestURI).forward(request, response);
logger.info("-----response has been commited");
} catch (Throwable t) {
t.printStackTrace();
request.getRequestDispatcher("/handleException").forward(request, response);
}
}
}
I need all incoming requests to go through this BaseController in order to make one global TRY-CATCH block. How can I implement that? Is this approach really good idea? Maybe there are some other awesome aproaches?
If you want to intercept every request to your controllers endpoint before it enters the controller method, you would need to implement a filter. You may go through this tutorial to understand how to implement a filter.
If you want to catch all the exceptions that result out of requests to your controller endpoints (the exception could have been thrown anywhere - controller, service, repository etc) at one place, then you should implement ExceptionHandlers within a ControllerAdvice. A simple example would be like this:
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(MismatchedInputException.class)
public ResponseEntity<Void> handleMismatchedInputException(MismatchedInputException e) {
return ResponseEntity.status(BAD_REQUEST).build();
}
#ExceptionHandler(InvalidFormatException.class)
public ResponseEntity<Void> handleInvalidFormatException(InvalidFormatException e) {
return ResponseEntity.status(UNPROCESSABLE_ENTITY).build();
}
}
The above will make sure any exception that's specified in the exception handler will be caught here so that exception response from your REST API can be streamlined. More on the same here.
You can use filters to execute some logic before or after requests. In your case a global error handling would be more helpful:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
This snippet is taken from here.
What you looking for is #ControllerAdvice. Here's how to use it http://blog.codeleak.pl/2013/11/controlleradvice-improvements-in-spring.html

Spring MVC - ExceptionHandler

I am using Spring Boot 1.5.7.
I have an ExceptionHandler for my exception that returns a ResponseEntity
#ExceptionHandler(MyException.class)
public ResponseEntity<ResponseExceptionEntity> handleException(MyException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ResponseExceptionEntity(e));
}
This works well in situations where the exception occurs during an api call returning a ResponseEntity / #ResponseBody (JSON/XML response)
I would like to return a ModelAndView in situations where the exception occurs during a request where HTML is returned. In my situation all Controllers are annotated with #Controller and not #RestController
how can I write a ExceptionHandler for both cases (api/html)?
Or How can I determine what the resolved view is for a controller method?
Or how can I determine what the return type of the Controller Method
is?
Or How can I determine what the resolved view is for the controller
method?
I've tried the suggestion in this answer, but it doesn't return a JSON response when the Controller Method is annotated with #ResponseBody.
https://stackoverflow.com/a/32071252/461055
Yes, you can write two exception handler to handle api and html request exception. Here is sample code to illustrate the solution.
#ControllerAdvice(annotations = RestController.class)
#Order(1)
class RestExceptionHandler {
#ExceptionHandler(MyException.class)
#ResponseBody
ResponseEntity<ErrorResponse> exceptionHandler() {
....
}
}
#ControllerAdvice(annotations = Controller.class)
#Order(2)
class ExceptionHandler {
#ExceptionHandler(Exception.class)
public ModelAndView handleError500(HttpServletRequest request, HttpServletResponse response, Exception ex) {
ModelAndView mav = new ModelAndView("error");
mav.addObject("error", "500");
return mav;
}
}

How to send HTTP OPTIONS request with body using Spring rest template?

I am trying to call a RESTfull web service resource, this resource is provided by a third party, the resource is exposed with OPTIONS http verb.
To integrate with the service, I should send a request with a specific body, which identities by a provider, but when I did that I got a bad request. After that I trace my code then I recognized that the body of the request is ignored by rest template based on the below code:
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
my question, is there a standard way to override this behavior or I should use another tool?
The code you've pasted is from
SimpleClientHttpRequestFactory.prepareConnection(HttpURLConnection connection, String httpMethod)
I know because I've debugged that code few hours ago.
I had to do a HTTP GET with body using restTemplate. So I've extend SimpleClientHttpRequestFactory, override prepareConnection and create a new RestTemplate using the new factory.
public class SimpleClientHttpRequestWithGetBodyFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
if ("GET".equals(httpMethod)) {
connection.setDoOutput(true);
}
}
}
Create a new RestTemplate based on this factory
new RestTemplate(new SimpleClientHttpRequestWithGetBodyFactory());
A test to prove the solution is working using spring boot (#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT))
public class TestRestTemplateTests extends AbstractIntegrationTests {
#Test
public void testMethod() {
RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestWithBodyForGetFactory());
HttpEntity<String> requestEntity = new HttpEntity<>("expected body");
ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:18181/test", HttpMethod.GET, requestEntity, String.class);
assertThat(responseEntity.getBody()).isEqualTo(requestEntity.getBody());
}
#Controller("/test")
static class TestController {
#RequestMapping
public #ResponseBody String testMethod(HttpServletRequest request) throws IOException {
return request.getReader().readLine();
}
}
}

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

415 error while ajax post in spring rest

after solving this Do we have to have to post json object with exactly same fields as in pojo object in controller? im getting 415 error while posting from AJAX ,I m using spring rest . And yes i have seen other similar questions but non of them solved my problem
controller:
#RequestMapping(value = "/createTest", method = RequestMethod.POST )
public #ResponseBody String createTest(#RequestBody TestJsonDTO testJson)
throws JsonProcessingException, IOException {
TestSet test = new TestSet();
//................
AJAX:
function createTest() {
$.ajax({
type : 'POST',
url : "http://localhost:8085/annotationBased/admin/createTest",
dataType : "json",
accept:"application/json",
contentType : "application/json",
data : testToJSON(),
success : function() {
alert("success")
},
complete : function(){
findAllTEst()
alert("OK")
},
});
function testToJSON() {
listOfQuestionForTest = questionToAdd;
return JSON.stringify({
"testSet" : {name : $('#testname').val(),
fullmark : parseInt($('#fullmark').val()),
passmark : parseInt($('#passmark').val())},
"questionsInTest" : listOfQuestionForTest
// "testDate":$('#testDate').value()
})
}
and i have added those class u suggested.
You're getting a 415 status code because the server is sending html in the response, while your client expects json.
This might indicate that a server-side exception occured. In such a case, application servers send back a html response.
You have to either make the server respond with json, even if an exception has occured, or let the client handle not only json responses, but also html ones.
I recommend you take the first approach:
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler(Exception.class)
public ErrorResponse handleException(Exception ex) {
ErrorResponse err = new ErrorResponse();
err.setStatusCode(/* 4XX or 500, depending on exception type */);
err.setERrorMessage(ex.getMessage());
return err;
}
}
public class ErrorResponse {
private int statusCode;
private String errorMessage;
// getters and setters or make the fields public
}
A #ControllerAdvice is like a Spring controller, except that it works for every request. #ExceptionHandler tells Spring to intercept exceptions of the specified type and run the code within the annotated method.
Depending on the type of the exception, you should set the right status code in the ErrorResponse object you'll be returning. This is a very basic example, you can also extend from default Spring exception resolvers and overwrite the default behavior. Please refer to this article for further details.
EDIT:
Another thing you could try is to force response's Content-Type to be always application/json, no matter the http stastus returned. You can do this by adding an interceptor in the class where you configure message converters and JSON serialization/deserialization properties:
#Configuration
public class ServiceContext
extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = this.getMappingJackson2HttpMessageConverter();
converters.add(converter);
}
#Bean
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = this.getObjectMapper();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
return mappingJackson2HttpMessageConverter;
}
#Bean
public ObjectMapper getObjectMapper() {
JsonFactory jsonFactory = new JsonFactory();
ObjectMapper objectMapper = new ObjectMapper(jsonFactory);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // this is what you need
objectMapper.setSerializationInclusion(Include.NON_NULL); // this is to not serialize unset properties
return objectMapper;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
ResponseHeadersInterceptor headersInterceptor = this.getResponseHeadersInterceptor();
registry.addInterceptor(headersInterceptor).addPathPatterns("/**");
}
#Bean
public ResponseHeadersInterceptor getResponseHeadersInterceptor() {
return new ResponseHeadersInterceptor();
}
}
With ResponseHeadersInterceptor being as follows:
public class ResponseHeadersInterceptor
extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
response.setContentType(MediaType.APPLICATION_JSON_VALUE + "; charset=UTF-8");
}
}
This way, the server always responds JSON. If you still get 404 or 415, no doubt it's due to some error in the client side.

Resources