Spring - Calling REST API with Webclient with an image(multipart) file - spring

I am having a use case to call a REST API and I am using webclient to call the REST API. As part of the call I have to pass an image/pdf. I call an internal method downloadFile() which gives me the image of File type. To transmit this file I am doing the following
File file = downloadFile(key);
InputStream stream = new FileInputStream(file);
MultipartFile multipartFile = new MockMultipartFile(file.getName(), file.getName(), "multipart/form-data", stream);
JsonNode response = Objects.requireNonNull(webClient.post()
.uri("/api/{groupId}/images", uriBuilder -> uriBuilder
.queryParam("referenceId", referenceId)
.build(Map.of("groupId", 1000)))
.header(HttpHeaders.AUTHORIZATION, "Bearer " + auth2Service.getAccessToken())
.bodyValue(multipartFile)
.retrieve()
.bodyToMono(JsonNode.class)
.block());
But I am getting this error,
"Type definition error: [simple type, class java.io.ByteArrayInputStream]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.mock.web.MockMultipartFile["inputStream"])"
Can someone please help if I am sending the image correctly? And if yes, what is the above issue?

The way in which you are sending the image through webflux is not proper. You can do something like this,
File file = downloadFile(key);
InputStream stream = new FileInputStream(file);
MultipartFile multipartFile = new MockMultipartFile(file.getName(), file.getName(), "multipart/form-data", stream);
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", multipartFile.getResource());
var payloadFile = builder.build();
JsonNode response = Objects.requireNonNull(webClient.post()
.uri("/api/{groupId}/images", uriBuilder -> uriBuilder
.queryParam("referenceId", referenceId)
.build(Map.of("groupId", 1000)))
.header(HttpHeaders.AUTHORIZATION, "Bearer " + auth2Service.getAccessToken())
.body(BodyInserters.fromMultipartData(payloadFile))
.retrieve()
.bodyToMono(JsonNode.class)
.block());
Here "file" in the builder variable is the key that contains the actual file that you want to send. On the receiving end, you have to use the same key to catch the file.

Thanks Akhil. This worked. However a small change that I did is
File file = downloadFile(key);
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", new FileSystemResource(file));
I don't need to have the MockMultipartFile dependency in the above case.
Thanks

Related

Is there a change to return a png file like response.png instead of response.bin

The aim of my code is to retrieve an image from a third-party service.
I struggled a little for endpoint of download to work and only partially succeeded. When I call the endpoint via postman the answer is a .bin file, but what I need is to have a .png file. The greatest success is being able to get a .png file being able to customize the name as well. But personalization of the is not strictly necessary.
The project is built with the initializer and has the following dependencies:
spring-boot-starter-web;
lombok
spring-boot-starter-webflux
reactor-spring
Below is the source code of my endpoint:
#GetMapping("/retrieve-image")
public Mono<byte[]> retrieveImage(ImageRequest request) throws ExecutionException, InterruptedException, IOException {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("attribute", request.getAttribute()); // fake for query string setting.
Mono<byte[]> image = webClient
.get()
.uri(uriBuilder -> uriBuilder
.path(Endpoint.THIRD_PARTY_SERVICE_URI)
.queryParams(queryParams)
.build())
.accept(MediaType.valueOf(String.valueOf(MediaType.IMAGE_PNG)))
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(byte[].class)
.doOnSuccess(body -> {
if (clientResponse.statusCode().isError()) {
log.error("HttpStatusCode = {}", clientResponse.statusCode());
log.error("HttpHeaders = {}", clientResponse.headers().asHttpHeaders());
log.error("ResponseBody = {}", body);
}
}));
return image;
}
You can also add the mime type of the file to the produces section of the #GetMapping annotation, it should look something like this:
#GetMapping(path = "/retrieve-image",
produces = "image/png")
Additionally, instead of returning a Mono<byte[]>, you can wrap your response in a ResponseEntity<Resource>. This gives you the possibility to add Headers and tell the browser the name of your file. For example:
HttpHeaders header = new HttpHeaders();
header.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=image.png");
header.add("Access-Control-Expose-Headers", "Content-Disposition");
return ResponseEntity.ok().
.headers(header)
.contentLength(Files.size(path))
.body(<<YOUR_FILE_HERE>>);
One last thought: If you add both spring-boot-starter-web and spring-boot-starter-webflux to your dependencies, the app will work, but it doesn't use Netty from Webflux, instead the usual Tomcat. So you don't benefit from the reactive features.

Jersey reading zipinputstream [duplicate]

I want to return a zipped file from my server-side java using JAX-RS to the client.
I tried the following code,
#GET
public Response get() throws Exception {
final String filePath = "C:/MyFolder/My_File.zip";
final File file = new File(filePath);
final ZipOutputStream zop = new ZipOutputStream(new FileOutputStream(file);
ResponseBuilder response = Response.ok(zop);
response.header("Content-Type", "application/zip");
response.header("Content-Disposition", "inline; filename=" + file.getName());
return response.build();
}
But i'm getting exception as below,
SEVERE: A message body writer for Java class java.util.zip.ZipOutputStream, and Java type class java.util.zip.ZipOutputStream, and MIME media type application/zip was not found
SEVERE: The registered message body writers compatible with the MIME media type are:
*/* ->
com.sun.jersey.core.impl.provider.entity.FormProvider
What is wrong and how can I fix this?
You are delegating in Jersey the knowledge of how to serialize the ZipOutputStream. So, with your code you need to implement a custom MessageBodyWriter for ZipOutputStream. Instead, the most reasonable option might be to return the byte array as the entity.
Your code looks like:
#GET
public Response get() throws Exception {
final File file = new File(filePath);
return Response
.ok(FileUtils.readFileToByteArray(file))
.type("application/zip")
.header("Content-Disposition", "attachment; filename=\"filename.zip\"")
.build();
}
In this example I use FileUtils from Apache Commons IO to convert File to byte[], but you can use another implementation.
You can write the attachment data to StreamingOutput class, which Jersey will read from.
#Path("/report")
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response generateReport() {
String data = "file contents"; // data can be obtained from an input stream too.
StreamingOutput streamingOutput = outputStream -> {
ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(outputStream));
ZipEntry zipEntry = new ZipEntry(reportData.getFileName());
zipOut.putNextEntry(zipEntry);
zipOut.write(data); // you can set the data from another input stream
zipOut.closeEntry();
zipOut.close();
outputStream.flush();
outputStream.close();
};
return Response.ok(streamingOutput)
.type(MediaType.TEXT_PLAIN)
.header("Content-Disposition","attachment; filename=\"file.zip\"")
.build();
}
In Jersey 2.16 file download is very easy
Below is the example for the ZIP file
#GET
#Path("zipFile")
#Produces("application/zip")
public Response getFile() {
File f = new File(ZIP_FILE_PATH);
if (!f.exists()) {
throw new WebApplicationException(404);
}
return Response.ok(f)
.header("Content-Disposition",
"attachment; filename=server.zip").build();
}
I'm not sure I it's possible in Jersey to just return a stream as result of annotated method. I suppose that rather stream should be opened and content of the file written to the stream. Have a look at this blog post. I guess You should implement something similar.

RestTemplate gives 400 Bad Request Error on a Get Request

When I try to make a get request with Spring's RestTemplate, it gives 400 BAD Request. I can call the same url from javascript successfully with the headers below :
But the code below does not work. What might be the cause?
public Entity getEntityByUri(String uri) {
String req = "http://live.dbpedia.org/sparql?query=DESCRIBE%20%3Chttp://dbpedia.org/resource/Concept_learning%3E&format=application%2Fjson-ld";
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.ALL));
HttpEntity<String> httpEntity = new HttpEntity<String>(headers);
new RestTemplate().exchange(req, HttpMethod.GET, httpEntity, Map.class);
Entity entity = new Entity();
return entity;
}
Your url is already encoded. Popular browsers such as Chrome are capable of understanding and responding appropriately. However, it's not the same case with RestTemplate.
I had to decode your uri here and the decoded uri is DESCRIBE <http://dbpedia.org/resource/Concept_learning>
Having checked the browser console, I got to know you have two query strings passed in the url, they are query and format holding values DESCRIBE <http://dbpedia.org/resource/Concept_learning> and application/json-ld respectively.
I assume Entity class is the pojo class of json response.
Have created Entity as from your json response:
public class Entity {
private String value;
private String type;
// getters and setters omitted for brevity
}
Finally in your getEntityByUri method have got the instance of UriComponentsBuilder which handles uri encoding and query params.
To sum up, your getEntityByUri looks below.
public HttpEntity<Entity> getEntityByUri() {
String req = "http://live.dbpedia.org/sparql";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(req)
.queryParam("query",
"DESCRIBE <http://dbpedia.org/resource/Concept_learning>")
.queryParam("format", "application/json-ld");
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.ALL));
HttpEntity<String> httpEntity = new HttpEntity<String>(headers);
return new RestTemplate().exchange(builder.build().encode().toUri(), HttpMethod.GET, httpEntity, Entity.class);
}
The above method didn't throw HTTP400 as the required query params have been passed in builder object.
Hope this helps and good luck!
Anyone getting same error make sure your URL is decoded means no percent symbols in url (if space in param values).
This worked for me
try {
requestURL = URLDecoder.decode("http://api.com?p=1&groups=3212&affected-since=2019-06-06T14%3A11%3A14.880&detail=full&after-id=43536", "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Maybe
headers.setAccept(Arrays.asList(MediaType.ALL));
generates a malformed "Accept" header field? (FWIW, why do you send it at all???)

Spring Rest Template to send JsonArray

I am using spring rest template to send json array as request. Source code to send request is as follow:
JSONArray jsonArray = new JSONArray();
for (Iterator iterator = itemlist.iterator(); iterator.hasNext();) {
Item item = (Item)iterator.next();
JSONObject formDetailsJson = new JSONObject();
formDetailsJson.put("id", item.getItemConfId());
formDetailsJson.put("name", item.getItems().getItemName());
formDetailsJson.put("price", item.getPrice());
formDetailsJson.put("Cost",item.getCost());
jsonArray.put(formDetailsJson);
}
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
// Prepare header
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
// Pass the new person and header
HttpEntity<JSONArray> entity = new HttpEntity<JSONArray>(jsonArray, headers);
System.out.println("Json Object : "+entity);
// Send the request as POST
try {
ResponseEntity<String> result = restTemplate.exchange("my url", HttpMethod.POST, entity, String.class);
} catch (Exception e) {
logger.error(e);
return "Connection not avilable please try again";
}
And to accept request:
#RequestMapping(value = "/testStock", method = RequestMethod.POST,headers="Accept=application/xml, application/json")
public #ResponseBody int testStock(#RequestBody List<ItemList> jsonArray) {
logger.debug("Received request to connect ms access : "+jsonArray.size());
//int returnSizecount = stockList.getStocklst().size();
return 1;
}
The problem is that it giving me following error:
Could not write request: no suitable HttpMessageConverter found for request type [org.json.JSONArray].Any suggestion is greatly acceptable.
There are no MessageConverter for JSONArray, so I suggest do the following.
HttpEntity<JSONArray> entity = new HttpEntity<JSONArray>(jsonArray, headers);
Convert Class JSONArray to String, and add that to HttpEntity, you know use toString
java.lang.String toString()
Make a JSON text of this JSONArray.
HttpEntity entity = new HttpEntity(jsonArray.toString(), headers);
Or change to Jackson implementation Spring have support to that. XD
If you dont want to do the above, consider create your own implementation of messageConverter, that will work but is harder
update
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
headers.setContentType(MediaType.APPLICATION_JSON);
update 2 Change endpoint to.
#RequestMapping(value = "/testStock", method = RequestMethod.POST)
public #ResponseBody int testStock(#RequestBody String jsonArray) {
you need to have httpmessageconverter configured for your resttemplate, please read my post for configuring http message conveter for you webservice
http://stackoverflow.com/questions/19963127/new-to-spring-and-jackson-2-what-does-this-bean-declaration-allow-for-in-a-spri/19973636#19973636.
and for you problem to convert your http request to json you might add this entry in your restemplate configuration
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
The error is quite straightforward. You do not have a converter for the JSONArray. Converting the array to a String (using toString) did help you here, but there is a better way:
Just add a converter for the json.org objects:
Add this to your pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
</dependency>
And then on your ObjectMapper add the JsonOrgModule:
mapper.registerModule(new JsonOrgModule());

Post data using Spring RestTemplate

I am trying to post data using Spring RestTemplate as below:
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
parameters.add("name1", "value1");
parameters.add("name2", "value2");
HttpMessageConverter<String> stringConverter = new StringHttpMessageConverter();
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
List<HttpMessageConverter<?>> msgConverters = new ArrayList<HttpMessageConverter<?>>();
msgConverters.add(formConverter);
msgConverters.add(stringConverter);
restTemplate.setMessageConverters(msgConverters);
String xml = restTemplate.postForObject(myurl, parameters, String.class);
On the server part, I am using a simple servlet to handle request as follow:
String name1 = request.getParameter("name1");
The server returns the xml as String.
When I used HashMap instead of MultiValueMap without Converter, the parameters are null on the server side. But after using the above code, I am getting error
Cannot extract response: no Content-Type found
Can you plz provide me a simple example to achieve what I want.
Here is what I used to format data for the Spring POST:
//FormHttpMessageConverter
is used to construct form parameters to POST on the URI
HttpMessageConverter<?> formHttpMessageConverter = new FormHttpMessageConverter();
HttpMessageConverter<?> stringHttpMessageConverter = new StringHttpMessageConverter();
List<HttpMessageConverter> msgConverters = new ArrayList<HttpMessageConverter>();
msgConverters.add(formHttpMessageConverter);
msgConverters.add(stringHttpMessageConverter);
// Prepare acceptable media type
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
acceptableMediaTypes.add(MediaType.ALL);
// Prepare header
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
HttpEntity<MultiValueMap<String,String>> httpEntity = new HttpEntity<MultiValueMap<String,String>>(map,headers);
ResponseEntity<String> resp = restTemplate.exchange("https://risk.XXXX.XXXXXX.net",HttpMethod.POST,httpEntity,String.class);

Resources