Send byteArray with WebClient - spring

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

Related

Spring Web Client response with polish characters

I'm using Spring WebClient for getting html. The response contains polish characters such as: ą, ę, ż and so on.
After calling service i expect the response to look like this: <div>plan zajęć</div>
But the actual response looks like this: <div>plan zaj�ć</div> - and this sign replaces all polish characters.
Here's a WebClient bean config:
#Bean
WebClient webClient() {
return WebClient.builder()
.build();
}
And here's how i use it:
Optional<String> resp = webClient.get()
.uri(uri)
.retrieve()
.bodyToMono(String.class)
.blockOptional();
And here's a link to page that i'm trying to web scrape: https://plan.polsl.pl/plan.php?winW=1000&winH=1000&type=0&id=343126158
I've no idea what to change in the WebClient configuration to get the desired effect, so I'm asking for help.
Please show how you use WebClient. I don't know Polish character but very likely your problem is related to the encoding of the response.
You can try to specify the charset to UTF_8 and see if that helps
WebClient webClient = WebClient.create();
Mono<String> response = webClient.get()
.uri(uri)
.acceptCharset(StandardCharsets.UTF_8)
.retrieve()
.bodyToMono(String.class);
String responseString = response.block();
== Updated 1/2/2023 ==
Note that Java String is using UTF-8 encoding. That's why we attempted to request the web server to return us a document in UTF-8 encoding. Unfortunately, the web server that you specified above returns ISO-8859-2 charset even though WebClient is requesting to return UTF-8 charset. You will have to transcode the response body from ISO-8859-2 to UTF-8 charset yourself. Here is the sample code to do that. I tested it with your web server.
WebClient webClient = WebClient.create();
Mono<ByteArrayResource> responseBody = webClient.get()
.uri(uri)
.retrieve()
.bodyToMono(ByteArrayResource.class);
String responseString = new String(responseBody.block().getByteArray(), Charset.forName("ISO-8859-2"));
If you are building a generic web crawler, instead of hardcoding the above code to always transcode from ISO-8859-2 to UTF-8, you will need to get the charset information from the Content-Type header. Most of the web server would tell you the media type as well as the charset encoding in Content-Type. Then, instead of hardcoding ISO-8859-2 in the above code, you can specify the correct charset. Here is the sample code to find the charset.
WebClient webClient = WebClient.create();
Mono<ClientResponse> response = webClient
.get()
.uri("http://example.com")
.exchange();
response.map(res -> {
String contentType = res.headers().contentType().get().toString();
String charset = null;
// parse the Content-Type header to extract the charset
Matcher m = Pattern.compile("charset=([^;]+)").matcher(contentType);
if (m.find()) {
charset = m.group(1);
}
return charset;
});
Unfortunately, the web server that you specified didn't tell you the charset in Content-Type header either. In this case, you may need to look elsewhere in the response to determine the character encoding.
One place you can check is the charset attribute of the element in the HTML document. Some web servers include a element in the HTML document with a charset attribute that specifies the character encoding of the document. This is how I found out your specified document is using ISO-8859-2 charset.
WebClient doesn't have an easy way to extract the charset information from tag but you can use regular expression to extract that. Here is the sample code
WebClient webClient = WebClient.create();
Mono<String> responseBody = webClient
.get()
.uri("http://example.com")
.retrieve()
.bodyToMono(String.class);
responseBody.map(html -> {
String charset = null;
// use a regular expression to extract the charset attribute from the <meta> element
Matcher m = Pattern.compile("<meta[^>]+charset=[\"']?([^\"'>]+)[\"']?").matcher(html);
if (m.find()) {
charset = m.group(1);
}
return charset;
});

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.

How to insert request body using BodyInserters in Spring5?

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

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