grpc status not sent from server interceptor to client - spring

In grpc server interceptor, I want to catch the exceptions and send the full stack trace to the client.
public void onHalfClose() {
try {
super.onHalfClose();
} catch (Exception ex) {
handleException(ex);
throw ex;
}
}
private void handleException(Exception exception) {
var status = Status.fromThrowable(exception);
// status.cause has the full stack trace now
// Add requestHeaders with new metadata.
call.close(status, requestHeaders);
}
On the client-side, I have a ClientInterceptor, which intercepts the OnClose()
#Override
public void onClose(Status status, Metadata requestHeaders) {
// status is different, requestHeaders include the added info
super.onClose(status, requestHeaders);
}
I am able to receive the headers, but not the status. I could copy the entire stack trace as a string in one of the headers, but its has problems of header size limitations of 8K, which is not enough for extra long stack traces I get in my application.

The Status's cause is dropped and not serialized and sent to the client. Only the Status code and its description are sent. So you will never receive the stacktrace.
You can make the augment the Status description with the stacktrace string (Status.augmentDescription) if you want to preserve it and have it sent to the client side.
But anyways, all the Status information is serialized in the response trailer, which needs to conform the 8 KB size limit. No matter where the stacktrace is embedded.

Related

Limit a #SendToUser broadcast on a specific browser tab

I am using STOMP websocket on Springboot and want to limit a broadcast to a particular page. Here is my process:
User fills up a message to a HTML input.
Browser will send the message through STOMP client.
The server receives the message and validates it. If the message is valid, it will broadcast to all tabs handled by user that User has made a message. If it is invalid, it will send back the error message only to that particular browser tab that sent the message and not with other tabs even if those tabs have the same User logged in.
I already made some parts of it work although I couldn't limit sending the error message to a particular tab, it always broadcast the the error message to all tab sharing the same User. Here is my initial code:
#MessageMapping("/api/secure/message")
#SendToUser("/api/secure/broadcast")
public HttpEntity createMessage(Message message, Authentication authentication) throws Exception {
Set<String> errors = TreeSet<String>();
// Process Message message and add every exceptions encountered to Set errors.
boolean valid = (errors.size() > 0);
if(valid) {
// Broadcast to all.
return new ResponseEntity(message, HttpStatus.OK);
}
else {
// Send the message to that particular tab only.
return new ResponseEntity(errors, HttpStatus.UNPROCESSABLE_ENTITY);
}
}
Is this achievable through websocket? Or should I return back to XHR?
With every tab you would be creating a new websocket session, hence your stomp session-id would be different as well. So we can decide on whether to send to a particular session or all sessions for a particular user.
#Autowired
private SimpMessagingTemplate template;
....
#MessageMapping(...)
public void sendMessage(Message<?> message...) {
.....
StompHeaderAccessor headerAccessor =
StompHeaderAccessor.wrap(message);
String sessionId = headerAccessor.getSessionId();
....
if(valid) {
//Not specifying session Id so sends all users of
<user_name>
template.cnvertAndSendToUser(<user_name>,
<destination>, <payload>)
}
else {
SimpMessageHeaderAccessor headerAccessor =
SimpMessagingHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
//This will send it to particular session.
template.convertAndSendToUser(<user_name>,
<destination>, <payload>,
headerAccessor.getMessageHeaders());
}
}
Useful References:
Medium post on sending to particular session.
convertAndSendToUser Documentation
User #Srinivas made a good starting reference point. I have modified the code block in my question with my working code:
// inject the [messagingTemplate] bean.
// class org.springframework.messaging.simp.SimpMessagingTemplate
#Autowired
private SimpMessagingTemplate messagingTemplate;
#MessageMapping("/api/secure/message")
// Remove the #SendToUser annotation and change return type to void.
// #SendToUser("/api/secure/broadcast")
// public HttpEntity createMessage(Message messageā€¦
public void createMessage(Message message, Authentication authentication) throws Exception {
Set<String> errors = TreeSet<String>();
// Process Message message and add every exceptions encountered to Set errors.
boolean valid = (errors.size() > 0);
if(valid) {
// Broadcast to all.
// Instead of returning to send the message, use the [messagingTemplate] instead.
// return new ResponseEntity(message, HttpStatus.OK);
messagingTemplate.convertAndSendToUser("/api/secure/broadcast", errors);
}
else {
// Send the message to that particular tab only.
// Each STOMP WebSocket connection has a unique ID that effectively differentiate
// it to the other browser tabs. Retrieve that ID so we can target that specific
// tab to send our error message with.
// class org.springframework.messaging.simp.stomp.StompHeaderAccessor
StompHeaderAccessor stompHeaderAccessor = StompHeaderAccessor.wrap(message);
String sessionId = stompHeaderAccessor.getSessionId();
// class org.springframework.messaging.simp.SimpMessageHeaderAccessor
// class org.springframework.messaging.simp.SimpMessageType
// class org.springframework.util.MimeType
// class java.nio.charset.StandardCharsets
SimpMessageHeaderAccessor simpHeaderAccessor =
SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
simpHeaderAccessor.setSessionId(sessionId);
simpHeaderAccessor.setContentType(new MimeType("application", "json",
StandardCharsets.UTF_8));
simpHeaderAccessor.setLeaveMutable(true);
// Instead of returning to send the message, use the [messagingTemplate] instead.
// It will ensure that it will only broadcast the message to the specific
// STOMP WebSocket sessionId.
// return new ResponseEntity(errors, HttpStatus.UNPROCESSABLE_ENTITY);
messagingTemplate.convertAndSendToUser(sessionId, "/api/secure/broadcast",
errors, simpHeaderAccessor.getMessageHeaders());
}
}
If you are using #ResponseBody #Valid on your controller method parameter, you'll have to move the logic lines to your ControllerAdvice exceptionHandler().

Get Stack trace from HttpServletRequest in 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;
};
}

Retrieve JMS Headers right after the message is sent without consuming the message

How can I retrieve JMS Message headers after sending the message but without consuming the message ?
this is my message sending code
jmsTemplate.convertAndSend(que, text, message -> {
LOGGER.info("setting JMS Message header values");
message.setStringProperty(RequestContext.HEADER_ID, id);
// LOGGER.info(message.getJMSMessageId()); -- this gives a null value here
return message;
});
the message headers get generated only after the message is posted to the queue so iis there a simple way to retrieve JMS message headers when using MessagePostProcessor?
I've referred the links - here and here but not much of help :(
You can't get the JmsMessageID header until the message is actually sent; the post processor just allows you to modify the converted message JUST BEFORE it is sent.
However, your second link should work ok, since it saves off a reference to the message which you can access later.
EDIT
Confirmed:
#SpringBootApplication
public class So48001045Application {
public static void main(String[] args) {
SpringApplication.run(So48001045Application.class, args).close();
}
#Bean
public ApplicationRunner runner(JmsTemplate template) {
return args -> {
final AtomicReference<Message> msg = new AtomicReference<>();
template.convertAndSend("foo", "bar", m -> {
msg.set(m);
return m;
});
System.out.println(msg.get().getJMSMessageID());
};
}
}
and
ID:host.local-61612-1514496441253-4:1:1:1:1

Spring controller, custom response via HttpServletResponse

I'm trying to write a custom response using HttpServletResponse.getOutputStream() and HttpServletResponse.setStatus(int).
But anything that is an status different from 200 doesn't consideres the response body that I wrote.
I have 2 web applications running on different ports, the application "A" must request data from application "B". For this I created a controller to tunnel all requests on application "A" to application "B".
Example:
#RequestMapping("/tunnel/**")
public void exchange(HttpServletRequest request, HttpServletResponse response) throws IOException {
// my service tunnel the request to another server
// and the response of the server must be replied
ResponseDescriptor tunnelResponse = tunnelService.request(request);
response.setStatus(tunnelResponse.getStatus());
// if the status was different them 200, the next line will not work
response.getOutputStream().write(tunnelResponse.getResponseBodyAsByteArray());
}
Note, I need to response from application A the exact response that come from application B.
You need to catch HttpStatusCodeException to get responses other than 200.
try {
ResponseDescriptor tunnelResponse = tunnelService.request(request);
response.setStatus(tunnelResponse.getStatus());
response.getOutputStream().write(tunnelResponse.getResponseBodyAsByteArray());
} catch (HttpStatusCodeException e) {
response.setStatus(e.getStatusCode().value());
response.getOutputStream().write(e.getResponseBodyAsByteArray());
}
Solved!
I created a #ExceptionHandler and a custom exception TunnelException extends RuntimeException.
So, on my exchange(...) method, I catch RestClientResponseException and throw my own exception encapsulating (in the exception) the Headers, HttpStatus and the ResponseBody byte array.
This is the exception handler:
#ExceptionHandler(TunnelException.class)
public ResponseEntity<?> handleTunnelException(TunnelException ex) throws IOException {
return ResponseEntity
.status(ex.getStatus())
.contentType(ex.getHeaders().getContentType())
.body(ex.getBody());
}

ActiveMQ message ordering - Session rollback for JMSException?

I have a Spring JMS Consumer class that reads messages off a queue (implements SessionAwareMessageListener) and processes them for sending off to a web service. We need to preserve the order in which the messages arrive and are processed, as they contain incremental updates to the same data.
To ensure this, we roll back the Session in the listener in case of any recoverable failure, such as a service timeout, so the same message can be retried again. However, if the message has an invalid format or contains bad data, it is discarded (session is not rolled back).
In case of a JMSException, which is thrown by the message.getText() method, I am not clear if I should roll back the session or not. Can this be considered a recoverable error or should the message be discarded in case this error occurs?
The code looks something like this:
public void onMessage(Message message, Session session) throws JMSException {
try {
String msgText = ((TextMessage) message).getText();
// Processing occurs
// Web service is called
} catch (JMSException jmse) {
// log error
session.rollback(); // Question about this line
} catch (InvalidMessageException ime) {
// log error
// session is NOT rolled back, proceed to next message
} catch (SocketTimeoutException | AnyOtherRecoverableException excp) {
// log error
session.rollback();
}
}
In order to preserve ordering (sequencing), you have to roll back the message, if there are any exceptions because of the JMS provider (MQ server) failures.
Also please find the below text from oracle doc on getText() method:
TextMessage getText() method Throws:
JMSException - if the JMS provider fails to get the text due to some internal error

Resources