Rest API returning byte[] i response - java

What should be in the consumes and produces for Rest API which is returning byte[] of file in the response.
No file params are included in the request .

You could use the below for returning byte[]
#Produces(MediaType.APPLICATION_OCTET_STREAM)

You can use 'MultipartFile' for the purpose of consuming and sending back a file in response.
You can have a look at the following tutorial at spring.io for detailed tutorial:
https://spring.io/guides/gs/uploading-files/
Hope it helps!

You should set the media type on basis of file content type.
for example:
#GetMapping
public HttpEntity returnByteArray() {
String filepath = ; //filepath
String contentType = FileTypeMap.getDefaultFileTypeMap().getContentType(filePath);
byte[] byteContent = ; //Content
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf(contentType));
return new HttpEntity(byteContent, headers);
}
OR
If you always return the same content file type then you can also set in
#GetMapping(produces = "mime_type")
public byte[] returnByteArray() {
return new byte[0];
}

Related

How to stream file from Multipart/form-data in Spring WebFlux

I want to receive Multipart/form-data from a client (frontend for example). And then stream file content of form-data to another backend service.
For now i can read the whole file and pass it somewhere via byte[] (base64 string) like this:
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<ResponseType> upload(#RequestPart("document") FilePart document,
#RequestPart("stringParam") String stringParam) {
return service.upload(document, stringParam);
}
// Casually convert to single byte array...
private Mono<byte[]> convertFilePartToByteArray(FilePart filePart) {
return Mono.from(filePart
.content()
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
}));
}
There're a few problems with this approach:
I don't want to read the whole file into memory;
Array size in limited to Integer.MAX_VALUE;
Array encodes as base64 String, which takes extra memory;
Since i put the whole array in Mono - "spring.codec.max-in-memory-size" must be bigger than array size.
I've already tried sending file via asyncPart of WebClientBuilder:
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.asyncPart("document", document.content(), DataBuffer.class);
But i'm getting an error:
java.lang.IllegalStateException: argument type mismatch
Method [public reactor.core.publisher.Mono<> upload(**org.springframework.http.codec.multipart.FilePart**,java.lang.String)] with argument values:
[0] [type=**org.springframework.http.codec.multipart.DefaultParts$DefaultFormFieldPart**]
UPD: full code, which generates error
// External controller for client.
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/v2")
public Mono<DocumentUploadResponse> uploadV2(#RequestPart("document") FilePart document,
#RequestPart("stringParam") String stringParam) {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.asyncPart("document", document.content(), DataBuffer.class);
builder.part("stringParam", stringParam);
WebClient webClient = webClientBuilder.build();
return webClient.post()
.uri("URL_TO_ANOTHER_SERVICE")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(builder.build()))
.retrieve()
.bodyToMono(FileMetaDto.class)
.map(DocumentUploadResponse::new);
}
// Internal service controller.
#PostMapping(path = "/upload/v2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<FileMetaDto> upload(#RequestPart("document") FilePart document,
#RequestPart("stringParam") String stringParam) {
return ...;
}
Looks like i was managed to stream file, working code below:
// External controller for client.
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/v2")
public Mono<DocumentUploadResponse> uploadV2(#RequestPart("document") FilePart document,
#RequestPart("stringParam") String stringParam) {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.asyncPart("document", document.content(), DataBuffer.class).filename(document.filename());
builder.part("stringParam", stringParam);
WebClient webClient = webClientBuilder.build();
return webClient.post()
.uri("URL_TO_ANOTHER_SERVICE")
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(builder.build()))
.retrieve()
.bodyToMono(FileMetaDto.class)
.map(DocumentUploadResponse::new);
}
// Internal service controller.
#PostMapping(path = "/upload/v2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<FileMetaDto> upload(#RequestPart("document") FilePart document,
#RequestPart("stringParam") String stringParam) {
return ...;
}
In the original question code i've been missing:
builder.asyncPart("document", document.content(), DataBuffer.class).filename(document.filename());

How to modify part of the Content-Disposition in java rest template client

I'm struggling with creating valid request to some internal service using java spring. The problem is with proper payload for multipart/form-data boundary.
Environment:
java server -> (rest) http multipart/form-data -> some service
(there is no browser in the middle)
Valid payload should look like this:
------WebKitFormBoundaryp8mrQWOb5GiyC90y
Content-Disposition: form-data; name="files"; filename="0000.png"
Content-Type: image/png
[binary data]
------WebKitFormBoundaryp8mrQWOb5GiyC90y--
Unfortunately I'm unable to change this "headers" and I'm getting something like this:
--fkGT7CJaQB9-2aa8G1ePv17iHKnWSsd
Content-Disposition: form-data; name="files"
Content-Length: 170096
[binary data]
--fkGT7CJaQB9-2aa8G1ePv17iHKnWSsd--
I've searched many stackoverlow questions, but nothing seems to work.
This is what I've done till now (generates above payload):
HashMap<String, List<String>> additionalHeaders = new HashMap<>();
String fileMd5 = "tgrlfG0pjblWZB6g1f7j5w=="; //#todo
File file = new File(systemFile.getAbsoluteFileLocation());
Path filePath = Paths.get(systemFile.getAbsoluteFileLocation());
try{
DiskFileItem fileItem = new DiskFileItem("file", "image/png", false, file.getName(), (int) file.length() , file.getParentFile());
InputStream input = new FileInputStream(file);
OutputStream os = fileItem.getOutputStream();
int ret = input.read();
while ( ret != -1 )
{
os.write(ret);
ret = input.read();
}
os.flush();
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
MultiValueMap<String, Object> parts =
new LinkedMultiValueMap<>();
ByteArrayResource resource = new ByteArrayResource(multipartFile.getBytes());
parts.add("files", resource);
additionalHeaders.put("Content-MD5", Collections.singletonList(fileMd5));
additionalHeaders.put("Content-Disposition", Collections.singletonList("attachment; filename=\""+systemFile.getFilenameWithExtension()+"\""));
ResponseEntity<FrpFileServer> responseEntity = formDataRestClient.post(this, parts, FrpFileServer.class, isServerSide, frpToken.getTokenId(), additionalHeaders, MediaType.MULTIPART_FORM_DATA);
return responseEntity.getBody();
} catch (IOException e) {
return null;
}
formDataRestClient builds the rest of the request via RestTemplate
public <K, T> ResponseEntity<T> post(RestClientInterface reference, K requestClass, Class<T> responseClass, boolean isServerSide, String resourceId, HashMap<String, List<String>> additionalHeaders, MediaType contentType) {
Ok, I've fixed my problem with replacing resource creation with this:
ByteArrayResource resource = new ByteArrayResource(multipartFile.getBytes()){
#Override
public String getFilename() {
return systemFile.getFilenameWithExtension();
}
};
Thanks to that, restTemplate handles it's magic in proper way :)
I wrote one generic REST client which you can use in any java based application or framework, see GIT source of delete method how to load anything in request header where i am passing Authorization- https://github.com/gajeralalji/JAVA-REST-Client/wiki
check source of REST-client.java class and let me know if you are still facing any issue.

Streaming data with REST

Using REST with RESTEasy and Swagger, is there any way to stream data back to the caller with a GET endpoint? I've seen a couple of examples where the entire stream can be returned, but I haven't seen any examples where the data can actually be streamed back. I also did have a look at this example(followed from this link-Return File From Resteasy Server) however, this example looks like it is returning a stream and expecting the caller to utilize the stream? Is this a correct assumption?:
#GET
#Produces(MediaType.APPLICATION_OCTET_STREAM)
#Path("/stream/test")
public Response getTestStream() {
String myName = "name";
InputStream stream = new ByteArrayInputStream(myName.getBytes(StandardCharsets.UTF_8));
return Response.ok().entity(stream).build();
}
But this does not seem to work for me. I get an exception: javax.ws.rs.NotAcceptableException: RESTEASY003635: No match for accept header.
Any help would be greatly appreciated!
You can return Object of inputstream object in Response.
For e.g.
#GetMapping(value = "/stream/test")
#ResponseBody
public ResponseEntity<?> getTestStream() {
String myName = "name";
InputStream stream = new ByteArrayInputStream(myName.getBytes(StandardCharsets.UTF_8));
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(stream));
}

Java rest WS to download docx

I´m developing a Springboot rest-based web app. One of the WS must return a .docx document. The code is:
#RequestMapping(value = "/get-doc",method = RequestMethod.GET, produces="application/vnd.openxmlformats-officedocument.wordprocessingml.document")
public #ResponseBody HttpEntity<File> getDoc() {
File file = userService.getDocx();
HttpHeaders header = new HttpHeaders();
header.set("Content-Disposition", "attachment; filename=DocxProject.docx");
header.setContentLength(file.length());
return new HttpEntity<File>(file,header);
}
but I´m facing this error:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
I searched other questions but none of them gave me a solution, mainly because they use javax.ws.rs but I don't want to rely on it.
What I´m looking for is a solution to the error I get or an alternative to my code (not javax.ws.rs dependant).
Thanks in advance.
Try returning array of bytes. Simplifying your code:
#RequestMapping(value = "/get-doc",method = RequestMethod.GET, produces="application/vnd.openxmlformats-officedocument.wordprocessingml.document")
public #ResponseBody byte[] getDoc() {
File file = userService.getDocx();
FileInputStream fis = new FileInputStream(file);
byte[] doc = IOUtils.toByteArray(fis);
return doc;
}
IOUtils is from org.apache.commons.io.IOUtils. I have not tested, but I have a similar method that return an image. I hope this help you.
You can set the stream directly in response.
#RequestMapping(value = "/get-doc",method = RequestMethod.GET)
public void getDoc(HttpServletResponse response){
InputStream inputStream = new FileInputStream(file);
IOUtils.copy(inputStream, response.getOutputStream());
..
response.flushBuffer();
}

FormHttpMessageConverter filename from byte array

I'm trying to upload file with POST request. POST must be of the enclosure-type multipart/form-data. I insert file as byte array:
MultiValueMap<String, Object> requestParameters = new LinkedMultiValueMap<String, Object>();
byte[] image=getImage();
// ... adding parameters in requestParameters
// TODO filename
requestParameters .add("file", image);
Then I make post request with
org.springframework.http.converter.FormHttpMessageConverter:
FormHttpMessageConverter multipartFormConverter = new FormHttpMessageConverter();
multipartFormTemplate = new RestTemplate();
multipartFormTemplate.setMessageConverters(Collections.<HttpMessageConverter<?>> singletonList(multipartFormConverter));
ResponseEntity<String> uploadAnswer=multipartFormTemplate.postForEntity(freePlanResponse.getUrl(), requestParameters, String.class);
The problem is - I cant found, how to change filename. The result is always default filename (${filename}) for file hosting, I'm using:
image hosting. It uses:
amazon s3. Here is post example >>
I found solution:
Inherit FormHttpMessageConverter and override getFileName method:
public class FormHttpMessageConverterImageName extends FormHttpMessageConverter {
private String filename;
public void setFilename(String filename){
this.filename=filename;
}
#Override
protected String getFilename(Object part) {
return filename;
}
}
but maybe there is some more beautifull solution.

Categories

Resources