So I wrote Slack reporter for my automated tests and wanted to switch from deprecated module 'request' to 'https' module. I changed the request sending a normal message but I don't know how to create a request for sending a file. I can't find any example in node documentation (no POST examples for 'https' there) nor any example of that kind of use on the internet. Can anyone help me with this?
That's the working request:
function sendLogFile() {
console.log("Sending log file to slack...");
return new Promise((resolve, reject) => {
request.post(
{
url: fileUploadUrl,
formData: {
token: token,
method: "POST",
title: "Test Log File",
filename: "testLog.txt",
filetype: "auto",
channels: "***",
file: fs.createReadStream("testLog.txt")
}
},
function(err, response) {
if (response.body.includes("created"))
resolve("File send successfully!");
if (response.body.includes("error")) reject(response.body);
if (err) reject(err);
}
);
});
}
And this is kinda (SEE THE EDIT BELOW) what I want (but it's not working):
function sendLogFile() {
return new Promise((resolve, reject) => {
const requestOptions = {
url: fileUploadUrl,
headers: headers,
formData: {
token: token,
method: "POST",
title: "Test Log File",
filename: "testLog.txt",
filetype: "auto",
channels: "***",
file: fs.createReadStream("testLog.txt")
}
};
const req = https.request(requestOptions, res => {
res.on("data", d => resolve(d));
});
req.on("error", e => {
reject(e);
});
// Probably that's the part where I'm stuck:
req.write('????????')
req.end();
});
}
I know there is slackapi for node but the thing is I need this reporter to be without any additional packages. And I know it's possible with request.promise or xhr but I need this to be 'https'.
EDIT:
Ok, so I was trying to get somewhere and I think it should look more like:
const file = fs.createReadStream("testLog.txt");
const options = {
channels: "***",
hostname: "slack.com",
port: 443,
path: '/api/files.upload',
method: 'POST',
headers: {
'Authorization': "Bearer ***",
'Content-Type': 'application/json; charset=utf-8',
}
}
const req = https.request(options, res => {
console.log(`statusCode: ${res.statusCode}`)
res.on('data', d => {
process.stdout.write(d)
})
})
req.on('error', error => {
console.error(error)
})
req.write(data)
req.end()
But I have no idea how to past file to req.write(data) since 'data' has to be string, Buffer, ArrayBuffer, Array, or Array-like Object.
So, for now, the response I get is:
statusCode: 200 {"ok":false,"error":"no_file_data"}%
And I'm also not sure if it's possible because slack API says the header should be formData but this response suggests this approach is fine I guess.
Anyone, please?
If you refer to https documentation you can see that options object does not accept such property as formData.
Instead, you should try to send the post data like in this answer.
Related
I am using this example to make a POST API call to an API: https://nodejs.dev/making-http-requests-with-nodejs#perform-a-post-request. No issues there, it works well.
Next, I wanted to create a function that makes this API call by taking in dynamic connection parameters, headers and payload. Did that and I am able to return the response object from the function so I can detect the response.statusCode, response.statusMessage, etc. Here's my Node.js code:
Module Code
const https = require("https");
function postLendingApplication(connection, data, callback) {
const options = {
hostname: connection.hostname,
port: connection.port,
path: connection.path,
method: connection.method,
headers: connection.headers
};
//console.log(options)
const req = https.request(options, res => {
console.log(`statusCode: ${res.statusCode}`);
res.on("data", d => {
process.stdout.write(d);
});
callback(res);
});
req.on("error", error => {
console.error(error);
});
req.write(data);
req.end();
}
exports.postLendingApplication = postLendingApplication;
Invoking the code from another file
const bpc = require("./public-api");
const data = JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1
});
const connection = {
hostname: 'jsonplaceholder.typicode.com',
port: 443,
path: '/posts',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length,
}
}
var response = bpc.postLendingApplication(connection, data, function(response) {
console.log("Inside the calling function");
//console.log(response);
console.log("Status Code: " + response.statusCode);
console.log("Status Message: " + response.statusMessage);
});
Successful console response
statusCode: 201
Inside the calling function
Status Code: 201
Status Message: Created
{
"title": "foo",
"body": "bar",
"userId": 1,
"id": 101
}
Question: In my callback method, I would like to receive the response body (The JSON) as well as the error so I can run some assertions based on the response/body/error that I received. I am not able to figure out how to setup callback in the module method so it can return all 3 values. If you can please help out with that, it would be greatly appreciated.
Here's the Repl URL in case you'd like to take a stab at it online: https://repl.it/#varun_verma/POST-API
I am not 100% on the question you are asking, I personally if you are wanting to use callbacks in this way use two functions one to handle the error and one for the succsessful response
however, you can use object destruction to give you undefined or default it to null if you like for the item not passed back as shown below:
Module Code
const https = require("https");
function postLendingApplication(connection, data, callback) {
const options = {
hostname: connection.hostname,
port: connection.port,
path: connection.path,
method: connection.method,
headers: connection.headers
};
//console.log(options)
const req = https.request(options, res => {
console.log(`statusCode: ${res.statusCode}`);
let data = ''
res.on("data", d => {
data += d;
});
res.on('end', () => {
callback({response: res, data});
});
});
req.on("error", error => {
console.error(error);
callback({response: res, error});
});
req.write(data);
req.end();
}
exports.postLendingApplication = postLendingApplication;
Invoking the code from another file
const bpc = require("./public-api");
const data = JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1
});
const connection = {
hostname: 'jsonplaceholder.typicode.com',
port: 443,
path: '/posts',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length,
}
}
var response = bpc.postLendingApplication(connection, data, function({ response, data, error}) {
// if error is not passed back in the object it defaults to undefined
if (error) console.error(error)
console.log("Inside the calling function");
//console.log(response);
console.log("Status Code: " + response.statusCode);
console.log("Status Message: " + response.statusMessage);
});
If I understand your question correctly, you want to have the response, data, and the error passed to the callback. (where you currently only pass the response)
You can pass the data to the callback like so:
const req = https.request(options, res => {
console.log(`statusCode: ${res.statusCode}`);
let data = '';
res.on("data", d => {
data += d;
});
res.on('end', () => {
callback(res, data);
});
});
This buffers the data from the response as it comes in into a string and then only when the response ends passes both the response object and the data as a string to the callback. (you can then use JSON.parse in the callback to convert the data string to an object)
Passing the error is more difficult as the error callback is given separately from the response. I would recommend having a separate callback for the error:
function postLendingApplication(connection, data, callback, error_callback) {
...
req.on("error", error => {
console.error(error);
error_callback(error);
});
...
}
However someone else on here may be able to give a better solution for the error.
I wold like to know how to make a GET request in node.js sending a body.
const options = {
hostname: 'localhost',
port: 3000,
path: '/abc',
method: 'GET'
}
http.get(options, (res) => {
res.on('data', (chunk) => {
console.log(String(chunk))
})
})
As it says in the documentation:
Since most requests are GET requests without bodies, Node.js provides this convenience method. The only difference between this method and http.request() is that it sets the method to GET and calls req.end() automatically.
So the answer is to use http.request directly. http.request has an example using POST, but it's the same for GET (start the request with http.request, use write to send the body data, use end when done sending data), other than the fact that (as noted above) GET usually doesn't have any body. In fact, RFC 7231 notes that:
A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.
Using the standard http:
`const http = require('http');
https.get('http://localhost:3000/abc', (resp) => {
let data = '';
// A chunk of data has been recieved.
resp.on('data', (chunk) => {
data += chunk;
});
// The whole response has been received. Print out the result.
resp.on('end', () => {
console.log(JSON.parse(data).explanation);
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});`
Hope this helps
Using Body in GET request is not recommended at all cause it is not the suggest behavior by HTTP 1.1 but you can use the following method:
const data = JSON.stringify({
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
});
const https = require('https')
const options = {
hostname: 'jsonplaceholder.typicode.com',
port: 443,
path: '/posts',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length
}
}
const req = https.request(options, (res) => {
console.log(`statusCode: ${res.statusCode}`)
res.on('data', (d) => {
process.stdout.write(d)
})
})
req.on('error', (error) => {
console.error(error)
})
req.write(data)
req.end()
I wrote a script that take in a document and convert it into a string and send it to redmine as a wiki page. THIS WORKED GREAT.
NOW, I am trying to attach a file to it and the wiki page is being created and uploaded with the expected text, however, the attach file is not being sent.
And the weird thing is, I am not getting an error saying it is not being sent. I am actually getting a 201 response for the post request of the attach file, which is good but I am not seeing the file attach in the wiki page.
I also receive the token when i post the attachment so i can use it to do a PUT request with both the wiki text and the attach file, so you can see why I am so confuse.
I will provide my code in the file. Any help would be much appreciated.
//a path to a txt file in my computer
var filePath = '../attached.txt'
fs.readFile(filePath,"utf8", (err, data) => {
if (err) { throw err; }
creatingWikiPage_AttachedFile(data)
console.log(data)
})
function creatingWikiPage_AttachedFile(file) {
axios({
method: 'post',
url: '<redmine_url>/uploads.json',
headers: {
'Content-Type': 'application/octet-stream'
},
params: { 'key': '<api_key>' },
data: file
})
.then(function (response) {
console.log(" POST attached Files---> ");
console.log(response)
axios({
method: 'put',
url: '<redmine_url>/projects/Testing/wiki/Wiki.json',
headers: { 'Content-Type': 'application/json' },
params: { 'key': '<api_key>' },
data: {
"wiki_page": {
"text": "This is a wiki page with images and other files.",
"uploads": [
{ "token": response.data.upload.token, "filename": "attached.txt", "content-type": "text/plain" }
]
}
}
})
.then(response => {
console.log("Put Document-------->>>>")
console.log(response);
})
.catch(error => {
console.log(error.response)
})
})
.catch(function (error) {
console.log(error.message)
})
}
I am expecting the File to be attach to the wiki page, but only the wiki page is being created and the attach file is not there.
I'm trying to retrieve, then POST a JPEG image to Foursquare's https://api.foursquare.com/v2/photos/add endpoint using Axios in Node. I've tried a few methods with Axios (and Postman) but always receive the same error response of Missing file upload:
{
"meta": {
"code": 400,
"errorType": "other",
"errorDetail": "Missing file upload",
"requestId": "NNNNNNNNNNNNNNNNNNNNN" // not the true requestId
},
"notifications": [
{
"type": "notificationTray",
"item": {
"unreadCount": 0
}
}
],
"response": {}
}
The image is created using the Google Static Map API and retrieved with an Axios GET request:
const image = await axios.get(imageURL, {
responseType: "arraybuffer"
});
which is wrapped in an async function and successfully returns a buffer. The data is read into a Buffer and converted to a string:
const imageData = new Buffer(image, "binary").toString();
Here's an example imageData string. I've also tried converting the string to base64.
This string is then POSTed to the Foursquare endpoint:
const postPhoto = await axios.post(
"https://developer.foursquare.com/docs/api/photos/add?
checkinId=1234&
oauth_token=[TOKEN]&
v=YYYYMMDD",
imageData,
{
headers: { "Content-Type": "image/jpeg" }
}
);
where the checkinId, oauth_token and v params are all valid.
I've tried different Content-Type values, base64 encoding the imageData and several other solutions found in forums and here on SO (most are several years old), but nothing works. The response errorDetail always says Missing file upload.
The issue could be in how the POST request is structured, but I could also be requesting/handling the image data incorrectly. A 2nd (or 3rd, or 4th) set of eyes to check I'm putting this together would be super helpful.
Whew, I have finally solved this.
I was eventually able to get it working thru Postman which provided some hints. Here's the Postman code snippet using request:
var fs = require("fs");
var request = require("request");
var options = { method: 'POST',
url: 'https://api.foursquare.com/v2/photos/add',
qs:
{ checkinId: [MY CHECKING ID],
public: '1',
oauth_token: [MY OAUTH TOKEN],
v: [MY VERSION] },
headers:
{ 'postman-token': '8ce14473-b457-7f1a-eae2-ba384e99b983',
'cache-control': 'no-cache',
'content-type': 'multipart/form-data; boundary=---- WebKitFormBoundary7MA4YWxkTrZu0gW' },
formData:
{ file:
{ value: 'fs.createReadStream("testimage.jpg")',
options: {
filename: 'testimage.jpg',
contentType: null
}
}
}
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
The key part of this was fs.createReadStream(). The part I was missing before was to pass the image as a stream to the request.
Using this I was able to figure out the Axios request:
const axios = require("axios");
const querystring = require("qs");
const FormData = require("form-data");
const getImageStream = async function(url) {
return await axios
.get(url, {
responseType: "stream"
})
.then(response => response.data);
};
let form = new FormData();
form.append("file", getImageStream([IMAGE URL]));
const requestURL = "https://api.foursquare.com/v2/photos/add";
const requestParams = {
checkinId: [MY CHECKIN ID],
public: 1,
oauth_token: [MY OAUTH TOKEN],
v: [MY VERSION]
};
const requestConfig = {
headers: form.getHeaders()
};
try {
const postPhoto = await axios.post(
requestURL + "?" + querystring.stringify(requestParams),
form,
requestConfig
);
return postPhoto;
} catch (e) {
console.error(e.response);
}
And voila, the request succeeds and the image is posted to the Foursquare checkin.
I managed to fetch video data from a channel but when it try to add comments to a video, I fail. So at some point I can read data successfully.
I have read that docummentation: https://developers.google.com/youtube/v3/docs/commentThreads/insert
And I'm not sure if I did the parameters correctly.
Besides Node.js and Express I'm using the request-promise package for promises if that's worth to mention.
const optionsComment = {
method: 'POST',
uri: 'https://www.googleapis.com/youtube/v3/commentThreads',
qs: {
part: 'snippet',
'snippet.channelId': 'a channel id',
'snippet.videoId': 'some video id',
'snippet.topLevelComment.snippet.textOriginal': 'a nice message',
key: "my key"
},
json: true
};
rp(optionsComment)
.then(result=>{
console.log("result of adding comment:", result);
})
.catch(function(err){
console.log("error during add comment");
console.log(err);
});
When I run the code I get this error:
error during add comment
{ StatusCodeError: 401 - {"error":{"errors":[{"domain":"global","reason":"required","message":"Login Required","locationType":"header","location":"Authorization"}],"code":401,"message":"Login Required"}}
at new StatusCodeError
Even if I'm logged in and try to comment my own video I get this error.
Maybe someone can give me a hint.
Thank you!
I'd similar issue as yours, sending the access_token in qs fixed it for me.
'use strict';
let request = require('request');
const sourceId = '< youtube video id>';
const comment_id = 'the comment id';
const comment = 'actual comment';
new Promise((resolve, reject) => {
request({
method: 'POST',
url: 'https://www.googleapis.com/youtube/v3/commentThreads',
headers: {
'User-Agent': 'Request-Promise'
},
body: {
"snippet": {
"videoId": sourceId,
"channelId": comment_id,
"topLevelComment": {
"snippet": {
"textOriginal": comment
}
}
}
},
qs: {
part: 'snippet',
access_token: token
},
json: true
}, function (error, response, body) {
if (error) {
console.log('body', body);
console.log('error in when posting comment ', error.stack);
return reject(error);
}
return resolve(body);
});
});