Angular 2 save file from Nodejs - node.js

I have the snipped in NodeJS to try to do a download from my server.
router.post("/download", function(req, res, next) {
console.log(req);
filepath = path.join(__dirname, "/files") + "/" + req.body.filename;
console.log(filepath);
res.sendFile(filepath);
});
This is my Angular 2 code:
Component.ts
download(index) {
var filename = this.attachmentList[index].uploadname;
this._uploadService
.downloadFile(filename)
.subscribe(data => saveAs(data, filename), error => console.log(error));
}
Service.ts
export class UploadService {
constructor(private http: HttpClient, private loginService: LoginService) {}
httpOptions = {
headers: new HttpHeaders({
"Content-Type": "application/json",
responseType: "blob"
})
};
downloadFile(file: string): Observable<Blob> {
var body = { filename: file };
console.log(body);
return this.http.post<Blob>(
"http://localhost:8080/download",
body,
this.httpOptions
);
}
In my HTML response I get the file correctly recording to my browser's network inspection tool. However Angular is trying to parse the response into a JSON object which obviously doesn't work since the response is a blob.
The error I get is this:
Unexpected token % in JSON at position 0 at JSON.parse ()
at XMLHttpRequest.onLoad angular 2
Does anyone have a solution for this?
Thanks in advance!

Try the following http options:
{
responseType: 'blob',
observe:'response'
}
You will then get a response of type HttpResponse<Blob>, so doing data.body in your service will give you the blob that you can pass to saveAs.
You can find more info on observe here:
https://angular.io/guide/http#reading-the-full-response

I solved that, in service code I deleted the after the post because it forced my return get a binary file, and not a JSON. Now the code it's like that:
downloadFile(file: string): Observable<Blob> {
var body = { filename: file };
console.log(body);
return this.http.post<Blob>(
"http://localhost:8080/download",
body,
this.httpOptions
);
}
Thank you all.

Related

Nodejs + Axios Post returns undefined value

I am using React + NodeJS & Axios but have been trying to send a post request but experiencing difficulties.
The request seems to be posting successfully, but all actions at the nodejs server is returning in the "undefined" data value, even if the data is passed successfully shown in the console.
REACT
const fireAction = (data1, data2) => {
const data = JSON.stringify({data1, data2})
const url = `http://localhost:5000/data/corr/fire`;
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'AUTHCODE',
}
}
axios.post(url, data, config)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
fireAction("Oklahoma", "Small apartment")
NODE
app.post('/data/corr/fire', async (req, res) => {
try {
const data = req.body.data1;
console.log(data)
} catch(e) {
res.send({success: "none", error: e.message})
}
});
Result of node: "undefined"
I have added the following body parser:
app.use(express.json());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
I am not sure why this error is happening. I see there is similar questions to mine: however none of them are applicable as I'm using both express and body parser which is already suggested.
You're POSTing JSON with a content-type meant for forms. There's no need to manually set content-type if you're sending JSON, but if you want to manually override it, you can use 'Content-Type': 'application/json', and access the response in your route with req.body. If it does need to be formencoded, you'll need to build the form:
const params = new URLSearchParams();
params.append('data1', data1);
params.append('data2', data2);
axios.post(url, params, config);

NodeJS server side - file Expected UploadFile, received: <class 'str'>

having issues uploading file from NodeJs server side, found 100 posts and reasearches but nothing works, would appreciate any help.
Structure of the App
Front App - React Admin framework receving file and i encode in base64 the content of the image to send to API
Backend - NestJS App - receving base64 image in API
From my backend API need to send file to an external backend (Python API) to upload - here is the problem
Please see below my code, something wrong with the file from JS
i have tried several methods and all of them ends in same error
1 solution
converting base64 image in buffer and send to external backend to upload the file
have tried to pass as well cleanImageBuffer but no changes
import axios from 'axios';
import FormData from 'form-data';
export async function upload(
fileBase64: string,
filename: string
): Promise<any> {
const buffer = Buffer.from(fileBase64, 'base64')
const extension = fileBase64.substring(fileBase64.indexOf('/') + 1, fileBase64.indexOf(";base64"))
const cleanBase64 = fileBase64.replace(/^data:image\/png;base64,/, '')
const cleanImageBuffer = Buffer.from(cleanBase64, 'base64')
const formData = new FormData();
// have tried to pass as well cleanImageBuffer but no changes
formData.append('file', buffer);
formData.append('fileName', filename + '.' + extension);
formData.append('namespace', 'test');
return await axios
.post('external_api_url', JSON.stringify(formData), {
headers: {
Authorization: `Bearer token`,
ContentType: 'multipart/form-data'
}
})
.then((response) => {
console.log('response = ' + JSON.stringify(response))
})
result 1 solution
{
"status": "error",
"error": {
"code": "bad_request",
"message": "file Expected UploadFile, received: <class 'str'>"
}
}
2 solution
from base64 image received saving on my disk
after creating a stream and sending the image
export async function upload (
fileBase64: string,
filename: string
): Promise<any> {
const extension = fileBase64.substring(fileBase64.indexOf('/') + 1, fileBase64.indexOf(";base64"))
const cleanBase64 = fileBase64.replace(/^data:image\/png;base64,/, '')
const TMP_UPLOAD_PATH = '/tmp'
if (!fs.existsSync(TMP_UPLOAD_PATH)) {
fs.mkdirSync(TMP_UPLOAD_PATH);
}
fs.writeFile(TMP_UPLOAD_PATH + '/' + filename + '.' + extension, cleanBase64, 'base64', function(err) {
console.log(err);
})
const fileStream = fs.createReadStream(TMP_UPLOAD_PATH + '/' + filename + '.' + extension)
const formData = new FormData();
formData.append('file', fileStream, filename + '.' + extension);
formData.append('fileName', filename + '.' + extension);
formData.append('namespace', 'test');
return await axios
.post('external_api_url', formData, {
headers: {
Authorization: `Bearer token`,
ContentType: 'multipart/form-data'
}
})
.then((response) => {
console.log('response = ' + JSON.stringify(response))
})
}
result 2 solution
{
"status": "error",
"error": {
"code": "bad_request",
"message": "file Expected UploadFile, received: <class 'str'>"
}
}
other solution that ended in same result
tried to use fetch from node-fetch - same result
found out that some people had an outdated version of axios and having this issues, i have installed latest axios version 1.1.3 but same result
best scenario that i need
from base64 image received
convert in buffer and send file to external Python API so to avoid saving the file on local disk
would appreciate any help
below is a python example that works but not JS (JS nothing works)
import requests
url = "http://127.0.0.1:8000/external_api"
payload={'namespace': 'test'}
files=[
('file',('lbl-pic.png',open('/local/path/lbl-pic.png','rb'),'image/png'))
]
headers = {
'Authorization': 'Bearer token'
}
response = requests.request("POST", url, headers=headers, data=payload, files=files)
print(response.text)
Just a suggestion:
Here's a line which returns mentioned error https://github.com/tiangolo/fastapi/blob/41735d2de9afbb2c01541d0f3052c718cb9f4f30/fastapi/datastructures.py#L20, you might find it useful.
First see if you can make it work with regular HTML file input (don't complicate with Base64 yet), as described here https://stackoverflow.com/a/70824288/2347084
If (1) works, then try converting base64 into a File object as suggested here https://stackoverflow.com/a/47497249/2347084
Combine (2) and (1)
I want to post my solution that worked, because as i can see in internet everybody have issues with FormData on nodejs
i was using axios to send the buffer for uploading file
issue is with axios and specially with FormData, it does not add Content-Lenght in headers, any version of axios does not do this
python API had required Content-Lenght
So kindly was asking to make this header optionally in python API and the code started to work
The solution is if anybody goes in similar issues
axios does not add Content-Lenght when working with FormData (could not find any version of axios that works)
if you work with a buffer without having the file on local disk than will be issue because of Content-Lenght
if u have the file locally than using the module fs u are able to read the file and add all hedears and Content-Lenght
on axios GitHub issue is saying that this bug is fixed in latest axios, but it was still not working in my case
below is a code by using buffer and Content-Lenght is not required in 3rd API
function upload (image: {imageBase64: string, fileName: string}) {
const { imageBase64, fileName } = image;
const cleanBase64 = imageBase64.substr(imageBase64.indexOf(',') + 1);
// buffer should be clean base64
const buffer = Buffer.from(cleanBase64, 'base64');
const formData = new FormData();
// filename as option is required, otherwise will not work, will say that received file is string and UploadFile
formData.append('file', buffer, { filename: fileName });
return client
.post('url', formData, {
headers: {
...formData.getHeaders(),
},
})
.then((response) => response.data)
.catch((error) => {
return {
status: 'error',
error,
};
});
}

Using multipart/form-data in Nestjs

I have a requirement of uploading a pdf file to the a third-party API to do that what we are using a controller and then sending its buffer to the endpoint in multipart/form-data but It's not working. What am i doing wrong??
I have my controller defined like
#Post('/upload')
#UseInterceptors(FileInterceptor('file'))
async uploadDocument(#UploadedFile() file: Express.Multer.File){
await this.httpRequestService.uploadDocument(file)
return [
Translation.translate('Create_Success_Message', {
Entity: 'File'
}),
{
file
}
]
}
This controller calls a service called uploadDocument which looks like:
async uploadDocument(file){
try{
const formData = new FormData()
formData.append('file', file)
const response = await this.httpService.post(`urlWhereItHits`,
formData,
{
headers:{
"X-AppSecretToken":"appsecrettoken",
"X-AgreementGrantToken":"agreementgranttoken",
'Content-Type' : 'multipart/form-data'
},
}).toPromise();
}
catch(error){
console.log(error)
}
}
Now i don't understand what's happening wrong here.
I get error saying
TypeError: source.on is not a function at Function.DelayedStream.create (C:\Users\Tushar\Squire-backend\node_modules\delayed-stream\lib\delayed_stream.js:33:10)
I had a similar task, receiving a .csv file and sending it to another server.
controller:
#UseInterceptors(FileInterceptor('file'))
#Post('upload')
async uploadFile(#UploadedFile() file: Express.Multer.File) {
await this.myService.uploadFile(file);
return {
message: 'File uploaded successfully.',
};
}
service:
import * as FormData from 'form-data';
import { Readable } from 'stream';
async uploadFile(file: Express.Multer.File) {
const form = new FormData();
form.append('files', Readable.from(file.buffer), {
filename: file.originalname,
});
try {
const result = await lastValueFrom(
this.httpService.post('The URL of your third-party service', form, {
headers: {
...form.getHeaders(),
},
}),
);
if (result.status === 200) return;
} catch {
throw new InternalServerErrorException(
'Error while uploading the .csv file.',
);
}
}
Some explanations here:
It requires importing Formdata from the package.
.append() requires a fieldName, and a readable Stream.
You can use the buffer of the file that's on memory (not recommended with large files) and use it to create a readable stream.
Also, you need to add the name of the file manually since Formdata only recognizes the filename if the file is saved on disk. (Which in our case is on memory as a Buffer).
You can create the stream in different ways, the example is not mandatory.
.getHeaders() will generate the default header 'Content-Type': 'multipart/form-data' and also it will generate the boundary of the file, like this: reference
{
'content-type': 'multipart/form-data; boundary=--------------------------286308325485491440714225'
}

How to download chunked file with NodeJs POST request?

i got a problem here.
Lets start from we have API, this API returns data with next headers:
Content-Type image/png
Transfer-Encoding chunked
Connection keep-alive
And response body where only file is.
When im try to write this body data into file - it's always broken. I mean i've binary data, his mime, body.length is match original filesize, but this image could not be opened in any viewer after i save it.
What i'm do:
public userFile(req, res: Response) {
const data = {
fileId: parseInt(req.body.fileId),
};
let params = {
headers: {
'Authorization': keys.token,
},
};
axios.post('/api/getfile/', data, params,)
.then((response: AxiosResponse) => {
const fs = require('fs');
const dir = require('path').resolve(__dirname + '../../../files/storage');
const ext = {
'image/png': '.png'
};
fs.writeFile(dir + '/' + img + ext[response.headers['content-type']], response.data, (er) => {
res.send(response.data);
});
})
.catch((err) => {
logger.error("AXIOS ERROR: ", err)
})
}
BUT! When i get this file with postman... here it is!
So, i need your help - what i do wrong?
You must specifically declare which response type it is in params as
responseType: 'stream'
then save stream to a file using pipe
response.data.pipe(fs.createWriteStream("/dir/xyz.png"))

How to upload video/image to Linkedin through API with node?

I need to upload an image/video to Linkedin through API. I am using axios and have Content-Type set to multipart/form-data and all my images/videos that need to be uploaded are stored with a url to the file. All files are stored remotely on cloudinary.
let bodyFormData = new FormData();
bodyFormData.append(
"fileupload",
request(file.url).pipe(fs.createWriteStream("video.mp4"))
);
axios
.post("https://api.linkedin.com/media/upload", bodyFormData, {
headers: {
Authorization: "Bearer " + account.accessToken,
"Content-Type": "multipart/form-data"
}
})
.then(linkedinImageResult => {
I am following this documentation here:
https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/rich-media-shares#
One of the common errors I have gotten is:
UnhandledPromiseRejectionWarning: TypeError: source.pause is not a function
If I change
request(file.url).pipe(fs.createWriteStream("video.mp4"))
to just
file.url
I get this error:
'java.io.IOException: Missing initial multi part boundary'
If I remove
"Content-Type": "multipart/form-data"
I get this error:
"Unable to parse form content"
Note:
file.url is a url to a cloudinary file, example of file.url: "https://res.cloudinary.com/dnc1t9z9o/video/upload/v1555527484/mn3tyjcpg1u4anlma2v7.mp4"
Any help is greatly appreciated :)
Please note that using Rich Media is being deprecated:
Uploading image using https://api.linkedin.com/media/upload is being
deprecated. Partners are recommended to use assets API that returns
response such as urn:li:digitalmediaAsset:C5522AQHn46pwH96hxQ to post
shares.
If you need help with the asset API, please see my answer here
I found the solution for this specific situation!
Here is my example code.
const postRichMedia = async (mediaURL, accessToken, fileName) => {
const formData = new FormData();
const key = S3Service.getSocialKeyFromUrl(mediaURL);
const file = await S3Service.downloadFileFromS3(key);
formData.append("fileupload", file.Body, fileName);
try {
const {data} = await axios.post('https://api.linkedin.com/media/upload', formData, {
headers: {
...formData.getHeaders(),
Authorization: `Bearer ${accessToken}`
},
});
const {location} = data;
return location;
} catch (err) {
console.error(err);
}
};

Resources