How to have a NodeJS/connect middleware execute after responde.end() has been invoked?
Asked Answered
O

3

15

I would like to achieve something like this:

var c = require('connect');
var app = c();

app.use("/api", function(req, res, next){
    console.log("request filter 1");
    next();
});

app.use("/api", function(req, res, next){
    console.log("request filter 2");
    next();
});

app.use("/api", function(req, res, next){
   console.log("request handler");
    res.end("hello");
    next();
});

app.use("/api", function(req, res, next){
    console.log("response post processor");
    next();
});
app.listen(3000);

When I curl for the address, I get an exception to the console complaining about headers cannot be bothered after being sent which is fair enough. Only that I do not touch the response object.

/usr/bin/node app2.js
request filter 1
request filter 2
request handler
Error: Can't set headers after they are sent.
    at ServerResponse.OutgoingMessage.setHeader (http.js:644:11)
    at ServerResponse.res.setHeader (/home/zpace/node_modules/connect/lib/patch.js:59:22)
    at next (/home/zpace/node_modules/connect/lib/proto.js:153:13)
    at Object.handle (/home/zpace/WebstormProjects/untitled1/app2.js:25:5)
    at next (/home/zpace/node_modules/connect/lib/proto.js:190:15)
    at Object.handle (/home/zpace/WebstormProjects/untitled1/app2.js:19:5)
    at next (/home/zpace/node_modules/connect/lib/proto.js:190:15)
    at Object.handle (/home/zpace/WebstormProjects/untitled1/app2.js:14:5)
    at next (/home/zpace/node_modules/connect/lib/proto.js:190:15)
    at Function.app.handle (/home/zpace/node_modules/connect/lib/proto.js:198:3)

Debugging the NodeJS/Connect layer I got into a part that somehow implies that if headers are already sent then executing a route handler must initialize response headers.

The question is if the above mentioned behavior is intentional (ie that the execution of any code after a route handler has finished sending a response is something utterly unimaginable or this is simply a bug in connect?

Octillion answered 4/7, 2012 at 20:46 Comment(4)
You are doing res.end("hello") in your codeExpiable
yes. response processing is done, response is ready to be transmitted. and now i would like to for example place a log or clean up something.Octillion
Have you found a way to do that since you asked this question? There seems to be no response on this thread and I'm trying to understand why the Connect team implemented things the way they did.Acquisitive
Nope, I haven't found any solution so opted out from the features required that.Octillion
R
32

Not sure whether you have found your solution.

If you want to design a post-processor for the request cycle, you can use a middleware that listens to the "finish" event on the response object. Like this:

app.use(function(req, res, next){
  res.on('finish', function(){
    console.log("Finished " + res.headersSent); // for example
    console.log("Finished " + res.statusCode);  // for example
    // Do whatever you want
  });
  next();
});

The function attached to the "finish" event will be executed after the response is written out (which means the NodeJS has handed off the response header and body to the OS for network transmission).

I guess this must be what you want.

Regina answered 18/2, 2014 at 15:30 Comment(1)
finish event is not fired in case the response object had already fired the close event - it happens when the socket is closed before a response can be sent. If you want to catch the moment in which the request/response cycle ends (even when an error-handling middleware sends the response) maybe it's better to override the end function of the response: var _end = res.end; res.end = function(){ console.log('the very end of the response'); _end.apply(this, arguments); }Priestcraft
W
1

I think this is a bad planning problem. You should solve this in a better way. I dont know why you have a request handler and a request post processor separated, but lets find out what we can do.

So yes, after response has ended you cant read the headers again.

So dont finish the response until the post processor is invoked.

var isEnd;

app.use("/*", function(req, res, next){
  isEnd = false;
})

app.use("/api", function(req, res, next){
   console.log("request handler");
    res.write("hello");
    isEnd = true;
    next();
});

app.use("/api", function(req, res, next){
    console.log("response post processor");
    if(isEnd) {
        res.end();
    }
    else next();
});

This is a kind of solution, but this may not be the best for your problem.

In my opinion it is really bad that you call next() after the response has been finished. If you need a post processor, why you do that in a request filterer (or what is this). Call a function but not next()

Maybe this:

app.use("/api", function(req, res, next){
   console.log("request handler");
    res.end("hello");
    setTimeout(function(){(postProcessor(req)},0);
});

function postProcessor(req) {
//doing post process stuff.
//response not needed because already ended.
}

Or this:

app.use("/api", function(req, res, next){
   console.log("request handler");
    res.writed("hello");
    setTimeout(function(){(postProcessor(req)},0);
    // u cant res.end here because setTimeout. 
    //If you dont use setTimeout you can use res.end here, but not bot function.
});

function postProcessor(req, res) {
//doing post process stuff.
res.end();
}

The next() is not for that usage, what you uses.

I hope my answer helps you, but i know it not covers everything, but your answer is not really concrete too.

Whitman answered 4/7, 2012 at 23:49 Comment(2)
Consider that the nodejs/connect layer gets populated with mixed logic from various sources: A) code representing the business logic and B) code representing the infrastructure. So if two different ppl creates the business and infrastructure parts there can not be cooperative/strongly coupled function development here. The business logic guy has to handle things like he was running alone (unaware of the infrastructure), the infrastructure code needs the same: being agnostic with regards the business layer code.Octillion
And again: in the last funciton (the "post processor") I am not touching headers, bodies anything that has been frozen.Octillion
M
1

What a great question to try work out with your morning coffee!

So looking through proto.js, if you have a look down to line 102 which is app.handle which is the handler code for the middleware stack, you'll see how next() operates.

Where the function next() is called, you can see it checks if res.headerSent is true and if so it throws an error.

If modify line 14 to:

app.use("/api", function(req, res, next){
   console.log("request handler");
    res.end("hello");
    console.log(res);
    next();
});

You will see that it actually sets "headersSent" to true. So after we've ended the request, you can see from the next() code that it throws the error because of the conditions discussed.

Molest answered 4/7, 2012 at 23:52 Comment(1)
yes, but sending out the request does not necessarily means that we have finished with the request cycle. IMHO its just one possible approach.Octillion

© 2022 - 2024 — McMap. All rights reserved.