lib/transport-http-server.js
function createLongpollTransport(req, res) {
  // The current response which can be used to send a message or close the connection. It's not
  // null when it's available and null when it's not available.
  var response;
  // A flag to mark this transport is aborted. It's used when `response` is not available, if
  // user closes the connection.
  var aborted;
  // A timer to prevent from being idle connection which may happen if the succeeding poll
  // request is not arrived.
  var closeTimer;
  // The parameters of the first request. That of subsequent requests are not used.
  var params = req.params;
  // A queue containing messages that the server couldn't send.
  var queue = [];
  // A transport object.
  var self = createBaseTransport(req, res);

  // In long polling, multiple HTTP exchanges are used to establish a channel for the server to
  // write message to client. This function will be called every time the client performs a request.
  self.refresh = function(req, res) {
    // Any error on request-response should propagate to transport.
    req.on("error", function(error) {
      self.emit("error", error);
    });
    res.on("error", function(error) {
      self.emit("error", error);
    });
    // When the underlying connection was terminated abnormally.
    res.on("close", function() {
      self.emit("close");
    });
    // When the complete response has been sent.
    res.on("finish", function() {
      // If this request was to `poll` and this response was ended through `end` not
      // `endedWithMessage`, it means it is the end of the transport. Hence, fires the `close`
      // event.
      if (req.params["cettia-transport-when"] === "poll" && !res.endedWithMessage) {
        self.emit("close");
        // Otherwise the client will issue `poll` request again. If not, this connection falls
        // into limbo. To prevent that, it sets a timer which fires `close` event unless the
        // client issues `poll` request in 3s.
      } else {
        closeTimer = setTimeout(function() {
          self.emit("close");
        }, 3000);
      }
    });
    // A custom property to show how it was ended through either `endWithMessage` for sending a
    // message or `end` for closing the connection. An ended response can't be used again.
    res.endedWithMessage = false;
    // A custom method to send a message through this response.
    res.endWithMessage = function(data) {
      // Flags the current response is ended with message.
      res.endedWithMessage = true;
      // `data` should be either a `Buffer` or a string.
      if (typeof data === "string") {
        // As for text message, the content-type header should be `text/javascript` for JSONP
        // and `text/plain` for the others.
        res.setHeader("content-type", "text/" +
            // If it's JSONP, `cettia-transport-jsonp` param is `true`.
          (params["cettia-transport-jsonp"] === "true" ? "javascript" : "plain") + "; charset=utf-8");
        // All the long polling transports have to finish the request after processing. The
        // `res`'s `finish` event will be fired after this.
        res.end(params["cettia-transport-jsonp"] === "true" ?
          // In case of JSONP, the response text is supposed to be a JavaScript code executing a
          // callback with data. The callback name is passed as the first request's
          // `cettia-transport-callback` param and the data to be returned have to be escaped to
          // a JavaScript string literal. For others, no formatting is needed. In any case,
          // data should be encoded in `utf-8`.
        params["cettia-transport-callback"] + "(" + JSON.stringify(data) + ");" : data, "utf-8");
      } else {
        // As for binary message, the content-type header should be `application/octet-stream`.
        // Old browsers depending on JSONP can't handle binary data so we don't need to consider
        // them here.
        res.setHeader("content-type", "application/octet-stream");
        res.end(data);
      }
    };
    switch (req.params["cettia-transport-when"]) {
      // The first request
      case "open":
        // `script` tag which is a host object used in old browsers to perform long polling
        // transport can't read HTTP response headers as well as write HTTP request headers. That's
        // why we uses the first response's body as a handshake output instead of HTTP headers. The
        // handshake result should be formatted in URI.
        res.endWithMessage(url.format({
          // Adds the following transport protocol headers:
          // * `cettia-transport-id`: an identifier.
          // * `cettia-transport-version`: the implemented long polling transport version.
          query: {
            "cettia-transport-version": "1.0",
            "cettia-transport-id": self.id
          }
        }));
        break;
      // The succeeding request after the first request
      case "poll":
        // Resets timer as new exchange is supplied.
        clearTimeout(closeTimer);
        // If aborted is `true` here, it means the user tried to close the connection but it
        // couldn't be done because `response` was null. So ends this incoming exchange. It will
        // fire `close` event through `res`'s `finish` event handler.
        if (aborted) {
          res.end();
        } else {
          // If the queue is not empty, it means there are remaining messages so that the server
          // should send them again.
          if (queue.length) {
            // Removes the first cached message from the queue and send it. FYI,
            // `[1,2,3].shift()` returns in `1` and results in `[2,3]`.
            res.endWithMessage(queue.shift());
          } else {
            // If not, assigns `res` to `response`.
            response = res;
          }
        }
        break;
      // It shouldn't happen.
      default:
        self.emit("error", new Error("protocol"));
        self.close();
        break;
    }
  };
  self.write = function(data) {
    // Only when the current response exists, it's possible to send a message.
    if (response) {
      // After `response.endWithMessage`, `response` can't be used again.
      var resp = response;
      response = null;
      resp.endWithMessage(data);
      // If not, the data will be cached and sent in next poll through `refresh` method.
    } else {
      queue.push(data);
    }
  };
  self.close = function() {
    // Only when the current response exists, it's possible to close the
    // connection.
    if (response) {
      // After `response.end`, `response` can't be used again.
      var resp = response;
      response = null;
      resp.end();
      // If not, a next poll request will be ended immediately by `aborted` flag through
      // `refresh` method.
    } else {
      aborted = true;
    }
    return this;
  };
  // Refreshes with the first exchange.
  self.refresh(req, res);
  return self;
}
function createLongpollTransport(req, res) {
  // The current response which can be used to send a message or close the connection. It's not
  // null when it's available and null when it's not available.
  var response;
  // A flag to mark this transport is aborted. It's used when `response` is not available, if
  // user closes the connection.
  var aborted;
  // A timer to prevent from being idle connection which may happen if the succeeding poll
  // request is not arrived.
  var closeTimer;
  // The parameters of the first request. That of subsequent requests are not used.
  var params = req.params;
  // A queue containing messages that the server couldn't send.
  var queue = [];
  // A transport object.
  var self = createBaseTransport(req, res);

  // In long polling, multiple HTTP exchanges are used to establish a channel for the server to
  // write message to client. This function will be called every time the client performs a request.
  self.refresh = function(req, res) {
    // Any error on request-response should propagate to transport.
    req.on("error", function(error) {
      self.emit("error", error);
    });
    res.on("error", function(error) {
      self.emit("error", error);
    });
    // When the underlying connection was terminated abnormally.
    res.on("close", function() {
      self.emit("close");
    });
    // When the complete response has been sent.
    res.on("finish", function() {
      // If this request was to `poll` and this response was ended through `end` not
      // `endedWithMessage`, it means it is the end of the transport. Hence, fires the `close`
      // event.
      if (req.params["cettia-transport-when"] === "poll" && !res.endedWithMessage) {
        self.emit("close");
        // Otherwise the client will issue `poll` request again. If not, this connection falls
        // into limbo. To prevent that, it sets a timer which fires `close` event unless the
        // client issues `poll` request in 3s.
      } else {
        closeTimer = setTimeout(function() {
          self.emit("close");
        }, 3000);
      }
    });
    // A custom property to show how it was ended through either `endWithMessage` for sending a
    // message or `end` for closing the connection. An ended response can't be used again.
    res.endedWithMessage = false;
    // A custom method to send a message through this response.
    res.endWithMessage = function(data) {
      // Flags the current response is ended with message.
      res.endedWithMessage = true;
      // `data` should be either a `Buffer` or a string.
      if (typeof data === "string") {
        // As for text message, the content-type header should be `text/javascript` for JSONP
        // and `text/plain` for the others.
        res.setHeader("content-type", "text/" +
            // If it's JSONP, `cettia-transport-jsonp` param is `true`.
          (params["cettia-transport-jsonp"] === "true" ? "javascript" : "plain") + "; charset=utf-8");
        // All the long polling transports have to finish the request after processing. The
        // `res`'s `finish` event will be fired after this.
        res.end(params["cettia-transport-jsonp"] === "true" ?
          // In case of JSONP, the response text is supposed to be a JavaScript code executing a
          // callback with data. The callback name is passed as the first request's
          // `cettia-transport-callback` param and the data to be returned have to be escaped to
          // a JavaScript string literal. For others, no formatting is needed. In any case,
          // data should be encoded in `utf-8`.
        params["cettia-transport-callback"] + "(" + JSON.stringify(data) + ");" : data, "utf-8");
      } else {
        // As for binary message, the content-type header should be `application/octet-stream`.
        // Old browsers depending on JSONP can't handle binary data so we don't need to consider
        // them here.
        res.setHeader("content-type", "application/octet-stream");
        res.end(data);
      }
    };
    switch (req.params["cettia-transport-when"]) {
      // The first request
      case "open":
        // `script` tag which is a host object used in old browsers to perform long polling
        // transport can't read HTTP response headers as well as write HTTP request headers. That's
        // why we uses the first response's body as a handshake output instead of HTTP headers. The
        // handshake result should be formatted in URI.
        res.endWithMessage(url.format({
          // Adds the following transport protocol headers:
          // * `cettia-transport-id`: an identifier.
          // * `cettia-transport-version`: the implemented long polling transport version.
          query: {
            "cettia-transport-version": "1.0",
            "cettia-transport-id": self.id
          }
        }));
        break;
      // The succeeding request after the first request
      case "poll":
        // Resets timer as new exchange is supplied.
        clearTimeout(closeTimer);
        // If aborted is `true` here, it means the user tried to close the connection but it
        // couldn't be done because `response` was null. So ends this incoming exchange. It will
        // fire `close` event through `res`'s `finish` event handler.
        if (aborted) {
          res.end();
        } else {
          // If the queue is not empty, it means there are remaining messages so that the server
          // should send them again.
          if (queue.length) {
            // Removes the first cached message from the queue and send it. FYI,
            // `[1,2,3].shift()` returns in `1` and results in `[2,3]`.
            res.endWithMessage(queue.shift());
          } else {
            // If not, assigns `res` to `response`.
            response = res;
          }
        }
        break;
      // It shouldn't happen.
      default:
        self.emit("error", new Error("protocol"));
        self.close();
        break;
    }
  };
  self.write = function(data) {
    // Only when the current response exists, it's possible to send a message.
    if (response) {
      // After `response.endWithMessage`, `response` can't be used again.
      var resp = response;
      response = null;
      resp.endWithMessage(data);
      // If not, the data will be cached and sent in next poll through `refresh` method.
    } else {
      queue.push(data);
    }
  };
  self.close = function() {
    // Only when the current response exists, it's possible to close the
    // connection.
    if (response) {
      // After `response.end`, `response` can't be used again.
      var resp = response;
      response = null;
      resp.end();
      // If not, a next poll request will be ended immediately by `aborted` flag through
      // `refresh` method.
    } else {
      aborted = true;
    }
    return this;
  };
  // Refreshes with the first exchange.
  self.refresh(req, res);
  return self;
}
function createLongpollTransport ( req , res ) { // The current response which can be used to send a message or close the connection. It's not // null when it's available and null when it's not available. var response ; // A flag to mark this transport is aborted. It's used when `response` is not available, if // user closes the connection. var aborted ; // A timer to prevent from being idle connection which may happen if the succeeding poll // request is not arrived. var closeTimer ; // The parameters of the first request. That of subsequent requests are not used. var params = req . params ; // A queue containing messages that the server couldn't send. var queue = [ ] ; // A transport object. var self = createBaseTransport ( req , res ) ; // In long polling, multiple HTTP exchanges are used to establish a channel for the server to // write message to client. This function will be called every time the client performs a request. self . refresh = function ( req , res ) { // Any error on request-response should propagate to transport. req . on ( "error" , function ( error ) { self . emit ( "error" , error ) ; } ) ; res . on ( "error" , function ( error ) { self . emit ( "error" , error ) ; } ) ; // When the underlying connection was terminated abnormally. res . on ( "close" , function ( ) { self . emit ( "close" ) ; } ) ; // When the complete response has been sent. res . on ( "finish" , function ( ) { // If this request was to `poll` and this response was ended through `end` not // `endedWithMessage`, it means it is the end of the transport. Hence, fires the `close` // event. if ( req . params [ "cettia-transport-when" ] === "poll" && ! res . endedWithMessage ) { self . emit ( "close" ) ; // Otherwise the client will issue `poll` request again. If not, this connection falls // into limbo. To prevent that, it sets a timer which fires `close` event unless the // client issues `poll` request in 3s. } else { closeTimer = setTimeout ( function ( ) { self . emit ( "close" ) ; } , 3000 ) ; } } ) ; // A custom property to show how it was ended through either `endWithMessage` for sending a // message or `end` for closing the connection. An ended response can't be used again. res . endedWithMessage = false ; // A custom method to send a message through this response. res . endWithMessage = function ( data ) { // Flags the current response is ended with message. res . endedWithMessage = true ; // `data` should be either a `Buffer` or a string. if ( typeof data === "string" ) { // As for text message, the content-type header should be `text/javascript` for JSONP // and `text/plain` for the others. res . setHeader ( "content-type" , "text/" + // If it's JSONP, `cettia-transport-jsonp` param is `true`. ( params [ "cettia-transport-jsonp" ] === "true" ? "javascript" : "plain" ) + "; charset=utf-8" ) ; // All the long polling transports have to finish the request after processing. The // `res`'s `finish` event will be fired after this. res . end ( params [ "cettia-transport-jsonp" ] === "true" ? // In case of JSONP, the response text is supposed to be a JavaScript code executing a // callback with data. The callback name is passed as the first request's // `cettia-transport-callback` param and the data to be returned have to be escaped to // a JavaScript string literal. For others, no formatting is needed. In any case, // data should be encoded in `utf-8`. params [ "cettia-transport-callback" ] + "(" + JSON . stringify ( data ) + ");" : data , "utf-8" ) ; } else { // As for binary message, the content-type header should be `application/octet-stream`. // Old browsers depending on JSONP can't handle binary data so we don't need to consider // them here. res . setHeader ( "content-type" , "application/octet-stream" ) ; res . end ( data ) ; } } ; switch ( req . params [ "cettia-transport-when" ] ) { // The first request case "open" : // `script` tag which is a host object used in old browsers to perform long polling // transport can't read HTTP response headers as well as write HTTP request headers. That's // why we uses the first response's body as a handshake output instead of HTTP headers. The // handshake result should be formatted in URI. res . endWithMessage ( url . format ( { // Adds the following transport protocol headers: // * `cettia-transport-id`: an identifier. // * `cettia-transport-version`: the implemented long polling transport version. query : { "cettia-transport-version" : "1.0" , "cettia-transport-id" : self . id } } ) ) ; break ; // The succeeding request after the first request case "poll" : // Resets timer as new exchange is supplied. clearTimeout ( closeTimer ) ; // If aborted is `true` here, it means the user tried to close the connection but it // couldn't be done because `response` was null. So ends this incoming exchange. It will // fire `close` event through `res`'s `finish` event handler. if ( aborted ) { res . end ( ) ; } else { // If the queue is not empty, it means there are remaining messages so that the server // should send them again. if ( queue . length ) { // Removes the first cached message from the queue and send it. FYI, // `[1,2,3].shift()` returns in `1` and results in `[2,3]`. res . endWithMessage ( queue . shift ( ) ) ; } else { // If not, assigns `res` to `response`. response = res ; } } break ; // It shouldn't happen. default : self . emit ( "error" , new Error ( "protocol" ) ) ; self . close ( ) ; break ; } } ; self . write = function ( data ) { // Only when the current response exists, it's possible to send a message. if ( response ) { // After `response.endWithMessage`, `response` can't be used again. var resp = response ; response = null ; resp . endWithMessage ( data ) ; // If not, the data will be cached and sent in next poll through `refresh` method. } else { queue . push ( data ) ; } } ; self . close = function ( ) { // Only when the current response exists, it's possible to close the // connection. if ( response ) { // After `response.end`, `response` can't be used again. var resp = response ; response = null ; resp . end ( ) ; // If not, a next poll request will be ended immediately by `aborted` flag through // `refresh` method. } else { aborted = true ; } return this ; } ; // Refreshes with the first exchange. self . refresh ( req , res ) ; return self ; }
The client performs a HTTP persistent connection and the server ends the response with data. Then, the client receives it and performs a request again and again.
The client performs a HTTP persistent connection and the server ends the response with data . Then the client receives it and performs a request again and again .