I have simple app.js for Node.js under localhost:3000
app.js:
let http = require('http');
http.createServer((req, res) => {
res.writeHead(200);
let response;
if(~req.url.indexOf('post')) {
response = req.body.content;
} else {
response = '<script src="http://localhost/fetch.js"></script>';
}
res.end(response);
}).listen(3000);
The file fetch.js is placed on my another local server and is successfully enqueued to the page
fetch.js:
read('http://localhost:3000/?post').then((response) => {
console.log(response);
});
async function read(url) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
body: JSON.stringify({
content: 'Text'
})
});
return response.text();
}
So I render HTML with fetch.js which then send POST request to the same page, but with a query ?post
However, when I run node app.js I get the error
Can not read property 'content' of undefined
So I don't get req.body
Why and how to resolve?
i think you are missing parser for your http server, there is no body because you actually didn't parse the body.
assemble the chunks like below then parse it as the header sais.
this is my work for myself
private parseBody(req: IncomingMessage) {
return new Promise((resolve, reject) => {
const chunks: any[] = []
req.on("data", (chunk) => {
chunks.push(chunk)
})
req.on("end", () => {
const data = Buffer.concat(chunks)
switch (req.headers["content-type"]) {
case "application/json":
resolve(this.parseJson(data.toString()))
break
case "application/x-www-form-urlencoded":
resolve(this.parseUrlEncoded(data.toString()))
break
default:
resolve({})
}
})
})
http server is very abstract and doesn't support anything basicly, i suggest using express or fastify.
working example: https://frontendguruji.com/blog/how-to-parse-post-request-in-node-js-without-expressjs-body-parser/
update
this is the class im using
http.resolver.ts
private parseJson(data: string) {
return JSON.parse(data)
}
private parseUrlEncoded(data: string) {
const parsedData = new URLSearchParams(data)
const dataObj: any = {}
for (var pair of parsedData.entries()) {
dataObj[pair[0]] = pair[1]
}
return dataObj
}
private parseBody(req: IncomingMessage) {
return new Promise((resolve, reject) => {
const chunks: any[] = []
req.on("data", (chunk) => {
chunks.push(chunk)
})
req.on("end", () => {
const data = Buffer.concat(chunks)
switch (req.headers["content-type"]) {
case "application/json":
resolve(this.parseJson(data.toString()))
break
case "application/x-www-form-urlencoded":
resolve(this.parseUrlEncoded(data.toString()))
break
default:
resolve(parse(req.url ?? "/", true).query)
}
})
})
}
you may use await behind the parseBody function after
Ok, thanks to #Fide. The link he posted has the answer:
let http = require('http');
http.createServer((req, res) => {
if(~req.url.indexOf('post')) {
let body;
req.on('data', function(data) {
body = data;
})
req.on('end', function() {
res.writeHead(200);
res.end(body);
})
} else {
res.writeHead(200);
res.end('<script src="http://localhost/fetch.js"></script>');
}
}).listen(3000);
Related
I am having trouble with making an API call in Next.js that is deleting an item in a database. I am using the "body" field of the fetch to send a string to the API. The fetch call is being made in a Next.JS page and the API endpoint is in the API folder generated by Next.js. When I attempt the console.log the body from the request it is returning an empty object. Below will be the code for the page and then the code for the API endpoint. A screenshot of the console.log from the API endpoint will also be given.
Page
const handleRemoveItem = useCallback(async(event) => {
event.preventDefault()
var itemSKU = event.target.value;
const response = await fetch('/api/SB-RemoveProduct', {
method:'POST',
body: itemSKU
}).then((r) => {
return r
}).catch((err) => {
throw(err)
});
var deleteConfirm = await response.json();
console.log(deleteConfirm);
},[])
API endpoint
export default withSession(async (req, res) => {
var itemSKU = req.body
console.log("You are here 1");
console.log(itemSKU);
switch (req.method) {
case 'POST': {
var productRemoved = await removeProduct(itemSKU, req, res)
return productRemoved;
break
}
case 'GET': {
console.log('try writting a route')
break
}
case 'DELETE': {
console.log('try writting a route')
break
}
case 'UPDATE': {
console.log('try writting a route')
break
}
}
});
export const removeProduct = async (itemSKU, req, res) => {
var params = {
TableName: "products",
Key: itemSKU
}
console.log("You are here 2");
console.log(itemSKU); // this console.log and the one above both return {}
DB.delete(params, function(err, data) {
if (err) {
console.error("Unable to delete item. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("DeleteItem succeeded:", JSON.stringify(data, null, 2));
res.status(200).send(data)
}
});
}
EDIT 1:
After receiving some feedback, I added headers with the 'Content-Type': 'text/plain', 'Accept': 'text/plain' and ended with the same result. I also verified that the variable that I am passing into the body is a string. Below will be page for the code updated.
const handleRemoveItem = useCallback(async(event) => {
event.preventDefault()
var itemSKU = event.target.value;
console.log(typeof(itemSKU));
const response = await fetch('/api/SB-RemoveProduct', {
method:'POST',
body: itemSKU,
mode: "cors",
headers: {
'Accept': 'text/plain',
'Content-Type': 'text/plain'
},
}).then((r) => {
return r
}).catch((err) => {
throw(err)
});
var deleteConfirm = await response.json();
console.log(deleteConfirm);
},[])
EDIT 2:
Following the suggestions from a solution below, I was able to return a different value for itemSKU than what I had before. This time, instead of the item being empty, it returned as undefined. The changes I made are below:
page:
const handleRemoveItem = useCallback(async(event) => {
event.preventDefault()
var itemSKU = event.target.value;
console.log(typeof(itemSKU));
const response = await fetch('/api/SB-RemoveProduct', {
method:'POST',
body: JSON.stringify({itemSKU}),
}).then((r) => {
return r
}).catch((err) => {
throw(err)
});
var deleteConfirm = await response.json();
console.log(deleteConfirm);
},[])
API Endpoint:
export default withSession(async (req, res) => {
var itemSKU = req.body.itemSKU //req.body.itemSKU is returning undefined.
console.log("You are here 1");
console.log(itemSKU);
switch (req.method) {
case 'POST': {
var productRemoved = await removeProduct(itemSKU, req, res)
return productRemoved;
break
}
case 'GET': {
console.log('try writting a route')
break
}
case 'DELETE': {
console.log('try writting a route')
break
}
case 'UPDATE': {
console.log('try writting a route')
break
}
}
});
export const removeProduct = async (itemSKU, req, res) => {
var params = {
TableName: "products",
Key: itemSKU
}
console.log("You are here 2");
console.log(itemSKU);
// DB.delete(params, function(err, data) {
// if (err) {
// console.error("Unable to delete item. Error JSON:", JSON.stringify(err, null, 2));
// } else {
// console.log("DeleteItem succeeded:", JSON.stringify(data, null, 2));
// res.status(200).send(data)
// }
// });
}
remove the headers, including Content-type: plain/text, then...
In your request, change
body: itemSKU,
to
body: JSON.stringify({ itemSKU });
In your API, you can
console.log('req.body.itemSKU', req.body.itemSKU)
Eventually...
//client side
fetch('/api/...', { method: 'POST', body: JSON.stringify({...}))
.then(res => res.json())
.then(data => console.log('data', data));
//prints { value1: 'abc', value2: 'efg'}
Then on API side
export default async(req, res) => {
console.log('req.body.itemSKU', req.body.itemSKU);
res.json({ value1: 'abc', value2: 'efg'})
}
** I am trying to fetch the data submitted by POST method but I am not able to fetch the data because body variable is not getting updated inside req.on("data", data=> {}).**
const {createServer} = require("http");
const {createReadStream} = require("fs");
const {decode} = require("querystring");
const sendfile = (res, status, type, filepath) => {
res.writeHead(status, {"Content-Type" : type});
createReadStream(filepath).pipe(res); //readable stream work with writable stream
};
createServer((req, res) =>{
if(req.method === 'POST') {
let body = "";
req.on("data", data => {
body+=data;
});
req.on("end", () => {
const {name, email, message} = decode(body);
console.log(`Name : ${name}`);
console.log(`Email : ${email}`);
console.log(`Message : ${message}`);
});
}
switch(req.url) {
case "/" : return sendfile(res, 200, "text/html", "./home_page.html");
case "/message" : return sendfile(res, 200, "text/html", "./forms.html")
default : return sendfile(res, 200, "text/html", "./404.html");
}
}).listen(8000);
should be:
req.on("data", (data)=> {})
I'm getting the following error:
TypeError: Converting circular structure to JSON
at JSON.stringify (<anonymous>)
at stringify (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/response.js:1123:12)
at ServerResponse.json (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/response.js:260:14)
at cors (/Users/landing-page-backend/functions/zohoCrmHook.js:45:43)
at process._tickCallback (internal/process/next_tick.js:68:7)
for the HTTP response in my createLead function, despite the fact that the function is executed properly and does what it's supposed to do (which is to create an entry in my CRM).
I've pointed out in the following code where the error occurs:
const axios = require('axios');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true })
const clientId = functions.config().zoho.client_id;
const clientSecret = functions.config().zoho.client_secret;
const refreshToken = functions.config().zoho.refresh_token;
const baseURL = 'https://accounts.zoho.com';
module.exports = (req, res) => {
cors(req, res, async () => {
const newLead = {
'data': [
{
'Email': String(req.body.email),
'Last_Name': String(req.body.lastName),
'First_Name': String(req.body.firstName),
}
],
'trigger': [
'approval',
'workflow',
'blueprint'
]
};
const { data } = await getAccessToken();
const accessToken = data.access_token;
const leads = await getLeads(accessToken);
const result = checkLeads(leads.data.data, newLead.data[0].Email);
if (result.length < 1) {
try {
return res.json(await createLead(accessToken, newLead)); // this is where the error occurs
} catch (e) {
console.log(e);
}
} else {
return res.json({ message: 'Lead already in CRM' })
}
})
}
function getAccessToken () {
const url = `https://accounts.zoho.com/oauth/v2/token?refresh_token=${refreshToken}&client_id=${clientId}&client_secret=${clientSecret}&grant_type=refresh_token`;
return new Promise((resolve, reject) => {
axios.post(url)
.then((response) => {
return resolve(response);
})
.catch(e => console.log("getAccessToken error", e))
});
}
function getLeads(token) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
axios.get(url, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
return resolve(response);
})
.catch(e => console.log("getLeads error", e))
})
}
function createLead(token, lead) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
const data = JSON.stringify(lead);
axios.post(url, data, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
console.log("response in createLead", response)
return resolve(response);
})
.catch(e => reject(e))
})
}
function checkLeads(leads, currentLead) {
return leads.filter(lead => lead.Email === currentLead)
}
Console.logging all the parameters indicate that the they are not the issue. The configuration of the Zoho API is, also, not the issue considering the fact that the entries are being properly made into the CRM. My assumption is that it would have to do with the HTTP response in the JSON format.
You're trying to convert a promise to JSON, not going to work. Your createLead function returns a promise, not a JSON. The promise is the 'circular object'.
I am currently working with azure functions in javascript. In my function, I am first getting a specific element from my CosmoDB (this is the async/await part). I get a result and then I want to do an https POST request. However, my problem is, that it never finished the HTTPs request and I don't really know why. What am I doing wrong?
(As you can see I tried 2 different ways of doing the request, once with the standard https function and the commented out the part with npm request package. However, both ways won't work).
Here is my code:
const CosmosClient = require('#azure/cosmos').CosmosClient;
var https = require('https');
var http = require('http');
var request = require('request');
const endpoint = "someEndpoint";
const masterKey = "anymasterkey";
const database = {
"id": "Database"
};
const container = {
"id": "Container1"
};
const databaseId = database.id;
const containerId = container.id;
const client = new CosmosClient({
endpoint: endpoint,
auth: {
masterKey: masterKey
}
});
module.exports = function (context, req) {
const country = "de";
const bban = 12345678;
const querySpec = {
query: "SELECT * FROM Container1 f WHERE f.country = #country AND f.bban = #bban",
parameters: [{
name: "#country",
value: country
},
{
name: "#bban",
value: bban
}
]
};
getContainers(querySpec).then((results) => {
const result = results[0];
context.log('here before request');
var options = {
host: 'example.com',
port: '80',
path: '/test',
method: 'POST'
};
// Set up the request
var req = http.request(options, (res) => {
var body = "";
context.log('request');
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
context.res = body;
context.done();
});
}).on("error", (error) => {
context.log('error');
context.res = {
status: 500,
body: error
};
context.done();
});
req.end();
// request({
// baseUrl: 'someURL',
// port: 443,
// uri: 'someuri',
// method: 'POST',
// headers: {
// 'Content-Type': 'text/xml;charset=UTF-8',
// 'SOAPAction': 'someaction'
// },
// function (error, response, body) {
// context.log('inside request')
// if (error) {
// context.log('error', error);
// } else {
// context.log('response');
// }
// }
// })
})
};
async function getContainers(querySpec) {
const {container, database} = await init();
return new Promise(async (resolve, reject) => {
const {
result: results
} = await container.items.query(querySpec).toArray();
resolve(results);
})
}
async function init() {
const {
database
} = await client.databases.createIfNotExists({
id: databaseId
});
const {
container
} = await database.containers.createIfNotExists({
id: containerId
});
return {
database,
container
};
}
The last thing that happens is the print of "here before request". After that the function just does nothing until it timesout. So what am I doing wrong? Can't I just this combination of await/async and requests?
As commented you are not sending any data to the POST call. You need to have a req.write before the req.end
req.write(data);
req.end();
That is why the POST call is failing for you. After this fix, it should work
In my function "reqHandler" I collect form data and pass it into my http.request. To chain requests, I declared a Promise and .then handler. Problem is that:
1. This is written into console "Unhandled promise rejection (rejection id: 2): TypeError: Cannot read property 'url' of undefined"
2. It seems like .then is not invoked, so no API calls are made.
Code:
"use strict";
const http = require("http");
const qs = require("querystring");
const fs = require("fs");
const PORT = 3000;
let resObject = {};
let hash = "";
const options = {
hostname: "netology.tomilomark.ru",
path: "/api/v1/hash",
method: "post"
};
const reqHandler = (req, res) => {
return new Promise((resolve, reject) => {
if (req.url === "/") {
switch (req.method.toLowerCase()) {
case "get":
// Browse my form with "Name" and "Surname" inputs
fs.readFile("./logs/form.html", (err, file) => {
if (err) {
reject("Promise rejected");
return
}
res.writeHead(200, {'Content-Type': 'text/html','Content-Length':file.length});
res.write(file);
res.end();
});
break;
case "post":
// Collect form data and parse it using querystring
let body = "";
req.setEncoding("utf8");
req.on("data", (data) => {
body += data;
if (body.length > 1e6)
req.connection.destroy();
});
req.on("end", () => {
let post = qs.parse(body);
console.log(post);
options.headers = {
"Content-Type": "application/json",
"firstname": `${post.firstName}`
};
options.body = {
"lastname": `${post.lastName}`
};
// Resolve with "options" object that has headers and body
resolve(options);
});
break;
default:
badRequest(res);
}
} else {
notFound(res);
}
});
};
reqHandler()
.then((options) => {
http.request(options, (res) => {
let resString = "";
res.on("data", (data) => {
resString += data;
});
res.on("end", () => {
console.log(resString);
});
res.end();
});
})
.catch(err => {throw err});
let badRequest = (res) => {
res.statusCode = 400;
res.setHeader('Content-Type', 'text/plain');
res.end('Bad Request');
};
let notFound = (res) => {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not Found');
};
const server = http.createServer();
server.on("error", (err) => console.error(err));
server.on("request", reqHandler);
server.on("listening", () => console.log(`Start HTTP on port ${PORT}`));
server.listen(PORT);
Ultimately, what´s wrong with my promise and .then? Any help will be appreciated!
Ok I have create a test environment and here you have an abstract version of your source.
Your mistakes was to wrap your promise in a function which pass the req and res parameters and you have to call resolve or reject inside your promise, what have been forgotten on several places.
This source is tested!
const http = require('http');
const reqHandler = (req, res) => {
return new Promise((resolve, reject) => {
if (req.url === "/") {
switch (req.method.toLowerCase()) {
case "get":
console.log('get');
return resolve('get');
case "post":
console.log('post');
return resolve('post');
default:
return resolve('default');
}
} else {
return resolve('some thing else');
}
});
};
const myReqHandler = (req, res) => {
reqHandler(req, res).then(()=> {
console.log('then reached')
});
}
const PORT = 8089;
const server = http.createServer();
server.on("error", (err) => console.error(err));
server.on("request", myReqHandler);
server.on("listening", () => console.log(`Start HTTP on port ${PORT}`));
server.listen(PORT);