I have an server API and a javascript frontend that downloads files from the server. Im using FileSaver (https://github.com/eligrey/FileSaver.js) to save the downloaded document data to the local filesystem. The solution works well for text files but not for the binaries. I've verified that in both cases the server sends the correct content. It's FileSaver that seems unable to save my binary content correctly. What gets saved is more or less trash, with the wrong file size.
Javascript:
$http({
method: 'GET',
cache: false,
url: <URL>,
headers: {
'Content-Type': 'application/octet-stream',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'accept-encoding': 'identity',
}
}).success(function (data, status, headers, config) {
console.log(headers());
console.log(headers('Content-Disposition'));
var res = headers('Content-Disposition').split('=');
console.log(res);
var fileName = res[1];
console.log(fileName);
var blob = new Blob([data]);
saveAs(blob, fileName);
}
The server side:
#GET
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response downloadDocument(#PathParam("documentId") UUID theid) {
doc = <retrieve document>
StreamingOutput stream = new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException {
try {
InputStream istream = new ByteArrayInputStream(doc.getData());
System.out.println(doc.getData().length);
IOUtils.copy(istream, output);
} catch (Exception e) {
throw new WebApplicationException(e);
}
}
};
return Response.ok(stream).header("Content-Disposition", "attachment; filename=" + doc.getName()).build();
}
Related
I am downloading a pdf file from API, but I am getting a blank PDF. I have tested the API endpoint and able to get the byte stream on the console and when I save it to File, it got saved and the file looks good. Getting the same response back to the front end using React and I could see the PDF byte stream in the response.
However, I could not see the content. It says the file is damaged or corrupted when I opened the downloaded PDF from my local.
I have looked at many examples and are following the same pattern, but I think I am missing something here.
My API Java endpoint definition looks like below
#GetMapping(value = "/fetchFile")
public ResponseEntity<byte[]> fetchFile(#RequestParam final String key) {
FileResponse response = myService.readFile(key);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + key.substring(key.lastIndexOf('/') + 1) + "\"");
return Mono.just(ResponseEntity.ok().headers(httpHeaders).contentLength(response.getContentLength())
.contentType(MediaType.parseMediaType(response.getContentType()))
.body(response.getResponseBytes()));
}
Frontend:
rounterFetchFile.js
router.get('/', (request, resp) => {
axios({
method: 'get',
baseURL: 'http://mybackend.apibase.url',
responseType: 'blob',
url: '/fetchFile',
params: {
fileKey: 'myfile.pdf'
}
})
.then(response => {
return resp.send(response.data)
})
.catch(error => {
console.error(error)
return resp.status(error.response.status).end()
})
})
in myFileComoponent.js
//a function that reads the response from rounterFetchFile.js
const getDocumentOnClick = async () => {
try {
var {data} = await pullMyPDF()
var blob = new Blob([data], { type: "application/pdf" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "myFileName.pdf";
link.click();
} catch (e) {
console.log(e)
}
}
Here
var {data} = await pullMyPDF()
is returning the following content. I compared it with the result returned by the Postman, and it is the same. The generated file size is not empty from the react too. I am not able to find out where is it wrong
Below is the response from API endpoint for the fetchFile
I had a similar problem and I fixed it with this:
spa
axios.post(
'api-url',
formData,
{
responseType: 'blob',
headers: {
'Accept': 'application/pdf'
}
})
.then( response => {
const url = URL.createObjectURL(response.data);
this.setState({
filePath: url,
fileType: 'pdf',
})
})
.catch(function (error) {
console.log(error);
});
api
[HttpPost]
public async Task<IActionResult> Post()
{
var request = HttpContext.Request;
var pdfByteArray = await convertToPdfService.ConvertWordStreamToPdfByteArray(request.Form.Files[0], "application/msword");
return File(pdfByteArray, "application/pdf");
}
When the response type is a blob and accepted 'application / pdf' in the header, with that config the job is done ;) ...
Something that worked for me was to send the bytes as base64 from the controller.
API:
public async Task<ActionResult> GetAsync() {
var documentBytes = await GetDocumentAsync().ConfigureAwait(false);
return Ok(Convert.ToBase64String(documentBytes))
}
Front End:
client.get(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => {
const link = document.createElement('a');
link.href = "data:application/octet-stream;base64," + response.data;
link.download = 'file.pdf';
link.click();
})
.catch(error => {
console.log(error);
})
I hope this solves your problem.
Below is the detail of JAVA rest service which downloads a file from server:
Method prototype:
#POST
#Path("/prop/export")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
#Consumes(MediaType.APPLICATION_JSON)
public Response exportItemsToFile(Map<String, String> params);
The Response is build from byte array in its implementation:
Response.ok(someByteArray)
I am using FileSaver.saveAs to download the response in a xls file using below code
var requestUri = '/wtk/rc/v1/pfm/prop/export';
var payload={"context":addCtx,"language":addLang,"country":addCountry,"swimlane":addSL};
$http.post(requestUri, payload, {
headers: {
'Content-Type': 'application/json'
},
responseType: 'arraybuffer',
}).success(function (data) {
var blob = new Blob([data],{type : 'application/vnd.ms-excel'});
var fileName = addLang+"_"+addCountry+".xls";
filesaver.saveAs(blob,fileName);
}).error(function () {
//download failed
});
The response which is xls file is corrupt using the above code. **
It is because the promise returned by $http.post have blank response
in case of if rest client produces octet-stream
**
But if I use some rest client like Postman, and select "Send and Download" option and save the response as xls. It is coming up fine.
Any help in this will be appreciated.
I want to download an excel from my angular controller using blob.
the server code of the mvc application is
[HttpGet]
public HttpResponseMessage Download(SearchModel search){
string fileName = "test.xls"
byte[] file = GetFile(search)
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.Content = new ByteArrayContent(file.ToArray());
httpResponseMessage.Content.Headers.Add("x-filename", fileName);
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentDisposition.FileName = fileName;
return httpResponseMessage;
}
and the js
$http({ method: 'GET', url: 'Download', params: {...some params}, responseType: 'arrayBuffer' })
.success(function (data, status, headers) {
headers = headers();
var filename = headers['x-filename'];
var contentType = headers['content-type'];
var linkElement = document.createElement('a');
try {
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(new Blob([data],{type: "application/octet-stream"}));
return;
}
var a = $("<a style='display: none;'/>");
var url = window.URL.createObjectURL(new Blob([data], {type: "application/octet-stream"}));
a.attr("href", url);
a.attr("download", "name");
$("body").append(a);
a[0].click();
window.URL.revokeObjectURL(url);
a.remove();
}
catch (ex) {
console.log(ex);
}
})
.error(function () { });
but there are some problems:
the filename in the javascript side is undefined and not test.xls as in the server side
the contentType is "text/html; charset=utf-8" and not "application/octet-stream" as in the server side
the file that is downloaded is this
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.ByteArrayContent, Headers:
{
x-filename: test.pdf
Content-Type: application/octet-stream
Content-Disposition: attachment; filename=test.pdf
}
The file is a JSON with header information .
Why is this file wrong?
I have used https://graph.microsoft.com/beta/me/photo/$value API to get the profile picture of the outlook user. I get an image on running the above API in the rest-client. The content-type of the API is "image/jpg"
But, in Node.js, the response of the API is as follows:
����\u0000\u0010JFIF\u0000\u0001\u0001\u0000\u0000\u0001\u0000\u0001\u0000\u0000��\u0000�\u0000\u0005\u0005\u0005\u0005\u0005\u0005\u0006\u0006\u0006\u0006\b\t\b\t\b\f\u000b\n\n\u000b\f\u0012\r\u000e\r\u000e\r\u0012\u001b\u0011\u0014\u0011\u0011\u0014\u0011\u001b\u0018\u001d\u0018\u0016\u0018\u001d\u0018+"\u001e\u001e"+2*(*2<66<LHLdd�\u
I used 'fs' to create an image file. The code is as follows:
const options = {
url: "https://graph.microsoft.com/beta/me/photo/$value",
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${locals.access_token}`,
'Content-type': 'image/jpg',
}
};
request(options, (err, res, body) => {
if(err){
reject(err);
}
console.log(res);
const fs = require('fs');
const data = new Buffer(body).toString("base64");
// const data = new Buffer(body);
fs.writeFileSync('profile.jpg', data, (err) => {
if (err) {
console.log("There was an error writing the image")
}
else {
console.log("The file is written successfully");
}
});
});
The file is written successfully, but the .jpg image file generated is broken. I am unable to open the image.
The output of the image file is as follows:
77+977+977+977+9ABBKRklGAAEBAAABAAEAAO+/ve
You can do this by streaming the response like this,
request(options,(err,res,body)=>{
console.log('Done!');
}).pipe(fs.createWriteStream('./profile.jpg'));
https://www.npmjs.com/package/request#streaming
https://nodejs.org/api/fs.html#fs_class_fs_writestream
The reason for this is that by default, request will call .toString() on the response data. In case of binary data, like a RAW JPEG, this isn't what you want.
It's also explained in the request documentation (albeit vaguely):
(Note: if you expect binary data, you should set encoding: null.)
Which means that you can use this as well:
const options = {
encoding : null,
url : "https://graph.microsoft.com/beta/me/photo/$value",
method : 'GET',
headers : {
'Accept' : 'application/json',
'Authorization' : `Bearer ${locals.access_token}`,
'Content-type' : 'image/jpg',
}
};
However, streaming is probably still the better solution, because it won't require the entire response being read into memory first.
Request is deprecated. You can do this with axios;
// GET request for remote image in node.js
axios({
method: 'get',
url: 'http://example.com/file.jpg',
responseType: 'stream'
})
.then(function (response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
I provide a method on my WebApi to download a file:
public HttpResponseMessage Get(int id)
{
MemoryStream file = RetrieveFile(id);
if (file != null)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(file);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "dummy.txt"
};
return response;
}
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
This works fine and i can consume it with a WebClient.
I also tied to implement the javascript front-end using FileSaver.js :
$http({
url: myUrl,
method: 'GET',
params: {
id: 1
},
headers: {
'Content-type': 'application/json'
}
}).success(function (data, status, headers, config) {
var file = new Blob([data], {
type: 'application/octet-stream'
});
saveAs(file, "test.txt");
}).error(function (data, status, headers, config) {
});
This works great but it fails on IE9 since FileSaver and Blop is not supported.
I have tried opening the url in a new window but can't find a decent working solution for IE9