How to upload multipart file without storing on server harddisk - java

My SpringBoot API receives multipart file. Then using RestTemplate I am sending same to different API. It works fine!!!
Problem here is its storing multipart file on server harddisk. My API should just act as an intermediate service between frontend and another API endpoint.
I do not want to store received file locally
One thing I can do is delete after uploaded to different API.
Is there any better way we do same thing i.e. without storing on server or automatic delete from server.
My code is as below
Controller
#PostMapping(value = "/uploadFile", consumes = { "multipart/form-data" })
public SomePojoObject uploadFile(#RequestParam("file") MultipartFile mFile,
#RequestParam("metaData") String metaData) {
return service.uploadFile(mFile, metaData);
}
And Service code is as below
private SomePojoObject uploadFile(MultipartFile mFile, String metaData) {
File file = convertFile(mFile);
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("FILE", new FileSystemResource(file));
map.add("metaData", metaData);
// Some more logic of resttemplate to upload document to server
}
public File convertFile(MultipartFile mFile) {
File file = new File(mFile.getOriginalFilename());
try {
file.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(mFile.getBytes());
fileOutputStream.close();
} catch (IOException e) {
e.printstackTrace();
}
return file;
}

Finally found a rather very simple way to achieve above.
There is no need to convert it to file.!!!!
private SomePojoObject uploadFile(MultipartFile mFile, String metaData) {
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("FILE", mFile.getResource());
map.add("metaData", metaData);
// Some more logic of resttemplate to upload document to server
}

Related

Spring boot download a large file from an endpoint and stream it to another one

I'm working on a Java Spring web project using spring boot and i have to download a file from one endpoint, and after it is downloaded, i need to upload it to another one. So, i was able to do it but there is a bottleneck at my code, that is when i create a multipart file (using MockMultipartFile), the file is entirely loaded at RAM, so it causes a bottleneck of memory. I tried some different strategies but using no one i could avoid this. Does anyone have any clue?
I've tried to use this approach... the upload itself works, but it keeps the whole file at RAM for a while, and i need to avoid this.
// Service method
PipedOutputStream outputStream = new PipedOutputStream();
PipedInputStream inputStream = new PipedInputStream(
Integer.parseInt(appCreationProperties.getPipeInputStreamSize()));
inputStream.connect(outputStream);
Flux<DataBuffer> dataBufferFlux = storageService.bulkDownload(app.getFileIds(),
appCreationInfo.getOrgId(), outputStream)
.doFinally(s -> {
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
DataBufferUtils.write(dataBufferFlux, outputStream)
.log("Writing to output buffer")
.subscribe();
String filenameZip = FileUtils.getFilenameWithNewExtension(appCreationInfo.getFilename(), ".zip");
MultipartFile multipartFileZip = new MockMultipartFile(app.getName(),
app.getName(),
MediaType.MULTIPART_FORM_DATA_VALUE, inputStream);
return appService.uploadApp(multipartFileZip, appCreationInfo.getOrgAppId(),
appCreationInfo.getOrgId());
This appService.uploadApp will call this function under the hood:
public static <T> T postDataMultipart(
WebClient webClient,
MultipartFile file,
String host,
String endpoint,
Class<T> clazz,
Object... params) {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", file.getResource());
return webClient.post()
.uri(uriBuilder -> generateUri(uriBuilder, host, endpoint, params))
// implicit headers
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(builder.build()))
.retrieve()
.bodyToMono(clazz)
.block();
}
Did you see any issues there?

How to return both JSON response and Byte Array of file in Springboot RestController Response while downloading a file

I am developing a rest controller to download a .docx file into the client system. My code is working fine as the file is getting downloaded. Now I want to enhance the response. My requirement is to also send a JSON payload in the response along with the .docx file content, something like
{"message":"Report downloaded Successfully"}
incase of successful download or with a different message incase of failure.
Below is my restcontroller code:
#RestController
public class DownloadController {
#PostMapping(value="/download",
consumes = {"multipart/form-data"},
produces = {"application/octet-stream"})
public ResponseEntity<?> downloadFile(#RequestParam("file") MultipartFile uploadFile){
//business logic to create the attachment file
try {
File file = new File("path_to_.DOCX file_I_have_created");
byte[] contents = Files.readAllBytes(Paths.get(file.getAbsolutePath()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDisposition(ContentDisposition.attachment().filename("survey.docx").build());
return new ResponseEntity<>(contents, headers, HttpStatus.OK);
} catch (Exception e){
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
How do I modify my response code to send both the JSON message and the byte[] contents so that the file gets downloaded and I can see the JSON message in the response preview tab in the chrome or response body in postman?
UPDATE: I tried to define a response class like below
public class Downloadresponse {
private byte[] content;
private String message;
//getter,setters
}
With this change in place, I am getting below exception:
Resolved [ HttpMessageNotWritableException: No converter for [class ...Downloadresponse] with preset content-type "application/octet-stream”]
You can't. HTTP doesn't allow you to defined multiple content-types on 1 request/response. That being said, you could send the byte array base64 encoded as part of a json response but would need to handle it in the front-end (if you have any) as it would not trigger the file download process of the browser.
You can define custom class which hold your current content and message . So you can return that class in the response
ResponseClass
{
byte[] contents;
String message;
}
You can send a json object that contain the message and the file as encoded64 string.
And in the client side you decode it and download it.
public record MyRecord(String message, String encodedStringBase64, String filename) {}
...
try {
File file = new File("path_to_.DOCX file_I_have_created");
byte[] contents = Files.readAllBytes(Paths.get(file.getAbsolutePath()));
String encodedString = Base64.getEncoder().encodeToString(contents);
MyRecord record = new MyRecord("Report downloaded Successfully", encodedString, file.getName());
return log.traceExit(ResponseEntity.ok().headers(headers)
.contentType(MediaType.valueOf("application/json")).body(record));
} ...

Rest Controller: uploading file using HttpServletRequest through Swagger UI

I wonder if it is possible to show the input file to select in my Swagger UI when exposing the API.
I confirm that it is working perfectly when defining the endpoint as following:
public ResponseEntity<String> upload(#RequestPart MultipartFile file)
There is no doubt to show the file chooser when using MultipartFile.
And as I would like to upload big files, I have to use HttpServletRequest instead in order to upload the file as a stream. The endpoint will be then like:
public #ResponseBody Response<String> upload(HttpServletRequest request) {
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload();
// Parse the request
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
FileItemStream item = iter.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (!item.isFormField()) {
String filename = item.getName();
// Process the input stream
OutputStream out = new FileOutputStream(filename);
IOUtils.copy(stream, out);
stream.close();
out.close();
}
}
return new Response<String>(true, "Success", "");
}
The endpoint is working well using a curl command. But unfortunately when using the Swagger UI, I am not able to show the file chooser.
Does anyone faced the same case or knows if Swagger supports this feature?

Java spring file download proxy with rest calls

I need to create a rest service in java which will in turn connect to another rest service for file download. For now, I just need to transfer the file from the other backend to client but in future some processing/transformations would be done.
For all the web services in my project, we are using spring rest (for providing as well as consuming the services).
My question is what would be the appropriate way of doing it considering that the files would be large and I don't want to run into OutOfMemory errors.
People in some other posts have suggested to use streams on both the ends but is that really possible? For this, do I need to write the file on disk first?
My current code for file download (consumer) -
public BackendResponse<byte[]> callBackendForFile(BackendRequest request) {
String body = null;
ResponseEntity<byte[]> responseEntity = null;
URI uri = createURI(request);
MultiValueMap<String, String> requestHeaders = getHeadersInfo(request.getHttpRequest());
if (HttpMethod.GET.equals(request.getMethod())) {
responseEntity = restTemplate.exchange(uri, request.getMethod(),
new HttpEntity<String>(body, requestHeaders), byte[].class);
} else {
LOG.error("Method:{} not supported yet", request.getMethod());
}
BackendResponse<byte[]> response = new BackendResponse<>();
response.setResponse(responseEntity);
return response;
}
My client code (provider):
#RequestMapping(value = "/file", method = RequestMethod.GET, produces = "application/xml")
#ResponseBody
public void downloadFileWithoutSpring(HttpMethod method, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) {
BackendRequest request = new BackendRequest(method,
httpRequest.getRequestURI(), httpRequest.getQueryString(), httpRequest);
BackendResponse<byte[]> backendResponse = dutyplanService.getFile(request);
ResponseEntity<byte[]> response = backendResponse.getResponse();
httpResponse.addHeader("Content-Disposition", "attachment; filename=\"" + "attachment.zip" + "\"");
httpResponse.getOutputStream().write(response.getBody());
httpResponse.flushBuffer();
}
Note: The code above doesn't work somehow as the attachment downloaded is a corrupt file
I don't think you will need to create that file on server as long as you are having the bytearray content of it received from another server.
You can try changing value of produces annotation to the value application/zip (or application/octet-stream, depending on the target browser) instead of 'application/xml'
you can pass HttpServletResponse#getOutputStream() directly in restTemplate and write it without save file in server.
public void getFile(HttpServletResponse response) throws IOException {
restTemplate.execute(
"http://ip:port/temp.csv",
HttpMethod.GET,
null,
clientHttpResponse -> {
StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream());
return null;
}
);
}
note that after call getFile(), you should close outputStream like this
response.getOutputStream().close()

Send file from Server to Client java with Spring and Rest web service

I have to send file from Server (from its file system) to Cliente (another pc and store file in a particularly folder) through java code and Rest web service. These files can be even 120MB.
I used MultipartFile to upload file from my web page but I don't know how to download from the client.
It would be good the possibility to use a REST web service that returns both file and message with result of method (true or false if there was an error).
Do you have an idea?
At the moment I use this code in server:
and the best way would be
#Override
#RequestMapping(value = "/oldmethod", method = RequestMethod.GET)
public #ResponseBody Response getAcquisition(#RequestParam(value="path", defaultValue="/home") String path){
File file;
try {
file = matlabClientServices.getFile(path);
if (file.exists()){
InputStream inputStream = new FileInputStream(path);
byte[]out=org.apache.commons.io.IOUtils.toByteArray(inputStream);
return new Response(true, true, out, null);
}
else
return new Response(false, false, "File doesn't exist!", null);
} catch (Exception e) {
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
LOG.error("Threw exception in MatlabClientControllerImpl::getAcquisition :" + errorResponse.getStacktrace());
return new Response(false, false, "Error during file retrieving!", errorResponse);
}
}
but to the client the code below doesn't work:
public Response getFileTest(#RequestParam(value="path", defaultValue="/home") String path){
RestTemplate restTemplate = new RestTemplate();
Response response = restTemplate.getForObject("http://localhost:8086/ATS/client/file/oldmethod/?path={path}", Response.class, path);
if (response.isStatus() && response.isSuccess()){
try {
Files.write(Paths.get("PROVIAMOCI.txt"),org.apache.commons.io.IOUtils.toByteArray(response.getResult().toString()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return response;
}
it writes the byte[] characters and not the original text
As per my understanding FileSystemResource used for get the file system using file system URI but it is not over the HTTP. Using FileSystemResource you can access files from your local machine to local accessible file system and from server to your server accesiable file system. But could not access from local to server file system.

Categories