How to properly propagate gRPC errors Spring-boot - spring-boot

I have been following this article.
I am trying to handle gRPC errors gracefully within Spring-boot application, the main goal is to be able to get the error status in the gRPC client.
Following the article above, I am stuck on adding the interceptor for the exceptions. How do I do this in Spring-boot app? Through #Configuration ?
Or in general, how do you accomplish getting a proper error message through the client of a gRPC call?

You can use the interceptor while creation the Server, see its Github code.
Server server = ServerBuilder
.forPort(8080)
.addService(new GreetingService())
.intercept(new ExceptionHandler())
.build();
You could also switch to Lognet's Spring Boot gRPC lib to use #GRpcGlobalInterceptor.
To use other components in your interceptor declare it as #Component.

You can simplify the error handling if you can convert your gRPC application as Spring Boot using yidongnan/grpc-spring-boot-starter, then you can write #GrpcAdvice, similar to Spring Boot #ControllerAdvice as
#GrpcAdvice
public class ExceptionHandler {
#GrpcExceptionHandler(ResourceNotFoundException.class)
public StatusRuntimeException handleResourceNotFoundException(ResourceNotFoundException cause) {
var errorMetaData = cause.getErrorMetaData();
var errorInfo =
ErrorInfo.newBuilder()
.setReason("Resource not found")
.setDomain("Product")
.putAllMetadata(errorMetaData)
.build();
var status =
com.google.rpc.Status.newBuilder()
.setCode(Code.NOT_FOUND.getNumber())
.setMessage("Resource not found")
.addDetails(Any.pack(errorInfo))
.build();
return StatusProto.toStatusRuntimeException(status);
}
}
On the client-side, you can catch this exception and unpack the registerUserResponse as: as
} catch (StatusRuntimeException error) {
com.google.rpc.Status status = io.grpc.protobuf.StatusProto.fromThrowable(error);
RegisterUserResponse registerUserResponse = null;
for (Any any : status.getDetailsList()) {
if (!any.is(RegisterUserResponse.class)) {
continue;
}
registerUserResponse = any.unpack(ErrorInfo.class);
}
log.info(" Error while calling product service, reason {} ", registerUserResponse.getValidationErrorsList());
//Other action
}
I have written a detailed post about gRPC error handling - Getting Error Handling right in gRPC

Related

Handling spring reactor exceptions in imperative spring application

I'm using the webflux in an imperative spring boot application. In this app I need to make rest calls to various backends using webclient and wait on all the responses before proceeding to the next step.
ClassA
public ClassA
{
public Mono<String> restCall1()
{
return webclient....exchange()...
.retryWhen(Retry.backoff(maxAttempts, Duration.ofSeconds(minBackOff))
.filter(this::isTransient)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
return new MyCustomException();
});
}
}
ClassB
public ClassB
{
public Mono<String> restCall2()
{
return webclient....exchange()...
.retryWhen(Retry.backoff(maxAttempts, Duration.ofSeconds(minBackOff))
.filter(this::isTransient)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
return new MyCustomException();
});
}
}
Mono<String> a = classAObj.restCall1();
Mono<String> b = classBObj.restCall2();
ArrayList<Mono<String>> myMonos = new ArrayList<>;
myMonos.add(a);
myMonos.add(b);
try {
List<String> results = Flux.mergeSequential(myMonos).collectList().block();}
catch(WebclientResponseException e) {
....
}
The above code is working as expected. The Webclient is configured to throw error on 5xx and 4xx which I'm able to catch using WebclientResponseException.
The problem is I'm unable to catch any exceptions from the react framework. For example my web clients are configured to retry with exponential backoff and throw exception on exhaustion and I have no way to catch it in my try catch block above. I explored the option to handle that exceptiom in the webclient stream using onErrorReturn but it does not propagate the error back to my subscriber.
I also cannot add the exception to the catch block as it's never being thrown by any part of the code.
Can anyone advice what is the best way to handle these type of error. scenarios. I'm new to webflux and reactive programming.

Quarkus hibernate validations exceptions not showing on the console

I have a simple project using Quarkus 1.4.2. When I use the #Valid annotation, and the validations fail with a status 500, the exception is not show on the console. Only in the Swagger UI. What should I do to print it out on the console?
#ApplicationScoped
public class ProductService {
public void validateProduct(#Valid Product product) {
}
}
The exception that is occurring is:
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint
The error is correct. It is just not shown on the console.
I would expect the error to be logged as it's definitely a usability issue. And I would expect it to be logged on startup when we collect the Hibernate Validator metadata, not for every call.
You could create a reproducer and open a GitHub issue in the Quarkus tracker here.
I'll check it out and see if something needs fixing.
If I understand correctly, you need to use the Validator object in order to catch possible Exceptions:
#Inject
Validator validator;
public void validateProduct(Product product) {
// Should throw an error
Set<ConstraintViolation<Product>> violations = validator.validate(product);
if(violations.isEmpty()) {
return;
}
for (ConstraintViolation<Product> violation : violations) { // or log whole set as json
System.out.println(violation.toString()); //TODO prettify
}
throw new ValidationException(JsonbBuilder.create().toJson(violations));
}
If you get a 500 error, you can now catch it and log.
Or just catch UnexpectedTypeException where you call your service. This might be better.

How to mask sensitive information while logging in spring integration framework

I have requirement to mask sensitive information while logging. We are using wire-tap provided by integration framework for logging and we have many interfaces already designed which logs using wire-tap. We are currently using spring boot 2.1 and spring integration.
I hope that all your integration flows log via the mentioned global single wire-tap.
This one is just a start from another integration flow anyway: it is not just for a channel and logger on it. You really can build a wire-tapped flow any complexity.
My point is that you can add a transformer before logging-channel-adapter and mask a payload and/or headers any required way. The logger will receive already masked data.
Another way is to use some masking functionality in the log-expression. You may call here some bean for masking or a static utility: https://docs.spring.io/spring-integration/reference/html/#logging-channel-adapter
Don't know if this is a fancy approach, but I ended up implementing some sort of "error message filter" to mask headers in case the sensitive one is present (this can be extended to multiple header names, but this gives the idea):
#Component
public class ErrorMessageFilter {
private static final String SENSITIVE_HEADER_NAME = "sensitive_header";
public Throwable filterErrorMessage(Throwable payload) {
if (payload instanceof MessagingException) {
Message<?> failedMessage = ((MessagingException) payload).getFailedMessage();
if (failedMessage != null && failedMessage.getHeaders().containsKey(SENSITIVE_HEADER_NAME)) {
MessageHeaderAccessor headerAccessor = new MessageHeaderAccessor(failedMessage);
headerAccessor.setHeader(SENSITIVE_HEADER_NAME, "XXX");
return new MessagingException(withPayload(failedMessage.getPayload()).setHeaders(headerAccessor)
.build());
}
}
return payload;
}
}
Then, in the #Configuration class, added a way to wire my filter with Spring Integration's LoggingHandler:
#Autowired
public void setLoggingHandlerLogExpression(LoggingHandler loggingHandler, ErrorMessageFilter messageFilter) {
loggingHandler.setLogExpression(new FunctionExpression<Message<?>>((m) -> {
if (m instanceof ErrorMessage) {
return messageFilter.filterErrorMessage(((ErrorMessage) m).getPayload());
}
return m.getPayload();
}));
}
This also gave me the flexibility to reuse my filter in other components where I handle error messages (e.g.: send error notifications to Zabbix, etc.).
P.S.: sorry about all the instanceof and ifs, but at certain layer dirty code has to start.

SpringBoot/Kotlin: Multipart MaxUploadSizeExceededException handler does not fire

Please excuse any horrible mistakes, I literally picked up Kotlin fully and some Spring about a week ago, due to a project requirement. I am trying to create a simple RESTful API with an endpoint that can accept a file via Multipart. Later on, there will be a few pages outside the API that will display HTML, I am using Koreander for that. From what I can see, and based on Java tutorials, exception handling should work like this.
For the API, my defined exception handler for MaxUploadSizeExceededException, however, does not fire at all. My application.kt:
#SpringBootApplication
#EnableConfigurationProperties(StorageProperties::class)
class JaApplication {
#Bean fun koreanderViewResolver(): ViewResolver = KoreanderViewResolver()
}
fun main(args: Array<String>) {
runApplication<JaApplication>(*args)
}
My controller:
#RestController
#RequestMapping("/api")
#EnableAutoConfiguration
class APIController {
#PostMapping(
value = "/convert",
produces = arrayOf(MediaType.APPLICATION_JSON_VALUE)
)
fun convert(#RequestParam("file") multipartFile: MultipartFile): Result {
return Result(status = 0, message = "You have uploaded: ${multipartFile.getOriginalFilename()}.")
}
}
#ControllerAdvice
class ExceptionHandlers {
#ExceptionHandler(MultipartException::class)
fun handleException(e: MultipartException): String = Result(status = 1, message = "File is too large.")
}
}
When I am attempting to post a large file via curl, I get a JSON reply:
curl -F 'file=#path-to-large-file' http://localhost:8080/api/convert
{"timestamp":"2018-11-27T15:47:31.907+0000","status":500,"error":"Internal Serve
r Error","message":"Maximum upload size exceeded; nested exception is java.lang.
IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$Siz
eLimitExceededException: the request was rejected because its size (4294967496)
exceeds the configured maximum (529530880)","path":"/api/convert"}
Is it possible that Tomcat does not pass this exception to Spring? If yes, how may I go about catching this? It also works if I can set the size to unlimited and check for file size at upload time, though I would need to do that before the server starts receiving the file, and I assume by the time I get to the /api/convert endpoint, it is too late.
Thanks for any help.
Found the issue. I'm posting it for anyone else who might have the same issue in the future.
#ControllerAdvice
class ExceptionHandlers():ResponseEntityExceptionHandler() {
#ExceptionHandler(MaxUploadSizeExceededException::class)
fun handleException(e: MultipartException, request:WebRequest): ResponseEntity<Any> {
return handleExceptionInternal(e, Result(message = "File is too large."), HttpHeaders(), HttpStatus.PAYLOAD_TOO_LARGE, request)
}
}

How can I use Spring Integration to only send a message if my transaction finishes successfully?

I am in the process of learning Spring Integration and using it to implement a basic email service in Grails. What I want to be able to do is call my email service but only have the email be sent if the transaction trying to send the email is successful. Although this is being done in Grails, it really shouldn't be different from a regular Spring app except for using the BeanBuilder DSL instead of the XML configuration.
Anyway, here is my configuration for the channel:
beans = {
xmlns integration:'http://www.springframework.org/schema/integration'
integration.channel(id: 'email')
}
Here is my service:
class MailService {
#ServiceActivator(inputChannel = "email")
MailMessage sendMail(Closure callable) {
//sending mail code
}
}
Now what I expect to happen is that when I inject this MailService into another service and call send mail, that will place a message on the email channel, which will only get published if my transaction completes. What leads me to believe this is the section on UserProcess here: http://docs.spring.io/spring-integration/reference/html/transactions.html, which states that a user started process will have all the transactional properties that Spring provides.
I am attempting to test this with an integration test:
void "test transactionality"() {
when:
assert DomainObject.all.size() == 0
DomainObject.withNewTransaction { status ->
DomainObject object = buildAndSaveNewObject()
objectNotificationService.sendEmails(object) //This service injects emailService and calls sendMail
throw new Exception()
}
then:
thrown(Exception) // This is true
DomainObject.all.size() == 0 // This is true
greenMail.receivedMessages.length == 0 // This fails
}
What this does is create and save an object and send emails all within the same transaction. I then throw an exception to cause that transaction to fail. As expected, none of my domain objects are persisted. However, I still receive emails.
I am quite new to Spring Integration and Spring in general, so it's possible I'm misunderstanding how this is all supposed to work, but I would expect the sendMail message to never be placed on the email channel.
It turns out that I don't think Spring Integration is the best way to achieve "only perform on commit" functionality (but if you do, Gary Russell's answer is the way to go.) You should instead use the TransactionSynchronizationManager provided as part of the Spring transaction management framework.
As an example, I created a TransactionService in grails:
class TransactionService {
def executeAfterCommit(Closure c) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
void afterCommit() {
c.call()
}
})
}
}
You can then inject this anywhere you need it and use it like so:
def transactionService
transactionService.executeAfterCommit {
sendConfirmationEmail()
}
I don't know how this would be done in Grails, but in Java, you could use a transaction synchronization factory whereby you can take different actions depending on success/failure...
<int:transaction-synchronization-factory id="syncFactory">
<int:after-commit expression="payload.renameTo('/success/' + payload.name)" channel="committedChannel" />
<int:after-rollback expression="payload.renameTo('/failed/' + payload.name)" channel="rolledBackChannel" />
</int:transaction-synchronization-factory>
The result of the expression evaluation is sent to the channel, where you can have your outbound mail adapter.

Resources