Java servlet cuts off file extension - java

I'm attempting to serve local or proxy files via my Java application.
With something like this
#ResponseBody
#RequestMapping(value = "/file/{file}", method = RequestMethod.GET)
public void doGet(HttpServletRequest request, HttpServletResponse response,#PathVariable("file") String f) throws IOException {
String filename = URLDecoder.decode(f, "UTF-8");
File file = new File("resources/files/", filename);
response.setHeader("Content-Type", "video/mp4");
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Disposition", "inline; filename=\"" + file.getName() + "\"");
Files.copy(file.toPath(), response.getOutputStream());
}
With an example URL such as
example.com/file/out2KP2_1.mp4
I'm getting the error:
Problem accessing /file/out2KP2_1.mp4. Reason:
resources\files\out2KP2_1 Caused by:
java.nio.file.NoSuchFileException: resources\files\out2KP2_1 at
sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
If I add a trailing / to the url and requestMapping, it works, but on iOS and a few other places the video player doesn't seem to like loading a file with a trailing slash so it doesn't suit my purposes.
Any help appriciated.

Thanks for the mark down. Take it you didn't know the answer!?
It's a problem with Spring, you have to add :.+ to the file URL variable
i.e. #RequestMapping(value = "/file/{file:.+}", method = RequestMethod.GET)

Related

Java-spingboot how to save text document via REST-API?

I am a beginner programmer, help with the implementation of uploading a text file via rest-api java.
I have already implemented the simplest action - unload a file from the server, here is my code:
#GetMapping(value = "/file/{filename:.+}")
public ResponseEntity<Resource> unloadFile(#PathVariable String filename) {
Resource file = storageService.loadAsResource(filename);
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"").body(file);
}
I can test the file unload by simply following the link!
I cannot test the upload. I find it difficult to write tests. Please tell me if I got a working code and maybe there is a better way to upload. My code upload:
#PostMapping(value = "/file")
public ResponseEntity<MultipartFile> uploadFile(MultipartFile file) {
storageService.store(file);
return ResponseEntity.ok().body(file);
}
Thank you so much!
To upload the file/files using spring boot application we use same method that we had for servlet containers. In your controller
#PostMapping("/uploadFile")
public ResponseEntity<Object> uploadFile(#RequestParam("file") MultipartFile file) {
String fileName = yourStorageService.storeFile(file);
String = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(fileName)
.toUriString();
//You can even generate download links.
return new ResponseEntity<Object>(HttpStatus.Ok, "Uploaded", fileDownloadUri);
}
To download the files:
#GetMapping("/downloadFile/{fileName}")
public ResponseEntity<Resource> downloadFile(#PathVariable String fileName, HttpServletRequest request) {
// Load file as Resource from DB or local
Resource resource = fileStorageService.loadFileAsResource(fileName);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
For #PostMapping(value = "/file") endpoint , its best to return a success/error status instead of returning the file,if file is larger ..it takes time to return back.
Better to return success state. 200 ok.

Streaming xlsx file from resources folder corrupts the file

I have a small problem which I've been unable to solve for a few hours. I am basically trying to stream an excel file from the resources folder withing a jar. The file has around 9KB in my file manager, however, when I download it by visiting the REST endpoint I receive a 13/14KB file which can no longer be opened by excel. The metadata is set correctly, as is the filename. I suspect the streaming/copying process is somehow corrupting the file. Here you may see the code snippet:
public void getTemplateByDataType(HttpServletResponse response, DataType dataType) {
String fileName = "excel_template.xlsx";
String templateDirectory = "templates";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
InputStream data = this.getClass().getClassLoader().getResourceAsStream(templateDirectory + "/" + fileName); // loading file from resources folder
try {
IOUtils.copy(data, response.getOutputStream()); // copying to httpservletresponse output stream
} catch (IOException e) {
//...
}
}
I've already tried reading from a simple text file in the same location in order to verify whether the getResourceAsStream call works and this is the case. So I am somehow breaking something with the IOUtils.copy I guess? Does anybody have any suggestions why this simple code snippet breaks my xlsx files?
Just to get the full picture, the controller is relatively simple:
#GetMapping(value = "/templates", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
public #ResponseBody
void getFileTemplate(HttpServletResponse response,
#ApiParam(value = "Type of data import", required = true) #RequestParam String dataType) {
importService.getTemplateByDataType(response, DataType.fromValue(dataType));
}
Try Streaming Output. Maybe this would help you Example of using StreamingOutput as Response entity in Jersey
If you want to download as an attachment, then return the Response like below:
Response.status(Response.Status.OK)
.header(HttpHeaders.CONTENT_DISPOSITION,
String.format("attachment; filename=\"download.gz\""))
.entity(streamingOutput)
.build();
StreamingOutput streams the content of the file and at the client end, it will be downloaded as an attachment.
try to copy this file directly to your output stream.
#GetMapping(value = "/templates", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
public #ResponseBody
void getFileTemplate(HttpServletResponse response,
#ApiParam(value = "Type of data import", required = true) #RequestParam String dataType) {
String fileName = "excel_template.xlsx";
String templateDirectory = "templates";
Path templateFilePath = Paths.get(getClass().getClassLoader().getResource(templateDirectory + "/" + fileName).toURI());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
try {
FileCopyUtils.copy(new BufferedInputStream(new FileInputStream(templateFilePath.toFile())), response.getOutputStream());
response.getOutputStream().flush();
response.getOutputStream().close();
} catch (IOException e) {
//...
}
}
```

Java Spring MVC - RequestMapping method won't invoke

I have the following code for a method thats supposed to return a file for download. The path variables are used to navigate to a specific file on the filesystem.
#GetMapping("/files/{username}/{docId}/{revisionNo}/{filename:.}")
#ResponseBody
public ResponseEntity<Resource> serveFile (#PathVariable("username") String username,
#PathVariable("docId") String docId,
#PathVariable("revisionNo") String revisionNo,
#PathVariable("filename") String filename)
{
System.out.println("Serve file firing"); // for debug
String filepath = username + "/" + docId + "/" + revisionNo + "/" + filename;
Resource file = storageService.loadAsResource(filepath); // this method works fine
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""+file.getFilename()+"\"")
.body(file);
}
However when I start the server and go to "http://localhost:8080/files/admin/0/0/Capture1.PNG" I get a 404 error and no download. The debug println on line 9 doesn't print which would indicate this method is not being triggered.
For context this similar code does work correctly although it doesn't use the filesystem hierarchy i need and only returns files in the root folder.
#GetMapping("/files/{filename:.+}")
#ResponseBody
public ResponseEntity<Resource> serveFile(#PathVariable String filename) {
Resource file = storageService.loadAsResource(filename);
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""+file.getFilename()+"\"")
.body(file);
}
I think then it must be to do with how I've used the path variables but all the research I've done indicates that its correct...running out of time to fix please help!

mockmvc download functionality for contentType "application/octet-stream" throws FileNotFound error

I am new to the mockmvc api.
I am trying to write units for my controllers and one of the method performs download. Please find the code snippet:
Controller :
#RequestMapping(value = "/download-template", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
#ResponseBody
public FileSystemResource downloadTemplate(HttpServletRequest request,
HttpServletResponse response) {
logger.info("User ID: " + request.getAttribute("userId")
+ " - POST: /upload/download-template");
String rootDir = config.getBaseFolder();
DownloadUploadTemplateResponse res = uploadService.downloadTemplate(
rootDir, false);
response.setHeader("Content-Disposition",
"attachment; filename=" + res.getFileName());
return new FileSystemResource(res.getTemplateFile());
}
Unit test code:
#Test
public void testDownloadTemplate(){
DownloadResponse res = new DownloadResponse();
String templateFile = "upload-template.xlsx";
String fileName = System.getProperty("java.io.tmpdir") + templateFile;
res.setFileName(fileName);
res.setTemplateFile(templateFile);
when(config.getBaseFolder()).thenReturn(System.getProperty("java.io.tmpdir"));
when(uploadService.downloadTemplate( System.getProperty("java.io.tmpdir"), false)).thenReturn(res);
//File file = new File(fileName);
FileSystemResource resource = new FileSystemResource(res.getTemplateFile());
try{
ResultActions action = mockMvc.perform(get("/upload/download-template").contentType(APP_OCTET_STREAM_VALUE_UTF8));
action.andExpect(header().string("Content-Disposition",
"attachment; filename=" + res.getFileName()));
action.andExpect(status().isOk());
}catch(Exception e){
e.printStackTrace();
fail();
}
}
Below line throws java.io.FileNotFoundException: upload-template.xlsx (The system cannot find the file specified):
ResultActions action = mockMvc.perform(get("/upload/download-template").contentType(APP_OCTET_STREAM_VALUE_UTF8));
Please guide what additional I need to add in the httprequestbuilder to resolve this issue.
My bad .. the error was clear enef to fix the issue . The problem was the file was not physically present :).
So by placing the file in the temp folder helped me resolve the issue. Feel like ahhh .

Downloading a file from spring controllers

I have a requirement where I need to download a PDF from the website. The PDF needs to be generated within the code, which I thought would be a combination of freemarker and a PDF generation framework like iText. Any better way?
However, my main problem is how do I allow the user to download a file through a Spring Controller?
#RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
#PathVariable("file_name") String fileName,
HttpServletResponse response) {
try {
// get your file as InputStream
InputStream is = ...;
// copy it to response's OutputStream
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
throw new RuntimeException("IOError writing file to output stream");
}
}
Generally speaking, when you have response.getOutputStream(), you can write anything there. You can pass this output stream as a place to put generated PDF to your generator. Also, if you know what file type you are sending, you can set
response.setContentType("application/pdf");
I was able to stream line this by using the built in support in Spring with it's ResourceHttpMessageConverter. This will set the content-length and content-type if it can determine the mime-type
#RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
#ResponseBody
public FileSystemResource getFile(#PathVariable("file_name") String fileName) {
return new FileSystemResource(myService.getFileFor(fileName));
}
You should be able to write the file on the response directly. Something like
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\"");
and then write the file as a binary stream on response.getOutputStream(). Remember to do response.flush() at the end and that should do it.
With Spring 3.0 you can use the HttpEntity return object. If you use this, then your controller does not need a HttpServletResponse object, and therefore it is easier to test.
Except this, this answer is relative equals to the one of Infeligo.
If the return value of your pdf framework is an byte array (read the second part of my answer for other return values) :
#RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
#PathVariable("fileName") String fileName) throws IOException {
byte[] documentBody = this.pdfFramework.createPdf(filename);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(documentBody.length);
return new HttpEntity<byte[]>(documentBody, header);
}
If the return type of your PDF Framework (documentBbody) is not already a byte array (and also no ByteArrayInputStream) then it would been wise NOT to make it a byte array first. Instead it is better to use:
InputStreamResource,
PathResource (since Spring 4.0) or
FileSystemResource,
example with FileSystemResource:
#RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
#PathVariable("fileName") String fileName) throws IOException {
File document = this.pdfFramework.createPdf(filename);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(document.length());
return new HttpEntity<byte[]>(new FileSystemResource(document),
header);
}
If you:
Don't want to load the whole file into a byte[] before sending to the response;
Want/need to send/download it via InputStream;
Want to have full control of the Mime Type and file name sent;
Have other #ControllerAdvice picking up exceptions for you (or not).
The code below is what you need:
#RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<FileSystemResource> downloadStuff(#PathVariable int stuffId)
throws IOException {
String fullPath = stuffService.figureOutFileNameFor(stuffId);
File file = new File(fullPath);
long fileLength = file.length(); // this is ok, but see note below
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentType("application/pdf");
respHeaders.setContentLength(fileLength);
respHeaders.setContentDispositionFormData("attachment", "fileNameIwant.pdf");
return new ResponseEntity<FileSystemResource>(
new FileSystemResource(file), respHeaders, HttpStatus.OK
);
}
More on setContentLength(): First of all, the content-length header is optional per the HTTP 1.1 RFC. Still, if you can provide a value, it is better. To obtain such value, know that File#length() should be good enough in the general case, so it is a safe default choice.
In very specific scenarios, though, it can be slow, in which case you should have it stored previously (e.g. in the DB), not calculated on the fly. Slow scenarios include: if the file is very large, specially if it is on a remote system or something more elaborated like that - a database, maybe.
InputStreamResource
If your resource is not a file, e.g. you pick the data up from the DB, you should use InputStreamResource. Example:
InputStreamResource isr = new InputStreamResource(...);
return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);
Do
Return ResponseEntity<Resource> from a handler method
Specify Content-Type
Set Content-Disposition if necessary:
filename
type
inline to force preview in a browser
attachment to force a download
Example
#Controller
public class DownloadController {
#GetMapping("/downloadPdf.pdf")
// 1.
public ResponseEntity<Resource> downloadPdf() {
FileSystemResource resource = new FileSystemResource("/home/caco3/Downloads/JMC_Tutorial.pdf");
// 2.
MediaType mediaType = MediaTypeFactory
.getMediaType(resource)
.orElse(MediaType.APPLICATION_OCTET_STREAM);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(mediaType);
// 3
ContentDisposition disposition = ContentDisposition
// 3.2
.inline() // or .attachment()
// 3.1
.filename(resource.getFilename())
.build();
headers.setContentDisposition(disposition);
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
}
Explanation
Return ResponseEntity<Resource>
When you return a ResponseEntity<Resource>, the ResourceHttpMessageConverter writes file contents
Examples of Resource implementations:
ByteArrayResource - based in byte[]
FileSystemResource - for a File or a Path
UrlResource - retrieved from java.net.URL
GridFsResource - a blob stored in MongoDB
ClassPathResource - for files in classpath, for example files from resources directory. My answer to question "Read file from resources folder in Spring Boot" explains how to locate the resource in classpath in details
Specify Content-Type explicitly:
Reason: see "FileSystemResource is returned with content type json" question
Options:
Hardcode the header
Use the MediaTypeFactory from Spring. The MediaTypeFactory maps Resource to MediaType using the /org/springframework/http/mime.types file
Use a third party library like Apache Tika
Set Content-Disposition if necessary:
About Content-Disposition header:
The first parameter in the HTTP context is either inline (default value, indicating it can be displayed inside the Web page, or as the Web page) or attachment (indicating it should be downloaded; most browsers presenting a 'Save as' dialog, prefilled with the value of the filename parameters if present).
Use ContentDisposition in application:
To preview a file in a browser:
ContentDisposition disposition = ContentDisposition
.inline()
.filename(resource.getFilename())
.build();
To force a download:
ContentDisposition disposition = ContentDisposition
.attachment()
.filename(resource.getFilename())
.build();
Use InputStreamResource carefully:
Specify Content-Length using the HttpHeaders#setContentLength method if:
The length is known
You use InputStreamResource
Reason: Spring won't write Content-Length for InputStreamResource because Spring can't determine the length of the resource. Here is a snippet of code from ResourceHttpMessageConverter:
#Override
protected Long getContentLength(Resource resource, #Nullable MediaType contentType) throws IOException {
// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
if (InputStreamResource.class == resource.getClass()) {
return null;
}
long contentLength = resource.contentLength();
return (contentLength < 0 ? null : contentLength);
}
In other cases Spring sets the Content-Length:
~ $ curl -I localhost:8080/downloadPdf.pdf | grep "Content-Length"
Content-Length: 7554270
This code is working fine to download a file automatically from spring controller on clicking a link on jsp.
#RequestMapping(value="/downloadLogFile")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
try {
String filePathToBeServed = //complete file name with path;
File fileToDownload = new File(filePathToBeServed);
InputStream inputStream = new FileInputStream(fileToDownload);
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment; filename="+fileName+".txt");
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
} catch (Exception e){
LOGGER.debug("Request could not be completed at this moment. Please try again.");
e.printStackTrace();
}
}
Below code worked for me to generate and download a text file.
#RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {
String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
byte[] output = regData.getBytes();
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("charset", "utf-8");
responseHeaders.setContentType(MediaType.valueOf("text/html"));
responseHeaders.setContentLength(output.length);
responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");
return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}
What I can quickly think of is, generate the pdf and store it in webapp/downloads/< RANDOM-FILENAME>.pdf from the code and send a forward to this file using HttpServletRequest
request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);
or if you can configure your view resolver something like,
<bean id="pdfViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="order" value=”2″/>
<property name="prefix" value="/downloads/" />
<property name="suffix" value=".pdf" />
</bean>
then just return
return "RANDOM-FILENAME";
The following solution work for me
#RequestMapping(value="/download")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
try {
String fileName="archivo demo.pdf";
String filePathToBeServed = "C:\\software\\Tomcat 7.0\\tmpFiles\\";
File fileToDownload = new File(filePathToBeServed+fileName);
InputStream inputStream = new FileInputStream(fileToDownload);
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment; filename="+fileName);
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
} catch (Exception exception){
System.out.println(exception.getMessage());
}
}
something like below
#RequestMapping(value = "/download", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
try {
DefaultResourceLoader loader = new DefaultResourceLoader();
InputStream is = loader.getResource("classpath:META-INF/resources/Accepted.pdf").getInputStream();
IOUtils.copy(is, response.getOutputStream());
response.setHeader("Content-Disposition", "attachment; filename=Accepted.pdf");
response.flushBuffer();
} catch (IOException ex) {
throw new RuntimeException("IOError writing file to output stream");
}
}
You can display PDF or download it examples here
If it helps anyone. You can do what the accepted answer by Infeligo has suggested but just put this extra bit in the code for a forced download.
response.setContentType("application/force-download");
In my case I'm generating some file on demand, so also url has to be generated.
For me works something like that:
#RequestMapping(value = "/files/{filename:.+}", method = RequestMethod.GET, produces = "text/csv")
#ResponseBody
public FileSystemResource getFile(#PathVariable String filename) {
String path = dataProvider.getFullPath(filename);
return new FileSystemResource(new File(path));
}
Very important is mime type in produces and also that, that name of the file is a part of the link so you has to use #PathVariable.
HTML code looks like that:
<a th:href="#{|/dbreport/files/${file_name}|}">Download</a>
Where ${file_name} is generated by Thymeleaf in controller and is i.e.: result_20200225.csv, so that whole url behing link is: example.com/aplication/dbreport/files/result_20200225.csv.
After clicking on link browser asks me what to do with file - save or open.
I had to add this to download any file
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename="+"file.txt");
all code:
#Controller
public class FileController {
#RequestMapping(value = "/file", method =RequestMethod.GET)
#ResponseBody
public FileSystemResource getFile(HttpServletResponse response) {
final File file = new File("file.txt");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename="+"file.txt");
return new FileSystemResource(file);
}
}
This can be a useful answer.
Is it ok to export data as pdf format in frontend?
Extending to this, adding content-disposition as an attachment(default) will download the file. If you want to view it, you need to set it to inline.

Categories

Resources