Spring MVC : Return CSS - java

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)
}

Related

Issue Downloading Plain Text File in Spring Controller from Web App

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

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:

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()

REST service sending corrupted file

I'm trying to create a java REST service that will download a word doc. The file downloads but the contents are just garbage hex, not the actual Word doc contents. My sample code is below. What am I missing? The before & after files have the same amount of bytes.
#SuppressWarnings("resource")
#RequestMapping(value = "get/testdoc", method=RequestMethod.GET, produces="application/octet-stream)
public #ResponseBody ResponseEntity<byte[]> getTestDoc() throws Throwable{
File doc = new File("C:\\temp\\file.doc");
InputStream is = new FileInputStream(doc);
byte[] bytes = IOUtils.toByteArray(is);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
responseHeaders.set("Content-Disposition" , "Attachment; filename=file.doc");
responseHeaders.setContentLength(ProposalDoc.length());
return new ResponseEntity<byte[]>(bytes, responseHeaders, HttpStatus.OK);
}
I think there are two problems:
1. The Length Header:
I my opinion there is at least one very strange line:
responseHeaders.setContentLength(ProposalDoc.length());
I think, it should be:
responseHeaders.setContentLength(bytes.length);
2. #ResponseBody Annotation
If you use return type ResponseEntity<byte[]>, then you must NOT add #ResponseBody.
#RequestMapping(value = "get/testdoc", method=RequestMethod.GET)
public ResponseEntity<byte[]> getTestDoc() throws Throwable{
...
}
try to replace produces="application/octet-stream")
with produces="application/vnd.ms-word")
Thanks for all the help. I ended up bypassing Spring & attaching the file to the response, as listed in the code below. I'm suspecting that sprint was converting the bytes somehow behind the scenes. I looked into configuring the ByteArrayHttpMessageConverter, but that didn't seem to help. This is good enough for me, for now.
#SuppressWarnings("resource")
#RequestMapping(value = "get/doc", method=RequestMethod.GET, produces="application/octet-stream")
public HttpEntity getProposalDocs(HttpServletResponse response) throws Throwable{
File doc = new File("C:\\temp\\file.doc");
InputStream is = new FileInputStream(doc);
response.setHeader("Content-Disposition", "attachment;filename=\"test.doc\"");
response.setHeader("Content-Type", "application/octet-stream;");
StreamUtils.copy(is ,response.getOutputStream());
return new ResponseEntity(HttpStatus.OK);
}
Check this code also, it works fine with me.
#RequestMapping(value = "/get/doc" , method = RequestMethod.GET ,
produces = "application/msword")
public ResponseEntity<InputStreamResource> getProposalDocs() throws IOException{
ClassPathResource docfile = new ClassPathResource("file.doc");
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)
.contentLength(docfile.contentLength())
.contentType(MediaType.parseMediaType("application/msword"))
.body(new InputStreamResource(docfile.getInputStream()));
}
EDITED: the idea that worked with me to return InputStreamResource instead of byte[].
Also specify the content type as produces="application/octet-stream".
This works fine with me without needed to bypass servlet response..

How to send an Image from Web Service in Spring

I am facing problem while sending an Image using Spring Web Service.
I have written controller as below
#Controller
public class WebService {
#RequestMapping(value = "/image", headers = "Accept=image/jpeg, image/jpg, image/png, image/gif", method = RequestMethod.GET)
public #ResponseBody byte[] getImage() {
try {
InputStream inputStream = this.getClass().getResourceAsStream("myimage.jpg");
BufferedImage bufferedImage = ImageIO.read(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write( bufferedImage , "jpg", byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
#ResponseBody converts response into JSON.
I am using RestClient to test Web Service.
But When I'm hitting with http://localhost:8080/my-war-name/rest/image URL.
Header
Accept=image/jpg
I facing following error on RestClient
Response body conversion to string using windows-1252 encoding failed. Response body not set!
When i'm using browsers Chrome and Firefox
Headers are not added so error was expected (Please guide me on this)
HTTP Status 405 - Request method 'GET' not supported
type Status report
message Request method 'GET' not supported
description The specified HTTP method is not allowed for the requested resource (Request method 'GET' not supported).
I have also faced below error once
The resource identified by this request is only capable
of generating responses with characteristics not acceptable according to the request "accept" headers ()
I have followed
http://krams915.blogspot.com/2011/02/spring-3-rest-web-service-provider-and.html tutorial.
My requirment is to send image in byte format to Android Client.
In addition to answer provided by soulcheck. Spring has added produces property to #RequestMapping annotation. Therefore solution is more easier now:
#RequestMapping(value = "/image", method = RequestMethod.GET, produces = "image/jpg")
public #ResponseBody byte[] getFile() {
try {
// Retrieve image from the classpath.
InputStream is = this.getClass().getResourceAsStream("/test.jpg");
// Prepare buffered image.
BufferedImage img = ImageIO.read(is);
// Create a byte array output stream.
ByteArrayOutputStream bao = new ByteArrayOutputStream();
// Write to output stream
ImageIO.write(img, "jpg", bao);
return bao.toByteArray();
} catch (IOException e) {
logger.error(e);
throw new RuntimeException(e);
}
}
The answer by #soulcheck is partially right. The configuration won't work in the latest version of Spring as it would clash with mvc-annotation element. Try the below configuration.
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
Once you have above configuration in your config file. The below code will work:
#RequestMapping(value = "/image", headers = "Accept=image/jpeg, image/jpg, image/png, image/gif", method = RequestMethod.GET)
public #ResponseBody BufferedImage getImage() {
try {
InputStream inputStream = this.getClass().getResourceAsStream("myimage.jpg");
return ImageIO.read(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
See this article on the excellent baeldung.com website.
You can use the following code in your Spring Controller:
#RequestMapping(value = "/rest/getImgAsBytes/{id}", method = RequestMethod.GET)
public ResponseEntity<byte[]> getImgAsBytes(#PathVariable("id") final Long id, final HttpServletResponse response) {
HttpHeaders headers = new HttpHeaders();
headers.setCacheControl(CacheControl.noCache().getHeaderValue());
response.setContentType(MediaType.IMAGE_JPEG_VALUE);
try (InputStream in = imageService.getImageById(id);) { // Spring service call
if (in != null) {
byte[] media = IOUtils.toByteArray(in);
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(media, headers, HttpStatus.OK);
return responseEntity;
}
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseEntity<>(null, headers, HttpStatus.NOT_FOUND);
}
Notes: IOUtils comes from common-io apache library. I am using a Spring Service to retrieve img/pdf Blobs from a database.
Similar handling for pdf files, except that you need to use MediaType.APPLICATION_PDF_VALUE in the content type. And you can refer the image file or the pdf file from an html page:
<html>
<head>
</head>
<body>
<img src="https://localhost/rest/getImgDetectionAsBytes/img-id.jpg" />
<br/>
Download pdf
</body>
</html>
... or you can call the web service method directly from your browser.
Drop conversion to json and sent the byte array as-is.
The only drawback is that it sends application/octet-stream content type by default.
If that doesn't suite you you can use BufferedImageHttpMessageConverter which can send any image type supported by registered image readers.
Then you can change your method to:
#RequestMapping(value = "/image", headers = "Accept=image/jpeg, image/jpg, image/png, image/gif", method = RequestMethod.GET)
public #ResponseBody BufferedImage getImage() {
try {
InputStream inputStream = this.getClass().getResourceAsStream("myimage.jpg");
return ImageIO.read(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
while having :
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="order" value="1"/>
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
</list>
</property>
</bean>
in your spring config.
Here's the method I wrote for this.
I needed to both display the image inline on a page, and optionally download it to the client, so I take an optional parameter to set the appropriate header for that.
Document is my entity model to represent documents. I have the files themselves stored on disc named after the ID of the record that stores that document. The original filename and mime type are stored in the Document object.
#RequestMapping("/document/{docId}")
public void downloadFile(#PathVariable Integer docId, #RequestParam(value="inline", required=false) Boolean inline, HttpServletResponse resp) throws IOException {
Document doc = Document.findDocument(docId);
File outputFile = new File(Constants.UPLOAD_DIR + "/" + docId);
resp.reset();
if (inline == null) {
resp.setHeader("Content-Disposition", "attachment; filename=\"" + doc.getFilename() + "\"");
}
resp.setContentType(doc.getContentType());
resp.setContentLength((int)outputFile.length());
BufferedInputStream in = new BufferedInputStream(new FileInputStream(outputFile));
FileCopyUtils.copy(in, resp.getOutputStream());
resp.flushBuffer();
}
If you are using spring boot, just placing the images in the right folder in your classpath will do the trick. Check https://www.baeldung.com/spring-mvc-static-resources

Categories