http request does not emit 'end' after proxy - node.js

I need to be able to use the http request body in my request proxy application and then again in the actual web service. I am using restreamer to 'reset' the stream (and even wrote the middleware myself with no change). The web service receives the body just fine, but because end is never emitted, I cannot continue with the request.
Testing with postman, sending a raw body, with content type set. Any suggestions would be greatly appreciated. Thanks!
var express = require('express')
, bodyParser = require('body-parser')
, http = require('http')
, restreamer = require('connect-restreamer')
, httpProxy = require('http-proxy')
var app = express();
app.use(function (req, res, next) {
var body = '';
req.on('data', function (chunk) {
body += chunk.toString('utf8');
});
req.on('end', function (chunk) {
req.body = JSON.parse(body)
next();
});
});
app.use(restreamer());
var proxy = httpProxy.createServer();
app.all('*', function (req, res) {
proxy.web(req, res, {
target: 'http://localhost:8001'
});
});
http.createServer(app).listen(8000);
app2 = express();
app2.use(function (req, res, next) {
var body = '';
req.on('data', function (chunk) {
body += chunk.toString('utf8');
});
req.on('end', function (chunk) {
req.body = JSON.parse(body)
next();
});
});
app2.all('*', function (req, res) {
res.send(req.body)
});
http.createServer(app2).listen(8001);

Using the request library in my application, it worked:
var request = require('request')
request.post({
url: 'http://localhost:8000',
json: {content: 123, type: "greeting", access_token: "here i am"}
},function(err, res,data){
console.log('return:' ,err, data)
});
But using curl with a file containing the same message, it would not work:
curl -X POST -d #data.json http://localhost:8000 --header "Content-Type:application/json"
I compared the request objects against each other and found a few differences and when I got to the request header for content-length, I found that editing it the "correct" length would end the steam (and the web server would send a response).
I will make the modifications needed and commit to connect-restreamer module.

Related

Writing express middleware to get raw request body before body-parser

I wrote an Express middleware to retrieve the raw body from the request, and I set it before body-parser middleware.
My custom middleware is calling req.setEncoding('utf8'), but this causes the following body-parser error:
Error: stream encoding should not be set
at readStream (/node_modules/body-parser/node_modules/raw-body/index.js:211:17)
at getRawBody (/node_modules/body-parser/node_modules/raw-body/index.js:106:12)
at read (/node_modules/body-parser/lib/read.js:76:3)
at jsonParser (/node_modules/body-parser/lib/types/json.js:127:5)
Here is my code:
var express = require('express');
var bodyParser = require('body-parser')
function myMiddleware() {
return function(req, res, next) {
req.rawBody = '';
req.setEncoding('utf8');
req.on('data', function(chunk) {
req.rawBody += chunk;
});
req.on('end', function() {
next();
});
}
}
var app = express();
app.use(myMiddleware());
app.use(bodyParser.json());
var listener = app.listen(3000, function() {
});
app.get('/webhook/', function (req, res) {
res.sendStatus(200);
});
Is there a way to unset the encoding? Is there another way to retrieve the raw body, but still use body-parser after it?
It turns out that body-parser has a verify option to call a function when the request body has been read. The function receives the body as a buffer.
Here is an example:
var express = require('express');
var bodyParser = require('body-parser')
function verifyRequest(req, res, buf, encoding) {
// The raw body is contained in 'buf'
console.log( buf.toString( encoding ) );
};
var app = express();
var listener = app.listen(3000);
// Hook 'verifyRequest' with body-parser here.
app.use(bodyParser.json({ verify: verifyRequest }))
app.post('/webhook/', function (req, res) {
res.status(200).send("done!");
});
You are calling next() inside "done", which means the stream has already been consumed. Instead, set up the handler for "data" then pass the request along using next(). The "done" event is likely being handled inside bodyParser so after it executes you have access to req.rawBody. If this was not the case you would add another middleware that calls next() inside a req.on('done') to hold the rest from processing until you have all the data.
// custom middleware - req, res, next must be arguments on the top level function
function myMiddleware(req, res, next) {
req.rawBody = '';
req.on('data', function(chunk) {
req.rawBody += chunk;
});
// call next() outside of 'end' after setting 'data' handler
next();
}
// your middleware
app.use(myMiddleware);
// bodyparser
app.use(bodyParser.json())
// test that it worked
function afterMiddleware(req, res, next) {
console.log(req.rawBody);
next();
}
app.use(afterMiddleware);
If you need to access the raw body you might also want to look into bodyParser.raw(). This will put the raw body into req.body, same as bodyParse.json() but can be made to run conditionally based on the content type - check out options.type.
I recommend a different approach, since your current approach actually consumes the message and makes it impossible for body-parser to read it (and there are a bunch of edge case bugs that spring up by calling next synchronously):
app.use(bodyParser.json());
app.use(bodyParser.text({type: '*/*'}));
This will read any application/json request as JSON, and everything else as text.
If you must have the JSON object in addition to the text, I recommend parsing it yourself:
app.use(bodyParser.text({type: '*/*'}));
app.use(myMiddleware);
function myMiddleware(req, res, next) {
req.rawBody = req.body;
if(req.headers['content-type'] === 'application/json') {
req.body = JSON.parse(req.body);
}
next();
}

Node.js: Unable to compress response using express

I am using express version 4.13.4 and the following code for my app.js. I have tried changing the location of app.use(compression()) which did not show any effect. when I run the application I saw no evidence of compression in viewing the chrome dev tools response headers i.e it doesn't have the gzip content-encoding header.
I am new to node js.I want to gzip compress my response to browser. Please help me fix this issue.
var compression = require('compression')
var express = require('express');
var http = require('http');
var app = express();
app.use(compression());
var settings = {
UiServerPort: 8080,
ApiServerHost: "localhost",
ApiServerPort: 12121
};
app.use('/ui', express.static('ui'));
app.all('/api/*', function (req, res) {
var options = {
host: settings.ApiServerHost,
port: settings.ApiServerPort,
path: req.url.substring(4),
method: 'POST'
};
var requestData = '';
req.on('data', function (data) { requestData += data; });
req.on('end', function () {
var request = http.request(options, function (response) {
var responseData = '';
res.flush();
response.on('data', function (data) { responseData += data; });
response.on('end', function () {
res.statusCode = response.statusCode;
res.write(responseData);
res.end();
});
});
request.write(requestData);
request.end();
});
});
app.listen(settings.UiServerPort)
you saw " Vary Accept-Encoding " ?? if you don't use compression , this won't show. and I paste your code ,but It can't run.
Instead of
app.use(compression())
You should add this piece of code :
app.use(compression({filter: shouldCompress}))
function shouldCompress (req, res) {
if (req.headers['x-no-compression']) {
// don't compress responses with this request header
return false
}
// fallback to standard filter function
return compression.filter(req, res)
}
PS : it works for me.

How to get the request body buffer?

var http = require('http');
http.createServer(function (req, res) {
if (req.method === 'POST') {
// How to obtain the body buffer?
}
});
I am aware that I can read data stream, e.g.
var requestBody = '';
req.on('data', function (data) {
requestBody += data;
});
req.on('end', function () {
console.log(requestBody);
});
I assume there is a way to access the data buffer directly or construct one myself?
The purpose is for forwarding an HTTP request (performing MITM for debugging purposes).
I recommend you, if you want, use express+bodyParser, that simple and effective, for example:
var express = require('express');
var app = express();
app.use(bodyParser.json({limit:1024*1024}));
app.post('/', function(req, res){
console.log(req.body); //YOUR BODY
});
app.listen(8080)

GET request from app with http basic authentication (node.js)

I have an nodejs app which use the http basic authentication in express.js.
In this app I make an http.get request to an external webpage to load and parse the html.
With the basic auth I get in each http.get(url, function(){}) request to the external host an error: "Unauthorized". If I remove the basic authentication, it works fine.
Anybody know's why I'm Unauthorized at an public resource if only my own server has this authentication?
e.g. (pseudocode):
with the express basic authentication, I'm getting "Unauthorized" as body from google.com. without the auth, I get the html
var auth = express.basicAuth(function(user, pass, callback) {
var result = (user === 'john' && pass === 'doe') ? true : false;
callback(null, result);
});
app.configure(function(){
app.use("/", auth, function(next) { next(); });
app.use("/", express.static(__dirname+'/html'));
});
http.get('http://google.com', function(res) {
res.setEncoding('utf8');
var body = '';
res.on('data', function (chunk) {
body = body + chunk;
});
res.on('end', function() {
cb(body);
});
}).on('error', function(err) {
cb('error', err);
});
You need to restructure your app to have your call to Google done inside a callback, after a GET is issued to your server. Here's working code:
var express = require('express');
var request = require('request');
var app = express();
// Authenticator
app.use(express.basicAuth('john', 'doe'));
app.get('/', function(req, res) {
request.get('http://www.google.com', function (err, response, body) {
res.send(body)
});
});
app.listen(process.env.PORT || 8080);
Here are details if you want to do fancier basic authentication: http://blog.modulus.io/nodejs-and-express-basic-authentication
It appears that you're trying to write code synchronously, which will not work. I recommend reading this article for a review of idiomatic Node: http://blog.ponyfoo.com/2013/07/12/teach-yourself-nodejs-in-10-steps
For HTTP Basic/Digest authentication you can also use http-auth module
var express = require('express');
var request = require('request');
// Authentication module.
var auth = require('http-auth');
var basic = auth.basic({
realm: "Simon Area.",
file: __dirname + "/../data/users.htpasswd" // gevorg:gpass, Sarah:testpass ...
});
// Application setup.
var app = express();
app.use(auth.connect(basic));
// Setup route.
app.get('/', function(req, res){
request.get('http://www.google.com', function (err, response, body) {
res.send(body)
});
});

Expressjs raw body

How can I access raw body of request object given to me by expressjs?
var express = require('./node_modules/express');
var app = express.createServer();
app.post('/', function(req, res)
{
console.log(req.body); //says 'undefined'
});
app.listen(80);
Something like this should work:
var express = require('./node_modules/express');
var app = express.createServer();
app.use (function(req, res, next) {
var data='';
req.setEncoding('utf8');
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
req.body = data;
next();
});
});
app.post('/', function(req, res)
{
console.log(req.body);
});
app.listen(80);
Using the bodyParser.text() middleware will put the text body in req.body.
app.use(bodyParser.text({type: '*/*'}));
If you want to limit processing the text body to certain routes or post content types, you can do that too.
app.use('/routes/to/save/text/body/*', bodyParser.text({type: 'text/plain'})); //this type is actually the default
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
If you want a raw Buffer, you can use bodyParse.raw().
app.use(bodyParser.raw({type: '*/*'}));
Note: this answer was tested against node v0.12.7, express 4.13.2, and body-parser 1.13.3.
Put the following middleware before bodyParser middleware. It'll collect raw body data in request.rawBody and won't interfere with bodyParser.
app.use(function(req, res, next) {
var data = '';
req.setEncoding('utf8');
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
req.rawBody = data;
next();
});
});
app.use(express.bodyParser());
Default express does not buffer data unless you add middleware to do so. The simple solution is to follow the example in #Stewe's answer below, which would just concatenate all of the data yourself. e.g.
var concat = require('concat-stream');
app.use(function(req, res, next){
req.pipe(concat(function(data){
req.body = data;
next();
}));
});
The downside of this is that you have now moved all of the POST body content into RAM as a contiguous chunk, which may not be necessary. The other option, which is worth considering but depends on how much data you need to process in the post body, would be to process the data as a stream instead.
For example, with XML you could use an XML parser that supports parsing XML as it comes in as chunks. One such parser would be XML Stream. You do something like this:
var XmlStream = require('xml-stream');
app.post('/', function(req, res) {
req.setEncoding('utf8');
var xml = new XmlStream(req);
xml.on('updateElement: sometag', function(element) {
// DO some processing on the tag
});
xml.on('end', function() {
res.end();
});
});
app.use(bodyParser.json({
verify: function (req, res, buf, encoding) {
req.rawBody = buf;
}
}));
app.use(bodyParser.urlencoded({
extended: false,
verify: function (req, res, buf, encoding) {
req.rawBody = buf;
}
}));
So, it seems like Express's bodyParser only parses the incoming data, if the content-type is set to either of the following:
application/x-www-form-urlencoded
application/json
multipart/form-data
In all other cases, it does not even bother reading the data.
You can change line no. 92 of express/node_modules/connect/lib/middleware/bodyParser.js from
} else {
next();
}
To:
} else {
var data='';
req.setEncoding('utf8');
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
req.rawBody = data;
next();
});
}
And then, read req.rawBody from your code.
If you want the body as a buffer:
var rawParser = function(req, res, next) {
var chunks = [];
req.on('data', function(chunk) {
chunks.push(chunk)
});
req.on('end', function() {
req.body = Buffer.concat(chunks);
next();
});
}
or
var rawParser = bodyParser.raw({type: '*/*'});
and then:
app.put('/:name', rawParser, function(req, res) {
console.log('isBuffer:', Buffer.isBuffer(req.body));
})
or for all routes:
app.use(bodyParser.raw({type: '*/*'}));
It seems this has become a lot easier now!
The body-parser module is able to parse raw and text data now, which makes the task a one-liner:
app.use(bodyParser.text({type: 'text/plain'}))
OR
app.use(bodyParser.raw({type: 'application/binary'}))
Both lines simply fill the body property, so get the text with res.body.
bodyParser.text() will give you the UTF8 string while bodyParser.raw() will give you the raw data.
This is the full code for text/plain data:
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
app.use(bodyParser.text({type: 'text/plain'}))
app.post('/', function (req, res, next) {
console.log('body:\n' + req.body)
res.json({msg: 'success!'})
next()
})
See here for the full documentation:
https://www.npmjs.com/package/body-parser
I used express 4.16 and body-parser 1.18
If you are having trouble with the above solutions interfering with normal post requests, something like this might help:
app.use (function(req, res, next) {
req.rawBody = '';
req.setEncoding('utf8');
req.on('data', function(chunk) { req.rawBody += chunk });
});
More info & source: https://github.com/visionmedia/express/issues/897#issuecomment-3314823
All the answers seems outdated, if anyone still struggling with this then express has built-in Express raw middeware.
This middleware is available in Express v4.16.0 onwards.
This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser.
var express = require("express");
var app = express();
app.use(express.raw({ type: "*/*" }))
app.post("/", (req, res) => {
// req.body = JSON.parse(req.body); // To parse JSON if needed (in-case)
console.log(req.body);
res.end();
});
app.listen(3000, (err) => {
if(!err) console.log("App running!!")
});
BE CAREFUL with those other answers as they will not play properly with bodyParser if you're looking to also support json, urlencoded, etc. To get it to work with bodyParser you should condition your handler to only register on the Content-Type header(s) you care about, just like bodyParser itself does.
To get the raw body content of a request with Content-Type: "text/xml" into req.rawBody you can do:
app.use(function(req, res, next) {
var contentType = req.headers['content-type'] || ''
, mime = contentType.split(';')[0];
if (mime != 'text/xml') {
return next();
}
var data = '';
req.setEncoding('utf8');
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
req.rawBody = data;
next();
});
});
When sending the request be sure to add this header:
'Content-Type': 'application/json'

Resources