Keep original content of request.body after express.urlencoded - node.js

Is there a way to keep the original text of the request.body after it is passed through and parsed by request.urlecoded?
request.body at any handler after this middleware (app.use(express.urlencoded())) is an object and I can't find any way to get the original raw body that was POSTed to the server.

The server can consume an incoming request only once, because the request body is streamed by the client and the HTTP protocol does not allow the server to request a "rewind" from the client. The express.urlencoded middleware parses the request into an object and express.text parses it into a string. In order to execute both in parallel, you need a "duplexer" that pipes the incoming stream into two streams, one for each middleware to parse.
app.use(function(req, res, next) {
var url = new stream.PassThrough();
url.headers = {
"content-length": req.get("content-length"),
"content-type": req.get("content-type")
};
var text = new stream.PassThrough();
text.headers = {
"content-length": req.get("content-length"),
"content-type": "text/plain"
};
req.pipe(new stream.Writable({
write(chunk, encoding, callback) {
url.push(chunk);
text.push(chunk);
callback();
},
destroy(err, callback) {
url.destroy(err);
text.destroy(err);
callback();
},
final(callback) {
url.push(null);
text.push(null);
callback();
}
}));
Promise.all([
new Promise(function(resolve, reject) {
express.urlencoded({extended: false})(url, res, function() {
resolve(url.body);
});
}),
new Promise(function(resolve, reject) {
express.text()(text, res, function() {
resolve(text.body);
});
})
]).then(function([urlBody, textBody]) {
req.urlBody = urlBody; // parsed into an object
req.textBody = textBody; // parsed as the original string
next();
});
});
Addendum
After reading the express.urlencoded documentation, I found a simpler solution:
app.use(express.urlencoded({extended: false,
verify: function(req, res, buf, encoding) {
req.textBody = buf.toString(encoding);
}
}));
This makes req.body an object (as usual) and additionally writes the original into req.textBody. But I hope my first attempt is still instructive.

Related

Not able to return values in another js file even export and require are used

I am using request module to return values .
{code}
var token;
const request = require('request');
const authKey='Bearer <APPKEY>'
const ContentType='application/x-www-form-urlencoded' ;
var postData={
'grant_type':'client_credentials'
};
const options = {
url: '<m/c detils>',
method: 'POST',
headers: {
'Content-Type': ContentType,
'Authorization':authKey
},
body:require('querystring').stringify(postData)
};
module.exports.getAccessToken=request(options, function(errror, response, body){
console.info("Token is caling");
// body...
console.info("inside request module----------------------",JSON.parse(body));
console.info("response details---------------------",response);
token= JSON.parse(body).access_token;
console.info("token1---------------------",token);
return token;
})
)
{code}
Here I am able to return value for token ,but same thing if I want to use in another file
for eg ::
var token = authtoken.getAccessToken;
I am getting value as undefined ,I have did
var authtoken=require('./utils/Utils.js');
Please help me out here
Since 'request' is async, you'd need to modify your code
// file 1
module.exports.getAccessToken = function (cb) {
request(options, function(errror, response, body) {
/**
* do something
*/
return cb(body.access_token)
})
}
// file 2
const file1 = require('file1')
file1.getAccessToken(function (token) {
// ..
})
Also, if you pass json: true in request options, it'll pass you json data in response
Your getAccessToken export isn't exporting a function, it's exporting the request result, which is, as Paul commented, not going to be defined.
I suspect what you wanted was to export a function that invokes the request, like this...
module.exports.getAccessToken = callback => request(options, function(error, response, body) {
token= JSON.parse(body).access_token;
callback(token);
});
Of course, you'll need to handle errors...
Solution provided by iyerrama29 is fine. But you can use Promise also here.
Check below link on how to use Promise here.
Node.js promise request return

Sending a buffer in multipart/form-data POST request Node | Express | request

I have a buffer that I have taken from users uploaded image, that I then want to send on to another API in a multipart/form-data POST request.
I'm having issues however with the request object. I want to send a stream or buffer rather than accessing local server file system / creating temp files. I'm fairly new to the concept of streams.
I get the correct response from API sending
image_file: fs.createReadStream('image.png')
But when I try:
image_file: data // buffer
I get an error from the API saying I'm missing the image_file parameter.
Please help!
Docs for the API I'm using (Face++)
I'm using request to make the post request.
Here is my code in question:
app.post('/', (req, res) => {
const url = 'https://api-us.faceplusplus.com/facepp/v3/detect';
let data = [];
req.on('data', (chunk) => {
data.push(chunk)
})
req.on('end', (req, res) => {
data = Buffer.concat(data);
const formData = {
api_key: process.env.FACEPP_API_KEY,
api_secret: process.env.FACEPP_API_SECRET,
// image_file: fs.createReadStream('image.png') // works
image_file: data // doesnt work
}
const options = {
uri: url,
method: 'POST',
formData
}
request(options, (err, response, body) => {
if (err) console.log(err)
console.log(body)
})
})
})
After a little playing about, I have the following code, it's working nicely for me. I used the Multer middleware (https://github.com/expressjs/multer) for the original multipart upload. As a matter of interest, request doesn't seem to play nice uploading files unless you specify a filename option.
const multer = require('multer');
const upload = multer();
app.post('/', upload.any(), (req, res) => {
const url = 'https://api-us.faceplusplus.com/facepp/v3/detect';
console.log('Image upload complete, creating request to: ' + url);
var formData = {
api_key: process.env.FACEPP_API_KEY,
api_secret: process.env.FACEPP_API_SECRET,
image_file: {
value: req.files[0].buffer, // Upload the first file in the multi-part post
options: {
filename: 'image_file'
}
}
};
const options = {
uri: url,
formData: formData,
method: 'POST'
}
request(options, (err, response, body) => {
console.log('Request complete');
if (err) console.log('Request err: ', err);
console.log(body)
})
})
I'm getting a response that looks like this:
{
"image_id": "GuF0MUPoaTcL/rbbcg+2kA==",
"request_id": "1520789753,d913cce4-118e-4893-a1ee-d1ace2b6a65b",
"time_used": 142,
"faces": [{
"face_rectangle": {
"width": 183,
"top": 125,
"left": 220,
"height": 183
},
"face_token": "8b8e327edfc10730f344b1465934a478"
}]
}
I tested the image upload to my local server using curl like so:
curl -v -i -F "data=#smiling_woman.jpg" -H "Content-Type: multipart/form-data" -X POST http://localhost:3000/

Unable to send request from request callback

I cant execute a second request inside the request callback.
request({
url: url,
headers: {
'auth-token': token
},
method: 'POST',
json: true,
body: data
}, (err, req, body) => {
if (err) {
console.log(err);
} else {
// Prosses data;
// This is the second request.
request({
url: url2,
headers; {
'auth-token': token
},
method: 'POST',
json: true,
body: data2
}, (err, req, body) => {
if (err) {
console.log(err);
return;
}
//Process data.
})
}
})
The problem is that the second request is not executing.
I am using nodemon to start the express server, but on the nodemon only the first request is receive on the express.
But the strange thing is that when I tried to call the method on the second time without closing the electron app, the second request is executed. And I can see it on the nodemon that the second request is executed first.
The output of the nodemon is like this.
POST /path/to/url 200 6.181 ms - 641 //-> this is the first execution. then there is nothing follows.
// Then I call the method again using the app. And the result is this.
POST /path/to/second/url 200 9.645 ms - 21
POST /path/to/url 200 21.628 - 641
It look like the /path/to/second/url is staying on somewhere nowhere and just send to the server if the method is called for the second time.
Please help, thanks.
Update: Added more info.
I have a folder could routes all the .js file is save there.
then I am loading it using this on the my app.js
let rs = fs.readdirSync(path.join(process.cwd(), '/routes/'));
rs.forEach((file) => {
if (file.indexOf('.js') !== -1) {
let fn = '/' + file.replace(/\.[^/.]+$/, '');
let pt = path.join(__dirname, './routes', fn);
let req = require(pt);
app.use(fn, req);
}
});
Then on the routes files I have something similar like this.
router.post('url', (req, res) => {
// here calling the controller.
// mostly just single line passing the request and result variable to the controller.
});
Actually that requests is called inside the ipc callback. I have a menuitems and on the click() event I just used the browserWindow.getFocusedWindow().webContents.send('ipc-name') then this will be triggered.
ipc.on('ipc-name', () => {
// The request is called here.
}
This does not solve the OP problem as the problem exists in Linux env but acts as an workaround.Instead of request module we have to make use of ClientRequest API in electron which is Event based and only makes the task much more difficult.But doesn't suffer from the issue faced in callback inside callback.
Example Code:
function triggerCall() {
const request = net.request(`${root}/routes/get1`);
request.on('response', (response) => {
response.on('data', (chunk) => {
console.log(`BODY: ${chunk}`)
})
response.on('end', () => {
console.log('req 1 end');
const request1 = net.request(`${root}/routes/get2`);
request1.on('response', (response) => {
response.on('data', (chunk) => {
console.log(`BODY: ${chunk}`)
})
response.on('end', () => {
console.log('req 2 end');
})
})
request1.end();
})
})
request.end();
};

TypeError: Request path contains unescaped characters, any idea

//route to search (POST http://localhost:8080/api/search)
apiRoutes.post('/search', function(req, res) {
console.log('search');
var query = req.params;
console.log(query);
options = {
protocol : "https:/",
host: "https://api.themoviedb.org",
path: "/3/search/movie?api_key=35f7a26be584f96e6b93e68dc3b2eabd&language=en-US&page=1&include_adult=false&query="+query,
};
var req = https.request(options, function(res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function () {
var body = Buffer.concat(chunks);
console.log(body.toString());
});
});
req.write("{}");
req.end();
})
DOES ANYONE KNOW WHERE THE PROBLEM IS?
I'm trying to do a request to do a research to the api the movie db and get the result back
There are some problems with the code. I have tested it and made it to work.
let options = {
host: "api.themoviedb.org",
path: "/3/search/movie?api_key=35f7a26be584f96e6b93e68dc3b2eabd&language=en-US&page=1&include_adult=false&query="+query.data.replace(' ','%20'),
};
first of all since you are using https module you don't need to specify the protocol nor you need to put it in the url. That's how your options variable should be.
Second you are appending the entire query object to the url which is {} instead you should append a string which will be in one of the key of your query object in my case its query.data
Third if there are spaces in the string Eg: Home Alone you to maintain space and avoid the error we replace the string with %20 which is a escaping character.
Forth Try giving a unique name for https request variable and its response variable in the callback function or it will override the route's req res variables cause your code to not work. Notice how I have used route's res function to send the data back and end the response
Also I am getting the data in req.body and you are using req.params however there are no params defined in your routes. Try going through the documentation for more information
Here is the complete code
apiRoutes.post('/search',function (req, res) {
https = require('https');
var query = req.body;
console.log(query.data);
let options = {
host: "api.themoviedb.org",
path: "/3/search/movie?api_key=35f7a26be584f96e6b93e68dc3b2eabd&language=en-US&page=1&include_adult=false&query="+query.data.replace(' ','%20'),
};
var request = https.request(options, function(response) {
var chunks = [];
response.on("data", function (chunk) {
chunks.push(chunk);
});
response.on("end", function () {
var body = Buffer.concat(chunks);
console.log(body.toString());
res.send(body);
res.end()
});
});
request.end();
});
Hope it helps.

how do you send html with restify

I want to send plain html instead of a json response for one of my routes in restify. I tried setting the contentType and header property of the response but it doesn't seem to set the contentType in the header (the browser tries to download the file rather than render it).
res.contentType = 'text/html';
res.header('Content-Type','text/html');
return res.send('<html><body>hello</body></html>');
Quick way to manipulate headers without changing formatters for the whole server:
A restify response object has all the "raw" methods of a node ServerResponse on it as well.
var body = '<html><body>hello</body></html>';
res.writeHead(200, {
'Content-Length': Buffer.byteLength(body),
'Content-Type': 'text/html'
});
res.write(body);
res.end();
If you've overwritten the formatters in the restify configuration, you'll have to make sure you have a formatter for text/html. So, this is an example of a configuration that will send json and jsonp-style or html depending on the contentType specified on the response object (res):
var server = restify.createServer({
formatters: {
'application/json': function(req, res, body){
if(req.params.callback){
var callbackFunctionName = req.params.callback.replace(/[^A-Za-z0-9_\.]/g, '');
return callbackFunctionName + "(" + JSON.stringify(body) + ");";
} else {
return JSON.stringify(body);
}
},
'text/html': function(req, res, body){
return body;
}
}
});
Another option is to call
res.end('<html><body>hello</body></html>');
Instead of
res.send('<html><body>hello</body></html>');
It seems like the behaviour #dlawrence describes in his answer has changed since when the answer was posted. The way it works now (at least in Restify 4.x) is:
const app = restify.createServer(
{
formatters: {
'text/html': function (req, res, body, cb) {
cb(null, body)
}
}
})

Resources