I am attempting to proxy a streaming HTTP call
by placing a Flux emitting client in a streaming controller method as follows:
#GetMapping(value = "/api/v1/data/flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamDataFlux() {
WebClient cl = WebClient.builder()
.baseUrl("https://example.com")
.build();
return cl.post().uri("/api/events")
.accept(MediaType.TEXT_EVENT_STREAM)
.header(HttpHeaders.CONNECTION, "keep-alive")
.bodyValue("")
.retrieve()
.bodyToFlux(String.class);
}
How do I ensure the client is cancelled if and when the calling response handler is cancelled?
I.e. If I cURL my URL, (/api/v1/data/flux) then CTRL-C that request ... the WebClient remains connected seemingly permanently...
Thx
Related
I have to consume this endpoint: localhost:5100/message/v1?topic=test.state.v2
This endpoint requires a body like this JSON:
{"topic":"test.state.v2"}
When I test this endpoint with this cURL returns correctly the response data.
curl -H "Content-Type: application/json" --request GET --data '{"topic": "test.state.v1"}' http://localhost:5100/test/message/v1
I'm using this client to consume:
public class MyClient {
//imports.. here
private final String dataStoreHostAndPort = "http://localhost:5100";
private finalJSONObject MY_BODY = new JSONObject().put("topic", "test.state.v1");
public MyClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 200)
.followRedirect(true)
.responseTimeout(Duration.ofMillis(200));
webClient = WebClient.builder()
.baseUrl(dataStoreHostAndPort)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
public Mono<String> getMessage() {
return this.webClient
.method(HttpMethod.GET)
.uri(builder -> builder
.path("/message/v1")
.queryParam("topic", "log.state.v1").build())
.body((BodyInserter<?, ? super ClientHttpRequest>) MY_BODY)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class);
}
}
I used practically all forms of body(BodyInserter) and the uri() but nothing worked.
How can I send MY_BODY to Spring Reactive WebClient request in the same way than cURL does?
You can use .body(BodyInserters.fromValue(YOUR_VALUE_HERE))
I am trying to use WebClient to download a file from a external service and return it to the client. In the Rest Controller, I have the following endpoint:
#GetMapping(value = "/attachment/{fileId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public Flux<byte[]> get(#PathVariable String fileId) {
return this.webClient.get()
.uri(builder ->
builder.scheme("https")
.host("external-service.com")
.path("/{fileId}")
.build(fileId)
).attributes(clientRegistrationId("credentials"))
.accept(MediaType.APPLICATION_OCTET_STREAM)
.retrieve()
.bodyToFlux(byte[].class);
}
When I try to hit the endpoint, I get the following error:
Exception Class:class org.springframework.http.converter.HttpMessageNotWritableException
Stack Trace:org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class org.springframework.web.servlet.mvc.method.annotation.ReactiveTypeHandler$CollectedValuesList] with preset Content-Type 'null'
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:317)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:181)
I have tried returning Flux<DataBuffer> instead, but getting the same error message.
I'm using spring-boot-starter-web and spring-boot-starter-webflux version 2.2.4.RELEASE. It is running on a tomcat server.
What am I missing?
Edit:
I'm trying to stream the result to the client without buffering the whole file into memory.
This works for me. Just change from Flux to Mono. Seem there is no converter for Flux<byte[]>
#GetMapping(value = "/attachment/{fileId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public Mono<byte[]> get(#PathVariable String fileId) {
return this.webClient.get()
.uri(builder ->
builder.scheme("https")
.host("external-service.com")
.path("/{fileId}")
.build(fileId)
).attributes(clientRegistrationId("credentials"))
.accept(MediaType.APPLICATION_OCTET_STREAM)
.retrieve()
.bodyToMono(byte[].class);
}
I have an endpoint that makes a request to a server to check the status. I use WebClient with vertex. I create a client like this.
WebClient client = WebClient.create(vertx);
and the request looks like the following
client.get(check.url).send(ar -> {
HttpResponse<Buffer> resp = ar.result();
if (resp != null) {
check.response = resp.bodyAsString();
} else {
check.response = "";
}
logger.debug("Received response from app: {} with result:{} on url:{} : {}",
check.appName, check.result, check.url, check.response);
check.result.complete(ar.succeeded());
});
The problem i have is when the client tries to send the request.
EDIT: I want to mock that client.send() and return my WebClient with the status I want like 200 or 400. Please be advised that I can't access this WebClient as it's declared under the method I want to test.
I have this test right now that I'm trying to fix.
TEST
#Test
public void testWongUrlsAndNormalMode() throws Exception{
when(healthCheckEndpoint.configuration.getMode()).thenReturn(HealthCheckerConfiguration.HealthCheckMode.NORMAL);
when(healthCheckEndpoint.configuration.getHealthCheckUrls()).thenReturn(urls);
Response a = healthCheckEndpoint.checkApplications(); //here is when the client.get(url).send() returns nullPointerException
assertEquals(a.getStatus(), 500);
assertEquals(a.getEntity(), "normal mode no app urls");
}
but as mentioned i get a null pointer exception when the WebClient tries to send the request.
I have also tried to mock the WebClient but I can't mock the send() method as it is a void method
I have a spring boot(version 2.1.8.RELEASE) rest application that contain the following mail functionality from the controller:
#PostMapping(value = "/sendMail1")
public #ResponseBody EmailResponse sendMail1( #RequestBody EmailRequestDto emailRequestDto) {
if (checkAllEmailAddValid(emailRequestDto)) {
sendMailService.sendEmail(emailRequestDto);
//return new ResponseEntity<>("Mail has been sent successfully", HttpStatus.OK);
return new EmailResponse(HttpStatus.OK, "Mail has been sent successfully");
} else {
LOG.error("Email addresse provided is invalid");
//return new ResponseEntity<>("Email address provided is invalid", HttpStatus.BAD_REQUEST);
return new EmailResponse(HttpStatus.BAD_REQUEST, "Email address provided is invalid");
}
}
An method to send mail for the service class is :
#Override
#Async("threadPoolExecutor")
public void sendEmail(EmailRequestDto emailRequestDto) {
LOG.debug("calling method sendMail");
...
}
I have deployed the above rest service on my machine and accessible with the url(http://localhost:9090/email-service/email/sendMail1). However when I use Spring WebClient to call the web service the following error is displayed and the web service is not called at all:
java.lang.IllegalStateException: The underlying HTTP client completed without emitting a response
Please find below my integration test to call the web service:
#Test
public void test() {
EmailRequestDto emailRequestDto = new EmailRequestDto();
emailRequestDto.setMailTo((Arrays.asList("dummy#hotmail.com")));
emailRequestDto.setEmailContent("Dear Sir");
emailRequestDto.setMailFrom("dummy12#hotmail.com");
emailRequestDto.setSubject("Sending Email subject - 17102019-0905");
emailRequestDto.setFileHtml(true);
WebClient webClient = WebClient
.builder()
.baseUrl("http://localhost:9090/email-service/email")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
Mono <EmailResponse> rs = webClient.post().uri("/sendMail1").body(Mono.just(emailRequestDto), EmailRequestDto.class).retrieve().bodyToMono(EmailResponse.class);
System.out.println(rs);
}
When I execute the same functionality with Postman client plugin the web service is called successfully.
Any idea what i am missing here please?
I have a Spring app acting as a passthrough from one app to another, making a http request and returning a result to the caller using WebClient. If the client http call returns a 500 internal server error I want to be able to catch this error and update the object that gets returned rather than re-throwing the error or blowing up the app.
This is my Client:
final TcpClient tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
.doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(readTimeout))
.addHandlerLast(new WriteTimeoutHandler(writeTimeout)));
this.webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(logRequest())
.filter(logResponse())
.filter(errorHandler())
.build();
And this is my error handler. I've commented where I want to modify the result, where ResponseDto is the custom object that is returned from the client call happy path
public static ExchangeFilterFunction errorHandler(){
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
ResponseDto resp = new ResponseDto();
if(nonNull(clientResponse.statusCode()) && clientResponse.statusCode().is5xxServerError()){
//this is where I want to modify the response
resp.setError("This is the error");
}
//not necessarily the correct return type here
return Mono.just(clientResponse);
});
}
How can I achieve this? I can't find any tutorials or any information in the docs to help explain it.
Disclaimer, I'm new to webflux. We're only starting to look at reactive programming