Unale to Call another api which accepts Http2 from Spring-Boot - spring-boot

Unable to invoke Http2 api call from springboot
I am unable to call POST Http request which is having Version HTTP/2. Please help on resolving this issue.
here are my method call for POST Request
public static void main(String[] args) throws Exception {
SpringApplication.run(Http2demoApplication.class, args);
postApiCall("http://192.XXX.0.XXX:XXX/XXX/v1/XXX");
}
public static boolean postApiCall(String url) throws Exception {
HttpClient httpClient = newHttpClient();
HttpRequest httpRequest = HttpRequest.newBuilder()
.version(HttpClient.Version.HTTP_2)
.uri(new URI(url))
.header("Content-Type", "application/json")
.header( "Accept", "application/json" )
.POST(HttpRequest.BodyPublishers.ofFile(new File("C:\\Users\\XXX\\Desktop\\test.json").toPath()))
.build();
httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
return true;
}
application.properties file has
server.http2.enabled=true
ERROR:
Exception in thread "main" java.io.IOException: HTTP/1.1 header parser received no bytes
at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:586)
at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
at com.shabodi.sample.http2demo.Http2demoApplication.postApiCall(Http2demoApplication.java:35)
at com.shabodi.sample.http2demo.Http2demoApplication.main(Http2demoApplication.java:19)
Caused by: java.io.IOException: HTTP/1.1 header parser received no bytes
at java.net.http/jdk.internal.net.http.common.Utils.wrapWithExtraDetail(Utils.java:348)
at java.net.http/jdk.internal.net.http.Http1Response$HeadersReader.onReadError(Http1Response.java:675)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.checkForErrors(Http1AsyncReceiver.java:302)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:268)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:205)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.EOFException: EOF reached while reading
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver$Http1TubeSubscriber.onComplete(Http1AsyncReceiver.java:596)
at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadSubscription.signalCompletion(SocketTube.java:640)
at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:845)
at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowTask.run(SocketTube.java:181)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:303)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:256)
at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.signalReadable(SocketTube.java:774)
at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadEvent.signalEvent(SocketTube.java:957)
at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowEvent.handle(SocketTube.java:253)
at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.handleEvent(HttpClientImpl.java:979)
at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.lambda$run$3(HttpClientImpl.java:934)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:934)

The problem is solved, if I am using this code:
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.HttpProtocol;
importorg.springframework.web.reactive.function.client.WebClient;
WebClient webClient = WebClient
.create()
.mutate()
.clientConnector(new ReactorClientHttpConnect(HttpClient.create()
.protocol(HttpProtocol.H2C)))
.baseUrl(url).build();
String response = webClient.post()
.header("content-type","application/json")
.bodyValue(jasonData)
.retrieve()
.bodyToMono(String.class)
.block();

Related

Unable to save data received by Spring Boot WebCliet

The things I want to do is: to get data from https://jsonplaceholder.typicode.com/ and save those data into my machine. I want to save the posts from this site. I want to do it by Spring Boot WebClient. I have followed several tutorials, articles, and also WebClient documentation. But Unable to save the response in my local database.
The below URL will return one post.
https://jsonplaceholder.typicode.com/posts/1
If I want to return the post as the response of another API it is working fine, but not able to use the inside program. I have tried with WebClient .block(), but it is working for standalone applications but not for web application.
GitLab link of the project
Controller :
#Autowired
private PostService postService;
// working fine.
#GetMapping(value = "posts", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
#ResponseStatus(HttpStatus.OK)
public Flux<Post> findAll() {
return postService.findAll();
}
#GetMapping(value = "postsSave")
#ResponseStatus(HttpStatus.OK)
public String saveAll() {
return postService.saveAll();
}
Service:
#Override
public String saveAll() {
// Post posts = webClient.get()
// .uri("/posts")
// .retrieve()
// .bodyToFlux(Post.class)
// .timeout(Duration.ofMillis(10_000)).blockFirst();
String url = "https://jsonplaceholder.typicode.com/posts/1";
WebClient.Builder builder = WebClient.builder();
Post p = builder.build()
.get()
.uri(url)
.retrieve()
.bodyToMono(Post.class)
.block(); // this line generating error.
postRepository.save(p);
return "saved";
}
Exception StackTrace:
2022-12-07 14:35:44.070 ERROR 6576 --- [ctor-http-nio-3] a.w.r.e.AbstractErrorWebExceptionHandler : [b48b7f19-1] 500 Server Error for HTTP GET "/postsSave"
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.3.9.RELEASE.jar:3.3.9.RELEASE]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ HTTP GET "/postsSave" [ExceptionHandlingWebHandler]
Stack trace:
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.3.9.RELEASE.jar:3.3.9.RELEASE]
at reactor.core.publisher.Mono.block(Mono.java:1680) ~[reactor-core-3.3.9.RELEASE.jar:3.3.9.RELEASE]
at com.quantsys.service.PostService.saveAll(PostService.java:53) ~[classes/:na]
at com.quantsys.controller.PostController.saveAll(PostController.java:26) ~[classes/:an]
But the same code of snippet is working within the Bootstrap class:
#SpringBootApplication
public class QuanrsysPostService {
public static void main(String[] args) {
SpringApplication.run(QuanrsysPostService.class, args);
String url = "https://jsonplaceholder.typicode.com/posts/1";
WebClient.Builder builder = WebClient.builder();
Post p = builder.build()
.get()
.uri(url)
.retrieve()
.bodyToMono(Post.class)
.block(); // working here.
System.out.println(p.toString());
}
}

Spring Webflux Webclient timesout intermittently

I am getting intermittent ReadTimeOut from netty with the below error:
The connection observed an error","logger_name":"reactor.netty.http.client.HttpClientConnect","thread_name":"reactor-http-epoll-3","level":"WARN","level_value":30000,"stack_trace":"io.netty.handler.timeout.ReadTimeoutException: null
One observation we made is this particular endpoint for which we are getting this issue is a POST with no request body. I am now sending a dummy json in body now which the downstream system ignores and now I don't see this error anymore at all.
Below is my code:
protected <T, S Mono<S sendMonoRequest (HttpMethod method,
HttpHeaders headers,
T requestBody,
URI uri, Class < S responseClass)
throws ApiException, IOException {
log.info("Calling {} {} {} {}", method.toString(), uri.toString(), headers.toString(),
mapper.writeValueAsString(requestBody));
WebClient.RequestBodySpec requestBodySpec = getWebClient().method(method).uri(uri);
headers.keySet().stream().forEach(headerKey -> headers.get(headerKey).stream().
forEach(headerValue -> requestBodySpec.header(headerKey, headerValue)));
return requestBodySpec
.body(BodyInserters.fromObject(requestBody != null ? requestBody : ""))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, this::doOn4xxError)
.onStatus(HttpStatus::is5xxServerError, this::doOn5xxError)
.onStatus(HttpStatus::isError, this::doOnError)
.bodyToMono(responseClass);
}
protected WebClient getWebClient () {
HttpClient httpClient = HttpClient.create().tcpConfiguration(
client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
20000).doOnConnected(conn - conn
.addHandlerLast(new ReadTimeoutHandler(20)).addHandlerLast(new WriteTimeoutHandler(20))));
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
return WebClient.builder().clientConnector(connector)
.filter(logResponse())
.build();
}
To resolve the intemrittent timeouts, I have to send a dummy pojo to sendMonoRequest() for request body. Any ideas ?

Spring WebClient Connection Refused Error

When I'm trying to send POST request with WebClient, I get the following error.However if I try to send the request to the same uri using postman it is successful.Please help me out on this I am stuck in this issue and I am new to Spring WebFlux.
threw exception [Request processing failed; nested exception is reactor.core.Exceptions$ReactiveException: io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused: localhost/127.0.0.1:8080] with root cause\n+ Throwable: java.net.ConnectException: finishConnect(..) failed: Connection refused\n at io.netty.channel.unix.Errors.throwConnectException(Errors.java:124)\n at io.netty.channel.unix.Socket.finishConnect(Socket.java:243)\n at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.doFinishConnect(AbstractEpollChannel.java:672)\n at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.finishConnect(AbstractEpollChannel.java:649)\n at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.epollOutReady(AbstractEpollChannel.java:529)\n at
My WebClient Code to Send Post Req:
String success =
webClient
.post()
.uri("/sendRequest")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.headers(
httpHeaders -> {
httpHeaders.add(
headerName, headerValue);
})
.body(Mono.just(messageBody), String.class)
.exchange()
.flatMap(
response -> {
HttpStatus httpStatus = response.statusCode();
if (httpStatus.is2xxSuccessful()) {
System.out.println("Message posted");
} else {
System.err.println("Message FAILED. Status=" + httpStatus.toString());
}
return response.bodyToMono(String.class);
})
.block();
}
//My WebClient Builder Code:
public WebClient getWebClient() {
return WebClient.builder().baseUrl(this.MyUrl()).build();
}

Spring RestTemplate + Basic Authentication + Post with request Body: 500 Internal Server Error

I am looking for a working approach for Rest Client using Spring (5.x) RestTemplate with Basic Authentication + passing Request Body as HTTP Post.
NOTE: the service works fine If I hit request using postman/ other rest client, instead of a java client/ test class.
I am getting 500 Internal Server Error
org.springframework.web.client.HttpClientErrorException: 500 Internal Server Error
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:79)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:777)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:730)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:704)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:459)
at com.xxx.xxx.xxx.utils.Util.updateFlag(Util.java:125)
at com.xxx.xxx.xxx.utils.UtilsImplTest.testUpdateFlag(UtilsImplTest.java:122)
My Test class:
#Test
public void testUpdateFlag() {
Request request = new Request();
request.setUserId("aa");
request.setFlag("Y");
request.setValue("D");
Response response = null;
try {
response = util.updateFlag(request);
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
assertNotNull(response);
}
My Implementation util class: where I am setting Basic Authorization in header.
#Autowired private RestTemplate restTemplate;
private HttpHeaders getHeaders(){
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + <base64_encrypted_password>);//500
// headers.setContentType(MediaType.APPLICATION_JSON); //401
return headers;
}
public Response updateFlag(Request request) throws JsonProcessingException, URISyntaxException {
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpEntity<Request> requestEntity = new HttpEntity<>(request, getHeaders());
Response response = restTemplate.postForObject(url, requestEntity, Response.class);
return response;
}
If I comment out the basic authorization line in getHeaders() method, then it throws 401 Unauthorized, which is fairly logical.
org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:94)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:79)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:777)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:730)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:704)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:459)
at com.xxx.xxx.xxx.utils.Util.updateFlag(Util.java:125)
at com.xxx.xxx.xxx.utils.UtilsImplTest.testUpdateFlag(UtilsImplTest.java:122)
I have tried almost every option suggested over stackoverflow in similar content, unable to identify the exact root cause why setting authorization in header doesn't validate & throws 500 Internal Server Error.
I have spent quite a handful time investigating, with no luck. Appreciate any pointers.

Spring rest template 401 error response

I have a rest controller answering on http://localhost:8080/documents.
I should have an authorization header to call it.
So in my client code i have :
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "myToken");
HttpEntity entity = new HttpEntity(null, headers);
restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
response = restTemplate.exchange("http://localhost:8080/documents", HttpMethod.GET, entity, Document[].class);
Everything works fine.
After that i want to test the errors.
So, i remove the authorization header.
When i test with a tool like postman, i receive the 401 response.
But with my rest template, i only receive an IllegalArgumentException.
I alse have tested the ResponseErrorHandler.
public class MyErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
return false; //i've also tried return true
}
#Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
String theString = IOUtils.toString(clientHttpResponse.getBody());
FunctionalTestException exception = new FunctionalTestException();
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("code", clientHttpResponse.getStatusCode().toString());
properties.put("body", theString);
properties.put("header", clientHttpResponse.getHeaders());
exception.setProperties(properties);
throw exception;
}
}
and in my client i have
restTemplate.setErrorHandler(new MyErrorHandler());
It didn't work.
So my question is how to find my 401 error response using the rest template.
Here is the exception :
java.lang.IllegalArgumentException: invalid start or end
and the stack trace :
sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1455)
sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441)
sun.net.www.protocol.http.HttpURLConnection.getHeaderField(HttpURLConnection.java:2979)
java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:489)
org.springframework.http.client.SimpleBufferingClientHttpRequest.executeInternal(SimpleBufferingClientHttpRequest.java:84)
org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:619)
org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580)
org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:498)
org.boite.dq.steps.UnauthorizedUser.callListCategories(UnauthorizedUser.java:61)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.jbehave.core.steps.StepCreator$ParametrisedStep.perform(StepCreator.java:733)
org.jbehave.core.embedder.PerformableTree$FineSoFar.run(PerformableTree.java:346)
org.jbehave.core.embedder.PerformableTree$PerformableSteps.perform(PerformableTree.java:1088)
org.jbehave.core.embedder.PerformableTree$AbstractPerformableScenario.performRestartableSteps(PerformableTree.java:953)
org.jbehave.core.embedder.PerformableTree$NormalPerformableScenario.perform(PerformableTree.java:992)
org.jbehave.core.embedder.PerformableTree$PerformableScenario.perform(PerformableTree.java:902)
org.jbehave.core.embedder.PerformableTree$PerformableStory.performScenarios(PerformableTree.java:825)
org.jbehave.core.embedder.PerformableTree$PerformableStory.perform(PerformableTree.java:798)
org.jbehave.core.embedder.PerformableTree.performCancellable(PerformableTree.java:422)
org.jbehave.core.embedder.PerformableTree.perform(PerformableTree.java:393)
org.jbehave.core.embedder.StoryManager$EnqueuedStory.call(StoryManager.java:292)
org.jbehave.core.embedder.StoryManager$EnqueuedStory.call(StoryManager.java:266)
java.util.concurrent.FutureTask.run(FutureTask.java:266)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
java.lang.Thread.run(Thread.java:745)
The crash is happening in HttpURLConnection::getHeaderField so I'd suspect that one of your response headers is malformed (not what HttpURLConnection expects it to be). Usually a 401 response comes with a WWW-Authenticate response header pointing the agent to the authentication methods supported by the service. I'd suspect that this header causes the crash.
A bug report in Jersey's issue-tracker shows that HttpURLConnection puts some constraints on the WWW-Authentication header format. In this particular case the value causing a similar crash is oauth_problem=token_rejected. A workaround proposed there is:
Workaround is to send valid header values (spec compliant) or using the ApacheConnector

Resources