Download file and serve it (Spring WebClient -> Liferay Porlet.serveResource) - spring

I want to implement a Liferay Portlet that downloads a ~1GB file from a separate server, and serves it to the website visitor who clicked the link.
The file must be streamed in a memory-efficient way (so no loading everything into memory), and the user should see the download progress shortly after clicking (so no storing everything onto the local disk).
I must use WebClient because it seems to be the standard for making web requests within Liferay 7 (RestTemplate will be deprecated).
I started writing something like this, inspired by an example from the javadoc:
Mono<DataBuffer> bodyMono = client.get()
.uri("https://theotherserver.com/file94875.pdf")
.retrieve()
.bodyToMono(DataBuffer.class);
... which I would feed into the portlet's MVCResourceCommand.serveResource() via PortletResponseUtil.sendFile, which expects a java.io.InputStream.
Unfortunately WebClient gives me a Mono<DataBuffer> (or Flux<DataBuffer>), and another answer claims that reconstructing the InputStream defeats the purpose of using WebClient in the first place.
What would be the most efficient and WebClient-savvy way to implement this?

In case of Liferay, the documentation states, that you can use ....getPortletOutputStream() to retrieve an OutputStream.
After setting contentlengh (so browser knows how much to expect), you can use this: Convert writes to OutputStream into a Flux<DataBuffer> usable by ServerResponse
To write your data to the OutputStream

Related

Any way to handle Stream Json with RestTemplate?

Is there any way to handle application/stream+json content with the old fashioned RestTemplate the way webClient does?
As far as my attempts go, wrapping the results of something like restTemplate.getForEntity in Flux.just(<convert response entity to mono here>) would just return the first element and stop at that, while webClient handles it properly, populating that resulting json with new entries as they appear. Haven't tried working with inputStream yet, but at first glance it doesn't seem to be what i need, despite having a "stream" in its name.
Unfortunately, using webClient would be a rather costly option in this case (still waiting for https://github.com/spring-projects/spring-security/issues/4921). I'd rather implement things that would "soon" appear officially only if there's absolutely no other way.
RestTemplate is exposing an API which is not meant to be used to stream the HTTP response. The underlying HTTP response is read and closed after each call, whereas the "application/stream+json" media type is meant for streaming responses.
I don't see any way to properly handle this use case (reading streaming responses) with RestTemplate, by design (check out SPR-14882 for another example of that). So you'll probably have to wait for that issue to be resolved or use another HTTP client with such features.

Uploading images to Spring Boot and S3 all In-Memory

I have an Angular webapp that uses a Spring Boot REST service as its backing web service.
I am adding a "Profiles" feature for users, and as part of this I want to stand up an endpoint that allows users to upload profile images for themselves and immediately upload those files to S3 (where I will host all the images from).
Looking at several Spring Boot/file upload tutorials :
http://www.mkyong.com/spring-boot/spring-boot-file-upload-example/
I update avatar image and display it but the avatar does not change in Spring Boot , why?
Many others
It seems that the standard way of handling such file upload is exposing a controller endpoint that accepts MultipartFiles like so:
#RestController
#RequestMapping("/v1/profiles")
public class ProfileController {
#PostMapping("/photo")
public ResponseEntity uploadProfilePhoto(#RequestParam("mpf") MultipartFile mpf)
// ...
}
Looking at all this code, I can't tell if the MultipartFile instance is in-memory or if Spring sets its location somewhere (perhaps under /tmp?) on the disk.
Looking at the AWS S3 Java SDK tutorial, it seems the standard way to upload a disk-based File is like so:
File file = new File(uploadFileName);
s3client.putObject(new PutObjectRequest(bucketName, keyName, file));
So it looks like I must have a File on disk in order to upload to S3.
I'm wondering if there is a way to keep everything in memory, or whether this is a bad idea and I should stick to disks/File instances!
Is there a way to keep the entire profile image (MultipartFile) in-mempory inside the controller method?
Is there a way to feed (maybe via serialization?!) a MultipartFile instance to S3's PutObjectRequest?
Or is this all a terrible idea (if so, why?!)?
Is there a way to keep the entire profile image (MultipartFile) in-mempory inside the controller method?
No, there is NO way to keep an image File in-memory because File object in java represents a path in file system.
Is there a way to feed (maybe via serialization?!) a MultipartFile instance to S3's PutObjectRequest?
No, from S3's API documentation, there is no way for S3 to deserialize to the image file for you after/during the upload.
Or is this all a terrible idea (if so, why?!)?
It depends on your specific case but it is generally not preferred.
If - there are not many users uploading images at the same time, your memory is probably enough to handle.
Else - You can easily get out-of-memory problems.
If you insist on doing so, S3 API can upload an InputStream (If I remember correctly). You can convert your Multipart File to an InputStream.
This SO thread talks about uploading to S3 with InputStream
You can also take a look at File.createTempFile() to create a temp file.
I have been looking at the same thing. Basically you want a user to be able to be able to upload a photo album and have those photos served from S3 and probably have them secured so only that user can upload/delete/etc.
I believe the simpler answer is in spring boot to get a Pre-signed URL from S3. https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObjectJavaSDK.html
which basically gives you a token defining the bucket, and object key ("/bobs_profile/smiling_bob.jpg") and a time limit for that image to be uploaded.
Give that to your angular app (or ionic app) to upload the image to that location.
That should do it. but someone let me know if I'm wrong.
The only issue that I see is if bob wants to upload "bobs_nude_photo.jpg" and only wants spring security logged in people to be able to see it... well I'm sure there is an S3 solution for that??

How to disable browser caching in Vaadin

My question is short (and hopefully simple to solve!): How can I completely disable browser-caching in my webservice realized with vaadin?
I want to completely disable caching since I'm getting problems when I try to do some PDF streaming and displaying them in my browers.
I have read about a solution for my problem for example here:
Using <meta> tags to turn off caching in all browsers?
They talk about adding some headers to the web application that disable browser caching. But how do I add them to my Vaadin application?
A short code snippet would be very welcome (and helpful!)
Thanks once again for every answer and thought you're sharing with me.
It seems to me that you want to disable caching when downloading a PDF file. Assuming you are using a DownloadStream to stream the content, then setting the Content-Disposition and Cache-Control headers as follows should work.
DownloadStream stream = new DownloadStream(getStreamSource().getStream(), contentType, filename);
stream.setParameter("Content-Disposition", "attachment;filename=" + filename);
// This magic incantation should prevent anyone from caching the data
stream.setParameter("Cache-Control", "private,no-cache,no-store");
// In theory <=0 disables caching. In practice Chrome, Safari (and, apparently, IE) all ignore <=0. Set to 1s
stream.setCacheTime(1000);
If you want to disable caching for all Vaadin requests, you'll have to look at the source of AbstractApplicationServlet, and extend methods such as #serveStaticResourcesInVAADIN and others - this is quick tricky, as alot of them are private methods.
A simpler method may be to use an Http Servlet Filter to add the appropriate parameters to the response, without having to modify your app at all. You can write this yourself - should be quick easy - although a quick search finds the Apache2 licensed Cache-Filter : http://code.google.com/p/cache-filter/wiki/NoCacheFilter
I have not used Cache-Filter, but a quick skim suggests it'll work just fine for you.

Servlet send image from server and save in client

I'm new and just developing on J2EE.
I am modifying an existing application (an OpenSource project).
I need to save an image on a client sent by the server, but I do not know how.
This activity must be done in a transparent manner without affecting the existing operation of the application.
From the tests done I get this error:
java.lang.IllegalStateException: getWriter () has Already Been Called for this response.
How should carry out this task, according to your own opinion?
How do I save on the client, locally, the image?
Update:
Thanks for the answers.
My problem is that:
the image is generated on the server, but not for direct client request (there is no link to click on web page), the picture is composed using other services on the Internet.
reconstruct the image on the server.
This image must be sent to the client to be saved locally.
so I'd like it to appear a window where you assign the destination image
plus I'd like the rest of the application were not affected by this activity.
The application is yet on production.
Thank you very much for your response.
From the tests done I get this error: java.lang.IllegalStateException: getWriter () has Already Been Called for this response.
In other words, you were trying to mix the binary data of the image with the character data of the HTML output, or you were trying to do this in a JSP instead of a Servlet. This is indeed not going to work. You need to send either the image or the HTML page exclusively in response to fully separate requests.
In your JSP/HTML page just have a link to the image, like so:
click to download image
Then, in a servlet listening on an url-pattern of /imageservlet/*, you just get the image as InputStream from some datasource (e.g. from local disk file system as FileInputStream) and then write it to the OutputStream of the response the usual Java IO way.
You only need to set at least the Content-Disposition response header to attachment to make sure that the client get a Save As popup dialogue, else it will be displayed straight in the browser. Setting the Content-Type and Content-Length are also important so that the browser knows what the server is sending and can predict how long the download may take.
response.setHeader("Content-Type", getServletContext().getMimeType(file.getName()));
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Disposition", "attachment;filename=\"" + file.getName() + "\"");
You can find complete basic servlet example in this article.
Note: you cannot control where the client would save the image, this would be a security hole. This way websites would be able to write malicious files on client's disk unaskingly.
Update: as per your update, there are two options:
You need to let the client itself fire two HTTP requests (I've answered this in your subsequent question)
Create a client side application which does all the task directly at the client side and then embed this in your webpage, for example a Java Applet. With an applet you have full control over the client environment. You can execute almost all Java code you'd like to execute and you can write files to disk directly without asking client for the location to save. You only need to sign the applet by a 3rd party company or the client needs to confirm a security warning before running.
Its up to the browser how all types of output are handled. Web pages are given a content type of html which the browser understands and ends up rendering ass a page that we can see. Images are given content type of image/jpeg etc which are rendered as images when in a page etc. To force a download prompt one needs to use a content type of a binary file rather than that of an image so the browser forces the download rather than shows the image. To ensure this use something like "application/octetstream"... i cant recall exactly but its easy to google for.

How to use ExternalDirectory Resource

How to use External Directory to Store Images.
And how i access that images thru my Web application ?
I am using Jboss as an application Server.
Web application is in Java,Jsp.
Presently images stored in WAR file.
After google i got the solution
C:\jboss-4.0.0\server\default\deploy\jbossweb-tomcat55.sar\server.xml
Then restart the server and access the
http://localhost:8080/contextname/images
Please provide comments
I've answered similar question before: Simplest way to serve static data from outside the application server in a Java web application
To summarize there are basically two ways:
Add a new Context to the server.xml denoting the absolute location where the images are.
Create a Servlet which gets an InputStream of the image using FileInputStream and writes it the usual Java IO way to the OutputStream of the response, along with at least Content-Type, Content-Length and Content-Disposition headers.
See the link for more detailed answers and code examples.

Resources