Get Stack trace from HttpServletRequest in spring boot - spring-boot

I'm currently running a spring boot application.
I am putting this webpage live for multiple people to use. However, this is the first time launching it and I'm unsure if I've worked out all the bugs so I'm trying to find a way to alert myself when something happens.
I have created a custom error controller in Spring. I want this to display a custom error page that just simply tells the user that something is wrong and that we've been made aware of it. This part is working already.
Also with that, I want it to send me an email with the stack trace information. I would love the same page that the default error controller shows, to be sent to me via email. I'm using AWS SES.
Here's my code sample.
#GetMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "404";
}
}
sesSender.sendErrorEmail("strack trace error");
return "error";
}
I found the following question provided 5 years ago Spring Boot Custom Error Page Stack Trace
I'm hoping that since then, they've allowed this functionality.

If you are using Spring Boot you can use this bean and this method ExceptionUtils.getStackTrace(). You will need to import the commons.lang dependency. The method from ExceptionUtils will get you the stack trace you are looking for to send to your email. But this will only work for Serverside Errors. If you want emails sent with a stack trace for front end errors you will need to create your own Dispatcher Servlet and handle it in the DoService Filter
#Bean
HandlerExceptionResolver errorHandler() {
return (request, response, handler, ex) -> {
Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
ModelAndView model = new ModelAndView("error/error");
model.addObject("exceptionType", ex);
model.addObject("handlerMethod", handler);
logger.error(ExceptionUtils.getMessage(ex));
System.out.println("" +
"\n" + ExceptionUtils.getStackTrace(ex));
try {
Utility.sendStackTraceToDeveloper(ex, request, javaMailSender);
} catch (MessagingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return model;
};
}

Related

How to apply #ControllerAdvice from a non-controller?

Is there a way in which we can generate the JSON response from within the code that an API endpoint would show when a controller is called.
I basically want the application to call one of its own controllers and generate the response as a JSON string but not by making an HTTP call.
Calling the controller method and applying an objectmapper on it gives back the results just fine, but when there are exceptions it throws it through. However, I want it to generate the error response that would have generated from #ControllerAdvice exception handlers.
Is it possible to apply the exception handlers defined in #ControllerAdvice to an exception caught in a variable from within the application (Not by making an HTTP call)?
I am building a bulk api so that instead of the user calling an api multiple times, they can just put a list of payloads to this new bulk api, and internally I want to save the responses of each subrequest within the bulk api's db entity itself, and for that I need the exact json responses for each subrequest as if the original api was called.
I can do an exception handling separately for this bulk api code but then that would duplicate the code I already have in my controller advice.
One solution I have come up with is given below. However, if I use it I'll have to remove all my current exception handlers and just use one single exception handler accepting Exception.
Controller Advice:
#ExceptionHandler(ConversionFailedException.class)
public ResponseEntity<ErrorResponse> handleConversionFailedException(final ConversionFailedException e) {
return buildResponseEntity(HttpStatus.BAD_REQUEST, e);
}
#ExceptionHandler(value = {ActionNotFoundException.class})
public ResponseEntity<ErrorResponse> handleActionNotFoundException(ActionNotFoundException e) {
return buildResponseEntity(HttpStatus.NOT_FOUND, e);
}
// someone just added it
#ExceptionHandler(value = {NewRuntimeException.class})
public ResponseEntity<ErrorResponse> handleNewRuntimeException(NewRuntimeException e) {
return buildResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, e);
}
Bulk API code:
try {
}catch(ConversionFailedException e){
return buildResponseEntity(HttpStatus.BAD_REQUEST, e);
}catch(ActionNotFoundException e){
return buildResponseEntity(HttpStatus.NOT_FOUND, e);
}
// they forgot to add it here
My current solution:
Controller Advice:
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAllExceptions(final Exception e) {
return ExceptionHandlerUtil.funcA(e);
}
ExceptionHandlerUtil class (New):
private ResponseEntity<ErrorResponse> funcA(Exception e) {
if (e instanceof ConversionFailedException) {
return buildResponseEntity(HttpStatus.BAD_REQUEST, e);
}
else if (e instanceof ActionNotFoundException) {
return buildResponseEntity(HttpStatus.NOT_FOUND, e);
}
// they can add any new exception here, and both the parts of the code will be able to handle it
}
Bulk API code:
try {
}catch(Exception e){
return ExceptionHandlerUtil.funcA(e);
}

Spring 5 exception handling - ResponseStatusException model

I was reading the article - https://www.baeldung.com/exception-handling-for-rest-with-spring
which says
Spring 5 introduced the ResponseStatusException class.
We can create an instance of it providing an HttpStatus and optionally
a reason and a cause:
I started implementing it , and the code is
custom exception
#ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Actor Not Found")
public class ActorNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
public ActorNotFoundException(String errorMessage) {
super(errorMessage);
}
}
method in service
public String updateActor(int index, String actorName) throws ActorNotFoundException {
if (index >= actors.size()) {
throw new ActorNotFoundException("Actor Not Found in Repsoitory");
}
actors.set(index, actorName);
return actorName;
}
controller
#GetMapping("/actor/{id}")
public String getActorName(#PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Actor Not Found", ex); //agreed it could be optional, but we may need original exception
}
}
repo:
https://github.com/eugenp/tutorials/tree/master/spring-5/src/main/java/com/baeldung/exception
Question:
why ResponseStatusException in controller again has to specify reason - "Actor Not Found" ?, as the service already said - ""Actor Not Found in Repsoitory"
What is the proper way to adapt to ResponseStatusException model?
It looks like a mistake. Ideally the service shouldn't use any HTTP code, so I would remove the annotation in ActorNotFoundException. Everything else seems fine, the exception is caught in the controller and ResponseStatusException is thrown which is good, because it's a proper layer to put HTTP stuff.
Overall it is better to use #ControllerAdvice instead of ResponseStatusException. it gives you a unified exception handling solution. Although it is not a good idea from a design point of view, ResponseStatusException can help you to avoid creating your custom exceptions and use it at the service level to throw in case of an Exception.
to avoid writing the message again you can use the message that is already available in thrown exception:
throw new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage() , ex);
for examples and more info you can refer to the following articles:
Spring Boot Exception Handling — #ControllerAdvice
Spring Boot Exception Handling — ResponseStatusException

Spring http status code - java.lang.IllegalArgumentException: No matching constant

I'm using The spring rest-template for calling the rest URL , I get a response from the server but the http-status code is invalid and the Spring throws , java.lang.IllegalArgumentException: No matching constant . Due to this exception the application is failing , this looks like a bug in the Spring code . Since the http status code received is not in the list spring framework is looking forit failed . Is there a Spring way to handle it ?
Spring seems to use the standard status code in their enum. You can find the status codes here: org.springframework.http.HttpStatus.
Probably the API you're querying is not returning a standard HTTP Status code. Your best bet is to create a custom error handler, like this:
var r = new RestTemplate();
r.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getRawStatusCode() != 550;
}
#Override
public void handleError(ClientHttpResponse response) {
// Do nothing?
}
});
var response = r.exchange("https://httpbin.org/status/550", HttpMethod.GET, null, String.class);
System.out.println(response.getStatusCodeValue());
What we're saying is basically if the status code returned is 550 (not a standard code), we don't want to do anything about it.
Another option you have is, of course, to catch the exception and do something about it.
try {
// Call the API here
} catch (IllegalArgumentException e) {
// Do something about it here...
}

Generic Error page Angular JS + Spring MVC + Controller Advice

similar or related question to this post.
I have written the multiple service calls using angular JS. psudo code here
$http.get('name').success(function(response){
$scope.name= response;
$log.info($scope.rate);
}).error(function() {
});
Now I would like to route to single error page let say error.html for any exception occurs
how would I route to error.html page in Angular JS instead of touching the hundreds of service calls.
I know I would have written/route in the error function below , but I DO NOT want to repeat in reset of my application or hundreds of service calls.
what is the alternate way. please respond
$http.get('indexrates').success(function(response){
$scope.rates= response;
$log.info($scope.rates);
}).error(function() {
$state.go('error');
});
Reference : https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
#ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
#ExceptionHandler(value = Exception.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with #ResponseStatus rethrow it and let
// the framework handle it - like the OrderNotFoundException example
// at the start of this post.
// AnnotationUtils is a Spring Framework utility class.
if (AnnotationUtils.findAnnotation
(e.getClass(), ResponseStatus.class) != null)
throw e;
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}

Session management in gwt

I am using GWT for my client side application. However, I am not sure how I can handle session management. The GWT application resides on one page, all server calls are done via AJAX. If a session expires on the server. let's assume the user didn't close the browser, and sending some request to server using RPC, how could my server notify the application that the session has expired and that the client side portion should show the login screen again?My sample code :
ContactDataServiceAsync contactDataService = GWT
.create(ContactDataService.class);
((ServiceDefTarget) contactDataService).setServiceEntryPoint(GWT
.getModuleBaseURL()
+ "contactDatas");
contactDataService.getContact(2,
new AsyncCallback<ContactData>() {
public void onFailure(Throwable caught) {
//code to show error if problem in connection or redirect to login page
}
public void onSuccess(ContactData result) {
displayContact(result);
}
});
If session expires only it has to show login screen, otherwise it wants to show some error using Window.alert().
How to do this and what are all the codes needed in server side and client side?
You could have the server throw an AuthenticationException to the client in case the user has been logged out.
This will be catched in the callbacks onFailure method, which then can redirect the user to the login-page.
Edit:
AuthenticationException is not a standard exception of course, i was just making an example. It might be best to stick with the standard exceptions.
To try if you caught an specific exception you could use the instanceof operator
public void onFailure(Throwable e) {
if(e instanceof AuthenticationException) {
redirecttoLogin();
}
else {
showError(),
}
}
This does not directly apply to those using RPC, but for those of you who are not using RPC, you should send a HTTP 401 from the server. Then you can check that status code in your RequestBuilder callback.
Client: All Callbacks extend a Abstract Callback where you implement the onFailur()
public abstract class AbstrCallback<T> implements AsyncCallback<T> {
#Override
public void onFailure(Throwable caught) {
//SessionData Expired Redirect
if (caught.getMessage().equals("500 " + YourConfig.ERROR_MESSAGE_NOT_LOGGED_IN)) {
Window.Location.assign(ConfigStatic.LOGIN_PAGE);
}
// else{}: Other Error, if you want you could log it on the client
}
}
Server: All your ServiceImplementations extend AbstractServicesImpl where you have access to your SessionData. Override onBeforeRequestDeserialized(String serializedRequest) and check the SessionData there. If the SessionData has expire then write a spacific error message to the client. This error message is getting checkt in your AbstrCallback and redirect to the Login Page.
public abstract class AbstractServicesImpl extends RemoteServiceServlet {
protected ServerSessionData sessionData;
#Override
protected void onBeforeRequestDeserialized(String serializedRequest) {
sessionData = getYourSessionDataHere()
if (this.sessionData == null){
// Write error to the client, just copy paste
this.getThreadLocalResponse().reset();
ServletContext servletContext = this.getServletContext();
HttpServletResponse response = this.getThreadLocalResponse();
try {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
try {
response.getOutputStream().write(
ConfigStatic.ERROR_MESSAGE_NOT_LOGGED_IN.getBytes("UTF-8"));
response.flushBuffer();
} catch (IllegalStateException e) {
// Handle the (unexpected) case where getWriter() was previously used
response.getWriter().write(YourConfig.ERROR_MESSAGE_NOT_LOGGED_IN);
response.flushBuffer();
}
} catch (IOException ex) {
servletContext.log(
"respondWithUnexpectedFailure failed while sending the previous failure to the client",
ex);
}
//Throw Exception to stop the execution of the Servlet
throw new NullPointerException();
}
}
}
In Addition you can also Override doUnexpectedFailure(Throwable t) to avoid logging the thrown NullPointerException.
#Override
protected void doUnexpectedFailure(Throwable t) {
if (this.sessionData != null) {
super.doUnexpectedFailure(t);
}
}

Resources