So I am trying to program express middleware, and I am using return statements for every res.json()
or res.send()
, yet I am still receiving the error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot remove headers after they are sent to the client
My express middle-ware runs off objects, but they are converted down to their raw functions before the express server starts listening.
code:
// first contact for all routes
_app.all("*", (req, res, next) => {
if(next == null || next == undefined) return res.json(null);
// Has user logged in?
info("Connection from: ", req.header("x-forwarded-for") || req.socket.remoteAddress);
if (!hauth(req)) {
// they havent
res.locals.authed = false;
if (req.route.path == NEW_AUTH_ROUTE) {
// get user credentials, validate, and create new session for user.
const auth = newauth(req);
// returns new auth key if user credentials are valid,
// otherwise an error prompting the user to login again.
return res.json(auth == null ? ServerResponse.authFail : prepareResponsePayload(auth));
}
// tells the console that the guest user is unauthorized,
// and passes false to the next() functions authed parameter, indicating
// the custom route defined that the user is un-authed. Some pages such as
// the profile page or account page will reject access instantly if this field is false.
warning("Un-authed user with auth header of: ", req.headers[AUTH_HEADER_NAME] || "NONE");
}
else
{
res.statusCode = 200; // can be changed in next() function
res.locals.authed = true;
}
/**
* -----------------
* - READ HERE -
* -----------------
*
* All custom routes created in this file must return their json response.
* The returned value can be anything, even null. It is good practice to be an object.
*
* The next function from your route also contains a field in the res object specifying
* if the user is authenticated or not:
* @see res.locals.authed
*
* If your return value is an error, examples like on line 25 show how to properly use them.
*
* @see ServerResponse houses static constants used for different responses. Includes errors.
* @see ServerSideError An object wrapper for an error recieved from the server.
*
* @see prepareResponsePayload prepareResponsePayload will prepare whatever is returned from next() to be sent back. Please read its contents.
*
*/
res.json(prepareResponsePayload(next()));
})
// init routes
for (const route of Object.values(routes)) {
try {
switch (route.method) {
case "POST": {
_app.post(route.method, route.func);
break;
}
case "DELETE": {
_app.delete(route.method, route.func);
break;
}
case "ALL": {
_app.all(route.method, route.func);
break;
}
case "PUT": {
_app.put(route.method, route.func);
break;
}
default: {
_app.get(route.method, route.func);
break;
}
}
info("Registered route '" + route.route + "'. ");
} catch (e) {
fatal("Route could not be established. Name: " + route.route)
}
}
_app.listen(SERVER_LISTEN_PORT, () => {
info("Server Listening on " + SERVER_LISTEN_PORT + ".");
})
The comments are for my collaborators...
Here is where I define my routes:
/**
* @type {Object.<string, Route>}
*/
module.exports.routes = {}
/**
* Route for pinging the server. Mostly for testing.
*/
module.exports.routes.
ping = new Route("GET", "/ping", async (req, res, next) => null); // all we are looking for is a ping, so no payload body is provided.
module.exports.routes.
register = new Route("POST", "/register", async (req, res, next) => {
try {
info("Route...");
const { username, email, password } = req;
if (!vparams(username, email, password)) return new ServerSideError(1, "Please provide all required fields.");
if (!vname(username)) return new ServerSideError(2, "Invalid Username.");
if (!vemail(email)) return new ServerSideError(3, "Invalid Email.");
if (!vpass(password)) return new ServerSideError(4, "Invalid Password.");
return new Promise((resolve, reject) => {
sql_addRow(new SQLRow({ username: username, password: password, email: email }, SQLTables.WEB_USERS),
(success, error) => {
if (error || !success) resolve(new ServerSideError(ServerSideError.ErrorCodes.OTHER, "Something unexpected happened."));
resolve(newauth(req));
});
});
} catch (e) { return new ServerSideError(-2, "Unexpected Error."); }
});
When I run a postman
request for /register
, I get this error. Should i be seeing something else? I have reviewed my code and seen other forums but they are all saying I'm sending 2+ responses, but I cant see that here. Thanks for the help in advance!
This, by itself:
res.json(prepareResponsePayload(next()));
is a problem. You are both sending a response AND calling next()
which will lead to other routes handling this request causing a duplicate response to be sent (and thus the warning).
If you send a response, DON'T call next()
.
But, there's something else wrong in the _app.all()
handler too and I don't really understand what you're trying to do there to know what to suggest to fix. The core problem is that ALL possible code paths through your if/else
logic need to either send a response or call next()
and if they send a response, they need to either return
or use some other way to make sure that no more code in the response handler runs and can also send a response or call next()
.
Your first nested if
that results in this:
return res.json(auth == null ? ServerResponse.authFail : prepareResponsePayload(auth));
is fine because you're sending a response and returning. But, all other code paths are confused as you don't clearly do one or the other. Perhaps you can just replace this:
res.json(prepareResponsePayload(next()));
with:
next()
But, I'm not really sure what you want all these code paths to do exactly and not sure what prepareResponsePayload()
is supposed to do so you'll have to sort that out.
Summary: ALL possible code paths through your if/else
logic need to either send a response or call next()
and if they send a response, they need to either return
or use some other way to make sure that no more code in the response handler runs that can send a response or call next()
. And, remember that the point of next()
is to pass the request on to the rest of your request handlers (one of which will send a response).