Trouble downloading binary file with angular $http or jquery.ajax - java

My problem is that I am getting the wrong sized file on the client side. Here is my #Controller ...
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
public ResponseEntity<?> download(final HttpServletRequest request,
final HttpServletResponse response,
#PathVariable("id") final int id) throws IOException {
try {
// Pseudo-code for retrieving file from ID.
Path zippath = getZipFile(id);
if (!Files.exists(zippath)) {
throw new IOException("File not found.");
}
ResponseEntity<InputStreamResource> result;
return ResponseEntity.ok()
.contentLength(Files.size(zippath))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(new FileInputStream(zippath.toFile())));
} catch (Exception ex) {
// ErrorInfo is another class, unimportant
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorInfo(ex));
}
}
... and here is my client-side code using angular-file-saver ...
$http({url: "export/download/" + exportitem.exportId, withCredentials: true})
.then(function(response) {
function str2bytes(str) {
var bytes = new Uint8Array(str.length);
for (var i=0; i<str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
var blob = new Blob([str2bytes(response.data)], {type: 'application/octet-stream'});
FileSaver.saveAs(blob, "download.zip");
}, $exceptionHandler);
The original file is 935673 bytes but response.data is 900728 and passing it through the transformation to Uint8Array results in a Blob that is 900728 in size as well. Either way, the resulting saved file is 900728 bytes (34945 bytes shy). Also it is not quite the same in what gets written. It seems to slightly get bloated but then the last part just seems to be truncated. Any ideas what I might be doing wrong?
UPDATE
I just updated my controller method to be the following and got the exact same result. Grrr.
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
public void download(final HttpServletRequest request,
final HttpServletResponse response,
#PathVariable("id") final int id) throws IOException {
// Pseudo-code for retrieving file from ID.
Path zippath = getZipFile(id);
if (!Files.exists(zippath)) {
throw new IOException("File not found.");
}
response.setContentType("application/zip");
response.setHeader("Content-Disposition",
"attachment; filename=download.zip");
InputStream inputStream = new FileInputStream(zippath.toFile());
org.apache.commons.io.IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
}

So the problem turned out to be angular's $http service. I also tried jQuery's ajax method. Both gave the same result. If I instead use the native XMLHttpRequest it works correctly. So the Java code was sound. I first verified this by exposing the file directly to the internet and then both using curl and directly accessing in the browser I managed to download the file of the correct size. Then I found this solution so that I could also download the file via javascript.
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = "blob";
xhr.withCredentials = true;
xhr.onreadystatechange = function (){
if (xhr.readyState === 4) {
var blob = xhr.response;
FileSaver.saveAs(blob, filename);
}
};
xhr.send();
Why does angular or jQuery give the wrong result? I still don't know but if anyone wishes to give an answer that uses those it would be appreciated.

responseType: blob
did the trick for a zip file
Angular 2 +
this.http.get('http://localhost:8080/export', { responseType: ResponseContentType.Blob })
.subscribe((res: any) => {
const blob = new Blob([res._body], { type: 'application/zip' });
saveAs(blob, "fileName.zip");

i just stumbled over the 'responseType' in $http requests, you are probably looking for 'blob': https://docs.angularjs.org/api/ng/service/$http#usage

Related

Rest template read response as stream and pass to spring controller(Get InputStream with rest Template)

I have a url which download the large size zip file.It returns the response as stream.though file size is large first it returns 200(HTTPSTATUK.OK) and continues download.
I have to implement a new spring controller which call the above url through rest template.I have to read the response returned by rest template and pass to controller.initially I have implemented in below way
#GetMapping("/export/downloadFile")
public ResponseEntity<byte[]> downloadData(Model model,
#ModelAttribute(EXCEPTION_COLLECTOR) ExceptionCollector exceptionCollector,
#RequestParam("userName") String userName,
#RequestParam("startDate") Date startDate,
#RequestParam("endDate") Date endDate,
#RequestParam("reason") String reason) {
URI uri = /building url here/;
return restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(httpHeaders), byte[].class);
}
since I am using ResponseEntity<byte[]> , rest template waits till entire file loaded into memory.so very frequently I am getting socket timeout issue.
Do we have way to read the response as stream and return to controller.
I found few things about restTemplate.execute .
restTemplate.execute(uri,HttpMethod.GET,requestCallBack,clientHttpResponse -> {
File ret = File.createTempFile("download", ".zip",new File("/Users/bokkavijay/Desktop"));
StreamUtils.copy(clientHttpResponse.getBody(), new FileOutputStream(ret));
return ret;
});
above snippet can copy the file to our local with out time out but this is not what I need.
how can we pipe the stream in clientHttpResponse to controller ?
I found the working implementation
Controller
#GetMapping("/readResponseAsStream")
public ResponseEntity<StreamingResponseBody> downloadAsStream(Model model,HttpServletResponse response) {
HttpHeaders httpHeaders=new HttpHeaders();
httpHeaders.add("Transfer-Encoding","chunked");
httpHeaders.add("Content-Type","x-zip-compressed");
httpHeaders.add("Content-Disposition", "attachment;filename=sample.zip");
ServletOutputStream servletOutputStream=response.getOutputStream();
StreamingResponseBody downloadFile = out -> {
RequestCallback requestCallBack=request->{
request.getHeaders().add(//Add headers here);
};
ResponseExtractor<ServletOutputStream> responseExtractor = clientHttpResponse -> {
//code snippet if you want to write response stream to HttpServletResponse
byte[] buff = new byte[800000];
int bytesRead = 0;
while ((bytesRead = clientHttpResponse.getBody().read(buff)) != -1) {
servletOutputStream.write(buff, 0, bytesRead);
}
return servletOutputStream;
//Incase if you want to copy file to you local
File ret = File.createTempFile("download", ".zip",new File("Add Local system directory address here"));
StreamUtils.copy(clientHttpResponse.getBody(), new FileOutputStream(ret));
//You can copy the the clientHttpResponse.getBody() to ByteArrayInputStream and return
// Don't return clientHttpResponse.getBody() directly because rest template will close the inputStream(clientHttpResponse.getBody()) after .execute completed
//if you want to write restTemplate.execute in dao layer , pass servletOutputStream as a argument to method
};
restTemplate.execute(URL_ADDRESS,HttpMethod.GET,requestCallBack,responseExtractor);
};
return new ResponseEntity(downloadFile,httpHeaders,HttpStatus.OK);
}
If you write the response directly to HttpServletResponse , controller download the file when we access in browser

How to consume spring StreamingResponseBody in Angular 8

I have the following code in my springboot backend
#GetMapping(value = "/stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<StreamingResponseBody> stream() {
File file = new File("C:\\test\\test.mp3");
try {
InputStream is = new FileInputStream(file);
StreamingResponseBody resp = outputStream -> {
IOUtils.copy(is, outputStream);
};
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(file.length())
.body(resp);
} catch(Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(null);
}
I am trying to consume it in my angular app like this
HTML
<p>{{ content }}</p>
Typescript
ngOnInit(): void {
const header = new HttpHeaders().set('Accept', 'application/octet-stream');
this.http.get('http://localhost:8080/stream', {headers: header}).subscribe(
(res: any) => {
this.content = JSON.stringify(res);
},
(err: any) => {
this.content = JSON.stringify(err);
}
);
}
However unless I specify responseType: 'text' for the get method, I get only error. Also, I tried specifying responseType as blob and arraybuffer and specified several combinations of observe: body|response|events. Only {} is shown on the page. Not sure what is wrong here.
I tried following this SO post but not of much use.
Can some one please throw some light on it? Thanks in advance.

Downloading a Csv file From angularjs Jax-Rs and java

Hello everyone i am using angularjs java and rest to implement one report. Based on UI field selected there is a call to Java Layer and from java there is some database call and the returned input stream i am downloading in a csv file.
There is one problem happening if i do the same with hitting the the same url by browser which i m passing through angularjs than i m able to download the file but if by using UI i m making the request than there is no download option and data is returned as a stream in http response to angular.
java code:
enter code here
#Path("/files")
public class DownloadCsvFile {
#GET
#Path("/csv")
#Produces({MediaType.APPLICATION_OCTET_STREAM})
public Response getFile() {
StreamingOutput outp = new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException,
WebApplicationException {
String url ="http://someurl?
indent=on&q=RCE_POST:2016&sort=id%20asc
&rows=100000&start=0&wt=csv";
final InputStreamReader is = new InputStreamReader(
((HttpURLConnection) (new URL(url)).openConnection())
.getInputStream(),
Charset.forName("UTF-8"));
IOUtils.copy(is, out);
}
};
ResponseBuilder response = Response.ok(outp);
response.header("Content-Disposition", "attachment;
filename=\"testFile_file.csv\"");
return response.build();
} }
AngularJs controller code :
enter code here
var app = angular.module('myApp', ['ngProgress']);
app.controller('myCtrl', function($scope,$http,ngProgressFactory) {
// on submit the fun is called
$scope.LMALLPeriodReport =function()
{
return $http.get("http://localhost:8080/IsaveIdeas/rest/files/csv?
parameters="+parameter)
//parameter contain the selected field in UI
.then(function (response) {
var result = response.data;
alert("printing data");
});
};
The same request from the browser http://localhost:8080/IsaveIdeas/rest/files/csv? parameters={parameter} enable me to download the file.
You can use Blob in your angularjs code like this:
....
.then(function (response) {
var fileName = "yourFileName.csv";
var a = document.createElement("a");
document.body.appendChild(a);
response.data = "\ufeff" + response.data;
var file = new Blob([response.data], {encoding:"UTF-8",type:'application/csv;charset=UTF-8'});
var fileURL = URL.createObjectURL(file);
a.href = fileURL;
a.download = fileName;
a.click();
}

How can I download zip file from controller on client side? Spring boot

I have file.zip in DB like BLOB. I want create method in Spring controller for download this file on client side.
#RequestMapping(value = "/downloadResolution/{resolutionId}", method = RequestMethod.GET)
public void downloadResolution(#PathVariable("resolutionId") Long resolutionId, HttpServletResponse response) {
Resolution resolution = resolutionService.findOne(resolutionId);
ResolutionArchive resolutionArchive = resolution.getResolutionArchive();
if (resolutionArchive == null) return;
byte[] archive = resolutionArchive.getArchive();
//this byte[] archive - my zip file from db
}
How can I change this methot In order to download this on client side?
User press download button. Methos get data from DB in byte[] and user can download it.
EDIT
I tried solution of #pleft and it work. and I knew - I use ajax for call method
function downloadResolution(resulutionId) {
$.ajax({
type: 'GET',
dataType: "json",
url: '/downloadResolution/' + resulutionId,
success: function (data) {
},
error: function (xhr, str) {
}
});
}
How realize this if I use ajax?
You can use the OutputStream of your HttpServletResponse to write your archive bytes there.
e.g.
response.setHeader("Content-Disposition", "attachment; filename=file.zip");
response.setHeader("Content-Type", "application/zip");
response.getOutputStream().write(archive);
EDIT
Sample download
#RequestMapping(value = "/downloadResolution/{resolutionId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadResolution(#PathVariable("resolutionId") Long resolutionId, HttpServletResponse response) throws IOException {
String test = "new string test bytes";
response.setHeader("Content-Disposition", "attachment; filename=file.txt");
response.getOutputStream().write(test.getBytes());
}

How to download file in Spring mvc and mongodb

Hi have written controller class like below. I am trying to get file from mongo db and try to download it.
organizationFileAttachmentService.setUser(getUser());
GridFSDBFile file = organizationFileAttachmentService.getGridFSDBFileById(new ObjectId(id), "File");
if (file != null) {
byte[] content = organizationFileAttachmentService.findByIdAndBucket(new ObjectId(id), "File");
try {
int size = content.length;
InputStream is = null;
byte[] b = new byte[size];
try {
is = new ByteArrayInputStream(content);
is.read(b);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null)
is.close();
} catch (Exception ex) {
}
}
response.setContentType(file.getContentType());
// String attachment =
// "attachment; filename=\""+file.getFilename()+"\"";
String attachment = "attachment; filename=" + file.getFilename();
// response.setContentLength(new
// Long(file.getLength()).intValue());
response.setCharacterEncoding(file.getMD5());
response.setHeader("content-Disposition", attachment);// "attachment;filename=test.xls"
// copy it to response's OutputStream
// FileCopyUtils.copy(is, response.getOutputStream());
IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
is.close();
} catch (IOException ex) {
_logger.info("Error writing file to output stream. Filename was '" + id + "'");
throw new RuntimeException("IOError writing file to output stream");
}
but i am not able to down load file. can any one help me.
In case you missed it, Spring provides various built in resource handlers.
http://docs.spring.io/spring/docs/3.2.5.RELEASE/spring-framework-reference/html/resources.html#resources-implementations
If your method returns one of those (perhaps the ByteArrayResource in your case), then you just need a couple of annotations on the interface like so:
#RequestMapping(value = "/foo/bar/{fileId}",
method = RequestMethod.GET,
produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE })
#ResponseBody FileSystemResource downloadFile(Long fileId);
No fiddling with encodings and headers for you that way. I'd recommend trying that before rolling your own.
Edit: The above worked fine in Spring 3.1.4. It no longer works for 3.2.x or 4.x. Whereas previously, the produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE } would cause Spring to add the appropriate headers, it now treats that as a restriction. If accessing the URL with a standard web browser, an accept header of "application/octet-stream" will not be sent. Spring will therefore return a 406 error. To get it working again, such a method needs to be re-written without the "produces" attribute. Instead, add HttpServletResponse to the method arguments and add the header inside the method. i.e.:
#RequestMapping(value = "/foo/bar/{fileId}",
method = RequestMethod.GET)
#ResponseBody FileSystemResource downloadFile(
Long fileId, HttpServletResponse response) {
...
response.setHeader( "Content-Disposition", "attachment;filename=" + fileName );
...
}
Edit redux:
Now using Spring 4.0.7 via Spring Boot 1.1.8. It would appear that setting the produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE } instruction is now working again. Just having that instruction seems to be enough for all the browsers I have tried. Note however, that I have also found that it does not set the Content-Disposition, which is left as application/json. Although this doesn't seem to be an issue for browsers, I have come across bugs in PHP client applications, which seem to behave only based on the Content-Disposition. So it seems that the current solution is to do both of the above!
I have changed my request as GET and added request in anchor tag in html. Aslo changed my code as
#RequestMapping(value = "/getFileById/{id}", method = RequestMethod.GET)
public #ResponseBody
void download(#PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
organizationFileAttachmentService.setUser(getUser());
GridFSDBFile file = organizationFileAttachmentService.getGridFSDBFileById(new ObjectId(id), "File");
if (file != null) {
try {
response.setContentType(file.getContentType());
response.setContentLength((new Long(file.getLength()).intValue()));
response.setHeader("content-Disposition", "attachment; filename=" + file.getFilename());// "attachment;filename=test.xls"
// copy it to response's OutputStream
IOUtils.copyLarge(file.getInputStream(), response.getOutputStream());
} catch (IOException ex) {
_logger.info("Error writing file to output stream. Filename was '" + id + "'");
throw new RuntimeException("IOError writing file to output stream");
}
}
}
Now it is working fine for me.

Categories

Resources