f69a2b2016-09-19Stephen R. van den Berg /*
922d162016-09-23Stephen R. van den Berg  * Clean-room Engine.IO implementation for Pike.
f69a2b2016-09-19Stephen R. van den Berg  */
922d162016-09-23Stephen R. van den Berg //! This is an implementation of the Engine.IO server-side communication's //! driver. It basically is a real-time bidirectional packet-oriented //! communication's protocol for communicating between a webbrowser //! and a server. //!
d6f2472016-10-03Pontus Östlund //! The driver mainly is a wrapper around @[Protocols.WebSocket] with
922d162016-09-23Stephen R. van den Berg //! the addition of two fallback mechanisms that work around limitations //! imposed by firewalls and/or older browsers that prevent native
d6f2472016-10-03Pontus Östlund //! @[Protocols.WebSocket] connections from functioning.
922d162016-09-23Stephen R. van den Berg //! //! This module supports the following features: //! @ul //! @item It supports both UTF-8 and binary packets.
d6f2472016-10-03Pontus Östlund //! @item If both sides support @[Protocols.WebSocket], then
922d162016-09-23Stephen R. van den Berg //! packet sizes are essentially unlimited. //! The fallback methods have a limit on packet sizes from browser //! to server, determined by the maximum POST request size the involved //! network components allow. //! @endul //! //! In most cases, Engine.IO is not used directly in applications. Instead //! one uses Socket.IO instead. //! //! @seealso
d6f2472016-10-03Pontus Östlund //! @[Protocols.SocketIO], @[Protocols.WebSocket],
922d162016-09-23Stephen R. van den Berg //! @url{http://github.com/socketio/engine.io-protocol@}, //! @url{http://socket.io/@}
f69a2b2016-09-19Stephen R. van den Berg #pike __REAL_VERSION__
20859d2016-09-20Stephen R. van den Berg //#define EIO_DEBUG 1 //#define EIO_DEBUGMORE 1
f69a2b2016-09-19Stephen R. van den Berg 
20859d2016-09-20Stephen R. van den Berg //#define EIO_STATS 1 // Collect extra usage statistics
f69a2b2016-09-19Stephen R. van den Berg  #define DRIVERNAME "Engine.IO" #define DERROR(msg ...) ({sprintf(msg),backtrace()}) #define SERROR(msg ...) (sprintf(msg)) #define USERERROR(msg) throw(msg) #define SUSERERROR(msg ...) USERERROR(SERROR(msg))
d75c0e2016-09-22Stephen R. van den Berg #define DUSERERROR(msg ...) USERERROR(DERROR(msg))
f69a2b2016-09-19Stephen R. van den Berg  #ifdef EIO_DEBUG #define PD(X ...) werror(X)
d75c0e2016-09-22Stephen R. van den Berg #define PDT(X ...) (werror(X), \ werror(describe_backtrace(PT(backtrace()))))
f69a2b2016-09-19Stephen R. van den Berg  // PT() puts this in the backtrace #define PT(X ...) (lambda(object _this){return (X);}(this)) #else #undef EIO_DEBUGMORE #define PD(X ...) 0
d75c0e2016-09-22Stephen R. van den Berg #define PDT(X ...) 0
f69a2b2016-09-19Stephen R. van den Berg #define PT(X ...) (X) #endif
20859d2016-09-20Stephen R. van den Berg #define SIDBYTES 16 #define TIMEBYTES 6
f69a2b2016-09-19Stephen R. van den Berg  //! Global options for all EngineIO instances.
922d162016-09-23Stephen R. van den Berg //! //! @seealso
2ef9492016-09-25Stephen R. van den Berg //! @[Socket.create()], @[Gz.compress()]
f69a2b2016-09-19Stephen R. van den Berg final mapping options = ([ "pingTimeout": 64*1000, // Safe for NAT "pingInterval": 29*1000, // Allows for jitter
2ef9492016-09-25Stephen R. van den Berg #if constant(Gz.deflate)
79c5572016-09-29Stephen R. van den Berg  "compressionLevel": 3, // Setting to zero disables.
2ef9492016-09-25Stephen R. van den Berg  "compressionStrategy": Gz.DEFAULT_STRATEGY, "compressionThreshold": 256, // Compress when size>= "compressionWindowSize": 15, // LZ77 window 2^x (8..15)
79c5572016-09-29Stephen R. van den Berg  "compressionHeuristics": .WebSocket.HEURISTICS_COMPRESS,
2ef9492016-09-25Stephen R. van den Berg #endif
79c5572016-09-29Stephen R. van den Berg  "allowUpgrades": 1,
f69a2b2016-09-19Stephen R. van den Berg ]);
922d162016-09-23Stephen R. van den Berg //! Engine.IO protocol version.
20859d2016-09-20Stephen R. van den Berg constant protocol = 3; // EIO Protocol version
f69a2b2016-09-19Stephen R. van den Berg  private enum { // 0 1 2 3 4 5 6 BASE64='b', OPEN='0', CLOSE, PING, PONG, MESSAGE, UPGRADE, NOOP, SENDQEMPTY, FORCECLOSE }; // All EngineIO sessions indexed on sid.
5836402016-09-22Stephen R. van den Berg private mapping(string:Socket) clients = ([]);
f69a2b2016-09-19Stephen R. van den Berg  private Regexp acceptgzip = Regexp("(^|,)gzip(,|$)"); private Regexp xxsua = Regexp(";MSIE|Trident/");
2ef8e72016-09-26Stephen R. van den Berg //! @param options
922d162016-09-23Stephen R. van den Berg //! Optional options to override the defaults. //! This parameter is passed down as is to the underlying //! @[Socket]. //!
f69a2b2016-09-19Stephen R. van den Berg //! @example //! Sample minimal implementation of an EngineIO server farm:
d6f2472016-10-03Pontus Östlund //! @code //! void echo(mixed id, string|Stdio.Buffer msg) { //! id->write(msg); //! }
f69a2b2016-09-19Stephen R. van den Berg //!
d6f2472016-10-03Pontus Östlund //! void wsrequest(array(string) protocols, object req) { //! httprequest(req); //! }
f69a2b2016-09-19Stephen R. van den Berg //!
d6f2472016-10-03Pontus Östlund //! void httprequest(object req) //! { switch (req.not_query) //! { case "/engine.io/": //! Protocols.EngineIO.Socket client = Protocols.EngineIO.farm(req); //! if (client) { //! client.set_callbacks(echo); //! client.write("Hello world!"); //! } //! break; //! } //! }
f69a2b2016-09-19Stephen R. van den Berg //!
d6f2472016-10-03Pontus Östlund //! int main(int argc, array(string) argv) //! { Protocols.WebSocket.Port(httprequest, wsrequest, 80); //! return -1; //! } //! @endcode
922d162016-09-23Stephen R. van den Berg //! //! @seealso //! @[Socket.create()]
2ef8e72016-09-26Stephen R. van den Berg final Socket farm(Protocols.WebSocket.Request req, void|mapping options) {
f69a2b2016-09-19Stephen R. van den Berg  string sid; PD("Request %O\n", req.query); if (sid = req.variables.sid) {
5836402016-09-22Stephen R. van den Berg  Socket client;
f69a2b2016-09-19Stephen R. van den Berg  if (!(client = clients[sid]))
d75c0e2016-09-22Stephen R. van den Berg  req.response_and_finish((["data":"Unknown sid",
f69a2b2016-09-19Stephen R. van den Berg  "error":Protocols.HTTP.HTTP_GONE])); else
d75c0e2016-09-22Stephen R. van den Berg  client.onrequest(req);
f69a2b2016-09-19Stephen R. van den Berg  } else
2ef8e72016-09-26Stephen R. van den Berg  return Socket(req, options);
f69a2b2016-09-19Stephen R. van den Berg  return 0; }
922d162016-09-23Stephen R. van den Berg //! Runs a single Engine.IO session. class Socket {
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  //! Contains the last request seen on this connection. //! Can be used to obtain cookies etc. final Protocols.WebSocket.Request request;
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  //! The unique session identifier (in the Engine.IO docs referred //! to as simply: id). final string sid;
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  private mixed id; // This is the callback parameter
2ef8e72016-09-26Stephen R. van den Berg  final mapping _options;
922d162016-09-23Stephen R. van den Berg  private Stdio.Buffer ci = Stdio.Buffer(); private function(mixed, string|Stdio.Buffer:void) read_cb; private function(mixed:void) close_cb; private Thread.Queue sendq = Thread.Queue(); private ADT.Queue recvq = ADT.Queue(); private string curtransport; private Transport conn; private Transport upgtransport; private enum {RUNNING = 0, PAUSED, SCLOSING, RCLOSING}; private int state = RUNNING;
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  class Transport { final function(int, void|string|Stdio.Buffer:void) read_cb; final protected int pingtimeout;
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  protected void create(Protocols.WebSocket.Request req) {
2ef8e72016-09-26Stephen R. van den Berg  pingtimeout = _options->pingTimeout/1000+1;
922d162016-09-23Stephen R. van den Berg  kickwatchdog(); }
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  private void droptimeout() { remove_call_out(close); }
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  protected void destroy() { droptimeout(); }
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  final protected void kickwatchdog() { droptimeout(); call_out(close, pingtimeout); } //! Close the transport.
7cac5c2016-09-24Stephen R. van den Berg  void close() {
922d162016-09-23Stephen R. van den Berg  droptimeout(); read_cb(FORCECLOSE); } final protected void sendqempty() { if (!sendq.size()) read_cb(SENDQEMPTY); }
f69a2b2016-09-19Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  class Polling {
a5bde72016-09-27Stephen R. van den Berg  inherit Transport;
922d162016-09-23Stephen R. van den Berg  private int forceascii; final protected Stdio.Buffer c = Stdio.Buffer(); final protected Stdio.Buffer ci = Stdio.Buffer(); final protected Protocols.WebSocket.Request req; private mapping headers = ([]); private Thread.Mutex getreq = Thread.Mutex(); private Thread.Mutex exclpost = Thread.Mutex();
2ef9492016-09-25Stephen R. van den Berg  #if constant(Gz.deflate)
922d162016-09-23Stephen R. van den Berg  private Gz.File gzfile; #endif protected string noop; protected void getbody(Protocols.WebSocket.Request _req);
79c5572016-09-29Stephen R. van den Berg  protected void wrapfinish(Protocols.WebSocket.Request req, string body, mapping(string:mixed) options);
922d162016-09-23Stephen R. van den Berg 
79c5572016-09-29Stephen R. van den Berg  protected void respfinish(Protocols.WebSocket.Request req, string body, int isbin, mapping(string:mixed) options) {
922d162016-09-23Stephen R. van den Berg  mapping|string comprheads;
79c5572016-09-29Stephen R. van den Berg  if (!sizeof(body)) body = noop; // Engine.IO does not like empty frames
2ef9492016-09-25Stephen R. van den Berg  #if constant(Gz.deflate)
79c5572016-09-29Stephen R. van den Berg  if (gzfile && options->compressionLevel>0 && sizeof(body) >= options->compressionThreshold
922d162016-09-23Stephen R. van den Berg  && (comprheads = req.request_headers["accept-encoding"]) && acceptgzip.match(comprheads)) { Stdio.FakeFile f = Stdio.FakeFile("", "wb"); gzfile.open(f, "wb");
79c5572016-09-29Stephen R. van den Berg  gzfile.setparams(options->compressionLevel, options->compressionStrategy, options->compressionWindowSize);
922d162016-09-23Stephen R. van den Berg  gzfile.write(body); gzfile.close(); comprheads = headers; if (sizeof(body)>f.stat().size) { body = (string)f; comprheads += (["Content-Encoding":"gzip"]); } } else #endif comprheads = headers; req.response_and_finish(([ "data":body,
79c5572016-09-29Stephen R. van den Berg  "type":isbin ? "application/octet-stream" : "text/plain;charset=UTF-8",
922d162016-09-23Stephen R. van den Berg  "extra_heads":comprheads])); } protected void create(Protocols.WebSocket.Request _req) { noop = sprintf("1:%c", NOOP); forceascii = !zero_type(_req.variables->b64); ci->set_error_mode(1);
2ef9492016-09-25Stephen R. van den Berg  #if constant(Gz.deflate)
2ef8e72016-09-26Stephen R. van den Berg  if (_options->compressionLevel)
2ef9492016-09-25Stephen R. van den Berg  gzfile = Gz.File();
922d162016-09-23Stephen R. van den Berg  #endif
a5bde72016-09-27Stephen R. van den Berg  ::create(_req);
922d162016-09-23Stephen R. van den Berg  if (_req.request_headers.origin) { headers["Access-Control-Allow-Credentials"] = "true"; headers["Access-Control-Allow-Origin"] = _req.request_headers.origin; } else headers["Access-Control-Allow-Origin"] = "*"; // prevent XSS warnings on IE string ua = _req.request_headers["user-agent"]; if (ua && xxsua.match(ua)) headers["X-XSS-Protection"] = "0"; onrequest(_req); } final void onrequest(Protocols.WebSocket.Request _req) { kickwatchdog(); switch (_req.request_type) { case "GET": flush(); req = _req; flush(); break; case "OPTIONS": headers["Access-Control-Allow-Headers"] = "Content-Type"; _req.response_and_finish((["data":"ok","extra_heads":headers])); break; case "POST": { getbody(_req); // Strangely enough, EngineIO 3 does not communicate back // over the POST request, so wrap it up. function ackpost() { _req.response_and_finish((["data":"ok","type":"text/plain", "extra_heads":headers])); }; #if constant(Thread.Thread) // This requires _req to remain unaltered for the remainder of // this function. Thread.Thread(ackpost); // Lower latency by doing it parallel #else ackpost(); #endif do { Thread.MutexKey lock; if (lock = exclpost.trylock()) { int type; string|Stdio.Buffer res; Stdio.Buffer.RewindKey rewind; rewind = ci->rewind_on_error(); while (sizeof(ci)) { if (catch { int len, isbin; if ((isbin = ci->read_int8()) <= 1) { for (len = 0; (type = ci->read_int8())!=0xff; len=len*10+type); len--; type = ci->read_int8() + (isbin ? OPEN : 0); res = isbin ? ci->read_buffer(len): ci->read(len); } else { ci->unread(1); len = ci->sscanf("%d:")[0]-1;
2bc3972016-09-22Stephen R. van den Berg  type = ci->read_int8();
922d162016-09-23Stephen R. van den Berg  if (type == BASE64) { type = ci->read_int8(); res = Stdio.Buffer(MIME.decode_base64(ci->read(len))); } else res = ci->read(len); } }) break; else { rewind.update(); if (stringp(res)) res = utf8_to_string(res); read_cb(type, res); }
2bc3972016-09-22Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  lock = 0; } else break; } while (sizeof(ci)); break; } default: _req.response_and_finish((["data":"Unsupported method", "error":Protocols.HTTP.HTTP_METHOD_INVALID, "extra_heads":(["Allow":"GET,POST,OPTIONS"])]));
f69a2b2016-09-19Stephen R. van den Berg  } }
922d162016-09-23Stephen R. van den Berg  constant forcebinary = 0; final void flush(void|int type, void|string|Stdio.Buffer msg) { Thread.MutexKey lock; if (req && sendq.size() && (lock = getreq.trylock())) { Protocols.WebSocket.Request myreq; array tosend; if ((myreq = req) && sizeof(tosend = sendq.try_read_array())) { req = 0; lock = 0; array m; int anybinary = 0;
79c5572016-09-29Stephen R. van den Berg  mapping(string:mixed) options = _options;
922d162016-09-23Stephen R. van den Berg  if (forcebinary && !forceascii) foreach (tosend;; m)
79c5572016-09-29Stephen R. van den Berg  if (sizeof(m)>=2 && !stringp(m[1])) {
922d162016-09-23Stephen R. van den Berg  anybinary = 1; break; } foreach (tosend;; m) { type = m[0];
79c5572016-09-29Stephen R. van den Berg  msg = ""; switch(sizeof(m)) { case 3: if (m[2]) options += m[2]; case 2: msg = m[1] || ""; }
922d162016-09-23Stephen R. van den Berg  if (!anybinary && stringp(msg)) { if (String.width(msg) > 8) msg = string_to_utf8(msg); c->add((string)(1+sizeof(msg)))->add_int8(':'); } else if (!forceascii) { if (stringp(msg)) c->add_int8(0); else { type -= OPEN; c->add_int8(1); } foreach ((string)(1+sizeof(msg));; int i) c->add_int8(i-'0'); c->add_int8(0xff); } else { msg = MIME.encode_base64(msg->read(), 1); c->add((string)(1+1+sizeof(msg))) ->add_int8(':')->add_int8(BASE64);
44d5b32016-09-22Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  c->add_int8(type)->add(msg);
44d5b32016-09-22Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  if (sizeof(c))
79c5572016-09-29Stephen R. van den Berg  wrapfinish(myreq, c->read(), options);
922d162016-09-23Stephen R. van den Berg  sendqempty(); } else lock = 0; }
2bc3972016-09-22Stephen R. van den Berg  }
f69a2b2016-09-19Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  class XHR {
a5bde72016-09-27Stephen R. van den Berg  inherit Polling;
922d162016-09-23Stephen R. van den Berg  constant forcebinary = 1;
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  final protected void getbody(Protocols.WebSocket.Request _req) { ci->add(_req.body_raw); }
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  final protected
79c5572016-09-29Stephen R. van den Berg  void wrapfinish(Protocols.WebSocket.Request req, string body, mapping(string:mixed) options) { respfinish(req, body, String.range(body)[1]==0xff, options);
922d162016-09-23Stephen R. van den Berg  }
f69a2b2016-09-19Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  class JSONP {
a5bde72016-09-27Stephen R. van den Berg  inherit Polling;
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  private string head;
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  protected void create(Protocols.WebSocket.Request req) {
a5bde72016-09-27Stephen R. van den Berg  ::create(req);
922d162016-09-23Stephen R. van den Berg  head = "___eio[" + (int)req.variables->j + "]("; noop = head+"\""+::noop+"\");"; }
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  final protected void getbody(Protocols.WebSocket.Request _req) { string d; if (d = _req.variables->d) // Reverse-map some braindead escape mechanisms ci->add(replace(d,({"\r\n","\\n"}),({"\r","\n"}))); }
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  final protected
79c5572016-09-29Stephen R. van den Berg  void wrapfinish(Protocols.WebSocket.Request req, string body, mapping(string:mixed) options) {
922d162016-09-23Stephen R. van den Berg  c->add(head)->add(Standards.JSON.encode(body))->add(");");
79c5572016-09-29Stephen R. van den Berg  respfinish(req, c->read(), 0, options);
922d162016-09-23Stephen R. van den Berg  }
f69a2b2016-09-19Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  class WebSocket {
a5bde72016-09-27Stephen R. van den Berg  inherit Transport;
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  private Protocols.WebSocket.Connection con; private Stdio.Buffer bb = Stdio.Buffer(); private String.Buffer sb = String.Buffer();
f69a2b2016-09-19Stephen R. van den Berg 
7cac5c2016-09-24Stephen R. van den Berg  final void close() { if (con) catch(con.close()); }
922d162016-09-23Stephen R. van den Berg  protected void create(Protocols.WebSocket.Request req, Protocols.WebSocket.Connection _con) { con = _con; con.onmessage = recv;
7cac5c2016-09-24Stephen R. van den Berg  con.onclose = ::close;
a5bde72016-09-27Stephen R. van den Berg  ::create(req);
922d162016-09-23Stephen R. van den Berg  }
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  final void flush(void|int type, void|string|Stdio.Buffer msg) {
79c5572016-09-29Stephen R. van den Berg  mapping(string:mixed) options;
922d162016-09-23Stephen R. van den Berg  void sendit() {
79c5572016-09-29Stephen R. van den Berg  .WebSocket.Frame f = stringp(msg) ? .WebSocket.Frame(.WebSocket.FRAME_TEXT, sprintf("%c%s", type, msg)) : .WebSocket.Frame(.WebSocket.FRAME_BINARY, sprintf("%c%s", type - OPEN, msg->read())); f.options += options; con.send(f);
922d162016-09-23Stephen R. van den Berg  };
79c5572016-09-29Stephen R. van den Berg  if (msg) { options = _options;
922d162016-09-23Stephen R. van den Berg  sendit();
79c5572016-09-29Stephen R. van den Berg  } else {
922d162016-09-23Stephen R. van den Berg  array tosend; while (sizeof(tosend = sendq.try_read_array())) { array m; foreach (tosend;; m) {
79c5572016-09-29Stephen R. van den Berg  options = _options;
922d162016-09-23Stephen R. van den Berg  type = m[0];
79c5572016-09-29Stephen R. van den Berg  msg = ""; switch(sizeof(m)) { case 3: if (m[2]) options += m[2]; case 2: msg = m[1] || ""; }
922d162016-09-23Stephen R. van den Berg  sendit(); }
44d5b32016-09-22Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  sendqempty();
2bc3972016-09-22Stephen R. van den Berg  } }
f69a2b2016-09-19Stephen R. van den Berg 
922d162016-09-23Stephen R. van den Berg  private void recv(Protocols.WebSocket.Frame f) { kickwatchdog(); switch (f.opcode) { case Protocols.WebSocket.FRAME_TEXT:
d75c0e2016-09-22Stephen R. van den Berg  sb.add(f.text);
922d162016-09-23Stephen R. van den Berg  break; case Protocols.WebSocket.FRAME_BINARY:
f69a2b2016-09-19Stephen R. van den Berg  bb->add(f.data);
922d162016-09-23Stephen R. van den Berg  break; case Protocols.WebSocket.FRAME_CONTINUATION: if (sizeof(sb)) sb.add(f.text); else bb->add(f.data); break; default: return;
f69a2b2016-09-19Stephen R. van den Berg  }
922d162016-09-23Stephen R. van den Berg  if (f.fin) if (sizeof(sb)) { string s = sb.get(); read_cb(s[0], s[1..]); } else { int type = bb->read_int8();
689cc12016-09-27Stephen R. van den Berg  read_cb(type + OPEN, bb->read_buffer(sizeof(bb)));
922d162016-09-23Stephen R. van den Berg  } }
f69a2b2016-09-19Stephen R. van den Berg  } //! Set initial argument on callbacks. final void set_id(mixed _id) { id = _id; }
5836402016-09-22Stephen R. van den Berg  //! Retrieve initial argument on callbacks. Defaults to the Socket
20859d2016-09-20Stephen R. van den Berg  //! object itself.
f69a2b2016-09-19Stephen R. van den Berg  final mixed query_id() {
20859d2016-09-20Stephen R. van den Berg  return id || this;
f69a2b2016-09-19Stephen R. van den Berg  }
20859d2016-09-20Stephen R. van den Berg  //! As long as the read callback has not been set, all received messages
f69a2b2016-09-19Stephen R. van den Berg  //! will be buffered.
20859d2016-09-20Stephen R. van den Berg  final void set_callbacks( void|function(mixed, string|Stdio.Buffer:void) _read_cb, void|function(mixed:void) _close_cb) { close_cb = _close_cb; // Set close callback first read_cb = _read_cb; // to avoid losing the close event
f69a2b2016-09-19Stephen R. van den Berg  flushrecvq(); }
922d162016-09-23Stephen R. van den Berg  //! Send text @[string] or binary @[Stdio.Buffer] messages.
79c5572016-09-29Stephen R. van den Berg  final void write(mapping(string:mixed) options, string|Stdio.Buffer ... msgs) {
f69a2b2016-09-19Stephen R. van den Berg  if (state >= SCLOSING)
d75c0e2016-09-22Stephen R. van den Berg  DUSERERROR("Socket already shutting down");
f69a2b2016-09-19Stephen R. van den Berg  foreach (msgs;; string|Stdio.Buffer msg) { PD("Queue %s %c:%O\n", sid, MESSAGE, (string)msg);
79c5572016-09-29Stephen R. van den Berg  sendq.write(({MESSAGE, msg, options}));
f69a2b2016-09-19Stephen R. van den Berg  } if (state == RUNNING)
44d5b32016-09-22Stephen R. van den Berg  flush();
f69a2b2016-09-19Stephen R. van den Berg  } private void send(int type, void|string|Stdio.Buffer msg) { PD("Queue %s %c:%O\n", sid, type, (string)(msg || ""));
79c5572016-09-29Stephen R. van den Berg  sendq.write(({type, msg}));
20859d2016-09-20Stephen R. van den Berg  switch (state) { case RUNNING: case SCLOSING:
44d5b32016-09-22Stephen R. van den Berg  flush();
20859d2016-09-20Stephen R. van den Berg  }
f69a2b2016-09-19Stephen R. van den Berg  }
44d5b32016-09-22Stephen R. van den Berg  private void flush() {
7cac5c2016-09-24Stephen R. van den Berg  if(catch(conn.flush())) {
bcd0792016-09-23Stephen R. van den Berg  catch(conn.close());
7cac5c2016-09-24Stephen R. van den Berg  if (upgtransport) catch(upgtransport.close()); }
44d5b32016-09-22Stephen R. van den Berg  }
f69a2b2016-09-19Stephen R. van den Berg  private void flushrecvq() {
d75c0e2016-09-22Stephen R. van den Berg  while (read_cb && !recvq.is_empty())
47c5bf2016-09-26Stephen R. van den Berg  read_cb(query_id(), recvq.get());
f69a2b2016-09-19Stephen R. van den Berg  }
20859d2016-09-20Stephen R. van den Berg  //! Close the socket signalling the other side. final void close() {
f69a2b2016-09-19Stephen R. van den Berg  if (state < SCLOSING) {
20859d2016-09-20Stephen R. van den Berg  if (close_cb) close_cb(query_id());
f69a2b2016-09-19Stephen R. van den Berg  PT("Send close, state %O\n", state); state = SCLOSING;
20859d2016-09-20Stephen R. van den Berg  catch(send(CLOSE));
f69a2b2016-09-19Stephen R. van den Berg  } }
20859d2016-09-20Stephen R. van den Berg  private void rclose() { close(); state = RCLOSING; m_delete(clients, sid); }
f69a2b2016-09-19Stephen R. van den Berg  private void clearcallback() { close_cb = 0; read_cb = 0; // Sort of a race, if multithreading
5836402016-09-22Stephen R. van den Berg  id = 0; // Delete all references to this Socket
b6ae972016-09-23Stephen R. van den Berg  upgtransport = conn = 0;
f69a2b2016-09-19Stephen R. van den Berg  }
20859d2016-09-20Stephen R. van den Berg  private void recv(int type, void|string|Stdio.Buffer msg) {
f69a2b2016-09-19Stephen R. van den Berg #ifndef EIO_DEBUGMORE if (type!=SENDQEMPTY) #endif PD("Received %s %c:%O\n", sid, type, (string)msg); switch (type) { default: // Protocol error or CLOSE
20859d2016-09-20Stephen R. van den Berg  close();
f69a2b2016-09-19Stephen R. van den Berg  break; case CLOSE:
20859d2016-09-20Stephen R. van den Berg  rclose();
2bc3972016-09-22Stephen R. van den Berg  if (!sendq.size())
f69a2b2016-09-19Stephen R. van den Berg  clearcallback(); break; case SENDQEMPTY: if (state == RCLOSING) clearcallback(); break; case FORCECLOSE:
20859d2016-09-20Stephen R. van den Berg  rclose();
f69a2b2016-09-19Stephen R. van den Berg  clearcallback(); break; case PING: send(PONG, msg); break; case MESSAGE:
d75c0e2016-09-22Stephen R. van den Berg  if (read_cb && recvq.is_empty())
20859d2016-09-20Stephen R. van den Berg  read_cb(query_id(), msg);
f69a2b2016-09-19Stephen R. van den Berg  else {
47c5bf2016-09-26Stephen R. van den Berg  recvq.put(msg);
f69a2b2016-09-19Stephen R. van den Berg  flushrecvq(); } break; } } private void upgrecv(int type, string|Stdio.Buffer msg) { switch (type) { default: // Protocol error or CLOSE upgtransport = 0; if (state == PAUSED) state = RUNNING;
5836402016-09-22Stephen R. van den Berg  if(conn)
44d5b32016-09-22Stephen R. van den Berg  flush();
f69a2b2016-09-19Stephen R. van den Berg  break; case PING: state = PAUSED;
2bc3972016-09-22Stephen R. van den Berg  if (!sendq.size())
f69a2b2016-09-19Stephen R. van den Berg  send(NOOP);
44d5b32016-09-22Stephen R. van den Berg  flush();
d75c0e2016-09-22Stephen R. van den Berg  upgtransport.flush(PONG, msg);
f69a2b2016-09-19Stephen R. van den Berg  break; case UPGRADE: {
d75c0e2016-09-22Stephen R. van den Berg  upgtransport.read_cb = recv;
5836402016-09-22Stephen R. van den Berg  conn = upgtransport;
f69a2b2016-09-19Stephen R. van den Berg  curtransport = "websocket"; if (state == PAUSED) state = RUNNING; upgtransport = 0;
44d5b32016-09-22Stephen R. van den Berg  flush();
f69a2b2016-09-19Stephen R. van den Berg  break; } } } protected void destroy() {
20859d2016-09-20Stephen R. van den Berg  close();
f69a2b2016-09-19Stephen R. van den Berg  }
2ef8e72016-09-26Stephen R. van den Berg  //! @param options
922d162016-09-23Stephen R. van den Berg  //! Optional options to override the defaults. //! @mapping //! @member int "pingTimeout" //! If, the connection is idle for longer than this, the connection
d6f2472016-10-03Pontus Östlund  //! is terminated, unit in @expr{ms@}.
922d162016-09-23Stephen R. van den Berg  //! @member int "pingInterval" //! The browser-client will send a small ping message every
d6f2472016-10-03Pontus Östlund  //! @expr{pingInterval ms@}.
922d162016-09-23Stephen R. van den Berg  //! @member int "allowUpgrades"
d6f2472016-10-03Pontus Östlund  //! When @expr{true@} (default), it allows the server to upgrade //! the connection to a real @[Protocols.WebSocket] connection.
922d162016-09-23Stephen R. van den Berg  //! @member int "compressionLevel" //! The gzip compressionlevel used to compress packets. //! @member int "compressionThreshold" //! Packets smaller than this will not be compressed. //! @endmapping protected void create(Protocols.WebSocket.Request req,
2ef8e72016-09-26Stephen R. van den Berg  void|mapping options) {
5836402016-09-22Stephen R. van den Berg  request = req;
2ef8e72016-09-26Stephen R. van den Berg  _options = .EngineIO.options; if (options && sizeof(options)) _options += options;
5836402016-09-22Stephen R. van den Berg  switch (curtransport = req.variables->transport) {
f69a2b2016-09-19Stephen R. van den Berg  default:
d75c0e2016-09-22Stephen R. van den Berg  req.response_and_finish((["data":"Unsupported transport",
f69a2b2016-09-19Stephen R. van den Berg  "error":Protocols.HTTP.HTTP_UNSUPP_MEDIA])); return; case "websocket":
5c68e82016-10-26Henrik Grubbström (Grubba)  conn = WebSocket(req, req.websocket_accept(0, UNDEFINED, _options));
f69a2b2016-09-19Stephen R. van den Berg  break; case "polling":
5836402016-09-22Stephen R. van den Berg  conn = req.variables.j ? JSONP(req) : XHR(req);
f69a2b2016-09-19Stephen R. van den Berg  break; }
5836402016-09-22Stephen R. van den Berg  conn.read_cb = recv;
f69a2b2016-09-19Stephen R. van den Berg  ci->add(Crypto.Random.random_string(SIDBYTES-TIMEBYTES)); ci->add_hint(gethrtime(), TIMEBYTES); sid = MIME.encode_base64(ci->read());
20859d2016-09-20Stephen R. van den Berg  clients[sid] = this;
f69a2b2016-09-19Stephen R. van den Berg  send(OPEN, Standards.JSON.encode( (["sid":sid, "upgrades":
2ef8e72016-09-26Stephen R. van den Berg  _options->allowUpgrades ? ({"websocket"}) : ({}), "pingInterval":_options->pingInterval, "pingTimeout":_options->pingTimeout
f69a2b2016-09-19Stephen R. van den Berg  ]))); PD("New EngineIO sid: %O\n", sid); }
5836402016-09-22Stephen R. van den Berg  //! Handle request, and returns a new Socket object if it's a new
f69a2b2016-09-19Stephen R. van den Berg  //! connection. final void onrequest(Protocols.WebSocket.Request req) { string s;
5836402016-09-22Stephen R. van den Berg  request = req;
f69a2b2016-09-19Stephen R. van den Berg  if ((s = req.variables->transport) == curtransport)
5836402016-09-22Stephen R. van den Berg  conn.onrequest(req);
f69a2b2016-09-19Stephen R. van den Berg  else switch (s) { default:
d75c0e2016-09-22Stephen R. van den Berg  req.response_and_finish((["data":"Invalid transport",
f69a2b2016-09-19Stephen R. van den Berg  "error":Protocols.HTTP.HTTP_UNSUPP_MEDIA])); return 0; case "websocket":
cbb6c02016-10-26Henrik Grubbström (Grubba)  upgtransport = WebSocket(req, req.websocket_accept(0, UNDEFINED, _options));
d75c0e2016-09-22Stephen R. van den Berg  upgtransport.read_cb = upgrecv;
f69a2b2016-09-19Stephen R. van den Berg  } } private string _sprintf(int type, void|mapping flags) { string res=UNDEFINED;
20859d2016-09-20Stephen R. van den Berg  switch (type) {
f69a2b2016-09-19Stephen R. van den Berg  case 'O':
20859d2016-09-20Stephen R. van den Berg  res = sprintf(DRIVERNAME"(%s.%d,%s,%d,%d,%d,%d)",
d75c0e2016-09-22Stephen R. van den Berg  sid, protocol, curtransport, state, sendq.size(), recvq.is_empty(),sizeof(clients));
f69a2b2016-09-19Stephen R. van den Berg  break; } return res; } }