Error with HTTP header when submit twice the same request

时间:2018-09-18 20:08:28

标签: node.js express mysqljs

Hi there I'm doing a very simple API using NodeJS, ExpressJS and MySQLJs. I have a little form that make the request login request to the server. The problem I have is when I'm trying to do the same request twice after the first has been finished, the server answer me with a ERR_CONNECTION_REFUSED

I was using this tutorial in order to make my code a little bit cleaner. I've changed a little because I think class makes the code cleaner than using functions.

https://code.tutsplus.com/tutorials/managing-the-asynchronous-nature-of-nodejs--net-36183

This is the full backtrace of the error I have

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:470:11)
    at User.<anonymous> (/home/ismael/Projects/internal_project/app.js:84:9)
    at User.emit (events.js:187:15)
    at Query.<anonymous> (/home/ismael/Projects/internal_project/model/user.js:20:14)
    at Query.<anonymous> (/home/ismael/Projects/internal_project/node_modules/mysql/lib/Connection.js:502:10)
    at Query._callback (/home/ismael/Projects/internal_project/node_modules/mysql/lib/Connection.js:468:16)
    at Query.Sequence.end (/home/ismael/Projects/internal_project/node_modules/mysql/lib/protocol/sequences/Sequence.js:83:24)
    at Query._handleFinalResultPacket (/home/ismael/Projects/internal_project/node_modules/mysql/lib/protocol/sequences/Query.js:139:8)
    at Query.EofPacket (/home/ismael/Projects/internal_project/node_modules/mysql/lib/protocol/sequences/Query.js:123:8)
    at Protocol._parsePacket (/home/ismael/Projects/internal_project/node_modules/mysql/lib/protocol/Protocol.js:278:23)

This is my main javascript where I define the main structure of my simple application. In the log I can see that the second request is enter in success callback, what am I missing?

app.post( '/login/', function(req, res){
  user.on('success', function(result){
    logger.debug( '[SUCCESS]' );

    res.cookie('user', { name : result.name, id : result.id  });
    res.setHeader( 'Content-Type', 'application/json' );
    res.status(200).end( JSON.stringify({success : true}) );
  });
  user.on('failure', function(reason){
    logger.debug( '[FAILURE]' );

    res.setHeader( 'Content-Type', 'application/json' );
    res.status(400).send( JSON.stringify({success: false}) );
  });
  user.on('error', function(error){
    // logger.debug( '[ERROR]' );
    //
    // res.setHeader( 'Content-Type', 'application/json' );
    // res.status(500).send( JSON.stringify({ error: 'Internal error' }) );
  });
  user.login( req.body.username, req.body.password );
});

And this is the login method of the User class

login ( username, password ){
    var that = this;

    this.sql.query( "SELECT * FROM user WHERE name = ? AND password = ?", [ username, password ], function(error, result, fields){
      // if ( error ){
      //   that.emit('error', error);
      //   return true;
      // }

      if ( result.length == 0 ){
        that.emit( 'failure' );
        return true;
      }

      if ( result.length >= 1 ){
        that.emit( 'success', result[0] );

        return true;
      }

    });
  }

I'm using this Javascript in front side to get all the values from the login form and send it to the API.

main.js

(function(){
  var _loginForm = {},
      _loginUsername = {},
      _loginPassword = {},
      _loginConfig = {
        url : '/login/',
        method : 'POST'
      },
      _loginRequest = new XMLHttpRequest();

  var
  _onLoginRequest = function(){
    console.log( this );

    if ( this.readyState == 4 ){
      JSON.parse(this.responseText)
    }
  },
  _onLoginSubmit = function(evt){
    evt.preventDefault();

    _loginRequest.onreadystatechange = _onLoginRequest;
    _loginRequest.open( _loginConfig.method, _loginConfig.url, true );
    _loginRequest.setRequestHeader( 'Content-Type', 'application/json' );
    _loginRequest.send(JSON.stringify({
      username : _loginUsername.value,
      password : _loginPassword. value
    }));
  },
  _onDomLoad = function(evt){
    _loginForm = document.getElementById( 'login-form' );
    _loginUsername = document.getElementById( 'username' );
    _loginPassword = document.getElementById( 'password' );

    _loginForm.addEventListener( 'submit', _onLoginSubmit );
  };

  document.addEventListener( 'DOMContentLoaded', _onDomLoad );

})();

Thanks in advance.

2 个答案:

答案 0 :(得分:2)

You are registering event handlers for every request. The problem is, you're not removing any of the request handlers afterwards.

For example, if you did 10 POST requests to the login endpoint, and then you do the 11th. If the 11th's is successful, the success event is triggered for the current request but also all the other 10.

The reason you're getting Cannot set headers after they are sent to the client is because the event listener is being triggered for a HTTP request that already completed.

The solution is to architect this different. The EventListener is not a good model for calling functions and getting results. EventListener is good for things that can trigger many times. In this case you don't want that, you only want 1 success or 1 failure per request.

The right model for this is:

A) A simple function call. It can return information related to it's success and throw an exception if it failed.

B) Return promises. Resolve them if login was successful, Reject them if not.

C) Use async/await. Again: return the result if it was successful, throw an error if not.

D) Use the callback pattern that you're already using with express. So pass a callback with a err and result parameter.

The last option might be preferable because you're working with a framework that doesn't support Promises and async/await well.

答案 1 :(得分:1)

Create User object inside app.post api call instead of creating it globally.