Issue Downloading Plain Text File in Spring Controller from Web App - java

I'm having an issue where I am trying to download a simple "text/plain" file in a spring controller method. I'm getting the text that I exactly want in the web tools response when running the app, which is "test". The response headers in the web developer tools are as follows:
Content-disposition: attachment; filename=file.txt
Content-type: text/plain
Content-length: 4
Length is 4 since that's the number of bytes that the text "test" is. In the controller, I have produces = MediaType.TEXT_PLAIN_VALUE. When I click the associated button in the application to download the file, however, rather than showing the download in the web browser, the download is made to the disk because the file.txt actually shows up in my project's workspace in intellij (which I'm using for my IDE). So, my question is how do I get the download to occur in the web browser, meaning what happens when you click on the 'Download Source Code' button at the following link https://howtodoinjava.com/spring-mvc/spring-mvc-download-file-controller-example/, rather than the file downloading to my workspace/disk?
The support methods/classes look like the following:
public class TextFileExporter implements FileExporter {
#Override
public Path export(String content, String filename) {
Path filepath = Paths.get(filename);
Path exportedFilePath = Files.write(filepath, content.getBytes(),
StandardOpenOption.CREATE);
}
}
public interface FileExporter {
public Path export(String content, String filename);
}
The controller at hand is the following:
#GetMapping(value="downloadFile")
public void downloadFile(HttpServletRequest request, HttpServletResponse response) {
String filename = "example.txt";
String content = "test";
Path exportedpath = fileExporter.export(content, filename);
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
Files.copy(exportedpath, response.getOutputStream());
response.getOutputStream.flush();
}

Try using directly the Response entity to return an InputStreamResource
#RequestMapping("/downloadFile")
public ResponseEntity<InputStreamResource> downloadFile() throws FileNotFoundException {
String filename = "example.txt";
String content = "test";
Path exportedpath = fileExporter.export(content, filename);
// Download file with InputStreamResource
File exportedFile = exportedPath.toFile();
FileInputStream fileInputStream = new FileInputStream(exportedFile);
InputStreamResource inputStreamResource = new InputStreamResource(fileInputStream);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
.contentType(MediaType.TEXT_PLAIN)
.contentLength(exportedFile.length())
.body(inputStreamResource);
}
As #chrylis -cautiouslyoptimistic said try avoiding using low level objects, let it handle it by Spring itself

Related

How to copy/rename a file and return it without altering the existing file?

I have a generic.exe file which doesn't contain any users detail in it.
I also have a REST API which takes the userId and returns a File to the client.
Now, what we want to implement in our project is that when someone hits the REST API, we want to take that generic.exe and rename it to manager_userId.exe and return back this "manager_userId.exe".
Points to be noted over here is that:
The generic.exe file should not be modified/deleted at all
When 2 users (userA and userB) hit that same API simultaneously , they should get their own copy of manager_userA.exe and manager_userB.exe
The code what I have written is
#RequestMapping(value = "/downloadExecutable", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON, produces = {MediaType.APPLICATION_OCTET_STREAM})
#ResponseBody
public Response downloadExecutable(#RequestBody DownloadExecutableRequest downloadExecutableRequest,
HttpServletRequest request, HttpServletResponse response) {
File file = downloadExecutable(downloadExecutableRequest, request, response,
getUserID(request), osDetails);
return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment;filename=" + file.getName()).build();
}
public File downloadExecutable(DownloadExecutableRequest downloadExecutableRequest, HttpServletRequest request,
HttpServletResponse response, String userId, String osDetails) {
File file = null;
String path = "/home/genericCopy/generic.exe";
synchronized (this) {
BufferedWriter fileWriter = null;
try {
File source = null;
source = new File(path);
Path destination = Paths.get("/tmp/");
Files.copy(source, destination.toFile());
fileWriter = new BufferedWriter(new FileWriter(destination.getFileName().toString()+"_"+userId));
file = new File(destination.getFileName().toString());
} catch (IOException e) {
} finally {
if (fileWriter != null) {
fileWriter.close();
}
}
}
return file;
}
The code is working , but it is creating a temporary file and then renaming it and then returning it back but it will keep on creating the copies of the file for each request.
Is there any smarter way that i can achieve not to create such temporary copies of the user specific files and also handle a scenario when 2 users hit the API simultaneously ?
The name of the file which is downloaded by user has no relationship to the name of the file on disk.
You can just specify any name of the file in the header and the user will see the name.
Specifically, you would just set the filename you want the user to see to the Content-Disposition header and always load the same exe file from the disk.
Something like this:
return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment;filename=executable_" + getUserID(request) + ".exe";
You don't need to do any copying in the downloadExecutable function.
You don't need to create a copy of generic.exe file to return it with changed name. You can use correctly parametrised Content-Disposition header, so it would return same file every time, with file name provided by user.
Here you can find example:
#RestController
public class DemoController {
#GetMapping(value = "/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
#ResponseBody
public ResponseEntity downloadExecutable(#RequestParam("userId") String userId) throws IOException {
byte[] file = Files.readAllBytes(Paths.get("/home/genericCopy/generic.exe"));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=generic_" + userId + ".exe")
.contentLength(file.length)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(file);
}
}
and result of executing this method:

How to write to a txt file in Servlet and automatically download it when requests

I want to create a txt file in my Servlet and automatically download it at the client side when client requests. I have below code to write to a txt, but it gives access denied error in Netbeans IDE using glassfishserver. How can I do it?
//File creation
String strPath = "C:\\example.txt";
File strFile = new File(strPath);
boolean fileCreated = strFile.createNewFile();
//File appending
Writer objWriter = new BufferedWriter(new FileWriter(strFile));
objWriter.write("This is a test");
objWriter.flush();
objWriter.close();
Its not a thing you do it in JSP. You better have a Servlet and just create a Outputstream and put your text in it. Then flush that stream into the HttpServletResponse.
#WebServlet(urlPatterns = "/txt")
public class TextServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/plain");
response.setHeader("Content-Disposition", "attachment; filename=\"example.txt\"");
try {
OutputStream outputStream = response.getOutputStream();
String outputResult = "This is Test";
outputStream.write(outputResult.getBytes());
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Remember you need to set the content-type text/plain and a Content-Disposition header that mentions filename and tells broswer that it should be downloaded as file attachment.
This is what Content-Disposition header is about in concise description
In a regular HTTP response, the Content-Disposition response header is
a header indicating if the content is expected to be displayed inline
in the browser, that is, as a Web page or as part of a Web page, or as
an attachment, that is downloaded and saved locally.
If you are a beginner. You may like to learn more about from this
What is HTTP, Structure of HTTP Request and Response?
How a Servlet Application works
Difference Between Servlet and JSP

Browsers do not automatically display an inline file returned from a back-end java HTTP response

I created a pdf on the fly with pdfbox, did corresponding tests and it is well formatted, I can save it, send it by email - and everything is working as expected.
Now that same pdf(without saving it), I returns it as a client hit a button; the respective request/response succeeds but the browser (any) do not display it.
Some context:
angularJS 1.6 on the front-end
jersey 1.9 as a rest api
HTTP POST method
No errors
It just stays on the current page
My code
final ByteArrayOutputStream pdfStream = (ByteArrayOutputStream) generatePricePDF((Price) services.includeClient(price), null);
StreamingOutput streamingOutput = new StreamingOutput() {
#Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
pdfStream.writeTo(outputStream);
pdfStream.flush();
pdfStream.close();
}
};
return Response.ok(streamingOutput, MediaType.valueOf("application/pdf"))
.header("Content-Length", pdfStream.toString().length())
.header("Content-Disposition", "inline; filename=price").build();
}
As I mentioned it above, I can copy the encoded response:
JVBERi0xLjQKJfbk/N8KMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovVmVyc2lvbiAvMS40Ci9QYWdlcyAyIDAgUgo+PgplbmRvYmoKMiAwIG9iago8PAovVHlwZSAvUGFnZXMKL0tpZHMgWzMgMCBSXQovQ291bnQgMQo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvUGFnZQovTWVkaWFCb3ggWzAuMCAwLjAgNjEyLjAgNzkyLjBdCi9QYXJlbnQgMiAwIFIKL0NvbnRlbnRzIDQgMCBSCi9SZXNvdXJjZXMgNSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL0xlbmd0aCA0MzEKL0ZpbHRlciAvRmxhdGVEZWNvZGUKPj4Kc3RyZWFtDQp4nO1Xy27bMBC86yv2mByUcvmUiiBA7Ui3nqofIFQ6UKFaqCQDSb++I1J20cfFQG4W4MNydrg7JAcLa9dkH2omltQcMsOCnET4Nbszpryn5luWq9JQbhK4H1qft0PvY0oZQSLi9djRZz+SkMTFRyHwo/2XhqTgYq0Cbs5FZH96OXU92Ds/j2FKpZxcS2nFVjPnLuJVk+2azBgyLqWZTUxok/iP2jjltGUpLCIpnLFVXOllbVHYlk5YZcta2Qq4A8LgIA92mZhWgi0r7MMeRECtrcF8tuyWPEQtVVGJl/1Pi4blREmEY7RFObRZiXZfPaOJiXFl6yivQN6c8yivkuil6CIwIpB43ZH+kvI+50N05ZH+lHHXDxOFPrTzOIRjGw6+H15Gf+imefUOHlBdnvRBiIgau7qAlZQX9LcJ5GaCmzeBvkyCYjPBLZgAo/9fE2yT4LZM8L9JoLb/BJsJ1DYJNhPIbRJsJpA3MwmeznfEV90RS+zQKn3Jd+E4DzSFbqLvXU9tWrcnPyLw9Abk2A704xTmn74PeK9X387D9LB+rzOdrxvSNDqdG/0CJBiXHA0KZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8Ci9Gb250IDYgMCBSCj4+CmVuZG9iago2IDAgb2JqCjw8Ci9GMSA3IDAgUgo+PgplbmRvYmoKNyAwIG9iago8PAovVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTEKL0Jhc2VGb250IC9UaW1lcy1Sb21hbgovRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZwo+PgplbmRvYmoKeHJlZgowIDgKMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDAwMDE1IDAwMDAwIG4NCjAwMDAwMDAwNzggMDAwMDAgbg0KMDAwMDAwMDEzNSAwMDAwMCBuDQowMDAwMDAwMjQ3IDAwMDAwIG4NCjAwMDAwMDA3NTIgMDAwMDAgbg0KMDAwMDAwMDc4NSAwMDAwMCBuDQowMDAwMDAwODE2IDAwMDAwIG4NCnRyYWlsZXIKPDwKL1Jvb3QgMSAwIFIKL0lEIFs8RkI2MUI4MDM5Q0RFQzU0OTkwNDFFMENBMDc0RTNDREU+IDxGQjYxQjgwMzlDREVDNTQ5OTA0MUUwQ0EwNzRFM0NERT5dCi9TaXplIDgKPj4Kc3RhcnQ=
and I can convert it (using an online converted), So there it is my pdf.
I have reviewed Content-Disposition
and it seems that the header are ok.
I was not managing the response accordingly, this is the code added in the AngularJs 1.x Controller:
$http.post('rest/processprice', $scope.price, {responseType: "blob"})
.then(function(response) {
var file = new Blob([response.data], {type: "application/pdf"});
var fileURL = URL.createObjectURL(file);
$window.open(fileURL, '_blank');
});
Managing the response this way, the pdf file opens up automatically.

Web service call fails with RESTClient

I have a web service developed using Eclipse. Now I want to test it using RESTClient program. I want the client to download the video, which I have defined like this in Eclipse:
#Path("/university")
public class Video {
//this is the location of the .avi
private static final String VIDEO_FILE = "F:\\file.avi";
#GET
#Path("/video")
#Produces("video/avi")
public Response getVideoFile() {
File file = new File(VIDEO_FILE);
ResponseBuilder response = Response.ok((Object) file);
response.header("Content-Disposition", "attachment; filename=\"abc.avi\"");
return response.build();
}
#GET
#Path("/{fileName}/video")
#Produces("video/avi")
public Response getFileInVideoFormat(#PathParam("fileName") String fileName) {
System.out.println("File requested is : " + fileName);
if (fileName == null || fileName.isEmpty()) {
ResponseBuilder response = Response.status(Status.BAD_REQUEST);
return response.build();
}
File file = new File("c:/abc.avi");
ResponseBuilder response = Response.ok((Object) file);
response.header("Content-Disposition", "attachment; filename=abc.avi");
return response.build();
}
}
but I am getting errors when I test using RESTClient (where I specify METHOD=GET, HEADER(key=accept,value=video/avi)). What might the problem?
First, if your REST service does not #Consume something, you don't have to specify "Accept" parameter in header (see HTTP request specification).
Second, your call should look like contextpath/university/video or contextpath/university/filename/video.
Third, don't use ambiguous REST paths like /video and /{anystring}/video, your application server can understand your calls wrong. Use patterns (for example /{id:[0-9]+) or rename /{filename}/video to /video/{filename} to avoid ambiguousness.

Spring MVC : Return CSS

My goal is to merge/minify all css files and return the result as String.
Here's my Spring test method :
#RequestMapping(value = "/stylesheet.css", method = RequestMethod.GET, produces = "text/css")
#ResponseBody
public void css(HttpServletResponse response) {
File path = new File(servletContext.getRealPath("/WEB-INF/includes/css/"));
File[] files = path.listFiles(...);
for (File file : files) {
InputStream is = new FileInputStream(file);
IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
is.close();
}
}
This is working with Chrome, Firefox and Safari but not with IE and Opera.
After some checks in the inspectors, the URL https://host/project/stylesheet.css is loading in each browsers. I can see the content but it does not seem to be recognized as text/css.
Also, even with produces = "text/css", I can not see the content-type http header in all browsers.
Error log in IE :
CSS ignored because of mime type incompatibility
Does anyone know how to correctly do this?
Working code :
#RequestMapping(value = "/stylesheet.css", method = RequestMethod.GET)
public ResponseEntity<Void> css(HttpServletResponse response) {
response.setContentType("text/css");
File path = new File(servletContext.getRealPath("/WEB-INF/includes/css/"));
File[] files = path.listFiles(...);
for (File file : files) {
InputStream is = new FileInputStream(file);
IOUtils.copy(is, response.getOutputStream());
IOUtils.closeQuietly(is);
}
response.flushBuffer();
return new ResponseEntity<Void>(HttpStatus.OK);
}
I suspect the problem is due to your usage of HttpServletResponse.flushBuffer().
As the API of HttpServletRequest states:
Forces any content in the buffer to be written to the client. A call
to this method automatically commits the response, meaning the status
code and headers will be written.
My assumption would be that Spring attempts to set the Content-Type header on the HttpServletResponse after the method on your controller has returned. However, because you have committed the response with your call to HttpServletResponse.flushBuffer(), it cannot do this.
I would try either:
Injecting the HttpServletResponse into your controller and setting the header yourself in code before calling HttpServletResponse.flushBuffer()
Removing your usage of HttpServletRequest.flushBuffer()
Since you're writing the content directly to the output stream, you don't need to use #ResponseBody. You just need to ensure that you set the Content-Type response header. Also, it'd be better to return a ResponseEntity (rather than void) to indicate to Spring that you're handling the response yourself.
#RequestMapping(value = "/stylesheet.css", method = RequestMethod.GET)
public ResponseEntity css(HttpServletResponse response) {
// Set the content-type
response.setHeader("Content-Type", "text/css");
File path = new File(servletContext.getRealPath("/WEB-INF/includes/css/"));
File[] files = path.listFiles(...);
for (File file : files) {
InputStream is = new FileInputStream(file);
IOUtils.copy(is, response.getOutputStream());
IOUtils.closeQuietly(is);
}
response.flushBuffer();
return new ResponseEntity(HttpStatus.OK)
}

Categories