• Jump To … +
    index.js server.js socket.js transport-base-transport.js transport-http-base-transport.js transport-http-longpoll-transport.js transport-http-server.js transport-http-stream-transport.js transport-websocket-server.js transport-websocket-transport.js
  • transport-http-longpoll-transport.js

  • ¶
    /*
     * Cettia
     * http://cettia.io/projects/cettia-protocol/
     * 
     * Copyright 2017 the original author or authors.
     * Licensed under the Apache License, Version 2.0
     * http://www.apache.org/licenses/LICENSE-2.0
     */
    var events = require("events");
    var url = require("url");
    var http = require("http");
    var createHttpBaseTransport = require("./transport-http-base-transport");
  • ¶

    This function is exposed to the module’s transport module’s createHttpLongpollTransport as a factory to create a HTTP long polling transport. In long polling, the client performs a HTTP persistent connection and the server ends the response with message. Then, the client receives it and performs a request again and again.

    module.exports = function(uri, options) {
      var urlObj = url.parse(uri, true);
  • ¶

    URI’s protocol should be either http or https and cettia-transport-name param should be longpoll.

      if ((urlObj.protocol === "http:" || urlObj.protocol === "https:")
          && urlObj.query["cettia-transport-name"] === "longpoll") {
  • ¶

    A transport object.

        var self = createHttpBaseTransport(uri, options);
  • ¶

    Any error on request-response should propagate to transport.

        function onerror(error) {
          self.emit("error", error);
        }
  • ¶

    If the underlying connection of request-response was closed, fires close event.

        function onclose() {
          self.emit("close");
        }
  • ¶

    The current holding request.

        var req;
        self.connect = function() {
  • ¶

    The following transport protocol headers should be added in the first request:

    • cettia-transport-when: open.
    • cettia-transport-version: the implemented long polling transport version.
    • cettia-transport-jsonp: true required in case of JSONP.
    • cettia-transport-callback: callback name required in case of JSONP.
          var reqOpts = url.parse(uri + "&cettia-transport-version=1.0&cettia-transport-when=open");
  • ¶

    Somehow it’s turned out that KeepAlive Agent is required on Node 4+.

          reqOpts.agent = new http.Agent({keepAlive: true});
  • ¶

    Performs a HTTP persistent connection through GET method.

          req = http.request(reqOpts)
          .on("error", onerror).on("close", onclose)
          .on("response", function(res) {
  • ¶

    If the response’s status code is not 200, fires an error and closes the connection.

            if (res.statusCode !== 200) {
              self.emit("error", new Error("protocol"));
              self.close();
              return;
            }
  • ¶

    Aggregates chunks to make a complete response body.

            var body = "";
            res.on("error", onerror).on("data", function(chunk) {
              body += chunk;
            })
            .on("end", function() {
  • ¶

    The response body of the open request contains a result of handshake. The handshake output is in the form of URI.

              var result = url.parse(body, true).query;
  • ¶

    A newly issued id for HTTP transport. It is used to identify which HTTP transport is associated with which HTTP exchange. Don’t confuse it with an id for socket, cettia-id.

              self.id = result["cettia-transport-id"];
  • ¶

    Before giving a user opportunity to handle transport through open event, polling must be started. Because, when a user closes the transport on open event, the active req is required.

              poll();
              self.emit("open");
  • ¶

    Then starts polling.

              function poll() {
  • ¶

    From the second request, cettia-transport-when param should be poll and cettia-transport-id param should be added.

                var reqOpts = url.parse(uri + "&cettia-transport-when=poll&cettia-transport-id=" +
                    encodeURIComponent(self.id));
  • ¶

    Somehow it’s turned out that KeepAlive Agent is required on Node 4+.

                reqOpts.agent = new http.Agent({keepAlive: true});
                req = http.request(reqOpts)
                .on("error", onerror).on("close", onclose)
  • ¶

    Reads the response body.

                .on("response", function(res) {
  • ¶

    If the response’s status code is not 200, fires an error and closes the connection.

                  if (res.statusCode !== 200) {
                    self.emit("error", new Error("protocol"));
                    self.close();
                    return;
                  }
    
                  var chunks = [];
                  res.on("error", onerror).on("data", function(chunk) {
                    chunks.push(chunk);
                  })
                  .on("end", function() {
                    if (chunks.length) {
  • ¶

    The complete body in the form of binary.

                      var body = Buffer.concat(chunks);
  • ¶

    Starts a poll request again before to fire text event. There must be no idle time between polling.

                      poll();
  • ¶

    Makes the content-type header lowercase and verifies it. Only when the body exists, verification is valid.

                      switch ((res.headers["content-type"] || "").toLowerCase()) {
  • ¶

    A list of allowed content-type headers for text message.

                        case "text/plain; charset=utf-8":
                        case "text/plain; charset=utf8":
                        case "text/plain;charset=utf-8":
                        case "text/plain;charset=utf8":
  • ¶

    Fires text event by decoding the body with utf-8.

                          self.emit("text", body.toString("utf-8"));
                          break;
  • ¶

    An allowed content-type header for binary message.

                        case "application/octet-stream":
  • ¶

    Fires binary event with the body.

                          self.emit("binary", body);
                          break;
                        default:
  • ¶

    If the content-type header is invalid, fires an error and closes the connection.

                          self.emit("error", new Error("protocol"));
                          self.close();
                          break;
                      }
  • ¶

    Empty body indicates the server closed the socket. Accordingly fires the close event.

                    } else {
                      self.emit("close");
                    }
                  });
                });
                req.end();
              }
            });
          });
          req.end();
        };
        self.abort = function() {
  • ¶

    Node.js fires a ‘socket hang up’ error if there was no response from the server by default. But, that is a normal case of close in long polling, hence removes all default error handlers.

          req.removeAllListeners("error");
  • ¶

    To fire close event, registers error event handler to req. The above response event handler is called only when response is returned from the server so that close event is called only once.

          req.on("error", function() {
            self.emit("close");
          });
          req.abort();
        };
        return self;
      }
    };