WebClient : How to Forward downloading a file from a remote microservice? - java

I have a microservice (batch-management) whitch handles multiple batch running and there files.
I have another microservice (swagger-api) whitch exposes a swagger interface to our consumers. The api exposed are calls to other microservices via a WebClient instance.
In my batch-management MS I can download files using this api :
#GetMapping(value = "/archive/download/{zipFileName}")
public ResponseEntity<byte[]> downloadArchiveByName(#PathVariable String zipFileName) throws IOException {
Path zipFile = //getZipFile;
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + zipFileName)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(Files.readAllBytes(zipFile));
}
Then in the swagger-api MS
#GetMapping(value = "/archive/download/{zipFileName}")
#Operation(summary = "Get specific zip", method = "GET")
public Mono<ResponseEntity<byte[]>> downloadArchiveByName(#PathVariable String zipFileName) {
return webClient.get()
.uri("http://localhost:8081/batch-management/archive/download/{zipFileName}", zipFileName)
.retrieve()
.bodyToMono(byte[].class)
.map(body -> ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + zipFileName)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(body)
);
}
It seems like I'm repeating myself ! Is there another way to forward the downloaded file ?

Related

How to redirect s3 link from rest controller java

we have S3 storage ,there are a lot of some files, jpg,mp3 and others
what i need to do?
i need to redirect client to get the file from our s3 without uploading it on our server
and i want that clien get the file on his pc with name and extension
so it looks like clien send us uuid - we find link of this file on s3 and redirect it like this
#GetMapping("/test/{uuid}")
public ResponseEntity<Void> getFile(#PathVariable UUID uuid) {
var url = storageServiceS3.getUrl(uuid);
try {
var name = storageServiceS3.getName(uuid);
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY)
.header(HttpHeaders.LOCATION, url)
.header(HttpHeaders.CONTENT_TYPE, new MimetypesFileTypeMap().getContentType(name))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + name)
.build();
} catch (NoSuchKeyException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.build();
}
}
everything works good ,the file is downloading but one problem - the file has no name (its name still is key from s3) and no extension.
i think this code not works correctly
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + name)
is there any way to do this or i still need upload file to server and then send it to client ?
Finally i found solution- i use S3Presigner ,make presigned url and redirect it with simple Http response
#Bean
public S3Presigner getS3Presigner() {
return S3Presigner.builder()
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY)))
.region(Region.of(REGION))
.endpointOverride(URI.create(END_POINT))
.build();
}
public String getPresignedURL(UUID uuid) {
var name = getName(uuid);
var contentDisposition = "attachment;filename=" + name;
var contentType = new MimetypesFileTypeMap().getContentType(name);
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
.bucket(BUCKET)
.key(uuid.toString())
.responseContentDisposition(contentDisposition)
.responseContentType(contentType)
.build();
GetObjectPresignRequest getObjectPresignRequest =
GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(5))
.getObjectRequest(getObjectRequest)
.build();
PresignedGetObjectRequest presignedGetObjectRequest =
s3Presigner.presignGetObject(getObjectPresignRequest);
return presignedGetObjectRequest.url().toString();
}
#GetMapping("/redirect/{uuid}")
public void redirectToS3(#PathVariable UUID uuid, HttpServletResponse response) {
try {
var URI = storageServiceS3.getPresignedURL(uuid);
response.sendRedirect(URI);
} catch (NoSuchKeyException | IOException e) {
response.setStatus(404);
}
}
It works pretty good ;)
#Алексеев станислав
Some work arround for this is consuming your rest service by javascript and add file's name in a new header response and rename file when download by client.
// don't forget to allow X-File-Name header on CORS in spring
headers.add("X-File-Name", nameToBeDownloaded );
Example on ANGULAR but can be parsed to other language
this.http.get(uri_link_spring_redirecting_to_S3, {
responseType: 'blob',
observe: 'response'
}).subscribe(
(response) => {
var link = document.createElement('a');
var file = new Blob([response.body], {
type: 'text/csv;charset=utf-8;'
});
link.href = window.URL.createObjectURL(file);
link.download = response?.headers?.get('X-File-Name');; 'download.csv';
link.click();
},
error => {
...
}
)

Spring WebFlux: How to return a file as byte array from DB

Just started leaning Spring Reactive, and I couldn't find any examples on how to do something as simple as this. Without WebFlux, My controller was looking like this:
#GetMapping("/retrieveAttachment/{id}")
public ResponseEntity<byte[]> downloadFile(#PathVariable String id) throws Exception {
Attachment attachment = documentManagerService.getAttachment(id);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + attachment.getFileName() + "\"")
.body(attachment.getFileAttachment().getData());
}
Now with WebFlux, my documenManagerService returning Mono<Attachment>, this is what I came up with:
public Mono<ServerResponse> getAttachment(ServerRequest request) {
Mono<byte[]> mono = documentManagerService.getAttachment(request.pathVariable("id"))
.map(attachment -> attachment.getFileAttachment().getData());
return ServerResponse
.ok()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM.toString())
.body(mono, byte[].class);
}
I'm able to download the file, but the problem is the file is without extension
since I've not set the Content Disposition Header. How do I set it without making a blocking call?
By using flatMap maybe. Im writing this on mobile so have not tried it out or double checked anything.
public Mono<ServerResponse> getAttachment(ServerRequest request) {
return documentManagerService.getAttachment(request.pathVariable("id"))
.flatMap(attachment -> {
return ServerResponse.ok()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM.toString())
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + attachment.getFileName() + "\"")
.bodyValue(attachment.getFileAttachment().getData())
});
}

Returning file for download REST (Java)

I have my REST response set up like so:
#MethodMapping(value = "/download", httpMethod = HttpMethod.GET)
public Response getLogFile() {
File log = new File("path");
if (log.exists())
{
return Response.ok(log, MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment; filename=\"" + log.getName() + "\"" ) //optional
.build();
}
}
But the link just gives me a text response rather than a download:
{"status":200,"entity":{"path":"path"},"metadata":{"Content-Type":["application/octet-stream"],"Content-Disposition":["attachment; filename\u003d\"proteus.log\""]},"entityClosed":false,"entityBufferred":false}
The two packages I'm using are javax.ws.rs.core.Response and javax.ws.rs.core.MediaType.
Am I understanding the functionality wrong? Thanks!

"Content-Disposition" and file name in headers with AngularJS

I use angularjs 1.3.14
I have a java REST services produce a xml file with this header: "Content-Disposition", "attachment; filename=yourFileName";
I need take the file with the name of my file on AngularJS.
I have this code:
$http.get('/someUrl').success(function(data, status, headers){
var myHeaders = headers();
...
});
but in myHeaders have only {content-type="application/xml"}. I need find "Content-Disposition", "attachment; filename=yourFileName"
Java Services:
#GET
#Path(EXPORT_URL)
#Produces(MediaType.APPLICATION_XML)
public Response export(#Context HttpServletRequest request) {
String userName = request.getRemoteUser();
if (userName != null) {
...
ResponseBuilder response = Response.ok(myObject);
response.header("Content-Disposition", "attachment; filename=myFile.xml");
return response.build();
} else {
return Response.status(Status.FORBIDDEN).build();
}
}
2 years after, I find a solution:
#RequestMapping(value = "/export", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML)
public ResponseEntity<String> export(...
HttpHeaders headers = new HttpHeaders();
headers.setAccessControlExposeHeaders(Collections.singletonList("Content-Disposition"));
headers.set("Content-Disposition", "attachment; filename=" + filename);
return new ResponseEntity<>(exportedContent, headers, HttpStatus.OK);
This is a server side CORS issue. You need to enable this:
"Access-Control-Expose-Headers", "Content-Disposition"

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.

Categories