Spring 5 WebClient- retrieve method not getting recognized with groovy but works with java - spring

I am using groovy, spring5, springboot2 and i was trying to replace Resttemplate with WebClient inorder to makes some synchronous HTTP(REST) calls. Retrieve() in webClient.get().uri("").retrieve() is not getting recognized(compilation error) in .groovy while the same code works in .java
WebClientJava.java
import org.springframework.web.reactive.function.client.WebClient;
public class WebClientJava {
String retriever(){
WebClient webClient = WebClient.create();
String responsess = webClient.get().uri("").retrieve().bodyToMono(String.class).block();
return responsess;
}
}
WebClientGroovy.groovy
import org.springframework.web.reactive.function.client.WebClient
class WebClientGroovy {
String retriever(){
WebClient webClient = WebClient.create()
WebClient.RequestHeadersSpec responsess = webClient.get().uri("").retrieve().bodyToMono(String.class).block()
return responsess
}
}
i expect it work with groovy as it is working with java. Does any one encounter or has any thoughts?

Your problem appears to be caused by a bug in IntelliJ IDEA as the sample project builds successfully on the command line with Maven.
The uri method returns S with S being defined as S extends RequestHeadersSpec<?>. The IDE appears to be unable to cope with this and believes it's dealing with a ? which it treats as java.lang.Object. I would recommend reporting the problem to JetBrains.
In the meantime, you can work around the problem by casting the return from uri:
String responses = ((WebClient.RequestHeadersSpec)webClient.get().uri(""))
.retrieve().bodyToMono(String.class).block()

Related

Spring boot actuator breaks #AutoConfigureMockRestServiceServer

I have a class which builds multiple RestTemplates using RestTemplateBuilder:
private RestTemplate build(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.rootUri("http://localhost:8080/rest")
.build();
}
For my test setup I use #AutoConfigureMockRestServiceServer and mock responses using MockServerRestTemplateCustomizer:
mockServerRestTemplateCustomizer.getServer()
.expect(ExpectedCount.times(2),
requestToUriTemplate("/some/path/{withParameters}", "withParameters"))
.andRespond(withSuccess());
My test passes when I uncomment the spring-boot-actuator dependency in my pom and fails in the other scenario with the following message.
Expected: /some/path/parameter
Actual: http://localhost:8080/rest/pos/some/path/withParameters
I noticed by debugging through MockServerRestTemplateCustomizer that spring-boot-actuator applies a "DelegateHttpClientInterceptor" for supporting their built in metrics for rest templates. However this creates a problem with the following code which I found in RootUriRequestExpectationManager:
public static RequestExpectationManager forRestTemplate(RestTemplate restTemplate,
RequestExpectationManager expectationManager) {
Assert.notNull(restTemplate, "RestTemplate must not be null");
UriTemplateHandler templateHandler = restTemplate.getUriTemplateHandler();
if (templateHandler instanceof RootUriTemplateHandler) {
return new RootUriRequestExpectationManager(((RootUriTemplateHandler) templateHandler).getRootUri(),
expectationManager);
}
return expectationManager;
}
Because as mentioned above spring-boot-actuator registers a "DelegateHttpClientInterceptor" which leads to the above code not recognizing the RootUriTemplateHandler and therefore not matching the request using requestToUriTemplate.
What am I missing here to get this working?
As Andy Wilkinson pointed out, this seems to be a bug in Spring boot. I created an issue with a sample project.

Mockito MockedStatic when() "Cannot resolve method"

I am trying to use Mockito MockedStatic to mock a static method.
I am using mockito-core and mockito-inline version 3.6.0 with Spring Boot and maven.
I can't manage to make the mock work, I have a "Cannot resolve method post" on the Unirest::post that you can see in the code below:
#Test
public void test() {
try (MockedStatic<Unirest> mock = Mockito.mockStatic(Unirest.class)) {
mock.when(Unirest::post).thenReturn(new HttpRequestWithBody(HttpMethod.POST, "url"));
}
}
The Unirest class comes from the unirest-java package.
Did someone encounter this issue already and have a solution?
The method Unirest.post(String url) takes an argument and hence you can't refer to it using Unirest::post.
You can use the following:
#Test
void testRequest() {
try (MockedStatic<Unirest> mockedStatic = Mockito.mockStatic(Unirest.class)) {
mockedStatic.when(() -> Unirest.post(ArgumentMatchers.anyString())).thenReturn(...);
someService.doRequest();
}
}
But keep in mind that you have to mock now the whole Unirest usage and every method call on it as the mock returns null by default.
If you want to test your HTTP clients take a look at WireMock or the MockWebServer from OkHttp. This way you test your clients with real HTTP communication and can test also corner cases like slow responses or 5xx HTTP codes.

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)
}
}

What is the most likely cause of exceptions mysteriously escaping a try-catch block in this case?

I am using a Spring WebClient in a Kotlin project like this:
data class DTO(val name: String)
#Component
class Runner: ApplicationRunner
{
override fun run(args: ApplicationArguments?)
{
try
{
val dto = get<DTO>()
}
catch (e: Exception)
{
println("ERROR, all exceptions should have been caught in 'get' ")
}
}
}
inline private fun<reified TResult: Any> get(): TResult?
{
var result: TResult? = null
try
{
result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting")
.get()
.retrieve()
.bodyToMono<TResult>()
.block()
}
catch (e: Exception)
{
println("WORKS AS EXPECTED!!")
}
return result
}
The client will throw an exception, because the API will return a 404. However the exception is not caught where it should be, namely in the body of the get function, but it is propagated to the outer exception handler.
It is interesting to note that this happens only if the exception is thrown by the WebClient. If I replace the code in the try clause with a simple throw Exception("error"), the exception is caught where it should be.
Similarly, when I change the signature of get to a non-generic inline private fun get(): DTO? the problem also goes away.
For an exception to escape the try-catch block seems like a fundamental bug in the Kotlin tools. On the other hand, the fact that this happens only with the WebClient class indicates that this is a Spring problem. Or, it may be just me, using the tools in a wrong way.
I am really baffled here and have no idea how to proceed. Any ideas on why this might be happening are most welcome. Just for completeness, this is what it looks like in the debugger:
EDIT
The issue goes away after upgrading Spring Boot to 2.0.0.M6, it is still present in M5.
So it seems that this was a Spring issue and not a Kotlin issue. On the other hand it would be still nice to understand how a library that you include can seemingly cause the program to violate the laws of the programming language it is written in.
I tried the code with Spring Boot version 2.0.0.M5 and 2.0.0.M6, and it seems the behavior of the following block is different between those 2 versions:
result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting")
.get()
.retrieve()
.bodyToMono<TResult>()
.block()
somewhere along the chain, on Spring Boot 2.0.0.M5, the WebClientResponseException is returned, on Spring Boot 2.0.0.M6 it is thrown.
If you add a e.printStackTrace() to your outer catch, you will notice that the stack trace is:
java.lang.ClassCastException:
org.springframework.web.reactive.function.client.WebClientResponseException
cannot be cast to com.example.demo.DTO at
com.example.demo.Runner.run(Test.kt:18) at
org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:780)
at
org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:770)
at
org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:760)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:328)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1245)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1233)
at com.example.demo.DemoApplicationKt.main(DemoApplication.kt:10)
So, actually, problem is, the returned WebClientResponseException is tried to be cast to DTO class on the moment of return of the call val dto = get<DTO>(). This means that, when you assign result = ..., there is no type checking done yet. So, if you change your code to, for example, call get<Object>() instead of get<DTO>(), it won't hit any catch blocks.
If you convert it to bytecode in IntelliJ Idea, and then decompile it to Java, you can see this block:
public class Runner implements ApplicationRunner {
public void run(#Nullable ApplicationArguments args) {
try {
Object result$iv = null;
try {
ResponseSpec $receiver$iv$iv = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting").get().retrieve();
Mono var10000 = $receiver$iv$iv.bodyToMono((ParameterizedTypeReference)(new Runner$run$$inlined$get$1()));
Intrinsics.checkExpressionValueIsNotNull(var10000, "bodyToMono(object : Para…zedTypeReference<T>() {})");
result$iv = var10000.block();
} catch (Exception var7) {
String var5 = "WORKS AS EXPECTED!!";
System.out.println(var5);
}
DTO var2 = (DTO)result$iv;
} catch (Exception var8) {
String var3 = "ERROR, all exceptions should have been caught in 'get' ";
System.out.println(var3);
}
}
}
Here you can notice that casting to DTO is done on the point of method return (which is not a return anymore because it is inlined), after the inner catch block: DTO var2 = (DTO)result$iv;. It seems like that's the behavior for the inlined methods with reified type parameters.
This is due to SPR-16025 (see related commit) since the Kotlin extension is using internally the ParameterizedTypeReference variant, which has been fixed in Spring Framework 5.0.1, and transitively in Spring Boot 2.0.0.M6.
Note than if you use bodyToMono(TResult::class.java) with Spring Boot 2.0.0.M5, it will works as expected.

Using Jackson as Jersey client serializer

Is it possible to use Jackson as the serializer/marshaller for JSON data instead of JAXB when using Jersey Client API?
If so how to configure it?
OK, I found it out, it turns out to be quite simple after all:
ClientConfig cc = new DefaultClientConfig();
cc.getClasses().add(JacksonJsonProvider.class);
Client clientWithJacksonSerializer = Client.create(cc);
The JacksonJsonProvider comes from the jackson-jaxrs package.
You may skip the creation of external config and register the provider directly:
Client client = ClientBuilder.newClient().register(JacksonJsonProvider.class)
Solution with JacksonJaxbJsonProvider
Common way how to use Jackson with custom configuration in Jersey client was to use JacksonJaxbJsonProvider for example like this
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(yourObjectMapper());
Client client = ClientBuilder.newClient(new ClientConfig(provider));
Unfortunately in Jersey 2.26 they copied JacksonJaxbJsonProvider class
from com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider artifact (Jackson)
to org.glassfish.jersey.media:jersey-media-json-jackson artifact (Jersey)
and changed package
from com.fasterxml.jackson.jaxrs.json
to org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.
It is still possible to use this approach it's just needed to change JacksonJaxbJsonProvider import.
Apart from JacksonJaxbJsonProvider being now in internal package drawback is also
that you must know on which Jersey version your code runs which might be a problem when different dependencies require different Jersey versions.
Better solution with ContextResolver<ObjectMapper>
Better possibility how to configure Jackson in Jersey client is to use the same way how it is configured in Jersey server which is to create ObjectMapper provider like this:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper = yourObjectMapper();
#Override
public ObjectMapper getContext(Class<?> objectType) {
return objectMapper;
}
}
and use it for example like this:
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(JacksonFeature.class); // usually auto-discovered
clientConfig.register(new ObjectMapperProvider());
Client client = ClientBuilder.newClient(clientConfig);
If you have both the server and the client you can reuse ObjectMapperProvider class.
It seems that this approach works from Jersey version 2.9.
You might also want to try org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider (jackson-jaxrs 1.6.1).
I ran into similar issue, but for me none of the suggestions given here worked.
What worked for me was below piece of code:
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
...
ClientBuilder clientBuilder = ClientBuilder.newBuilder()
clientBuilder.register(JacksonFeature.class);
...
Client client = clientBuilder.build();
The key change was usage of JacksonFeature.class - it comes from jersey-media-json-jackson-x.yy.jar
I got clue to use this solution from this article - http://www.baeldung.com/jersey-jax-rs-client
For jersey 2.22.2 and Jackson 2.7.2 gradle dependencies are:
dependencies {
compile("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.7.2")
compile("org.glassfish.jersey.core:jersey-client:2.22.2")
}
Sample client code is:
final String name = "world";
final Client client = ClientBuilder.newClient().register(JacksonJaxbJsonProvider.class);
final WebTarget target = client.target("http://localhost:8080").path("hello").path(name);
final Message message = target.request().get(Message.class);
System.out.println(message.getWelcomeMessage()); // hello world

Resources