SendFile issues with respect to control flow in Express Middleware - node.js

Here is relevant portion of my code, simplified for narrowing the issue:
app.use(middleware1);
app.use(middleware2);
function middleware1(req,res,next) {
...//get extension of request URL
switch (extension)
{
case 'js' :
..
case 'html': res.sendFile(res.originalUrl,function(err) {});
break; //break1
case 'njm' : break; //break2
default : console.log('default');
break;
}
}
function middleware2(req,res,next) {
console.log("I am in middleware2");
}
Question is this: In case extension is html, for example, I would not expect middleware2 to be called at all but it does!
It appears that sendFile initiates the sending of the file and control execution falls thru before the sendFile's callback is called. If I replace break1 by next() or return next() that would be equally flawed - Control will go to next middleware2 before sendFile's callback is executed. How do I stop middleware2 from getting called for the first set of extensions? Also, if extension is 'njm', even without a next(), middleware2 is called. Why?
Please do not suggest using Express static middleware because I have some logic involved in serving different file types which is more complex then the simplified scenario given above.

res.sendFile() is a bit unique. If you don't pass it a completion callback, then it will call next() for you. See details later in this answer.
What you are reporting is opposite of how Express says it works so I think there must be something that is not quite happening the way you report it.
The whole point of the Express middleware is that any given middleware call gets a chance to field the request and then it either handles the request by generating a response or if it wants the middleware chain to continue, then it calls next(). If next() is not called, then the middleware chain stops and nothing else is called in the current middleware chain. If this is application level middleware (with app.use()), then there should be no further app level middleware processing if you do not call next() from your middleware.
Here's a quote from the Express middleware page:
If the current middleware does not end the request-response cycle, it
must call next() to pass control to the next middleware, otherwise the
request will be left hanging.
This is a pretty good article about Express middleware: Express Middleware Demystified which helps explain a lot more of the details. It also confirms that if you don't call next() then no more handlers will be called in the middleware chain.
There is one special case with res.sendFile(). If you don't pass it a completion callback, then it will call next() itself. If you pass it the completion callback, then it will not call next(). This does not appear to be well documented, but if you look at the res.sendFile() code here, you can see how it works.
One thing to watch out for with your debugging is that sometimes the browser issues more requests than you may realize. For example, when you first hit a homepage of a site, the browser may ask for the website favicon which causes an extra request to hit your web server. So, I'm wondering if your console.log() debugging is confusing you because perhaps there is more than one request coming in, not a single request that is going through both pieces of middleware. Also, a cross origin Ajax call may request AJAX options before requesting the actual Ajax call too.
You can differentiate multiple requests like this and more accurately see whether it is actually going from middleware1 to middleware2 on the same request:
var reqCntr = 1;
app.use(middleware1);
app.use(middleware2);
function middleware1(req,res,next) {
if (!req.reqCntr) {
req.reqCntr = reqCntr++;
}
console.log("middleware1: " + req.reqCntr);
...//get extension of request URL
switch (extension)
{
case 'js' :
..
case 'html': res.sendFile(res.originalUrl,function(err) {});
// return here because the request is now handled
return;
case 'njm' : break; //break2
default : console.log('default');
break;
}
// the request was not handled so call the next link in the middleware chain
next();
}
function middleware2(req,res,next) {
if (!req.reqCntr) {
req.reqCntr = reqCntr++;
}
console.log("middleware2: " + req.reqCntr);
}
Also, it seems like the middleware1 cases where you are not handling the request should call next() so I've modified the above middleware1 to do that. If you handle the request in the switch statement, then return. If not, it will fall through to a call to next().

Once you write app.use(middleware2), middleware2 will be used for all routes on app once middleware1 is completely executed.
As you want to use middleware2 conditionally I would suggest you to use the following method:
app.use(middleware1);
function middleware1(req,res,next) {
...//get extension of request URL
switch (extension)
{
case 'js' : middleware2(req, res, next);
break;
case 'html': res.sendFile(res.originalUrl,function(err) {});
break;
case 'njm' : middleware2(req, res, next);
break;
default : middleware2(req, res, next);
break;
}
}
function middleware2(req,res,next) {
console.log("I am in middleware2");
}

Related

How can I add a new ACL middleware?

I'm handling a client access by IP ACL.
function localAcl (options) {
let {allowedMethods} = options
if (!Array.isArray(allowedMethods)) {
allowedMethods = []
}
return function(req, res, next) {
if (allowedMethods.includes(req.method)) {
next()
} else {
(ipAccessControl(options))(req, res, next)
}
}
}
app.use('/api/v1', localAcl(options), apiV1Router)
The above reads a config file and decides whether to accept or not.
Now, I want to add a new middleware which reads another config file from a web file and decides the permission.
function newAcl () {
...
switch(result) {
case SUCCESS:
next();
break;
case FAIL:
next(new Error('Access Denied'));
break;
}
}
//something like this..
I want to check the permission with the localAcl first.
If localAcl accepts, I don't want the newAcl to be checked and the request will be handled.
If localAcl denies, I want the newAcl to check once more and if the newAcl accepts, the request should be handled.
How can I connect these two?
I see two options
Use next('route') to skip the rest of the middleware functions
Handle programmatically the authorization to let the last middleware decide
The first option seems like a better option for what you're describing
To skip the rest of the middleware functions from a router middleware
stack, call next('route') to pass control to the next route.
NOTE:
next('route') will work only in middleware functions that were loaded
by using the app.METHOD() or router.METHOD() functions.
Source
This means that you should probably call next('route') in your ipAccessControl function.

GET and POST on the same route

I'm building rest end point server (nodejs and restify).
I need to support the same route for two type of client's requests , one for GET and the other for POST.
Currently I solved it by this way :
server.get('/foo' , _ProcessRequest);
server.post('/foo' , _ProcessRequest);
function _ProcessRequest(req, res , next){...}
But I was wondering if there another way to support this type of request
Thanks
Personally the way you have your route structured I find to be the cleanest without use Router Middleware to abstract away the .get() and .post() calls. Since your question asks for other ways to do this here are others ways that you could structure your Route handlers to achieve the same funcitonality.
One way is to structure your routes would be to use router.route() and then specify a handler for each HTTP Method.
server.route('/foo')
.get(_ProcessRequest)
.post(_ProcessRequest)
Alternatively you could modify _ProcessRequest to have a condition that checks req.method with a more middleware style handler using next() to short circuit requests to /foo that aren't a GET or POST.
server.use('/foo', _ProcessRequest)
function _ProcessRequest(req, res, next) {
// If not either a GET or a POST then continue to next handler
if (req.method !== 'GET' && req.method !== 'POST') {
return next()
}
// Request is a HTTP GET or POST so perform logic
}

500 TypeError: listener must be a function at ServerResponse.addListener

Based upon the voted answer events.js:130 throw TypeError('listener must be a function')
I am trying to implement Logging wherein we save every action in DB. To cater this, I was planning to listen to finish or close event of response stream in app.js
I understand that I need to put this code in Express frameworks root folder.Can you please specify where exactly so that I can access listen to this event and access requested URL as well.
Below is the code snippet
app.use(function (req, res,next) {
function afterResponse (req,res,next){
console.log("Response finished from the path "+ req.url);
}
res.on('finish', afterResponse(req,res,next));
});
While the 'finish' event emitted by the response object is not in the documentation, it does exist. However, as an undocumented feature, you may want to avoid it since it could be removed anytime. Without more information about exactly what you are trying to accomplish, it's hard to say whether there might be a better alternative, although I suspect there is. With that caveat, here is the answer to your question.
If you want to capture every response, you need to define a middleware function near the top of your app, before any response has been sent (order matters in Express 4). The example you have above is too complicated; the 'finish' event listener doesn't take parameters. Instead, you can simply do this:
// This example uses ES2015 syntax
app.use((req, res, next) => {
res.on('finish', () => {
console.log(`Response finished from the path ${req.url}`);
});
next();
});
The example above adds the listener before any request is sent, but the listener will only be called after the request is sent. Make sure the listener itself takes no parameters; if you pass req as a parameter as in your example, it will be undefined, which is exactly what you don't want. Just define your litsener in the middleware function itself, and you can access req or res as much as you like.
Alternatively, if you don't want the listener to be redefined on every request, you can access req through the response object itself, which is available as the this context within the listener:
// This example uses ES2015 syntax
app.use((req, res, next) => {
res.on('finish', onFinish);
next();
});
function onFinish() {
console.log(`Response finished from the path ${this.req.url}`);
}

Exit after res.send() in Express.js

I have a fairly simple Express.js app with a login component that I'd like to exit early if login fails. I'm seeing indications that the app isn't doing that and I haven't found a definitive answer that indicates whether calling res.send() halts any further processing. Here's my code as it stands now:
client.login( username, password, function( auth, client ) {
if( !auth ) {
res.send( 401 );
}
// DO OTHER STUFF IF AUTH IS SUCCESSFUL
}
If I read the source code correctly, it should end the request (aborting further processing), but I'm new to node, so I'm not quite ready to trust what I think I'm reading. To boil it down, I guess I'm mostly looking for a definitive answer from a more trustworthy source that my own interpretation of unfamiliar source code. If send() doesn't abort processing, what's the right way to do that?
Of course express can not magically make your javascript function stop executing from somewhere else.
I don't like the next([error]) solution because I think errors should be only used for circumstances you usually don't expect (like an unreachable database or something). In this case, a simple wrong password would cause an error. It is a common convention to not use exceptions/errors for ordinary control flow.
I therefore recommend to place a return statement after the res.send call to make your function stop executing further.
client.login( username, password, function( auth, client ) {
if( !auth ) {
res.send( 401 );
return;
}
// DO OTHER STUFF REALLY ONLY IF AUTH IS SUCCESSFUL
}
If you are using express as your framework, you should call next() instead.
Each handler in express receives 3 parameters (unlinke 2 for basic http) which are req, res and next
next is a function that when called with no arguments will trigger the next handler in the middleware chain.
If next is called with an arguments, this argument will be interpreter as an error, regardless of the type of that argument.
Its signature is next([error]). When next is called with an error, then instead of calling the next handler in the middleware chain, it calls the error handler. You should handle the 401 response code in that error handler. See this for more info on error handling in Express
EDIT: As #Baptiste Costa commented, simply calling next() will not cease the current execution but it will call on the next middleware. It is good practice to use return next() instead to prevent Node from throwing errors further on (such as the can't set headers after they are sent - error). This includes the above suggestion of error throwing which is common:
return next(new Error([error]));
For your specific case you can just add the 'else' statement:
client.login( username, password, function( auth, client ) {
if( !auth ) {
res.send( 401 );
}else {
// DO OTHER STUFF IF AUTH IS SUCCESSFUL
}
}
Or, in general, you can use 'return':
return res.send( 401 );
in these cases , i tend to use a try...catch bloc .
client.login( username, password, function( auth, client ) {
try{
if(error1){
throw {status : 401 , message : 'error1'}
}
if(error2){
throw {status : 500 , message : 'error2'}
}
}catch(error){
res.status(error.status).json(error.message);
}
}
Simply do something like this to stop further execution.
function (request, response, next) {
var path = request.body.path;
if(path === undefined){
response.status(HttpStatus.BAD_REQUEST);
response.send('path is required!');
}
next(response)
};
You only need to do a return to end the flow:
return res.send( 401 );
That will send the 401 response back and won't proceed forward in the flow.
Why is no-one suggesting using an 'else' block?
if(!auth){
// Auth fail code
res.send 'Fail'
} else {
// Auth pass code
res.send 'Pass'
}
'next' is used when creating your own middleware for the "app.use(function(req, res, next));".
If you have set up a route like "app.get(route, function(req, res));" then the code in the function is where you can have the code you are specifying without needing to use 'next'.

How to get req and res object of Expreejs in .ejs file

I am trying to use Express js with .ejs views.
I want to redirect my page to some another page on any event let say "onCancelEvent"
As per Express js documentation,I can do this by using res.redirect("/home");
But I am not able to get res object in my ejs file.
Can anyone Please tell me how to access req and res object in .ejs file
Please help.
Thanks
Short Answer
If you want to access the "req/res" in the EJS templates, you can either pass the req/res object to the res.render() in your controller function (the middleware specific for this request):
res.render(viewName, { req : req, res : res /* other models */};
Or set the res.locals in some middleware serving all the requests (including this one):
res.locals.req = req;
res.locals.res = res;
Then you will be able to access the "req/res" in EJS:
<% res.redirect("http://www.stackoverflow.com"); %>
Further Discussion
However, do you really want to use res in the view template to redirect?
If the event initiates some request to the server side, it should go through the controller before the view. So you must be able to detect the condition and send redirect within the controller.
If the event only occurs at client side (browser side) without sending request to server, the redirect can be done by the client side javascript:
window.location = "http://www.stackoverflow.com";
In my opinion: You don't.
It is better to create the logic that determines the need to redirect inside some middleware that happens long before you call res.render()
It is my argument that your EJS file should contain as little logic as possible. Loops and conditionals are ok as long as they are limited. But all other logic should be placed in middleware.
function myFn( req, res, next) {
// Redirect if something has happened
if (something) {
res.redirect('someurl');
}
// Otherwise move on to the next middleware
next();
}
Or:
function myFn( req, res, next) {
var options = {
// Fill this in with your needed options
};
// Redirect if something has happened
if (something) {
res.redirect('someurl');
} else {
// Otherwise render the page
res.render('myPage', options);
}
}

Resources