I have the following code in a NodeJS app using the library node-html-pdf:
pdf.create(PDF_html).toStream((err, pdfStream) => {
if (err) {
console.log(err)
return res.sendStatus(500)
} else {
res.statusCode = 200
res.setHeader('Content-type', 'application/pdf')
res.attachment()
pdfStream.on('end', () => {
return res.end()
})
pdfStream.pipe(res)
}
})
On the client side I am using fetch to retrieve and download the PDF using the following code:
document.getElementById('pdf_button').addEventListener("click", function() {
let query_nr = ''
let query_spnr = ''
let url = 'https://{my.public.server.url}/getPDF/'
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"nr": query_nr,
"spnr": query_spnr
})
})
.then((response) => response.blob())
.then((blob) => {
blob = new Blob([blob], {type: "application/pdf"});
const objectURL = URL.createObjectURL(blob);
const a = document.createElement("a")
document.body.appendChild(a)
a.style = "display: none"
a.href = objectURL
a.type = 'application/pdf'
a.download = query_nr + '-' + query_spnr + '.pdf'
console.log(a);
a.click()
// window.open(objectURL, '_blank');
})
.catch((error) => {
console.error(error);
})
});
The above code works fine as long I am using localhost as url, and I am able to select text from the downloaded text.
When I download the PDF from the server it seems like the whole PDF-file gets converted to an image (I might be very wrong), and I am not able to select any text.
Does anyone know how I can fix this? And why is this happening?
Related
I am calling an api and getting pdf in return.
fetch(`api` + guid, {
method: "GET",
headers: {
"Accept": "application/octet-stream",
"Authorization": "Bearer " + token,
},
responseType: 'arraybuffer',
})
.then((res) => res.text())
.then((data) => {
fs.writeFileSync('file.pdf', data);
});
I get the pdf file but the issue is the pdf file is always empty. But when I accept response as json, it works fine.
I found similar problems like this but none of the solution worked for me yet.
It would be great if someone can point out the issue.
I found the issue.
As I am using fetch not Axios.
We cannot pass responseType as Fetch's option.
fetch(`api` + guid, {
method: "GET",
headers: {
"Accept": "application/octet-stream",
"Authorization": "Bearer " + token,
},
// responseType: 'arraybuffer' //#1 remove this,
})
Instead the response in itself can be passed as arraybuffer as below.
.then((res) => res.arraybuffer())
instead of
.then((res) => res.text())
Now instead of directly using the response to write our pdf file. We can change the data to base64 string and decode it back again to create our pdf file. I used base64ToPdf npm package to handle that.
.then(data => {
var base64Str = Buffer.from(data).toString('base64');
base64.base64Decode(base64Str, "file.pdf");
})
I hope this help others. :)
Change res.arraybuffer() to res.arrayBuffer()
Below is the working code with webdriverio-
var headers = {
Authorization: "Bearer " + accessToken,
Accept: 'application/pdf'
}
fetch(
apiurl,
{
headers: {
Accept: "application/octet-stream",
Authorization: "Bearer " + accessToken
},
},
)
.then((res) => {
if (!res.ok) {
return res.status.toString()
}
return res.arrayBuffer()
})
.then((data) => {
var base64Str = Buffer.from(data).toString('base64');
base64.base64Decode(base64Str, filename);
})
.catch(
(err) => {
return err.Message;
})
Here's example which works for me:
async createPdf(context, data) {
let url = new URL(baseURL + '/invoice/createPdf');
url.search = new URLSearchParams({
id: data
})
await fetch(url, {
method: 'GET',
headers: {
'Authorization': "Bearer " + localStorage.getItem("jwt"),
'Accept': 'application/octet-stream'
},
}).then((res) => res.arrayBuffer())
.then(data => {
var base64Str = Buffer.from(data).toString('base64');
var binaryString = window.atob(base64Str);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
var arrBuffer = bytes;
var newBlob = new Blob([arrBuffer], { type: "application/pdf" });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(newBlob);
return;
}
data = window.URL.createObjectURL(newBlob);
var link = document.createElement('a');
document.body.appendChild(link);
link.href = data;
link.download = "Faktura.pdf";
link.click();
window.URL.revokeObjectURL(data);
link.remove();
})
}
In my case, the response is same as yours and I'm trying to convert it to a pdf file so that I can preview it on the UI.
For this, I need to fetch the URL already present in the response which is of type blob... to fetch the URL I did URL.createObjectURL(myblob)
const [url,seturl] = useState('');
response
.then((resp) => resp.blob())
.then((myBlob) => {
seturl(URL.createObjectURL(myBlob)); //<-- use this for fetching url from your response
console.log(myBlob);
})
.catch((err) => {
console.log(err.message());
});
I am receiving a ReadableStream from a server, returned from my fetch call.
A ReadableStream is returned but I don't know how to trigger a download from this stage. I can't use the url in an href because it requires an Authorization token.
I don't want to install fs on the client so what options do I have?
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/octet-stream'
}
});
const blob = await res.blob();
const newBlob = new Blob([blob]);
const newUrl = window.URL.createObjectURL(newBlob);
const link = document.createElement('a');
link.href = newUrl;
link.setAttribute('download', 'filename');
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
window.URL.revokeObjectURL(newBlob);
} catch (error) {
console.log(error);
}
Update 1
I converted the file to a Blob, then passed it into a newly generated href. Successfully downloaded a file. The end result was the ReadStream contents as a .txt file.
Meaning stuff like this
x:ÚêÒÓ%¶âÜTb∞\܃
I have found 2 solutions, both worked but I was missing a simple addition to make them work.
The native solution is
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`
}
});
const blob = await res.blob();
const newBlob = new Blob([blob]);
const blobUrl = window.URL.createObjectURL(newBlob);
const link = document.createElement('a');
link.href = blobUrl;
link.setAttribute('download', `${filename}.${extension}`);
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
// clean up Url
window.URL.revokeObjectURL(blobUrl);
This version is using the npm package steamSaver for anyone who would prefer it.
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`
}
});
const fileStream = streamSaver.createWriteStream(`${filename}.${extension}`);
const writer = fileStream.getWriter();
const reader = res.body.getReader();
const pump = () => reader.read()
.then(({ value, done }) => {
if (done) writer.close();
else {
writer.write(value);
return writer.ready.then(pump);
}
});
await pump()
.then(() => console.log('Closed the stream, Done writing'))
.catch(err => console.log(err));
The key for why it was not working was because I did not include the extension, so it either errored out because of the mimetype was wrong or it opens a .txt file with a string of the body instead of the image.
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.
Im working on ReactJs. I have a button that downloads a excel file that is provided from an API REST.The problem is that i read all the content from the response and then i download all this information on a string.
But i have been trying all different stuff and i allways have the same problem, when i try to open the file in Excel,it show a message saying that the file is corrupted.
This is my code:
export const getFilesFromApi = (path, callBack, contentType) => apiFileCalls({ path, method: 'GET', callBack, contentType: contentType || 'text/plain' });
export const apiFileCalls = ({ path, callBack, data, method, contentType, attachFiles = [], additionalHeaders = [] }) => {
debugger;
new Promise(() => {
const request = superagent(method, path);
request.set('Access-Control-Allow-Origin', '*');
if (contentType) {
request.set('Content-Type', contentType);
}
const accessToken = getCookie_accessToken();
if (accessToken) {
request.set('Authorization', `Bearer ${accessToken}`);
}
if (data) {
request.send(data);
}
additionalHeaders.forEach((header) => request.set(header.name, header.value));
attachFiles.forEach((file) => request.attach(file.fieldName, file.file));
request.query().then((res) => callBack(res.text)).catch((error) => {
if (error.status !== 401) {
callBack(true, error.response);
}
});
});
};
export const downloadFile = (content, fileName) => {
debugger;
const urlrtMP = window.URL.createObjectURL(new Blob([ content ]));
const link = document.createElement('a');
link.href = urlrtMP;
link.setAttribute('download', fileName);
link.click();
};
Any help? Thanks a lot
You have to use responseType('blob') for receiving Blob content.
Ref: https://visionmedia.github.io/superagent/#binary
I am receiving a pdf file as api call response from a node.js backend.The file opens in browser window in an encoded format.I have tried to download but the downloaded file has error opening it (error: failed to load pdf document).I am told the response body is base64 encoded.
Is their any way the pdf can be open /downloaded correctly.I am using react.js and is new to it.
code snippet :
import FileDownload from 'js-file-download';
export function getTaxInvoice({token}){
const authString = `Bearer ${token}`;
return (dispatch) => {
return axios.get(`${MAIN_URL}/rental_invoice`,{
headers: {Authorization: authString, 'Accept': 'application/json','Content-Type': 'application/pdf'},
responseType: "arraybuffer",//I have tried with blob as well
encoding: null
})
.then((response)=>{
FileDownload(response, 'some.pdf');
const taxInvoiceUrl = window.URL.createObjectURL(new Blob([response.data]));
window.open(taxInvoiceUrl, "_blank");
console.log( response);
// dispatch(taxInvoiceLoadSuccess(taxInvoiceUrl));
// dispatch(onViewChanged("rental_invoice"));
})
.catch((error)=>{
dispatch(taxInvoiceLoadFailed());
})
}
}
response from api call:image snippet
Here is an example of some code I have used in the past to do this:
function downloadURI (url, name) {
var link = document.createElement('a')
link.download = name
link.href = url
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
export function download (url, type = 'application/pdf', name = 'example') {
get(url, (err, result) => {
if (err) return handleError(err)
const blob = new Blob([result.body], { type })
const downloadUrl = URL.createObjectURL(blob)
downloadURI(downloadUrl, name)
})
}
It will download the file and create an object url and automatically trigger opening the file by programatically clicking a link.
Finally solved the issue.(My senior dev helped me).Final code is below:
install base64js and filedownload on npm .
export function getTaxInvoice({token}){
const authString = `Bearer ${token}`;
return (dispatch) => {
return axios.get(`${MAIN_URL}/rental_invoice`,{
headers: {Authorization: authString, 'Accept': 'application/pdf','Content-Type': 'application/pdf'}
})
.then((response)=>{
FileDownload(base64js.toByteArray(response.data), 'some.pdf');
const taxInvoiceUrl = window.URL.createObjectURL(new Blob([base64js.toByteArray(response.data)], { type: "application/pdf" }) );
window.open(taxInvoiceUrl, "_blank");
dispatch(taxInvoiceLoadSuccess(response.data));
dispatch(onViewChanged("rental_invoice"));
})
.catch((error)=>{
console.log(error);
dispatch(taxInvoiceLoadFailed());
})
}
}
Try changing
FileDownload(response, 'some.pdf');
to
FileDownload(response.data, 'some.pdf');