Why am I getting 404 ("Resource media not found") on the media upload from the docs? - node.js

The docs for uploading rich media to LinkedIn (https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/rich-media-shares#upload-rich-media) say to make a POST to https://api.linkedin.com/media/upload with form data. As far as I can tell I am doing that correctly using request-promise on my Node server, but I am still getting a 404.
Initially I had a problem with my file, but now I think I am properly creating a Buffer. Even if I'm not, that was preventing me from even making the request, and now I am and I don't think that would cause a 404.
I have also tried using 1.0.0 and 2.0.0 versions of the X-Restli-Protocol-Version (LinkedIn API thing).
// See LinkedIn docs on Rich Media shares https://learn.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/rich-media-shares
const stream = require('stream');
const rp = require('request-promise')
async function postRichMediaShare(accessToken) {
try {
const file = await rp({
method: 'get',
url: 'https://local-image-bucket.s3.amazonaws.com/Artboard+copy.png'
});
// Buffer magic
const buffer = new Buffer.from(file);
const bufferStream = new stream.PassThrough();
bufferStream.end( buffer );
bufferStream.pipe( process.stdout );
const options = {
method: 'post',
url: 'https://api.linkedin.com/v2/media/upload',
headers: { 'X-Restli-Protocol-Version': '2.0.0',
"Authorization": `Bearer ${accessToken}` },
formData: {
file: {
value: bufferStream,
options: {
filename: 'Artboard+copy.png',
contentType: 'image/png'
}
}
},
};
const response = await rp(options);
console.log("response", response);
return response;
} catch (error) {
throw new Error(error);
}
}
Instead of the response suggested in the docs, I'm getting this error message from LinkedIn:
error: "{"serviceErrorCode":0,"message":"Resource media does not exist","status":404}"

I'm an idiot. 404 should be expected because I'm requesting https://api.linkedin.com/v2/media/upload and the docs say https://api.linkedin.com/media/upload (no v2/). I believe every other call is versioned. Perhaps an empowered LinkedIn employee reading this could make a route for v2/ that does all the same stuff.
Note, there may be other problems with the code above, I am still struggling but now I'm working on things outside the scope of this question about 404.

Related

form-data request body is empty

I'm trying to send an image and some data from an API to another. The image is stored in memory with multer. But when I want to send it, the body is just empty. I tried the same request with postman and it worked perfectly.
postman test
postman test image
server test
server test image
Here is some code. I removed some of it so you can read it better
export const saveImage = async ({ image, name, folder, options }: { image: any, name?: any, folder: string, options?: any }) => {
try {
const fd = new FormData();
fd.append("image", image.buffer, image.originalname);
if(options) {
fd.append("options[resize][height]", options?.resize?.height);
fd.append("options[resize][width]", options?.resize?.width);
}
if(name) fd.append("name", name);
fd.append("folder", folder);
fd.append("servideId", IMAGES_ID);
fd.append("serviceSecret", IMAGES_SECRET);
console.log(fd)
const formHeaders = fd.getHeaders();
const request = await axios.post(`${IMAGES_URL}/api/images`, {
headers: formHeaders,
body: fd
});
return request.data.id;
} catch (error) {
const { response } = error;
console.log(response.request.data)
if(error?.response?.data?.error) {
throw { statusCode: error.response.status, message: error.response.data.error }
}
console.error("Images API", error);
throw new InternalError("Something gone wrong");
}
}
When I log the FormData, I can see in _streams, the data that I'm sending, but the Images API receives an empty body.
FormData screenshot
If you need more information tell me, please! Thank you
The axios API for the post method is: axios.post(url[, data[, config]]). The second argument must always be the data you send along.
In your case axios thinks { headers: formHeaders, body: fd } is the body and the request ends up being application/json. To send a file with data using axios in Node.js, do the following:
const response = await axios.post(`${IMAGES_URL}/api/images`, fd, {
headers: {
...formHeaders,
'X-Custom-Header': 'lala', // optional
},
});
Your question inspired me to turn this answer into an article — Send a File With Axios in Node.js. It covers a few common pitfalls and you'll learn how to send files that are stored as a Buffer or coming from a Stream.
With Axios, you can directly use the form data without having to deal with headers.
axios.post("/api/images", fd)
If you wish to modify headers at some point in the future, you should pass the formData to the `data` field instead of `body`.
axios.post("/api/images", { headers: formHeaders, data: fd })
Correction in comments.
It can also be done using the Axios API syntax.
axios({method: 'post', url: 'url', data: fd, headers: {} })
In the backend, multer will add your files to req.file instead of req.body, if you have properly configured it to do so.

Get image from Axios and send as Form Data to Wordpress API in a Cloud Function

What I'm trying to accomplish is using a Firebase Cloud Function (Node.js) to:
First download an image from an url (f.eg. from unsplash.com) using an axios.get() request
Secondly take that image and upload it to a Wordpress site using the Wordpress Rest API
The problem seems (to me) to be that the formData doesnt actually append any data, but the axios.get() request actually does indeed retrieve a buffered image it seems. Maybe its something wrong I'm doing with the Node.js library form-data or maybe I get the image in the wrong encoding? This is my best (but unsuccessfull) attempt:
async function uploadMediaToWordpress() {
var FormData = require("form-data");
var formData = new FormData();
var response = await axios.get(
"https://images.unsplash.com/photo-1610303785445-41db41838e3e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80"
{ responseType: "arraybuffer" }
);
formData.append("file", response.data);
try {
var uploadedMedia = await axios.post("https://wordpresssite.com/wp-json/wp/v2/media",
formData, {
headers: {
"Content-Disposition": 'form-data; filename="example.jpeg"',
"Content-Type": "image/jpeg",
Authorization: "Bearer <jwt_token>",
},
});
} catch (error) {
console.log(error);
throw new functions.https.HttpsError("failed-precondition", "WP media upload failed");
}
return uploadedMedia.data;
}
I have previously successfully uploaded an image to Wordpress with Javascript in a browser like this:
async function uploadMediaToWordpress() {
let formData = new FormData();
const response = await fetch("https://images.unsplash.com/photo-1610303785445-41db41838e3e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80");
const blob = await response.blob();
const file = new File([blob], "image.jpeg", { type: blob.type });
formData.append("file", file);
var uploadedMedia = await axios.post("https://wordpresssite.com/wp-json/wp/v2/media",
formData, {
headers: {
"Content-Disposition": 'form-data; filename="example.jpeg"',
"Content-Type": "image/jpeg",
Authorization: "Bearer <jwt_token>",
},
});
return uploadedMedia.data;
},
I have tried the last couple of days to get this to work but cannot for the life of me seem to get it right. Any pointer in the right direction would be greatly appreciated!
The "regular" JavaScript code (used in a browser) works because the image is sent as a file (see the new File in your code), but your Node.js code is not really doing that, e.g. the Content-Type value is wrong which should be multipart/form-data; boundary=----...... Nonetheless, instead of trying (hard) with the arraybuffer response, I suggest you to use stream just as in the axios documentation and form-data documentation.
So in your case, you'd want to:
Set stream as the responseType:
axios.get(
'https://images.unsplash.com/photo-1610303785445-41db41838e3e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80',
{ responseType: 'stream' }
)
Use formData.getHeaders() in the headers of your file upload request (to the /wp/v2/media endpoint):
axios.post( 'https://wordpresssite.com/wp-json/wp/v2/media', formData, {
headers: {
...formData.getHeaders(),
Authorization: 'Bearer ...'
},
} )
And because the remote image from Unsplash.com does not use a static name (e.g. image-name.jpg), then you'll need to set the name when you call formData.append():
formData.append( 'file', response.data, 'your-custom-image-name.jpeg' );
I hope that helps, which worked fine for me (using the node command for Node.js version 14.15.4, the latest release as of writing).

heroku times out every time on post request

My app works fine locally and I have a much more complicated app running on heroku. But I consistently get a 503 / H12 timeout when it tries to run a POST request. It takes two seconds locally. Any ideas?
The post route in question (uses a node.js sdk for aylien nlp api):
app.post('/apiRequest', (req, res) => {
const reqUrl = req.body.url;
aylienApi.combined({
'url': reqUrl,
'endpoint': ['language', 'sentiment', 'summarize']
}, function(error, result) {
if (error === null){
res.send(result.results)
}
})
})
The async POST request:
const postData = async (url = '', data = {}) => {
const res = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
try {
const articleData = await res.json();
return articleData;
} catch (error) {
console.log('error', error)
}
}
I've read on the heroku documentation that it can be caused by large bits of data, but I'm only grabbing a small amount of JSON and send a url.
It turns out I had not uploaded by .env file with the api keys etc, as it was still in the gitignore file.
Silly me. But turns out the Aylien nlp api doesn't warn you if the key isn't set.

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);
}
};

Node JS upload file streams over HTTP

I'm switching one of my projects from request over to something a bit more light-weight (such as got, axios, or fetch). Everything is going smoothly, however, I'm having an issue when attempting to upload a file stream (PUT and POST). It works fine with the request package, but any of the other three return a 500 from the server.
I know that a 500 generally means an issue on the server's end, but it is consistent only with the HTTP packages that I'm testing out. When I revert my code to use request, it works fine.
Here is my current Request code:
Request.put(`http://endpoint.com`, {
headers: {
Authorization: `Bearer ${account.token.access_token}`
},
formData: {
content: fs.createReadStream(localPath)
}
}, (err, response, body) => {
if (err) {
return callback(err);
}
return callback(null, body);
});
And here is one of the attempts using another package (in this case, got):
got.put(`http://endpoint.com`, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${account.token.access_token}`,
},
body: {
content: fs.createReadStream(localPath)
}
})
.then(response => {
return callback(null, response.body);
})
.catch(err => {
return callback(err);
});
Per the got documentation, I've also tried using the form-data package in conjunction with it according to its example and I still get the same issue.
The only difference between these 2 I can gather is with got I do have to manually specify the Content-Type header otherwise the endpoint does give me a proper error on that. Otherwise, I'm not sure how the 2 packages are constructing the body with the stream, but as I said, fetch and axios are also producing the exact same error as got.
If you want any of the snippets using fetch or axios I'd be happy to post them as well.
I know this question was asked a while ago, but I too am missing the simple pipe support from the request package
const request = require('request');
request
.get("https://res.cloudinary.com/demo/image/upload/sample.jpg")
.pipe(request.post("http://127.0.0.1:8000/api/upload/stream"))
// Or any readable stream
fs.createReadStream('/Users/file/path/localFile.jpeg')
.pipe(request.post("http://127.0.0.1:8000/api/upload/stream"))
and had to do some experimenting to find similar features from current libraries.
Unfortunately, I haven't worked with "got" but I hope the following 2 examples help someone else that are interested in working with the Native http/https libraries or the popular axios library
HTTP/HTTPS
Supports piping!
const http = require('http');
const https = require('https');
console.log("[i] Test pass-through: http/https");
// Note: http/https must match URL protocol
https.get(
"https://res.cloudinary.com/demo/image/upload/sample.jpg",
(imageStream) => {
console.log(" [i] Received stream");
imageStream.pipe(
http.request("http://localhost:8000/api/upload/stream/", {
method: "POST",
headers: {
"Content-Type": imageStream.headers["content-type"],
},
})
);
}
);
// Or any readable stream
fs.createReadStream('/Users/file/path/localFile.jpeg')
.pipe(
http.request("http://localhost:8000/api/upload/stream/", {
method: "POST",
headers: {
"Content-Type": imageStream.headers["content-type"],
},
})
)
Axios
Note the usage of imageStream.data and that it's being attached to data in the Axios config.
const axios = require('axios');
(async function selfInvokingFunction() {
console.log("[i] Test pass-through: axios");
const imageStream = await axios.get(
"https://res.cloudinary.com/demo/image/upload/sample.jpg",
{
responseType: "stream", // Important to ensure axios provides stream
}
);
console.log(" [i] Received stream");
const upload = await axios({
method: "post",
url: "http://127.0.0.1:8000/api/upload/stream/",
data: imageStream.data,
headers: {
"Content-Type": imageStream.headers["content-type"],
},
});
console.log("Upload response", upload.data);
})();
Looks like this was a headers issue. If I use the headers directly from FormData (i.e., headers: form.getHeaders()) and just add in my additional headers afterwards (Authorization), then this ends up working just fine.
For me just works when I added other parameters on FormData.
before
const form = new FormData();
form.append('file', fileStream);
after
const form = new FormData();
form.append('file', fileStream, 'my-whatever-file-name.mp4');
So that way I can send stream from my backend to another backend in node, waiting a file in multipart/form-data called 'file'

Resources