In Spring, how do I POST to a URL using WebClient? - spring

Right now, I have a class Message representing a JSON object as so
#Value
#Builder
public class Message {
#JsonProperty("#msgType")
String msgType;
#JsonProperty("#type")
String type;
String category;
#Singular("characteristic") List<CharacteristicItem> characteristic;
#Singular("receiver") List<ReceiverItem> receiver;
Sender sender;
}
I would like to send an instance of the class Message as a JSON to remote URL https:example.com/message. How can I create a method that can POST to this URL with the JSON? Note, I am not creating a mapping, I just want to POST to URL and retrieve the HTTP response and convert to string. I'm thinking of using WebClient. Note, I am only interested in seeing if the response is 200, 400, etc., I don't need to handle the response JSON.

If you want to use WebClient you can write below code to post JSON data to URL and retrieve response status code.
WebClient client = WebClient.builder()
.baseUrl("https://example.com")
.build();
Message m = Message.builder().build();
Mono<HttpStatus> httpStatusMono = client.post().uri("/createMessage")
.body(Mono.just(m), Message.class)
.exchange().map(r -> r.statusCode());
// s.value() will give you response status code
httpStatusMono.subscribe(s -> s.value());
If you dont wan't to use WebClient another alternative is RestTemplate
below is the code to achieve the same thing with RestTemplate
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
ResponseExtractor<Integer> responseExtractor = clientHttpResponse -> {
return Integer.valueOf(clientHttpResponse.getStatusCode().value());
};
Message m = Message.builder().build();
RequestCallback requestCallback = clientHttpRequest -> {
objectMapper.writeValue(clientHttpRequest.getBody(), m);
};
Integer status = restTemplateBuilder.build()
.execute("https://example.com/createMessage",
HttpMethod.POST, requestCallback, responseExtractor);
System.out.println(status);

Related

REST API call from spring boot not working

I am trying to fetch live data from NSE options trading. Below code is not working and the request made is stuck without any response.
Any workaround on this?
public void getLiveBankNiftyData() {
String RESOURCE_PATH = "https://www.nseindia.com/api/option-chain-indices?symbol=BANKNIFTY";
ResponseEntity<Object[]> responseEntity = restTemplate.getForEntity(RESOURCE_PATH, Object[].class);
Object[] objects = responseEntity.getBody();
}
i tried this
// request url
String url = "https://www.nseindia.com/api/option-chain-indices?symbol=BANKNIFTY";
// create an instance of RestTemplate
RestTemplate restTemplate = new RestTemplate();
// make an HTTP GET request
String json = restTemplate.getForObject(url, String.class);
// print json
System.out.println(json);
I found a way out. Instead of using RestTemplate I used WebClient and this solved the issue.

How to return application/pdf through Mono in a Reactive way

I am currently using Spring WebFlux to try build an async end-point, which fetches a PDF from a third-party end-point via Web Client before returning the PDF back to our API consumer. However, I am struggling with returning a Mono<ResponseEntity> with content type application/pdf due to the below exception:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class reactor.core.publisher.MonoMapFuseable] with preset Content-Type 'application/pdf']
Here is controller implementation. My question is:
Is my implementation in the right direction, or would I need to create some sort of converter?
Does Mono<ResponseEntity> even support returning a PDF as a response body?
#RequestMapping(value="/get-pdf", method = RequestMethod.GET)
public Mono<ResponseEntity> getPDFAsync() {
String url = "http://some-end-point";
WebClient client = WebClient.create(url);
return client.get()
.accept(MediaType.APPLICATION_PDF)
.exchangeToMono(response ->
Mono.just(ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF)
.body(response.bodyToMono(ByteArrayResource.class)
.map(byteArrayResource -> byteArrayResource.getByteArray())
)));
}
To download a file reactively, you could supply the file as a Flux<DataBuffer>, where DataBuffer is org.springframework.core.io.buffer.DataBuffer, like this:
// some shared buffer factory.
private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
#RequestMapping(value = "/download",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_PDF_VALUE}
)
public Mono<ResponseEntity<Flux<DataBuffer>>> downloadDocument(
...
) {
return Mono.fromCallable(() -> {
return ResponseEntity.ok(
DataBufferUtils.read(
new File("somepdf.pdf").toPath(),
dataBufferFactory,
8096
))
});
}
Or more specifically, since you seem to be using the WebFlux WebClient, you can forward the response body flux directly to your own response, without having to buffer the complete response first:
#RequestMapping(value = "/download",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_PDF_VALUE}
)
public Mono<ResponseEntity<Flux<DataBuffer>>> downloadDocument(
...
) {
String url = "http://some-end-point";
WebClient client = WebClient.create(url);
return client.get()
.accept(MediaType.APPLICATION_PDF)
.exchange()
.map(response -> response.bodyToFlux(DataBuffer.class))
.map(ResponseEntity::ok);
}
Hint: I hope you are reusing the WebClient instance and not instantiating a new one on each request.
I have found the answer! In short, returning Mono<byte[]>, and add produces = {MediaType.APPLICATION_PDF_VALUE} to #RequestMapping works. See example below.
#RequestMapping(value="/get-pdf", produces = {MediaType.APPLICATION_PDF_VALUE}, method = RequestMethod.GET)
public Mono<byte[]> getPdf() {
String url = "some-end-point";
WebClient client = WebClient.create(url);
return client.get()
.accept(MediaType.APPLICATION_PDF)
.exchangeToMono(response -> response
.bodyToMono(ByteArrayResource.class))
.map(byteArrayResource -> byteArrayResource.getByteArray());
}

Simple logging dump of WebClient request?

I'm trying to use Spring WebClient to make some basic REST API calls. I'm getting an error that the request is malformed, but I can't tell exactly why. Is there any way to easily log the contents of the request (really, just the request body)? Everything I find online is super complicated. Here's what I have:
LinkedMultiValueMap params = new LinkedMultiValueMap();
params.add("app_id", getOneSignalAppId());
params.add("included_segments", inSegment);
params.add("content_available", true);
params.add("contents", new LinkedMultiValueMap() {{
add("en", inTitle);
}});
BodyInserters.MultipartInserter inserter = BodyInserters.fromMultipartData(params);
WebClient client = WebClient.builder()
.baseUrl("https://onesignal.com")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + getOneSignalKey())
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
Mono<NotificationResponse> result = client
.post()
.uri("/api/v1/notifications")
.body(inserter)
.retrieve()
.bodyToMono(NotificationResponse.class);
I just want a string of the JSON that will be inserted into the request body.
You can create your own wrapper/proxy class around the JSON encoder (assuming you're using JSON) and intercept the serialized body before it is sent into the intertubes.
If your request is going to send JSON.
Specifically, you would extend the encodeValue method (or encodeValues in case of streaming data) of Jackson2JsonEncoder (the default encoder). Then you can do with that data what you wish, such as logging etc. And you could even do this conditionally based on environment/profile.
This custom logging-encoder can be specified when creating the WebClient, by providing it as a codec:
CustomBodyLoggingEncoder bodyLoggingEncoder = new CustomBodyLoggingEncoder();
WebClient.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(bodyLoggingEncoder);
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
})
...
I made a blog post about this. You might be able to find the encoder for Multipart data and apply similar principles
For completeness, the encoder might look something like this:
public class CustomBodyLoggingEncoder extends Jackson2JsonEncoder {
#Override
public DataBuffer encodeValue(final Object value, final DataBufferFactory bufferFactory,
final ResolvableType valueType, #Nullable final MimeType mimeType, #Nullable final Map<String, Object> hints) {
// Encode/Serialize data to JSON
final DataBuffer data = super.encodeValue(value, bufferFactory, valueType, mimeType, hints);
// This is your code:
SomethingAmazing.doItWithThisData(extractBytes(data));
// Return the data as normal
return data;
}
private byte[] extractBytes(final DataBuffer data) {
final byte[] bytes = new byte[data.readableByteCount()];
data.read(bytes);
// We've copied the data above to our array, but must reset the buffer for actual usage
data.readPosition(0);
return bytes;
}
}
Hope that helps somehow!

Spring WebClient Post method Body

i'm trying to send a POST request with body data as described here: https://scrapyrt.readthedocs.io/en/stable/api.html#post.
Here's what i've tried to do but it gives me HTTP code 500
String uri = "http://localhost:3000";
WebClient webClient = WebClient.builder()
.baseUrl(uri)
.build();
LinkedMultiValueMap map = new LinkedMultiValueMap();
String q = "\"url\": \"https://blog.trendmicro.com/trendlabs-security-intelligence\",\"meta\":{\"latestDate\" : \"18-05-2020\"}}";
map.add("request", q);
map.add("spider_name", "blog");
BodyInserter<MultiValueMap<String, Object>, ClientHttpRequest> inserter2
= BodyInserters.fromMultipartData(map);
Mono<ItemsList> result = webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/crawl.json")
.build())
.body(inserter2)
.retrieve()
.bodyToMono(ItemsList.class);
ItemsList tempItems = result.block();
Here's what i've tried to do but it gives me HTTP code 500
Most likely because you're sending the wrong data in a mixture of wrong formats with the wrong type:
You're using multipart form data, not JSON
You're then setting the request parameter as a JSON string (q)
The JSON string you're using in q isn't even valid (it's at least missing an opening curly brace) - and handwriting JSON is almost universally a bad idea, leverage a framework to do it for you instead.
Instead, the normal thing to do would be to create a POJO structure that maps to your request, so:
public class CrawlRequest {
private CrawlInnerRequest request;
#JsonProperty("spider_name")
private String spiderName;
//....add the getters / setters
}
public class CrawlInnerRequest {
private String url;
private String callback;
#JsonProperty("dont_filter")
private String dontFilter;
//....add the getters / setters
}
...then simply create a CrawlRequest, set the values as you wish, then in your post call use:
.body(BodyInserters.fromValue(crawlRequest))
This is a rather fundamental, basic part of using a WebClient. I'd suggest reading around more widely to give yourself a better understanding of the fundamentals, it will help tremendously in the long run.
For me following code worked:
public String wcPost(){
Map<String, String> bodyMap = new HashMap();
bodyMap.put("key1","value1");
WebClient client = WebClient.builder()
.baseUrl("domainURL")
.build();
String responseSpec = client.post()
.uri("URI")
.headers(h -> h.setBearerAuth("token if any"))
.body(BodyInserters.fromValue(bodyMap))
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
clientResponse.body((clientHttpResponse, context) -> {
return clientHttpResponse.getBody();
});
return clientResponse.bodyToMono(String.class);
}
else
return clientResponse.bodyToMono(String.class);
})
.block();
return responseSpec;
}

Get status response (like 400,500)

I am using the spring Framework which has the header below:
import org.springframework.web.client.RestTemplate;
I want to fetch the status code to write my Logger. How do I get the response from restTemplate?
public boolean performTransition(String transitionId,String jiraId){
JiraID id = new JiraID(transitionId);
JiraTransition transition = new JiraTransition();
transition.setTransition(id);
String transitionUrlFormat = String.format(transitionUrl,jiraId);
RestTemplate template = new RestTemplate();
HttpEntity epicEntityRequest = new HttpEntity(transition,createHttpHeaders());
HttpEntity<String> epicEntityResponse= template.exchange(transitionUrlFormat , HttpMethod.POST, epicEntityRequest, String.class);
//TODO: verify code 204
ResponseEntity<String> responseEntity= (ResponseEntity<String>) epicEntityResponse;
epicEntityResponse.getBody();
//System.out.println("LOG" +responseEntity);
//responseEntity.getStatusCode();
HttpStatus statusCode = responseEntity.getStatusCode();
return true;
}
Also, I want to check for the response code above 400 I want write log.warning().
Question needs more elaboration. Are you meaning something like this:
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, request, String.class);
int statusCode = response.getStatusCode().value();
This gives status code as an int, you can do something like:
if(statusCode > 400){
//Log here
}
The class ResponseEntity can give you the entire HTTP response status code, body and headers.
Ofcourse, you need to initialize restTemplate, either using default:
RestTemplate restTemplate = new RestTemplate();
This uses, default: SimpleClientHttpRequestFactory, or if you want something more configurable you can use: HttpComponentsClientHttpRequestFactory which has many configs, like connection pooling etc, read timeout, connection timeout etc.

Resources