I'm working on creating a middleware for an express router that will execute some code for every request and response. Intercepting a request is easy and there are plenty of examples, but I have not found anything for an elegant approach to intercepting a response. After some research, the best I have come up with is to replace the send function of the response object, shown in the following snippet:
const express = require('express');
const app = express();
var router = express.Router();
var port = process.env.PORT || 3000;
router.get('/test', function(req, res) {
res.send({
message: "testing"
});
});
app.use(function(req, res, next){
console.log("INTERCEPT-REQUEST");
const orig_send = res.send;
res.send = function(arg) {
console.log("INTERCEPT-RESPONSE");
orig_send.call(res, arg);
};
next();
});
app.use("/api", router);
app.listen(process.env.PORT || 3000) && console.log("Running");
There's a problem with this approach: for some reason "INTERCEPT-RESPONSE" is printed in the console twice, meaning res.send is being called twice...
I can set a flag on res.locals the first time it's called to avoid processing the response twice, but I am wondering why res.send is being called twice?
Better example
Try this code to see what are the arguments passed to res.send:
const express = require('express');
const app = express();
var router = express.Router();
var port = process.env.PORT || 3000;
router.get('/test', function(req, res) {
console.log('ACTUAL RESPONSE');
res.send({
message: "testing"
});
});
app.use(function(req, res, next){
console.log("INTERCEPT-REQUEST");
const orig_send = res.send;
res.send = function(arg) {
console.log("INTERCEPT-RESPONSE", JSON.stringify(arguments));
orig_send.call(res, arg);
};
next();
});
app.use("/api", router);
app.listen(process.env.PORT || 3000, function () {
console.log("Running");
});
(I also changed the printing of "Running" to print it when the server is actually listening - your code && was printed before the server was listening - but that's not that important here).
Now after running:
curl http://localhost:3000/api/test
the output on the server console is:
Running
INTERCEPT-REQUEST
ACTUAL RESPONSE
INTERCEPT-RESPONSE {"0":{"message":"testing"}}
INTERCEPT-RESPONSE {"0":"{\"message\":\"testing\"}"}
What happens
As you can see your handler is actually called once by your code, with an object as the first (and only) argument. But then it is called once again with an object serialized to JSON. This is how res.send internally works - see below for details. Since you put your intercepting function on the actual response object then I guess it is calling itself with JSON argument and it doesn't even know that it calls your function in the meantime.
How to avoid it
Try it with the object serialized to JSON by yourself:
const express = require('express');
const app = express();
var router = express.Router();
var port = process.env.PORT || 3000;
router.get('/test', function(req, res) {
console.log('ACTUAL RESPONSE');
res.send(JSON.stringify({
message: "testing"
}));
});
app.use(function(req, res, next){
console.log("INTERCEPT-REQUEST");
const orig_send = res.send;
res.send = function(arg) {
console.log("INTERCEPT-RESPONSE", JSON.stringify(arguments));
orig_send.call(res, arg);
};
next();
});
app.use("/api", router);
app.listen(process.env.PORT || 3000, function () {
console.log("Running");
});
Now it prints:
Running
INTERCEPT-REQUEST
ACTUAL RESPONSE
INTERCEPT-RESPONSE {"0":"{\"message\":\"testing\"}"}
Calling the res.send only once.
Explanation
Now, this is the code that handles object arguments to res.json:
if (chunk === null) {
chunk = '';
} else if (Buffer.isBuffer(chunk)) {
if (!this.get('Content-Type')) {
this.type('bin');
}
} else {
return this.json(chunk);
}
See: https://github.com/expressjs/express/blob/master/lib/response.js#L144-L154
You get the else branch and it calls this.json() (which is res.json() really) with your argument.
But guess what - res.json() calls res.send() in this line:
return this.send(body);
See: https://github.com/expressjs/express/blob/master/lib/response.js#L250
Which calls your intercepting function (for the second time) before running the real res.send().
So, mystery solved. :)
Related
The problem I'm having is, the content that I try to send in my post request to the server doesn't get sent, but the request works.
Here's the code for the client:
$("#searchBtn").click(function(e){
try{
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "/search/searchRequest", true);
console.log(($("#searchedSymptoms").val())) // gets posted in the console correctly
xhttp.setRequestHeader("Content-type", "text/plain"); // doesn't work without it either
xhttp.send($("#searchedSymptoms").val());
//xhttp.send(JSON.stringify($("#searchedSymptoms").val())); // doesn't work either
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
console.log(xhttp.responseText); // gets the correct response from server
}
else{
console.log(xhttp.responseText);
}
};
}
catch(err){
console.log(err);
}
});
And here's the server-side code:
var express = require("express");
var router = express.Router();
router.post("/searchRequest", function(req, res, next){
console.log("must get the client request:");
console.log(req.body);
//console.log(JSON.stringify(req.body)); // doesn't work either
});
In the server, what get's outputed to the console is this:
{}
Any thoughts on what I'm doing wrong ?
You need to use a text body-parser, Express won't do it by default, here's an example, using pretty much the same server side code you are:
"use strict";
var express = require("express");
var router = express.Router();
var bodyParser = require("body-parser");
router.post("/searchRequest", function(req, res, next){
console.log("must get the client request:");
console.log("SearchRequest: " + req.body);
res.end('ok', 200);
});
var port = 8081;
var app = express();
app.use(bodyParser.text());
app.use(router);
app.listen(port);
console.log("Express listening on port " + port);
You can configure the way the text body parser operates exactly by using the guide here:
https://www.npmjs.com/package/body-parser#bodyparsertextoptions
I am new to node and express. I have seen app.get and app.post examples using both "res.send" and "return res.send". Are these the same?
var express = require('express');
var app = express();
app.get('/', function(req, res) {
res.type('text/plain');
res.send('i am a beautiful butterfly');
});
or
var express = require('express');
var app = express();
app.get('/', function(req, res) {
res.type('text/plain');
return res.send('i am a beautiful butterfly');
});
The return keyword returns from your function, thus ending its execution. This means that any lines of code after it will not be executed.
In some circumstances, you may want to use res.send and then do other stuff.
app.get('/', function(req, res) {
res.send('i am a beautiful butterfly');
console.log("this gets executed");
});
app.get('/', function(req, res) {
return res.send('i am a beautiful butterfly');
console.log("this does NOT get executed");
});
I would like to point out where it exactly made a difference in my code.
I have a middleware which authenticates a token. The code is as follows:
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1] || null;
if(token === null) return res.sendStatus(401); // MARKED 1
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if(err) return res.sendStatus(403); // MARKED 2
req.user = user;
next();
});
}
On the // MARKED 1 line, if I did not write return, the middleware would proceed and call next() and send out a response with status of 200 instead which was not the intended behaviour.
The same goes for like // MARKED 2
If you do not use return inside those if blocks, make sure you are using the else block where next() gets called.
Hope this helps in understanding the concept and avoiding bugs right from the beginning.
app.get('/', function(req, res) {
res.type('text/plain');
if (someTruthyConditinal) {
return res.send(':)');
}
// The execution will never get here
console.log('Some error might be happening :(');
});
app.get('/', function(req, res) {
res.type('text/plain');
if (someTruthyConditinal) {
res.send(':)');
}
// The execution will get here
console.log('Some error might be happening :(');
});
To Add a little bit more context to the examples above. Express has layers. So if you return in your function you end the execution. If you not end that you can go further in your layered logic.
So the next function passed to each layer can be called to execute the next layer. If you don't call next the execution stops after your method excuted is executed. (Return just exits the function)
The response object is still available after sending. It is just not possible to write to it again because it has already completed after you did res.end() or res.send().
const express = require('express');
const app = express();
const port = process.env.PORT || 5000;
// a controller handles a http request and terminat it
const controller = (req, res, next) => {
// return http response to client
res.send('hello world');
// do something after you sended request
console.log('do something else');
// if you call next the request will go to the next layer -> afterSend,
// if you do not call next the execution will end
next();
};
// this middleware/layer is executed after response is send to client
const afterSend = (req, res, next) => {
// do something after you sended request, but not send again -> readonly
console.log(res);
// this would throw an error
// res.send()
// res.end()
// etc...
};
// we skip routers here
app.get('/hello', controller, afterSend);
app.listen(port, () => {
console.log(`Running on ports ${port}`);
});
I am pretty new to NodeJS and this is my first time with Express 4, I'm trying to build a simple RESTful front-end around a command-line application. There will ultimately only be one GET and one POST necessary, with the POST handling about 3 or 4 different parameters. The GET should call the command-line application with all default parameters, which is basically just a status check and return the exit status upon completion. The POST will pass along POST parameters on the commandline. I know that this basically calls for an asynchronous call, like child_process.execFile(), but I can't seem to figure out how to actually return the response from within the callback function.
This is the tutorial I used as a starting point, omitting the mongoose dependency, because I have no need for MongoDB, so I basically just followed it up to the point where you start the server. At this point, I'm pretty lost. I always hate writing async code...
var express = require('express'); // call express
var app = express(); // define our app using express
var bodyParser = require('body-parser');
var child_process = require('child_process');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var port = process.env.PORT || 8080; // set our port
var router = express.Router(); // get an instance of the express Router
router.get('/', function(req, res) {
res.json({ message: 'hooray! welcome to our api!' });
});
router.get('/myapp/status', function(req, res) {
console.log(req.user);
child_process.execFile(
'casperjs',
['myfile.js', '--cmd="Status"', '--user="myuser"', '--pass="#mypass"'],
null,
function(response) {
// ???
}, res);
});
app.use('/api', router);
app.listen(port);
console.log('Magic happens on port ' + port);
You can try the following:
router.get('/myapp/status', function(req, res) {
console.log(req.user);
child_process.execFile(
'casperjs', //command
["myfile.js --cmd=Status --user=myuser --pass=#mypass"], // args
function(err, stdout, stderr) { //callback
if (err) {
return res.status(500).send(err);
}
res.send(stdout); // to send response to client
});
});
In POST requests through Postman in a NodeJS API I receive empty bodies... (I receive exactly this: {}), however from automated tests it works perfectly. Actually from Postman that happends when I send it as "form" or as "raw" with "text", but if I send it as a "JSON" in raw it simply freezes in "loading..."
When I search I read about adding these 2 lines related to body parse it made it work for the tests but not for Postman:
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
The entire code is here: https://github.com/nemenosfe/TakeMe-Api
But the 3 key files are the following (simplified):
app.js:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const cors = require('cors');
const user = require('./routes/users');
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use('/users', user);
app.use(cors());
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
});
app.listen(8888, function() {
console.log("Node server running on http://localhost:8888");
});
module.exports = app;
routes/users:
"use strict"
const express = require('express'),
router = express.Router(),
/* ... many require and other code probably not relevant for the problem ... */
router
.post('/', function(req, res, next) {
console.log(`conditions: ${(!req.body)} ${(!req.body.uid)} ${(!req.body.provider)} ${JSON.stringify(req.body)}`);
// This console.log appears as follows in the console once I make the request by Postman: false true true {}
// But it receives what it shoulds with automated tests
if (!req.body || !req.body.uid || !req.body.provider) { utilsErrors.handleNoParams(res); }
else {
/* ... There is a lot of code here but it's not relevant for the problem because it doesn't even reaches this point. */
}
})
module.exports = router
tests/users:
"use strict"
let request = require('supertest-as-promised');
const api = require('../app');
/* ... Another require not relevant for the problem ... */
request = request(api);
describe('Users route', function() {
describe.only('POST /users', function() {
it("should create a new user when it doesn't exist", function(done) {
const params = {
'appkey': helperCommon.appkey,
'uid': 1,
'provider': 'providerTest',
'name': 'fakeName'
};
request
.post('/users')
.set('Accept', 'application/json')
.send(params)
.expect(201)
.expect('Content-Type', /application\/json/)
.then((res) => {
expect(res.body).to.have.property('user');
const userResponse = res.body.user;
expect(userResponse).to.have.property('uid', params.uid);
expect(userResponse).to.have.property('provider', params.provider);
expect(userResponse).to.have.property('name', params.name);
/* ... other expectectations that are not important for the problem ... */
done();
}, done)
});
});
Thanks!
Make sure your are sending the POST request In postman as a x-www-form-urlenconded
When app.listen() is in the same file as app.get(), it works; and when I add app.get() calls in other files via require, they don't work:
// ~ means root folder
// This below is in ~/index.js
var routes = require('~/routes/routes.js');
var server = app.listen(3000, function () {
console.log('Listening on port %d', server.address().port);
});
app.get('/snails', function (req, res) {
res.send('ESCARGOT');
});
// This below is in ~/routes/routes.js
var app = module.exports = require('exports')();
app.get('/birds', function () {
res.send('PENGUIN');
});
// SUCCESS -> localhost:3000/snails will return "ESCARGOT"
// FAIL -> localhost:3000/birds will return "Cannot GET /birds"
Second example to prove the point; this time, app.listen() is moved to routes.js:
// ~ means root folder
// The below is in ~/index.js
var routes = require('~/routes/routes.js');
app.get('/snails', function (req, res) {
res.send('ESCARGOT');
});
// The below is in ~/routes/routes.js
var app = module.exports = require('exports')();
app.get('/birds', function () {
res.send('PENGUIN');
});
var server = app.listen(3000, function () {
console.log('Listening on port %d', server.address().port);
});
// FAIL -> localhost:3000/snails will return "Cannot GET /snails"
// SUCCESS -> localhost:3000/birds will return "PENGUIN"
Why is this so? Is it because app.listen() only targets the file that it is called in?
You need to export your app and include it in your routes file
module.exports = app;
And then in your routes file
var app = include('pathtoyourapp.js');
Then you'll have access to your app in your routes file.
You should be doing something along the lines of this in routes/routes.js
module.exports = function(app) {
app.get('/birds', function(req, res, next) {
res.send('Hello');
});
};
and in the index.js
var app = express();
app.get('/snails', function(req, res, next) {
res.send('SNAILS');
});
require('./routes/routes')(app);
app.listen(3000);
should now work.
BTW i'm not 100% sure what you are trying to do by doing require('exports')(), and it looks weird that you are actually exporting that, instead of the app (that contains the new birds route) in routes/routes.js, so that's why it probably doesn't work. Try the way I suggested.
Let me know if you need any additional things.
Use example:
var express = require('express'),
http = require('http'),
port = Number(process.env.PORT || 3000),
app = express();
app.get('/', function(req, res) {
res.end('Test message');
});
http.createServer(app).listen(port);
Most important is:
http.createServer(app).listen(port);
Send app argument for manipulation of servers behaviors.