0

I'm creating some kind of SignIn request/response in Express. When user give valid username & password, then server will return valid authorization code.

This is code.

export const getAuthKey = async (req, res, next) => {
    // Query parameter : :username, :hash(SHA256 encoded pasword)
    // Return : { authKey: authKey }
    if (req.query.username === undefined) { res.status(400).send('`username` query paramter missing.'); }
    if (req.query.hash === undefined) { res.status(400).send('`hash` query paramter missing.'); }
    getConnection()
        .then(conn => new Promise((resolve, reject) => {
            const escapeUserName = conn.escape(req.query.username);
            const escapeSHA256 = conn.escape(req.query.hash);
            return conn.query(`SELECT HEX(AuthorizationKey) AS authKey FROM UserInfo WHERE UserName = ${escapeUserName} AND UserPassword = UNHEX(${escapeSHA256});`)
                .then(result => resolve(result))
                .catch(err => reject(err))
                .finally(() => { conn.end(); });
        }))
        .then(auth => {
            if (auth.length < 1) { res.status(401).send('No such user found'); }
            else { 
                log(`User ${req.query.username} requests valid authorization.`, `AUTH`)
                res.json(auth[0]); 
            }
        })
        .catch(err => {
            log('Unable to get authKey', 'AUTH', 'ERROR', err);
            res.status(500).send(`Unable to logging-in`);
        });
}

The steps are simple,

1. User send request with parameter username and hash(password) 1-1. If parameter is not fulfilled, it returns status 400. 2. Check Database and find if user is valid, if so, return it's authorization code. 3-2 If there are some unexpected error, returns status 500.

This code works. But I always see (node:11580) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

I think I miss something about http request. Can you tell me what is wrong? Also would you provide me a code pattern for this kind of process?

p.s please ignore async on function definition.

Gipyo.Choi
  • 155
  • 2
  • 14
  • Does this answer your question? [Error: Can't set headers after they are sent to the client](https://stackoverflow.com/questions/7042340/error-cant-set-headers-after-they-are-sent-to-the-client) – Prakash Karena Jan 20 '20 at 07:12
  • Please, please stop inserting a manually created promise in the middle of a promise chain. There is no reason to use that promise anti-pattern. Just return the promises you already have, no need to manually create one in the middle of the chain. – jfriend00 Jan 20 '20 at 07:24
  • This `new Promise((resolve, reject) => {` and this `.then(result => resolve(result)).catch(err => reject(err))` are completely unnecessary. – jfriend00 Jan 20 '20 at 07:25
  • @PrakashKarena I read that, but seems like not in my case, I think I dont have any of those. If dont think so, can you specify which article i need to read in that link? – Gipyo.Choi Jan 20 '20 at 07:28
  • @jfriend00 Are you talking about near `conn.query(...)???` I searched for that, and I decided it is necessary, DB interaction should be clearly finished. Without that, how can I distinguish if it is DB problem or anything else? And ultimate reason for that codes are for closing DB without error.(It is not implemented yet) – Gipyo.Choi Jan 20 '20 at 07:32
  • `return conn.query().finally()` is all that is needed. – jfriend00 Jan 20 '20 at 07:33
  • @jfriend00 seems like there are no 'finally()' in my DB api (I use MariaDB connector). Also regarding `conn.query().finally()` Is it possible to classify which function throws error? And is it possible to deal with proper error handling depend on errors? In my API, `conn.query()`, `conn.end()`, `conn.ping()`(Check if connection is still alive) also returns Promise. – Gipyo.Choi Jan 20 '20 at 07:40
  • What version of nodejs are you running? – jfriend00 Jan 20 '20 at 07:41
  • I'm using 10.16.3 LTS. – Gipyo.Choi Jan 20 '20 at 07:42
  • If you are running a modern version of node.js with `.finally()`, but the DB is returning an older promise that doesn't have `.finally()`, then you can do this: `return Promise.resolve(conn.query()).finally()`. That "casts" an older promise into a newer one while preserving the promise state appropriately. – jfriend00 Jan 20 '20 at 07:43
  • Oh sorry, I misunderstood, you mean Promise's `finally()` function, not DB API itself. If so, as I told, is it possible to handle proper error in it? In a big perspective, there can be various of errors, 1)ConnectionFailure 2)QueryFailure 3)ConnectionCloseFailure 4)FailureOnHandlingFailure.... Regarding updating query, I need to to do below, 1)getConnection 2)beginTransaction 3)Query 4)Another Query 4)Commit 5)Error handling I describe right above. Actually nested promise code above is what I found somewhere, so I didn't know it is anti pattern. Can you advise how to handle them well? – Gipyo.Choi Jan 20 '20 at 07:51
  • Are you trying to send a different result back to the client for every one of those possible errors? If so, that will take a bunch more code than you currently have. Or, is it OK to send a more general error back to the client and log the exact error on the server? – jfriend00 Jan 20 '20 at 08:36
  • Regarding result of request, yes i just want to returns general result for error. But what i want to is handle each errors properly regardless of the result of request. – Gipyo.Choi Jan 20 '20 at 15:07

1 Answers1

1

The particular error you asked about "Cannot set headers after they are sent to the client" is caused by attempting to send a second response to the same request. So, there is typically a problem with the flow through your code that causes it to execute multiple code paths that can send a response.

On these two statements, you need to add a return statement so code does not continue executing in your function after you've sent a response:

if (req.query.username === undefined) { res.status(400).send('`username` query paramter missing.'); }
if (req.query.hash === undefined) { res.status(400).send('`hash` query paramter missing.'); }

Even though you've called res.send(), normal Javascript flow control still applies and the rest of your function will continue to execute, causing you to attempt to send another response which is the source of the "headers already sent" warning.

So, add a return to each one of these if statements to stop the function from further execution after sending the response.

if (req.query.username === undefined) { 
    res.status(400).send('`username` query parameter missing.'); 
    return;
}
if (req.query.hash === undefined) { 
    res.status(400).send('`hash` query parameter missing.'); 
    return;
}

I'd also suggest you modify these if statements slightly to include more conditions like empty strings:

if (!req.query.username) { 
    res.status(400).send('`username` query parameter missing.'); 
    return;
}
if (!req.query.hash) { 
    res.status(400).send('`hash` query parameter missing.'); 
    return;
}

FYI, also fix the spelling of "parameter".

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • It seems like it is just-in-case for me.(Yes, it is needed as you told, Thank you :) ) But weird thing is that, when that codes are executed, only res.json(...) is executed. Others cannot executed if there are no problem. Is it predicted warning? Why those warnings are popped out even only single response functions are executed? – Gipyo.Choi Jan 20 '20 at 07:37
  • @Gipyo.Choi - Those warnings are NOT present until your code attempts to send a second response (only the first response is actually send, the second is blocked). The second response could even be in higher level code that calls this code. You don't show the actual request handler to know for sure about that. We would have to be able to debug your code to see for sure which one it was. The examples I point out in my answer are situations that can cause this error (bugs in your code you need to fix). You haven't disclosed enough detail to know if these two bugs are all there are. – jfriend00 Jan 20 '20 at 07:40
  • Hmm yes, it can be. I think I need to check middlewares. One more question, is it possible to say `return next()` in that phrase? If it is, what happened? – Gipyo.Choi Jan 20 '20 at 07:46
  • @Gipyo.Choi - `return next()` tells Express to continue look at other routes. You should NOT do that if you have already sent a response. When you send a response, you are done. You don't want any more routing. You should just do `return;`. – jfriend00 Jan 20 '20 at 08:20
  • Aprreciate for detailed answer. Thank you :) – Gipyo.Choi Jan 20 '20 at 15:08