Spring Boot Rest Response MultipartFile with additional fields - java

We have a RestController with the below endpoint
#PostMapping(path = "/downloadFile", produces = MediaType.MULTIPART_FORM_DATA_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
public FileDownloadResponse downloadFile(#RequestBody FileDownloadRequest request) {
FileDownloadResponse downloadResponse = new FileDownloadResponse();
File file = new File("c:/fileLocation/"+request.getFileName());
try (InputStream stream = new FileInputStream(file)) {
byte[] bytes = IOUtil.toByteArray(stream);
downloadResponse.setFileName(file.getName());
downloadResponse.setCheckSum(calculateCheckSum(bytes));
downloadResponse.setFileContents(new FileSystemResource(bytes, file.getName()));
} catch (Exception e) {
e.printStackTrace();
}
return downloadResponse;
}
public class FileDownloadResponse {
private String fileName;
private Long checkSum;
private Resource fileContents;
}
public static class FileSystemResource extends ByteArrayResource {
private String fileName;
public FileSystemResource(byte[] byteArray , String filename) {
super(byteArray);
this.fileName = filename;
}
public String getFilename() { return fileName; }
public void setFilename(String fileName) { this.fileName= fileName; }
}
And on the Client Side we have the below code,
public class FileDownloadResponseClient {
private String fileName;
private Long checkSum;
private MultipartFile fileContents;
}
public FileDownloadResponseClient download(FileDownloadRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(Mediatype.ALL));
HttpEntity<FileDownloadRequest> requestEntity = new HttpEntity<>(request, headers);
return restTemplate.postForEntity(downloadUrl, requestEntity, FileDownloadResponseClient.class);
}
When we run the Rest Client above, we are getting the below error,
org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : [no body]
Is it possible to download a multipartfile along with other additional fields? If yes, what is that we are missing here, please let us know.
Thanks in Advance!

org.springframework.web.multipart has a method boolean isEmpty() to find if the file has no content. Best put that check there and redirect to a message about such a file multipart form.
Of [no body] i have found that message on test requests to http server but in entirety generally means there is nothing in the form or no extra information needed for the server to complete the request.
For now i presume the spring framework handles all the url decoding and boundary marker stripping (on both ends) of uploaded files.

Related

Problems downloading zip archive Spring boot

Good afternoon.
I can't get the browser to download the file from the server. I took the code from a previous project and it doesn't work. Please explain why.
On the server, files are collected in a zip archive. It is necessary to download the archive. I am using this:
My controller
#SneakyThrows
#GetMapping("/report/UploadDocuments")
#ResponseBody
public ResponseEntity<InputStreamResource> uploadDocuments(HttpServletResponse response,
#RequestParam("check") String check){
deleteAllFilesFolder(Directories.DYRECTORY_EXPORT);
ArrayList<Long> idDocuments = Converter.arrayStringInLong(check);
List<PaymentOrderArchive> documents = paymentOrderArchiveService.findAllById(idDocuments);
for (PaymentOrderArchive p : documents){
UploadingFiles.dowloadFileInDirectory(p);
}
//"method" create zip-file and return path
String s = method(documents);
//Problem here
UploadingFiles up = new UploadingFiles();
return up.downloadFile1(servletContext);
}
My method for dowload
public ResponseEntity<InputStreamResource> downloadFile1(ServletContext servletContext) throws IOException {
MediaType mediaType = MediaTypeUtils.getMediaTypeForFileName(servletContext, Directories.NAME_ZIP);
File file = new File(Directories.DYRECTORY_EXPORT + Directories.NAME_ZIP);
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
return ResponseEntity.ok()
// Content-Disposition
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + Directories.NAME_ZIP)
// Content-Type
.contentType(mediaType)
// Contet-Length
.body(resource);
}
public class MediaTypeUtils {
public static MediaType getMediaTypeForFileName(ServletContext servletContext, String
fileName) {
String mineType = servletContext.getMimeType(fileName);
try {
MediaType mediaType = MediaType.parseMediaType(mineType);
return mediaType;
} catch (Exception e) {
return MediaType.APPLICATION_OCTET_STREAM;
}
}
Everything is working. The program does not give errors. But the file is not downloading. And I can't understand why.

#FormParameter data becomes null after reading and setting the same data in ContainerRequestContext entityStream

I have implemented filter and I have called getEntityStream of ContainerRequestContext and set the exact value back by using setEntitystream. If i use this filter then #FormParameter data becomes null and if i don't use filter then everything will be fine (as I am not calling getEntityStream) and i have to use filter to capture request data.
Note: I am getting form params from MultivaluedMap formParams but not from #FormParameter.
Environment :- Rest Easy API with Jboss Wildfly 8 server.
#Provider
#Priority(Priorities.LOGGING)
public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter{
final static Logger log = Logger.getLogger(CustomLoggingFilter.class);
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
MDC.put("start-time", String.valueOf(System.currentTimeMillis()));
String entityParameter = readEntityStream(requestContext);
log.info("Entity Parameter :"+entityParameter);
}
private String readEntityStream(ContainerRequestContext requestContext){
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final InputStream inputStream = requestContext.getEntityStream();
final StringBuilder builder = new StringBuilder();
int read=0;
final byte[] data = new byte[4096];
try {
while ((read = inputStream.read(data)) != -1) {
outStream.write(data, 0, read);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] requestEntity = outStream.toByteArray();
if (requestEntity.length == 0) {
builder.append("");
} else {
builder.append(new String(requestEntity));
}
requestContext.setEntityStream(new ByteArrayInputStream(requestEntity) );
return builder.toString();
}
return null;
}
}
class customResource
{
//// This code is not working
#POST
#Path("voiceCallBack")
#ApiOperation(value = "Voice call back from Twilio")
public void voiceCallback(#FormParam("param") String param)
{
log.info("param:" + param);
}
// This code is working
#POST
#Path("voiceCallBackMap")
#ApiOperation(value = "Voice call back from Twilio")
public void voiceCallbackMap(final MultivaluedMap<String, String> formParams)
{
String param = formParams.getFirst("param");
}
}
please suggest me solution & Thanks in Advance.
I found during run time that instance of the entity stream (from http request) is of type org.apache.catalina.connector.CoyoteInputStream (I am using jboss-as-7.1.1.Final). But we are setting entity stream with the instance of java.io.ByteArrayInputStream. So Resteasy is unable to bind individual formparmeters.
There are two solutions for this you can use any one of them :
Use this approach How to read JBoss Resteasy's servlet request twice while maintaing #FormParam binding?
Get form parameters like this:
#POST
#Path("voiceCallBackMap")
#ApiOperation(value = "Voice call back from Twilio")
public void voiceCallbackMap(final MultivaluedMap<String, String> formParams)
{
String param = formParams.getFirst("param");
}

Spring - What is right way to response file stream?

I have a controller which returns file stream using ResponseEntity class.
But I'm not sure if the resource is closed after finished the method.
#RequestMapping(value = "/VMS-49001/playlist/{listName:.+}")
#ResponseBody
public ResponseEntity<?> playlist(HttpServletRequest request, HttpServletResponse response,
#PathVariable String listName) throws IOException {
String hlsPath = getHLSPath(request.getParameter("dt"), listName, OtuEnum.URLType.HLS);
Path filePath = Paths.get(hlsPath);
if (filePath.toFile().exists()) {
Path fileNamePath = filePath.getFileName();
String fileName = "";
if (fileNamePath != null) {
fileName = fileNamePath.toString();
}
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData(fileName, fileName);
return ResponseEntity.ok().contentLength(filePath.toFile().length())
.contentType(MediaType.parseMediaType("application/vnd.apple.mpegurl")).headers(headers)
.body(new InputStreamResource(Files.newInputStream(filePath)));
} else {
String errorMsg = "404 file not found";
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.contentType(MediaType.parseMediaType("text/html"))
.body(errorMsg);
}
}
if you see below code fragment, Files.newInputStream(filePath) implements Closeable, so it should be closed after use but I can't find the code closing it. :
return ResponseEntity.ok()
.contentLength(filePath.toFile().length())
.contentType(MediaType.parseMediaType("application/vnd.apple.mpegurl"))
.headers(headers)
.body(new InputStreamResource(Files.newInputStream(filePath)));
To response file stream, is it good to serve the file with this code? Or is there any better approach?
With Spring 4.1 your approach will work there is no issue in it.
Here below is another approach in case if you want to look :
#RequestMapping(value = "/VMS-49001/playlist/{listName:.+}")
public ResponseEntity<byte[]> testphoto() throws IOException {
InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/vnd.apple.mpegurl"));
headers.setContentDispositionFormData(fileName, fileName);
return new ResponseEntity<byte[]>(IOUtils.toByteArray(in), headers, HttpStatus.CREATED);
}

Issues with streaming apis on spring mvc framework

I am writing a spring boot app which has REST apis (using spring mvc framework) that stream audio/video to HTML5 player on the browser. These apis support range requests for the content.
I have run into an issue where the HTML5 video player complains with error ERR_CONTENT_LENGTH_MISMATCH periodically during streaming.
It seems that bytes received from server do not match bytes advertised by server in Content-Length header.
Please advise what could be the root cause of this.
Things that I have researched so far that could potentially solve the issue but haven't in my case:
No buffering in response.
No apache in front of tomcat.
Here is my code:
#Api("Player API")
#RestController
public class PlayerController {
#Autowired
FetchAssetService fetchAssetService;
#ApiOperation("Get video")
#RequestMapping(value = "player/video/{packageId}/{username}", method = RequestMethod.GET)
public ResponseEntity<StreamingResponseBody> getProxy(#RequestHeader(value="Range", required=false) String range, #PathVariable Long packageId, #PathVariable String username) throws Exception {
Optional<Stream> videoAssetMetaData = fetchAssetService.fetchVideoAssetMetaData(packageId);
if (!videoAssetMetaData.isPresent()) {
throw new AssetNotFoundException("Video asset not found in MPL for package: "+packageId);
}
HttpHeaders httpHeaders = new HttpHeaders();
HttpStatus status = HttpStatus.OK;
Optional<AssetRange> optionalAssetRange = AssetRange.create(range,videoAssetMetaData.get().getLength());
if (optionalAssetRange.isPresent()) {
if (optionalAssetRange.get().isSatisfiable()) {
setSuccessRangeHeaders(httpHeaders,optionalAssetRange.get());
status = HttpStatus.PARTIAL_CONTENT;
} else {
setErrorRangeHeaders(httpHeaders,optionalAssetRange.get());
status = HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE;
return new ResponseEntity(null,httpHeaders,status);
}
}
setContentHeaders(httpHeaders, “video.mp4");
try {
return new ResponseEntity(fetchAssetService.getStreamingResponseBody(packageId,videoAssetMetaData.get(),optionalAssetRange,username),
httpHeaders,
status);
} catch (Exception ex) {
log.error("Exception while video streaming: package={}, user={}, range={}",packageId,username,range,ex);
throw ex;
}
}
private void setContentHeaders(HttpHeaders httpHeaders, String fileName) {
httpHeaders.add(HttpHeaders.ACCEPT_RANGES,"bytes");
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename="+ fileName);
}
private void setSuccessRangeHeaders(HttpHeaders httpHeaders, AssetRange range) {
httpHeaders.add(HttpHeaders.CONTENT_LENGTH, Long.toString(range.getRangeLength()));
httpHeaders.add(HttpHeaders.CONTENT_RANGE, String.format("bytes %d-%d/%d", range.getStart(), range.getEnd(), range.getTotalLength()));
}
private void setErrorRangeHeaders(HttpHeaders httpHeaders, AssetRange range) {
httpHeaders.add(HttpHeaders.CONTENT_RANGE, String.format("bytes */%d", range.getTotalLength()));
}
#ExceptionHandler(AssetNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public String handleAppException(AssetNotFoundException ex) {
return ex.getMessage();
}
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAppException(Exception ex) {
return ex.getMessage();
}
}
Best guess,
in setSuccessRangeHeaders, you are setting the content length to a range value rather than the actual content length of your response.
try not setting content_length at all or try setting it more accurately.
this might help:
How to set content length as long value in http header in java?

Play 2.4: How to write a test case for file upload with MultipartFormData

So far (up to Play 2.3) to create a mock request I used Helpers.fakeRequest().withAnyContent(). E.g.
private Request getMultiPartFormDataRequestForFileUpload(File file,
String filePartKey, String contentType) {
FilePart<TemporaryFile> part = new MultipartFormData.FilePart<>(
filePartKey, file.getName(), Scala.Option(contentType),
new TemporaryFile(file));
List<FilePart<TemporaryFile>> fileParts = new ArrayList<>();
fileParts.add(part);
scala.collection.immutable.List<FilePart<TemporaryFile>> files = scala.collection.JavaConversions
.asScalaBuffer(fileParts).toList();
MultipartFormData<TemporaryFile> formData = new MultipartFormData<TemporaryFile>(
null, files, null, null);
return Helpers.fakeRequest().withAnyContent(formData);
}
In Play 2.4 this isn't possible any more. If I look at the source of RequestBuilder (which is implemented by FakeRequest) a similar function exist, but it's protected and I can't use it.
protected Http.RequestBuilder body(play.api.mvc.AnyContent anyContent)
Set a AnyContent to this request.
Does anyone know how I can write a test case to check a file upload with MultipartFormData in 2.4?
As Helpers.fakeRequest is a very, very simple method (taken from github)
/**
* Build a new GET / fake request.
*/
public static RequestBuilder fakeRequest() {
return fakeRequest("GET", "/");
}
/**
* Build a new fake request.
*/
public static RequestBuilder fakeRequest(String method, String uri) {
return new RequestBuilder().method(method).uri(uri);
}
you can extend Http.RequestBuilder thus getting access to protected method:
public class FakeRequestBuilder extends HttpRequestBuilder() {
public RequestBuilder setAnyBody(RequestBody body, String contentType) {
header("Content-Type", contentType);
body(body);
}
}
And use that in your test:
//OLD return Helpers.fakeRequest().withAnyContent(formData);
//NEW
return new FakeRequestBuilder().setAnyBody(formData, "multipart/form-data").build();
//or is it application/x-www-form-urlencoded for you?
In Play2.4, body() does not accept a RequestBody, and you have to create an AnyContent from multipart first. Example:
private class FakeRequestBuilder extends Http.RequestBuilder {
public FakeRequestBuilder(String method, String uri) {
method(method).uri(uri);
}
protected RequestBuilder setAnyBody(MultipartFormData<TemporaryFile> formData, String contentType) {
header("Content-Type", contentType);
AnyContent anyContent = new AnyContentAsMultipartFormData(formData);
body(anyContent);
return this;
}
}
A MultiPartFormData thingie can be created by this for example:
private MultipartFormData<TemporaryFile> createFormData(String contentType, String filePartKey, File file) {
FilePart<TemporaryFile> part = new MultipartFormData.FilePart<>(filePartKey, file.getName(), Scala.Option(contentType), new TemporaryFile(file));
List<FilePart<TemporaryFile>> fileParts = new ArrayList<>();
fileParts.add(part);
scala.collection.immutable.List<FilePart<TemporaryFile>> files = scala.collection.JavaConversions.asScalaBuffer(fileParts).toList();
MultipartFormData<TemporaryFile> formData = new MultipartFormData<TemporaryFile>(null, files, null, null);
return formData;
}

Categories

Resources