How to insert request body using BodyInserters in Spring5? - spring

I am using Sping webflux module and create a WebClient, request uri and request body as follows:
// create webclient
WebClient wc3 = WebClient.builder()
.baseUrl("http://localhost:8080")
.defaultCookie("key", "val")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
// set uri
WebClient.RequestBodySpec uri1 = wc3.method(HttpMethod.POST).uri("/getDocs");
// set a request body
WebClient.RequestBodySpec requestSpec1 = WebClient.create().method(HttpMethod.POST).uri("/getDocs")
.body(BodyInserters.fromPublisher(Mono.just("data")), String.class);
and when i am setting the request body, i am getting the following compilation error:
Multiple markers at this line
- Type mismatch: cannot convert from Mono<String> to P
- The method fromPublisher(P, Class<T>) in the type BodyInserters is not applicable for the arguments
(Mono<String>)
The java editor is showing just "Rename in file" as the suggestion.
I am not sure if i am using the BodyInserters perfectly or not. Please suggest.

It has to be like this
// set a request body
WebClient.RequestHeadersSpec<?> data = WebClient.create().method(HttpMethod.POST).uri("/getDocs")
.body(BodyInserters.fromPublisher(Mono.just("data"), String.class));

Related

Send byteArray with WebClient

I'm trying to send a byte[] from a client to a server using WebClient, this is what I have:
HttpClient httpClient = HttpClient.create();
// some proxy Settings to httpClient..
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
WebClient client = WebClient.builder().clientConnector(connector).build();
MultipartBodyBuilder formDataBuilder = new MultipartBodyBuilder();
String header = String.format("form-data; pack=%s;", pack); // pack is byte[]
formDataBuilder.part("pack", new ByteArrayInputStream(pack)).header("Content-Disposition", header);
formDataBuilder.part("simpleParam", "testParam");
client.post().uri("myurl.test").accept(MediaType.APPLICATION_XML).contentType(MediaType.MULTIPART_FORM_DATA)
.header("Content-type", MediaType.MULTIPART_FORM_DATA_VALUE)
.body(BodyInserters.fromMultipartData(formDataBuilder.build()))
.retrieve()
.bodyToMono(Response.class)
.block();
Executing this code though i get this error:
org.springframework.core.codec.CodecException: No suitable writer found for part: pack
at org.springframework.http.codec.multipart.MultipartHttpMessageWriter.encodePart(MultipartHttpMessageWriter.java:260)
at org.springframework.http.codec.multipart.MultipartHttpMessageWriter.lambda$encodePartValues$4(MultipartHttpMessageWriter.java:213)
....
I don't understand what is missing.
Any help is appreciated, thank you
Your problem is that your Content-Disposition header is invalid. You shouldn't put your byteArray into the header. You can read more about Content-Disposition Header here
Also in my case it helps me to pass a ByteArrayResource instead of ByteArrayInputStream. I would recommend you to try one of these solutions:
Set Content-Disposition Header correct:
// ...
String header = String.format("form-data; name=%s; filename=%s", "part", "testFilename.txt");
// ...
Use ByteArrayResource instead of ByteArrayInputStream
formDataBuilder.part("pack", new ByteArrayResource(pack)).filename("testFilename.txt");

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

Jersey REST client post with authorization

I am using Jersey 1.9 in my Java Spring MVC web application. I am trying to make a post request for which I have to set two header values - authorization and content type. I was able to successfully make the post using postman REST client. I have tried many solutions found online, but the response I get is 401-Authorization failed. Following is the code is the code I use:
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource webResource = client.resource("https://api.constantcontact.com/v2/contacts?action_by=ACTION_BY_OWNER&api_key=tntkzy2drrmbwnhdv12s36vq");
WebResource.Builder builder = webResource.type(MediaType.APPLICATION_JSON);
builder.header(HttpHeaders.AUTHORIZATION, "Bearer 28ac08bc-58d5-426e-b811-3b1d1e505a9b");
ClientResponse responseMsg = webResource
.post(ClientResponse.class, jsonString);
responseMsg.getEntity(String.class);
Adding the postman screenshot:
After some research, I finally found what was missing in my case, based on the suggestions I got from the comments, I removed the query params from URL and added them as query params. ANd then added all query params to a multivalued map and used this multivalued map as the query param. My code was modified as follows:
MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
queryParams.add("action_by", "ACTION_BY_OWNER");
queryParams.add("api_key", apiKey);
WebResource webResource = client.resource(url);
ClientResponse responseMsg = webResource
.queryParams(queryParams)
.header("Content-Type", "application/json;charset=UTF-8")
.header("Authorization", "Bearer "+authorisationToken.trim())
.post(ClientResponse.class, jsonString);
responseMsg.getEntity(String.class);
Somehow when there are multiple query params, adding headers was not working, but when the params were added as a single multivalued map, everything works. Hope this helps someone.

Send Status code and message in SpringMVC

I have the following code in my web application:
#ExceptionHandler(InstanceNotFoundException.class)
#ResponseStatus(HttpStatus.NO_CONTENT)
public ModelAndView instanceNotFoundException(InstanceNotFoundException e) {
return returnErrorPage(message, e);
}
Is it possible to also append a status message to the response? I need to add some additional semantics for my errors, like in the case of the snippet I posted I would like to append which class was the element of which the instance was not found.
Is this even possible?
EDIT: I tried this:
#ResponseStatus(value=HttpStatus.NO_CONTENT, reason="My message")
But then when I try to get this message in the client, it's not set.
URL u = new URL ( url);
HttpURLConnection huc = (HttpURLConnection) u.openConnection();
huc.setRequestMethod("GET");
HttpURLConnection.setFollowRedirects(true);
huc.connect();
final int code = huc.getResponseCode();
String message = huc.getResponseMessage();
Turns out I needed to activate custom messages on Tomcat using this parameter:
-Dorg.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER=true
The message can be in the body rather than in header. Similar to a successful method, set the response (text, json, xml..) to be returned, but set the http status to an error value. I have found that to be more useful than the custom message in header. The following example shows the response with a custom header and a message in body. A ModelAndView that take to another page will also be conceptually similar.
#ExceptionHandler(InstanceNotFoundException.class)
public ResponseEntity<String> handle() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("ACustomHttpHeader", "The custom value");
return new ResponseEntity<String>("the error message", responseHeaders, HttpStatus.INTERNAL_SERVER_ERROR);
}

Attempting to test rest service with multipart file

I am attempting to test a rest service I created. The service is a post.
I wanted to create a file to pass the parameters(including a multi-part file).
From there I am trying to call the service at this point.
Pretty sure the service that doesn't work. But when I call rest Service. I have a simple form that just passes in a couple values including the jpg.
Here is the code.
HttpMessageConverter bufferedIamageHttpMessageConverter = new ByteArrayHttpMessageConverter();
restTemplate.postForObject("http://localhost:8080/sendScreeenAsPostCard", uploadItem.getFileData(), String.class));
My method signature is:
ResultStatus sendScreenAsPostcard( #RequestParam MultipartFile image, #RequestParamString userId)
That is the error I am getting.
Could not write request: no suitable HttpMessageConverter found for request type [org.springframework.web.multipart.commons.CommonsMultipartFile]
You need to simulate a file upload, which requires a particular content type header, body parameters, etc. Something like this should do the trick:
// Fill out the "form"...
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>();
parameters.add("file", new FileSystemResource("file.jpg")); // load file into parameter
parameters.add("blah", blah); // some other form field
// Set the headers...
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "multipart/form-data"); // we are sending a form
headers.set("Accept", "text/plain"); // looks like you want a string back
// Fire!
String result = restTemplate.exchange(
"http://localhost:8080/sendScreeenAsPostCard",
HttpMethod.POST,
new HttpEntity<MultiValueMap<String, Object>>(parameters, headers),
String.class
).getBody();

Resources