spring integration dsl http outboundgateway - spring

We are trying to call a REST API using HTTP OutboundGateway using DSL. We are able to make the call using both GET and POST and getting the response as expected. However we couldnt figure a way to pass http headers while making this call using DSL. There are quite a lot of articles about XML approach but couldnt find out documentation with DSL
return IntegrationFlows.from("FileContentChannel")
.handle(Http.outboundGateway("http://host:port/paymentinfo/")
.charset("UTF-8")
.httpMethod(HttpMethod.GET)
.headerMapper(headers)
.expectedResponseType(String.class))
.channel(MessageChannels.queue("APIResponseChannel"))
.get();
We tried with DefaultHttpHeaderMapper as well but it didnt work. Can you please guide us on this?
Thanks to Gary..it worked with this
Update1
return IntegrationFlows.from("FileContentChannel")
.handle(Http.outboundGateway("http://host:port/paymentinfo/")
.charset("UTF-8")
.httpMethod(HttpMethod.GET)
.mappedRequestHeaders("pay*")
.headerMapper(headerMapper())
.expectedResponseType(String.class))
.channel(MessageChannels.queue("APIResponseChannel"))
.get();
#Bean
HeaderMapper headerMapper() {
DefaultHttpHeaderMapper headerMapper = new DefaultHttpHeaderMapper();
String[] headerNames = {"payment-hdr1","payment-hdr2"};
headerMapper.setOutboundHeaderNames(headerNames);
headerMapper.setUserDefinedHeaderPrefix("");
return headerMapper;
}

return IntegrationFlows.from("FileContentChannel")
.enrichHeaders(h -> h.header("foo1", "bar")
.header("foo2", "baz"))
.handle(Http.outboundGateway("http://host:port/paymentinfo/")
.charset("UTF-8")
.httpMethod(HttpMethod.GET)
.mappedRequestHeaders("foo*")
.expectedResponseType(String.class))
.channel(MessageChannels.queue("APIResponseChannel"))
.get();
Custom headers will (currently) get an X- prefix.
To avoid that use a customized DefaultHeaderMapper to map the required header patterns and use a userDefinedHeaderPrefix of "".

Related

Spring webClient getResponseBodyAsString() is empty(over spring 5.3.13)

I'm using spring webClient for call some Rest API.
try {
webClient.post().uri("apiUrl")
.bodyValue(someParams)
.retrive()
.awaitBody<SomeResponseClass>()
} catch (ex: WebCleintResponseException) {
ex.reponseBodyAsString
}
This is work right on under spring 5.3.12.
I can take response body in ex.
but over spring 5.3.13. is not work.
resposeBody is empty.
webClient.post().uri("apiUrl")
.bodyValue(someParams)
.awaitExchange { response: ClientResponse ->
if (response.satusCode().is2xxSuccessful)
response.awatieBody<SomeResponseClass>
else if (response.statusCodes().isError)
response.awatieBody<SomeResponseClass>
else throw response.createExceptionAndAwaite
}
This working one over spring 5.3.13.
So...What is different from this? I don't find anything on release note.
https://github.com/spring-projects/spring-framework/releases/tag/v5.3.13
I want to know what is changed.
Thanks guys.
Updated
I found this commit on github.
https://github.com/spring-projects/spring-framework/commit/9197f15a306a497b3aa10c6ed48c581b3e938f58

How to extract content from Mono<List<T>> in WebFlux to pass it down the call chain

I want to be able to extract the List<Payload> from the Mono<List<Payload>> to pass it to a downstream service for processing (or maybe return from the read(RequestParams params) method, instead of it returning void):
#PostMapping("/subset")
public void read(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
....
}
where reader.read(...) is a method on an autowired Spring service utilizing a webClient to get the data from external web service API:
public Mono<List<Payload>> read(String date, String assetClasses, String firmAccounts, String id, String password) {
Flux<Payload> nodes = client
.get()
.uri(uriBuilder -> uriBuilder
.path("/api/subset")
.queryParam("payloads", true)
.queryParam("date", date)
.queryParam("assetClasses", assetClasses)
.queryParam("firmAccounts", firmAccounts)
.build())
.headers(header -> header.setBasicAuth("abc123", "XXXXXXX"))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
System.out.println("4xx error");
return Mono.error(new RuntimeException("4xx"));
})
.onStatus(HttpStatus::is5xxServerError, response -> {
System.out.println("5xx error");
return Mono.error(new RuntimeException("5xx"));
})
.bodyToFlux(Payload.class);
Mono<List<Payload>> records = nodes
.collectList();
return records;
}
Doing a blocking result.block() is not allowed in WebFlux and throws an exception:
new IllegalStateException("block()/blockFirst()/blockLast() are blocking, which is not supported in thread ..." ;
What is a proper way to extract the contents of a Mono in WebFlux?
Is it some kind of a subscribe()? What would be the syntax?
Thank you in advance.
There is no "proper way" and that is the entire point. To get the value you need to block, and blocking is bad in webflux for many reasons (that I won't go into right now).
What you should do is to return the publisher all the way out to the calling client.
One of the things that many usually have a hard time understanding is that webflux works with a producer (Mono or Flux) and a subscriber.
Your entire service is also a producer, and the calling client can be seen as the subscriber.
Think of it as a long chain, that starts at the datasource, and ends up in the client showing the data.
A simple rule of thumb is that whomever is the final consumer of the data is the subscriber, everyone else is a producer.
So in your case, you just return the Mono<List<T> out to the calling client.
#PostMapping("/subset")
public Mono<List<Payload>> read(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
return result;
}
While the following does return the value of the Mono observable in the logs:
#PostMapping("/subset")
#ResponseBody
public Mono<ResponseEntity<List<Payload>>> read1(#RequestBody RequestParams params){
Mono<List<Payload>> result = reader.read(params.getDate(), params.getAssetClasses(), params.getFirmAccounts(), params.getUserId(), params.getPassword());
return result
.map(e -> new ResponseEntity<List<PayloadByStandardBasis>>(e, HttpStatus.OK));
}
the understanding I was seeking was a proper way to compose a chain of calls, with WebFlux, whereby a response from one of its operators/legs (materialized as as a result from a webclient call, producing a set of records, as above) could be passed downstream to another operator/leg to facilitate a side effect of saving those records in a DB, or something to that effect.
It would probably be a good idea to model each of those steps as a separate REST endpoint, and then have another endpoint for a composition operation which internally calls each independent endpoint in the right order, or would other design choices be more preferred?
That is ultimately the understanding I was looking for, so if anyone wants to share an example code as well as opinions to better implement the set of steps described above, I'm willing to accept the most comprehensive answer.
Thank you.

Multiple Mono and common subscriber

I am a newbie to reactive programming in Java. I plan to use spring-webclient instead of restclient as the latter is being decommissioned. I have a situation when I make several http post requests to different endpoints and the response structure is identical. With webclient code as below,
List<Mono<CommonResponse>> monolist = new ArrayList<>();
for(String endpoint : endpoints) {
Mono<CommonResponse> mono = webClient.post()
.uri(URI.create(endPoint))
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(CommonResponse.class);
monolist.add(mono);
}
I get a mono per request. As the response is common, I would like each mono to be subscribed a common method, but how can I distinguish the endpoints, assuming that the response data is not helping.
Can I pass additional arguments to the method while subscribing?
You can do this in following way. If you have many monos you can treat team as flux which actually means that you have many of Mono. Then you can subscribe all of them with single method. To pass to subscribing method some extra arguments like information about endpoint, you can create extra object with additional information.
Flux<ResponseWithEndpoint> commonResponseFlux = Flux.fromIterable(endpoints)
.flatMap(endpoint -> webClient.post()
.uri(URI.create(endpoint))
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(CommonResponse.class)
.map(response -> new ResponseWithEndpoint(response, endpoint)));
...
class ResponseWithEndpoint {
CommonResponse commonResponse;
String endpoint;
public ResponseWithEndpoint(CommonResponse commonResponse, String endpoint) {
this.commonResponse = commonResponse;
this.endpoint = endpoint;
}
}

How to set multiple headers at once in Spring WebClient?

I was trying to set headers to my rest client but every time I have to write
webclient.get().uri("blah-blah")
.header("key1", "value1")
.header("key2", "value2")...
How can I set all headers at the same time using headers() method?
If those headers change on a per request basis, you can use:
webClient.get().uri("/resource").headers(httpHeaders -> {
httpHeaders.setX("");
httpHeaders.setY("");
});
This doesn't save much typing; so for the headers that don't change from one request to another, you can set those as default headers while building the client:
WebClient webClient = WebClient.builder().defaultHeader("...", "...").build();
WebClient webClient = WebClient.builder().defaultHeaders(httpHeaders -> {
httpHeaders.setX("");
httpHeaders.setY("");
}).build();
The consumer is correct, though it's hard to visualize, esp. in that you can continue with additional fluent-composition method calls in the webclient construction, after you've done your work with the headers.
....suppose you have a HttpHeaders (or MutliValue map) holding your headers in scope. here's an example, using an exchange object from spring cloud gateway:
final HttpHeaders headersFromExchangeRequest = exchange.getRequest().headers();
webclient.get().uri("blah-blah")
.headers( httpHeadersOnWebClientBeingBuilt -> {
httpHeadersOnWebClientBeingBuilt.addAll( headersFromExchangeRequest );
}
)...
the addAll can take a multivalued map. if that makes sense. if not, let your IDE be your guide.
to make the consumer clearer, let's rewrite the above as follows:
private Consumer<HttpHeaders> getHttpHeadersFromExchange(ServerWebExchange exchange) {
return httpHeaders -> {
httpHeaders.addAll(exchange.getRequest().getHeaders());
};
}
.
.
.
webclient.get().uri("blah-blah")
.headers(getHttpHeadersFromExchange(exchange))
...
I found this problem came up again for me and this time I was writing groovy directly using WebClient. Again, the example I'm trying to drive is using the Consumer as the argument to the headers method call.
In groovy, the additional problem is that groovy closure syntax and java lambda syntax both use ->
The groovy version is here:
def mvmap = new LinkedMultiValueMap<>(headersAsMap)
def consumer = { it -> it.addAll(mvmap) } as Consumer<HttpHeaders>
WebClient client = WebClient.create(baseUrlAsString)
def resultAsMono = client.post()
.uri(uriAsString).accept(MediaType.APPLICATION_JSON)
.headers(consumer)
.body(Mono.just(payload), HashMap.class)
.retrieve()
.toEntity(HashMap.class)
The java version is here:
LinkedMultiValueMap mvmap = new LinkedMultiValueMap<>(headersAsMap);
Consumer<HttpHeaders> consumer = it -> it.addAll(mvmap);
WebClient client = WebClient.create(baseUrlAsString);
Mono<ResponseEntity<HashMap>> resultAsMono = client.post()
.uri(uriAsString).accept(MediaType.APPLICATION_JSON)
.headers(consumer)
.body(Mono.just(payload), HashMap.class)
.retrieve()
.toEntity(HashMap.class);
In Spring Boot 2.7.5:
webClient
.get()
.uri("blah-blah")
.headers(
httpHeaders -> {
httpHeaders.set("key1", "value1");
httpHeaders.set("key2", "value2");
})

How to write async data to remote endpoint without getting "No suitable writer found exception"?

I have the following controller method:
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, path = "/upload")
public Mono<SomeResponse> saveEnhanced(#RequestPart("file") Mono<FilePart> file) {
return documentService.save(file);
}
which calls a service method where I try to use a WebClient to put some data in another application:
public Mono<SomeResponse> save(Mono<FilePart> file) {
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.asyncPart("file", file, FilePart.class);
bodyBuilder.part("identifiers", "some static content");
return WebClient.create("some-url").put()
.uri("/remote-path")
.syncBody(bodyBuilder.build())
.retrieve()
.bodyToMono(SomeResponse.class);
}
but I get the error:
org.springframework.core.codec.CodecException: No suitable writer found for part: file
I tried all variants of the MultipartBodyBuilder (part, asyncpart, with or without headers) and I cannot get it to work.
Am I using it wrong, what am I missing?
Regards,
Alex
I found the solution after getting a reply from one of the contributes on the Spring Framework Github issues section.
For this to work:
The asyncPart method is expecting actual content, i.e. file.content(). I'll update it to unwrap the part content automatically.
bodyBuilder.asyncPart("file", file.content(), DataBuffer.class)
.headers(h -> {
h.setContentDispositionFormData("file", file.name());
h.setContentType(file.headers().getContentType());
});
If both headers are not set then the request will fail on the remote side, saying it cannot find the form part.
Good luck to anyone needing this!

Resources