I've implemented a service which makes ReST calls out to other services to implement part of its functionality. I'm using the reactive WebClient for this, something like:
webClient.post()
.uri(....)
.contentType(....)
.accept(....)
.header(....)
.syncBody(someRequestObject)
.exchange()
.flatMap(someResponseHandler::handleResponse)
.doOnError(throwable -> {
// do interesting things depending on throwable
})
.retry(1, this::somePredicateDependingOnThrowable);
Now... I handle HTTP statuses in someResponseHandler::handleResponse, but what I really want to know is, what other kinds of exception/error to expect from the exchange() - i.e.
what exceptions/errors do I get if I can't connect to the downstream service at all?
What exceptions/errors do I get if the connection attempt times out?
What exceptions/errors do I get if I can connect but then the request times out before I get a response?
None of these are HTTP status codes, obviously - but I can't find any documentation to tell me what I can look for. Am I just not looking in the right places? I've had a look through the documentation for the reactive WebClient, and I've had a look through the Reactor Netty Reference Guide, but no luck.
For background, this is important because we do HATEOAS-based service discovery - for some of these exceptions, I want to trigger rediscovery, for some of them, I don't.
I recommend testing your code that uses the WebClient to see how it handles the various scenarios you mentioned. You can test your code against something like MockWebServer easily from unit tests. MockWebServer can simulate most of the errors mentioned here.
Having said that, here's what I have seen in my testing when using WebClient with the ReactorClientHttpConnector. Other connectors may throw slightly different exceptions, but will likely share a super class in the exception class hierarchy as those mentioned below.
Unknown host
java.net.UnknownHostException
Connection refused (port not open on server)
java.net.ConnectException (or subclass)
reactor-netty throws io.netty.channel.AbstractChannel$AnnotatedConnectException
Connect timeout
If you have configured a connect timeout, then you will receive java.net.ConnectException (or subclass)
reactor-netty throws io.netty.channel.ConnectTimeoutException
SSL handshake errors
javax.net.ssl.SSLHandshakeException (or subclass)
Request body encoding error
This varies by the encoder being used, but generally will be org.springframework.core.codec.EncodingException (or subclass)
Some encoders also throw java.lang.IllegalStateException if encoding is configured incorrectly
Response body decoding error
This varies by the decoder being used, but generally will be org.springframework.core.codec.DecodingException (or subclass)
Some decoders also throw java.lang.IllegalStateException if decoding is configured incorrectly
Read Timeout
If using reactor-netty, and you configured a io.netty.handler.timeout.ReadTimeoutHandler, then io.netty.handler.timeout.ReadTimeoutException
If you use the .timeout operator somewhere in the reactive stream call chain, then java.util.concurrent.TimeoutException
Write Timeout
If using reactor-netty, and you configured a io.netty.handler.timeout.WriteTimeoutHandler, then io.netty.handler.timeout.WriteTimeoutException
Connection closed by server prematurely (before response completes)
java.io.IOException (or subclass)
reactor-netty throws reactor.netty.http.client.PrematureCloseException
Others
Any exceptions that occur during your someResponseHandler::handleResponse
Related
My spring boot app has a feign client that POST a payload to external endpoint.
There is requirement to audit all the calls, especially when any errors occurs in calling the endpoint (connection, unauthorized etc), after retry are exhausted. To audit,
need some information from original payload
http status
underlying exception message,
backup original payload for reference
endpoint
I tried below approaches (for error handling), but still unable to find a solution that covers all above
feign Fallbackfactory - it has handle to exception, but unable to effectively capture feign response/original payload
fallbackMethod with resilence4j - here i get access to original payload and exception, but again unable to capture actual response status (401, 503 etc)
CustomErrorDecoder - Here able to get access to feign Response, but then have to map response body (any request body/payload). Also have to map exception
Appreciate any suggestion/ advise?
Fault tolerance #Asynchronous-annotation on a REST client cause a core dump on Windows and the following error on MacOS:
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at ./src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 873
Affected version: (Open Liberty 20.0.0.6/wlp-1.0.41.cl200620200528-0414) on OpenJDK 64-Bit Server VM, version 11.0.8+10-LTS
Used features:
[appSecurity-3.0, beanValidation-2.0, cdi-2.0, concurrent-1.0, distributedMap-1.0, ejbLite-3.2, el-3.0, jaxb-2.2, jaxrs-2.1, jaxrsClient-2.1, jdbc-4.2, jndi-1.0, jpa-2.2, jpaContainer-2.2, json-1.0, jsonb-1.0, jsonp-1.1, monitor-1.0, mpConfig-1.4, mpFaultTolerance-2.1, mpHealth-2.2, mpMetrics-2.3, mpOpenAPI-1.1, mpRestClient-1.4, requestTiming-1.0, servlet-4.0, ssl-1.0, transportSecurity-1.0].
To re-produce:
#ApplicationScoped
#RegisterRestClient(baseUri = "https://postman-echo.com")
public interface TestingClient {
#GET
#Asynchronous
#Path("delay/4")
#Consumes(value = MediaType.APPLICATION_JSON)
CompletionStage<Response> doesItCrashWithDelay(String dummyData);
Inject it:
#Inject
#RestClient
private TestingClient testingClient;
Use it:
#GET
#Path("doesitcrash")
public void doesItCrash() {
final Response response = testingClient.doesItCrashWithDelay("dummydata").toCompletableFuture().join();
logger.info(response.readEntity(String.class));
}
Workaround is to have another CDI bean invoking the rest client which have the fault tolerance annotations. But according to this blog post the REST client interface should be able to have fault tolerance annotations:
https://openliberty.io/blog/2020/06/04/asynchronous-programming-microprofile-fault-tolerance.html
Are #Asynchronous allowed on REST client that is already async due to CompletionStage? As mentioned, all other annotations like #Timeout and #Retry seems to work.
First, you are 100% correct that you don't need the #Asynchronous annotation on the MP Rest Client interface method - per the MP Rest Client specification, a return type of CompletionStage makes it asynchronous. If you remove the #Asynchronous annotation, it should work.
While investigating the JVM error message, I came across this helpful post that indicates that this message means the JVM encountered a super large exception - probably a StackOverflowError. My guess is that the error occurred because there are now two different asynchronous mechanisms (MP Rest Client and MP Fault Tolerance) playing together - and probably not playing nicely. Without seeing the exception stack trace, we won't know for sure.
I would first suggest removing the annotation and verifying that that works - that is probably a better workaround than using a separate CDI bean. Next, I would suggest opening an issue at https://github.com/OpenLiberty/open-liberty/issues to investigate a better overall solution.
I created Spring Boot 2.0 demo application which contains two applications that communicate using WebClient. And I'm suffering that they often stop communicating when I use block() method of Flux from the WebClient's response. I want to use List not Flux by some reasons.
The server side application is like this. It just returns Flux object.
#GetMapping
public Flux<Item> findAll() {
return Flux.fromIterable(items);
}
And the client side (or BFF side) application is like this. I get Flux from the server and convert it to List by calling block() method.
#GetMapping
public List<Item> findBlock() {
return webClient.get()
.retrieve()
.bodyToFlux(Item.class)
.collectList()
.block(Duration.ofSeconds(10L));
}
While it works well at first, findBlock() won't respond and timeouts after several times access. When I modify the findBlock() method to return Flux deleting collectList() and block(), it works well. Then I assume that block() method cause this problem.
And, when I modify the findAll() method to return List, nothing changes.
Source code of the entire example application is here.
https://github.com/cero-t/webclient-example
"resource" is the server application, and "front" is the client application. After running both application, when I access to localhost:8080 it works well and I can reload any times, but when I access to localhost:8080/block it seems to work well but after several reloads it won't respond.
By the way, when I add "spring-boot-starter-web" dependency to the "front" applications's (not resource application's) pom.xml, which means I use tomcat, this problem never happens. Is this problem due to Netty server?
Any guidance would be greatly appreciated.
First, let me point that using Flux.fromIterable(items) is advised only if items has been fetched from memory, no I/O involved. Otherwise chances are you'd be using a blocking API to get it - and this can break your reactive application. In this case, this is an in-memory list, so no problem. Note that you can also go Flux.just(item1, item2, item3).
Using the following is the most efficient:
#GetMapping("/")
public Flux<Item> findFlux() {
return webClient.get()
.retrieve()
.bodyToFlux(Item.class);
}
Item instances will be read/written, decoded/encoded on the fly in a very efficient way.
On the other hand, this is not the preferred way:
#GetMapping("/block")
public List<Item> findBlock() {
return webClient.get()
.retrieve()
.bodyToFlux(Item.class)
.collectList()
.block(Duration.ofSeconds(10L));
}
In this case, your front application is buffering in memory the whole items list with collectList but is also blocking one of the few server threads available. This might cause very poor performance because your server might be blocked waiting for that data and can't service other requests at the same time.
In this particular case it's worse, since the application totally breaks.
Looking at the console, we can see the following:
WARN 3075 --- [ctor-http-nio-7] io.netty.util.concurrent.DefaultPromise : An exception was thrown by reactor.ipc.netty.channel.PooledClientContextHandler$$Lambda$532/356589024.operationComplete()
reactor.core.Exceptions$BubblingException: java.lang.IllegalArgumentException: Channel [id: 0xab15f050, L:/127.0.0.1:59350 - R:localhost/127.0.0.1:8081] was not acquired from this ChannelPool
at reactor.core.Exceptions.bubble(Exceptions.java:154) ~[reactor-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
This is probably linked to a reactor-netty client connection pool issue that should be fixed in 0.7.4.RELEASE. I don't know the specifics of this, but I suspect the whole connection pool gets corrupted as HTTP responses aren't properly read from the client connections.
Adding spring-boot-starter-web does make your application use Tomcat, but it mainly turns your Spring WebFlux application into a Spring MVC application (which now supports some reactive return types, but has a different runtime model). If you wish to test your application with Tomcat, you can add spring-boot-starter-tomcat to your POM and this will use Tomcat with Spring WebFlux.
I am trying to create a websocket server using programmatic endpoints with tyrus 1.8.2. I have found that the constructor:
public Server(String hostName,
int port,
String contextPath,
Map<String,Object> properties,
Class<?>... configuration)
does not accept a class implementing ServerEndpointConfig. When I try that it throws a DeploymentException "Class XXX is not ServerApplicationConfig descendant nor has #ServerEndpoint annotation."
Since I am using programmatic endpoints (not annotated), this would seem to imply that I must implement ServerApplicationConfig. That is contrary to the websocket API documentation.
So when I implement ServerApplicationConfig, I no longer get this exception, and the server appears to start without problems, but it returns 404 to what I believe are valid connection attempts (correct host, port, and context path.)
What am I missing?
Additional information: I extended TyrusServerEndpointConfigurator and provided an override for the modifyHandshake() method. The server is returning 404's without ever calling this method.
The problem turned out to be confusion about the way Tyrus constructs the context path. There is a path passed to the Server constructor, and a path returned by the ServerEndpointConfig getPath() method. These are concatenated to form the full context path.
So if you specify "/server" in the Server constructor and "/endpoint" in ServerEndpointConfig.getPath(), the server will accept connection requests on "/server/endpoint".
I have searched for tutorials on this topics, but all of them are outdated. Could anyone provide to me any links, or samples about integrating Spring security into GWT?
First of all, you have to bear in mind that GWT application is turned into javascript running on client-side, so there is nothing you can really do about securing some resources out there. All sensitive information should be stored on server side (as in every other case, not only for GWT), so the right way is to think of Spring Security integration from the point of view of application services layer and integrating that security with communication protocol you use - in case of GWT it is request factory in most cases.
The solution is not very simple, but I could not do it in any better way... any refinement suggestions are welcome.
You need to start with creating GWT ServiceLayerDecorator that will connect the world of request factory with world of Spring. Overwrite createServiceInstance method taking name of spring service class to be invoked from ServiceName annotation value and return instance of this service (you need to obtain it from Spring ApplicationContext):
final Class<?> serviceClass = requestContext.getAnnotation(ServiceName.class).value();
return appContext.getBean(serviceClass);
Also, you need to override superclass invoke(Method, Object...) method in order to catch all thrown runtime exceptions.
Caught exception cause should be analyzed, if it's an instance of Spring Security AccessDeniedException. If so, exception cause should be rethrown. In such case, GWT will not serialize exception into string, but rethrow it again, thus, dispatcher servlet can handle it by setting appropriate HTTP response status code. All other types of exceptions will be serialized by GWT into String.
Actually, you could catch only GWT ReportableException, but unfortunately it has package access modifier (heh... GWT is not so easily extensible). Catching all runtime exceptions is much more safe (althouth not very elegant, we have no choice) - if GWT implementation change, this code will still work fine.
Now you need to plug in your decorator. You can do it easily by extending request factory servlet and defining your's servlet constructor as follows:
public MyRequestFactoryServlet() {
this(new DefaultExceptionHandler(), new SpringServiceLayerDecorator());
}
The last thing - you need to do a dirty hack and overwrite request factory servlet doPost method changing the way how it handles exceptions - by default, exception is serialized into string and server sends 500 status code. Not all exceptions should result in 500 s.c - for example security exceptions should result in unauthorized status code. So what you need to do is to overwrite exception handling mechanism in the following way:
catch (RuntimeException e) {
if (e instanceof AccessDeniedException) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} else {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
LOG.log(Level.SEVERE, "Unexpected error", e);
}
}
Instead of extending classes, you can try to use some 'around' aspects - it is cleaner solution in this case.
That's it! Now you can annotate your application services layer as usual with Spring Security annotations (#Secured and so forth).
I know - it's all complicated, but Google's request factory is hardly extendable. Guys did a great work about communication protocol, but design of this library is just terrible. Of course the client-side code has some limitations (it is compiled to java script), but server-side code could be designed much better...