0

Currently building a microservice application. The communication between each container is via web sockets and the gateway is a REST gateway.

I want to be able to use emit from within a post request, which is working fine as of right now. But when the the socket is to handle a on event and then reply back via the gateway using a express response object, that object is not closed with a response.json call. The object is still alive inside another on event inside the same route, which then makes the new request generate the error, headers cannot be set after they are send. This makes sense, but how do I ensure that the new request, gets a new response object ?

First request
gateway -> (ws) -> microserver -> (ws) -> gateway -> (http) user
Second request
gateway -> (ws) -> microserver -> (ws) -> gateway -> ERROR

Gateway snippet:

app.post("/api/user/create", function (req, res) {
  console.log("Create request");
  let token = req.get("Authentication");
  let userData = req.body;
  ioClientUsers.emit("create", {
    "token": token,
    "userData": userData
  });
  ioClientUsers.once("user created", success => {
    console.log(res.headersSent);
    if (success) {
      return res.json({
        "status": "created"
      }); 
    } else {
      return res.json({
        "status": "not created"
      });
    }
  });
  ioClientUsers.once("user not created", () => {
    console.log(res.headersSent);
    res.json({
      "status": "not created"
    });
  });
});

Microservice snippet

 socket.on("create", createObj => {
 console.log(createObj);
 userController.createUser(createObj.token, createObj.userData, (err, success) => {
   console.log(err, success);
   if (err) {
     socket.emit("user not created");
   } else {
     socket.emit("user created", success);
   }
 });
});

EDIT Just posting again, so that if anybody else has the same problem, either the one answer to this or this edit solves the problem:

 app.post("/api/user/create", function (req, res) {
  let token = req.get("Authentication");
  let userData = req.body;
  ioClientUsers.emit("create", {
    "token": token,
    "userData": userData
  });
  ioClientUsers.once("user created", userCreated);
  ioClientUsers.once("user not created", userNotCreated);

  function userCreated(success){
    if (success) {
      res.json({
        "status": "created"
      }); 
    } else {
      res.json({
        "status": "not created"
      });
    }  
    rmListner();
  }
  function userNotCreated(){
    console.log(res.headersSent);
    res.json({
      "status": "not created"
    });
    rmListner();
  }

  function rmListener(){
    ioClientUsers.removeListener("user created", userCreated);
    ioClientUsers.removeListener("user not created", userNotCreated);
  }
});
Lasse
  • 597
  • 2
  • 10
  • 33
  • You need to remove your listeners after you call `res.json`. And you might want some way of making sure that your `socket.emit` calls specify, somewhere somehow, something that can tell the `ioClientUsers.once` callbacks that *this callback is for you*, if you know what I mean. – TKoL Apr 23 '18 at 13:04
  • Yeah I see where you are going with this. The problem is tho, that I don't think there is a way to "unsubscribe" or remove the instance of the callback. At least that's something I have to research. – Lasse Apr 23 '18 at 13:07
  • There is a way to unsubscribe with socket.io. https://stackoverflow.com/questions/23092624/socket-io-removing-specific-listener -- you'll have to redesign your functions so you can remove them after any `res.json` is called – TKoL Apr 23 '18 at 13:12
  • Hmm, yes that's properly the way to do it. The next problem is that `res.json` is returning out of the function, which makes it impossible to exec the function after... – Lasse Apr 23 '18 at 13:28
  • Just be aware, still, that if 'app.post' is called a second time before the first 'app.post' is done, whle it's still waiting for 'user created' or 'user not created', then **the first time user created callback happens** will (if I'm reading this correctly) hit the callback for both the first and second `app.post`s, which is why i recommended you come up with a way to distinguish which callback is being run. – TKoL Apr 23 '18 at 15:14
  • Ahh, I see. Hmm, have to test if it will work if i pass the response object to the socket, and then use that object again in the answer. – Lasse Apr 24 '18 at 05:28

1 Answers1

1

I'd do something like this

app.post("/api/user/create", function (req, res) {
  console.log("Create request");
  let token = req.get("Authentication");
  let userData = req.body;
  ioClientUsers.emit("create", {
    "token": token,
    "userData": userData
  });

  function createdCallback(success) {
    if (success) {
      sendStatus('created')
    } else {
      sendStatus('not created')
    }
  }

  function notCreatedCallback() {
    sendStatus('not created')
  }

  function sendStatus(status) {
    console.log(res.headersSent);
    // UNSUBSCRIBE YOUR LISTENERS HERE
    ioClientUsers.removeListener("user created", createdCallback);
    ioClientUsers.removeListener("user not created", notCreatedCallback);
    res.json({'status': status})
  }

  ioClientUsers.once("user created", createdCallback);
  ioClientUsers.once("user not created", notCreatedCallback);
});

The socket listeners are all removed before res.json is called. See if that works.

TKoL
  • 13,158
  • 3
  • 39
  • 73