I'm trying to make a service in reactive java,
another service will send me requests.
I catch these requests and want to run methods according to the incoming URI
Here I receive a request from another service and run the desired method:
public Mono<Response> requestResponse(Request message, ByteBuf metadata) {
return controller.startMethod(message)
.onErrorResume(error -> Mono.just(buildResponse(error.getMessage(), message, Status.STATUS_INTERNAL_SERVER_ERROR)))
.switchIfEmpty(Mono.just(buildResponse("NULL", message, Status.STATUS_BAD_REQUEST)))
.doOnNext(logResponse());
}
This is the launch itself, the method is located by the annotation and the http method
public Mono<Response> startMethod(Request message) {
for (Method method : clazz.getDeclaredMethods()) {
if (isNecessaryMethod(method, message)) {
try {
return (Mono<Response>) method.invoke(context.getBean(clazz), initParameters(method, message));
} catch (Throwable e) {
return Mono.error(e);
}
}
}
return Mono.error(new PathNotFound());
}
Subsequently, the method should run, this is an example:
#Request(url = "/save-token", method = POST)
public Mono<Response> saveToken(String token) {
return Mono.empty();
}
I would like to know how to properly process method.invoke(...) so that everything works reactively and correctly
Related
I have implemented the error handling in a filter that looks like this:
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
URI url = request.url();
HttpMethod method = request.method();
return next.exchange(request).flatMap(response -> {
if (response.statusCode().isError()) {
return response.bodyToMono(String.class).flatMap(responseBody -> {
Optional<Exception> exception = errorResponseHandler.handleError(method, response.statusCode(), url, responseBody);
if (exception.isPresent()) {
return Mono.error(exception.get());
} else {
// fallback
return Mono.error(new UnsupportedOperationException("The fallback functionality is still missing"));
}
});
} else {
return Mono.just(response);
}
});
}
This should work fine in the case where the response comes with a body as then the response.bodyToMono(String.class).flatMap(...) is executed. However when the body is empty nothing happens, but what I want is to also deal with the error. It is my understanding that I would do this something like this;
response.bodyToMono(String.class).flatMap(...)
.switchIfEmpty(Mono.error(new UnsupportedOperationException("The body was empty")));
This does not work as the expected type to be returned is Mono instead of Mono.
How can I achieve the handling of errors with and without response body, which is needed to construct to correct exception?
This question brought me onto the right track:
The switchIfEmpty invocation has to come before the flatMap. As there is no body, flatMap is not executed and neither is anything after, therefore the switchIfEmpty has to come first:
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
URI url = request.url();
HttpMethod method = request.method();
return next.exchange(request).flatMap(response -> {
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.switchIfEmpty(Mono.error(new UnsupportedOperationException("The body was empty")));
.flatMap(responseBody -> {
Optional<Exception> exception = errorResponseHandler.handleError(method, response.statusCode(), url, responseBody);
if (exception.isPresent()) {
return Mono.error(exception.get());
} else {
// fallback
return Mono.error(new UnsupportedOperationException("The fallback functionality is still missing"));
}
});
} else {
return Mono.just(response);
}
});
}
I am new with Spring Integration. I was making some tests I realized the behavior of my app changes when the Gateway return void or return String. I'm trying to process the flow in the background (async) meanwhile I return a http message. So I did a async pipeline
#Bean
MessageChannel asyncChannel() {
return new QueueChannel(1);
}
#Bean
public MessageChannel asyncChannel2() {
return new QueueChannel(1);
}
#Bean
public MessageChannel asyncChannel3() {
return new QueueChannel(1);
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
PollerMetadata customPoller() {
PeriodicTrigger periodicTrigger = new PeriodicTrigger(2000, TimeUnit.MICROSECONDS);
periodicTrigger.setFixedRate(true);
periodicTrigger.setInitialDelay(1000);
PollerMetadata poller = new PollerMetadata();
poller.setMaxMessagesPerPoll(500);
poller.setTrigger(periodicTrigger);
return poller;
}
3 Activators
#ServiceActivator(inputChannel = "asyncChannel", outputChannel = "asyncChannel2")
public String async(String message) {
try {
Thread.sleep(5000);
log.info("Activator 1 " + message);
return message;
} catch (InterruptedException e) {
log.error("I don't want to sleep now");
}
return "";
}
#ServiceActivator(inputChannel = "asyncChannel2", outputChannel = "asyncChannel3")
public String async(String message){
log.info("Activator 2 "+ message);
try {
Thread.sleep(2000);
return message;
} catch (InterruptedException e) {
log.error("I don't want to sleep");
}
return "";
}
#ServiceActivator(inputChannel = "asyncChannel3")
public String result(String message) throws InterruptedException {
Thread.sleep(2000);
log.info("Activator 3 " + message);
return message;
}
I receive a message from Controller class
private final ReturningGateway returningGateway;
#PostMapping("/example")
public ResponseEntity post() {
returningGateway.processWhileResponse("Message example");
return ResponseEntity.ok(Map.of("Message","Http Done. Check the logs"));
}
The gateway
#Gateway(requestChannel = "asyncChannel")
public void processWhileResponse(String message_example);
The curious thing is when the gateway returns a void it making the process async so I can see the http message "Http Done. Check the logs" first, then I go to the logs and I see the async execution. but when the gateway returns a String I see the logs first and then the http message.
So I need the gateway returns a value but it keep the async way so I can get a http message
could you give a hand?
Sorry if I'm not using the right term. Thanks
So I need the gateway returns a value but it keep the async way so I can get a http message.
As long as you return some non-async type, it is going to block your code on the gateway call and wait for that return value to come back. Even if your flow behind that gateway is async, it still waits for a reply on the CountDownLatch barrier for replyChannel. In case of void return type there is no reply expectations and gateway exists immediately after sending a request message.
You may consider to have a Future as return type, but it still not clear when you would like to get the value: before returning from your controller method, or it is OK already after.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#async-gateway
I'm trying to implement OAuth2 between my gRPC server and client applications using interceptors, with the following steps:
client app calls a server's gRPC method
server app responds with UNAUTHENTICATED status and a redirect-url in the headers
client obtains the redirect-url, uses it to access Authorization server, and finally gets an access_token
client app calls a server's gRPC method (this time, with access_token)
However, step #4 seems impossible in just one call since the transaction is already closed at step #2. Is there a way to do these 4 steps in just one gRPC service call?
Here is my ClientInterceptor class. I indicated the 4 steps in the code (see code comments).
public class OAuthClientInterceptor implements ClientInterceptor {
#Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
return new CheckedForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
#Override
public void checkedStart(Listener<RespT> responseListener, Metadata headers) {
if (redirectUrl != null) {
try {
//[Step #3] Obtain the access token
accessToken = obtainAccessToken(redirectUrl);
} catch (ConnectException e) {
throw new StatusRuntimeException(Status.UNAUTHENTICATED.withCause(e));
}
}
if (accessToken != null) {
headers.put(Key.of("Authorization",
Metadata.ASCII_STRING_MARSHALLER), "Bearer " + accessToken);
}
if (recursiveCall) {
//[Step #4] PROBLEM: still results to UNAUTHENTICATED
next.newCall(method, callOptions).start(responseListener, headers);
recursiveCall = false;
return;
}
OAuthResponseListener<RespT> oAuthRespListener = new OAuthResponseListener(responseListener);
oAuthRespListener.setUnauthenticatedListener(trailers->{
//[Step #2] Obtain the redirect-url
redirectUrl = trailers.get(Key.of("redirect-url", Metadata.ASCII_STRING_MARSHALLER));
recursiveCall = true;
//[Step #3 and 4] Invoke the retrieval of access token and the 2nd call to gRPC method
checkedStart(responseListener, headers);
});
//[Step #1] Call the gRPC method
delegate().start(oAuthRespListener, headers);
}
};
}
}
I have resolved this issue, but I was hoping to find some built-in authentication mechanism or multiple calls to a blockingStub function to support this oAuth flow, but couldn't find any.
So I resorted to calling blockingStub.invokeServerMethod() at least twice;
to know if it's not yet authenticated, and to be able to obtain redirect-url
to be able to call invokeServerMethod with access_token attached to headers.
Note that I have 30 server methods, and I have to implement the same steps to all of these method calls. To minimize code duplicates for each server method, I created a RetryUtil class which will be called for each server method. Here's what I did:
public class GrpcClient {
public SomeResponse callServerMethod() {
//invoke the method twice
return RetryUtil.retry(() -> blockingStub.invokeServerMethod(), 2);
}
}
public class RetryUtil {
public static <T> T retry(Supplier<T> supplier, int retryCount) {
StatusRuntimeException finalEx = null;
for (int i=0; i<retryCount; i++) {
try {
return supplier.get();
} catch (StatusRuntimeException e) {
if (e.getStatus() != Status.UNAUTHENTICATED) {
throw e;
}
finalEx = e;
}
}
throw finalEx;
}
}
This is current code..
#Bean
public IntegrationFlow flowForHandlingPlainEncryptHistory() {
return IntegrationFlows.from(InputWithPlainEncryptHistory())
.handle(ENCRYPT_HISTORY_SERVICE, EXTRACT_ENCRYPT_HISTORY)
.channel(outputWithPlainStringOfXml()).get();
}
Methods that work in ENCRYPT_HISTORY
The INSERT into the DB and returns a success.
However, in order to improve speed
Unconditional return success, and then try to INSERT a DB.
#Bean
public IntegrationFlow flowForHandlingPlainEncryptHistory() {
return IntegrationFlows.from(InputWithPlainEncryptHistory())
.handle(ENCRYPT_HISTORY_SERVICE, "extractEncryptHistoryReturn")
.channel(outputWithPlainStringOfXml()
.handle(ENCRYPT_HISTORY_SERVICE, "extractEncryptHistoryInsert").get();
}
#Override
public Object extractEncryptHistoryReturn(Object payload) throws Exception {
log.debug("[INFO] extractEncryptHistoryReturn payload : {}", payload.toString());
Map<String, Object> result = initResult();
result.put(Constant.KEY_NAME_RESULT_CODE, Constant.CODE_SUCCESS);
result.put(Constant.KEY_NAME_RESULT_MSG, Constant.MSG_SUCCESS);
return result;
}
#Override
#Transactional
public void extractEncryptHistoryInsert(Object payload) throws Exception {
log.debug("[INFO] extractEncryptHistoryInsert payload : {}", payload.toString());
Map<String, Object> params = initParam(payload);
try {
long headerInfoSeq = insertHeaderInfo(params);
insertHeaderAclList(headerInfoSeq, (String) params.get("ACL_COUNT"), (String) params.get("ACL_LIST"));
} catch (Exception e) {
log.debug("[ERROR] extractEncryptHistory : Insert errors in the header information and acl list. {}", e.toString());
}
}
extractEncryptHistoryInsert payload coming to the method is not of the first payload.
What can I do to fix it?
You need a publish subscribe channel with each handler being a subscriber to it. Add a task executor so the two handlers run in parallel.
You can either have two IntegrationFlows starting with the same channel or use subflows in a single IntegrationFlow bean.
I have a function in controller class that calls a Rest Easy web service which returns a response. I need to unit test that particular function.
public void createOrderRequest(OrderModel orderModel, ResourceBundle resourceBundle, AspectModel aspectModel) {
try {
LOG.debug("Creating order request");
OrderReq orderRequest = new OrderReq();
orderRequest.getHeader().setDestination("http://localhost:8080/middleware/ws/services/txn/getReport");
orderRequest.setUserId("abc");
OrderResp response = (OrderResp) OrderService.getInstance().getOrderService().sendRequest(orderRequest);
if (response.getHeader().getErrorCode() == ErrorCode.SUCCESS.getErrorCode()) {
LOG.debug("Successfully send order request");
orderModel.setErrorDescription("Order successfully sent");
aspectModel.set(orderModel);
}
} catch (Exception ex) {
LOG.error("Error while sending order request: " + ex.getMessage());
}
}
I want to mock the order request object OrderReq and response object OrderResp. My intention is to create mock response for the rest easy web service request. How can I achieve it ?
The most simple way is to move the object creation into a help method which you can override in a test:
public void createOrderRequest(OrderModel orderModel, ResourceBundle resourceBundle, AspectModel aspectModel) {
try {
LOG.debug("Creating order request");
OrderReq orderRequest = createOrderReq();
....
}
}
/*test*/ OrderReq createOrderReq() { return new OrderReq(); }
Using package private (default) visibility, a test can override the method (since they are in the same package).
Alternatively, you can create a factory and inject that.