4b99e12016-09-19Stephen R. van den Berg /*
3074172016-09-23Stephen R. van den Berg  * Clean-room Engine.IO implementation for Pike.
4b99e12016-09-19Stephen R. van den Berg  */
3074172016-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. //! //! The driver mainly is a wrapper around @[Protocol.WebSocket] with //! the addition of two fallback mechanisms that work around limitations //! imposed by firewalls and/or older browsers that prevent native //! @[Protocol.WebSocket] connections from functioning. //! //! This module supports the following features: //! @ul //! @item It supports both UTF-8 and binary packets. //! @item If both sides support @[Protocol.WebSocket], then //! 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 //! @[Protocol.SocketIO], @[Protocol.WebSocket], //! @url{http://github.com/socketio/engine.io-protocol@}, //! @url{http://socket.io/@}
4b99e12016-09-19Stephen R. van den Berg #pike __REAL_VERSION__
3ad2e42016-09-20Stephen R. van den Berg //#define EIO_DEBUG 1 //#define EIO_DEBUGMORE 1
4b99e12016-09-19Stephen R. van den Berg 
3ad2e42016-09-20Stephen R. van den Berg //#define EIO_STATS 1 // Collect extra usage statistics
4b99e12016-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))
85e9e12016-09-22Stephen R. van den Berg #define DUSERERROR(msg ...) USERERROR(DERROR(msg))
4b99e12016-09-19Stephen R. van den Berg  #ifdef EIO_DEBUG #define PD(X ...) werror(X)
85e9e12016-09-22Stephen R. van den Berg #define PDT(X ...) (werror(X), \ werror(describe_backtrace(PT(backtrace()))))
4b99e12016-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
85e9e12016-09-22Stephen R. van den Berg #define PDT(X ...) 0
4b99e12016-09-19Stephen R. van den Berg #define PT(X ...) (X) #endif
3ad2e42016-09-20Stephen R. van den Berg #define SIDBYTES 16 #define TIMEBYTES 6
4b99e12016-09-19Stephen R. van den Berg  //! Global options for all EngineIO instances.
3074172016-09-23Stephen R. van den Berg //! //! @seealso
f12f522016-09-25Stephen R. van den Berg //! @[Socket.create()], @[Gz.compress()]
4b99e12016-09-19Stephen R. van den Berg final mapping options = ([ "pingTimeout": 64*1000, // Safe for NAT "pingInterval": 29*1000, // Allows for jitter
f12f522016-09-25Stephen R. van den Berg #if constant(Gz.deflate) "compressionLevel": 1, "compressionStrategy": Gz.DEFAULT_STRATEGY, "compressionThreshold": 256, // Compress when size>= "compressionWindowSize": 15, // LZ77 window 2^x (8..15) #endif "allowUpgrades": 1
4b99e12016-09-19Stephen R. van den Berg ]);
3074172016-09-23Stephen R. van den Berg //! Engine.IO protocol version.
3ad2e42016-09-20Stephen R. van den Berg constant protocol = 3; // EIO Protocol version
4b99e12016-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.
a362df2016-09-22Stephen R. van den Berg private mapping(string:Socket) clients = ([]);
4b99e12016-09-19Stephen R. van den Berg  private Regexp acceptgzip = Regexp("(^|,)gzip(,|$)"); private Regexp xxsua = Regexp(";MSIE|Trident/");
920d092016-09-26Stephen R. van den Berg //! @param options
3074172016-09-23Stephen R. van den Berg //! Optional options to override the defaults. //! This parameter is passed down as is to the underlying //! @[Socket]. //!
4b99e12016-09-19Stephen R. van den Berg //! @example //! Sample minimal implementation of an EngineIO server farm: //! //!void echo(mixed id, string|Stdio.Buffer msg) { //! id->write(msg); //!} //! //!void wsrequest(array(string) protocols, object req) { //! httprequest(req); //!} //! //!void httprequest(object req)
3ad2e42016-09-20Stephen R. van den Berg //!{ switch (req.not_query)
4b99e12016-09-19Stephen R. van den Berg //! { case "/engine.io/":
a362df2016-09-22Stephen R. van den Berg //! Protocols.EngineIO.Socket client = Protocols.EngineIO.farm(req); //! if (client) { //! client.set_callbacks(echo); //! client.write("Hello world!");
4b99e12016-09-19Stephen R. van den Berg //! } //! break; //! } //!} //! //!int main(int argc, array(string) argv) //!{ Protocols.WebSocket.Port(httprequest, wsrequest, 80); //! return -1; //!}
3074172016-09-23Stephen R. van den Berg //! //! @seealso //! @[Socket.create()]
920d092016-09-26Stephen R. van den Berg final Socket farm(Protocols.WebSocket.Request req, void|mapping options) {
4b99e12016-09-19Stephen R. van den Berg  string sid; PD("Request %O\n", req.query); if (sid = req.variables.sid) {
a362df2016-09-22Stephen R. van den Berg  Socket client;
4b99e12016-09-19Stephen R. van den Berg  if (!(client = clients[sid]))
85e9e12016-09-22Stephen R. van den Berg  req.response_and_finish((["data":"Unknown sid",
4b99e12016-09-19Stephen R. van den Berg  "error":Protocols.HTTP.HTTP_GONE])); else
85e9e12016-09-22Stephen R. van den Berg  client.onrequest(req);
4b99e12016-09-19Stephen R. van den Berg  } else
920d092016-09-26Stephen R. van den Berg  return Socket(req, options);
4b99e12016-09-19Stephen R. van den Berg  return 0; }
3074172016-09-23Stephen R. van den Berg //! Runs a single Engine.IO session. class Socket {
4b99e12016-09-19Stephen R. van den Berg 
3074172016-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;
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  //! The unique session identifier (in the Engine.IO docs referred //! to as simply: id). final string sid;
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  private mixed id; // This is the callback parameter
920d092016-09-26Stephen R. van den Berg  final mapping _options;
3074172016-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;
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  class Transport { final function(int, void|string|Stdio.Buffer:void) read_cb; final protected int pingtimeout;
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  protected void create(Protocols.WebSocket.Request req) {
920d092016-09-26Stephen R. van den Berg  pingtimeout = _options->pingTimeout/1000+1;
3074172016-09-23Stephen R. van den Berg  kickwatchdog(); }
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  private void droptimeout() { remove_call_out(close); }
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  protected void destroy() { droptimeout(); }
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  final protected void kickwatchdog() { droptimeout(); call_out(close, pingtimeout); } //! Close the transport.
508f482016-09-24Stephen R. van den Berg  void close() {
3074172016-09-23Stephen R. van den Berg  droptimeout(); read_cb(FORCECLOSE); } final protected void sendqempty() { if (!sendq.size()) read_cb(SENDQEMPTY); }
4b99e12016-09-19Stephen R. van den Berg  }
3074172016-09-23Stephen R. van den Berg  class Polling { inherit Transport:t; 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();
f12f522016-09-25Stephen R. van den Berg  #if constant(Gz.deflate)
3074172016-09-23Stephen R. van den Berg  private Gz.File gzfile; #endif protected string noop; protected void getbody(Protocols.WebSocket.Request _req); protected void wrapfinish(Protocols.WebSocket.Request req, string body); protected void respfinish(Protocols.WebSocket.Request req, void|string body, void|string mimetype) { mapping|string comprheads; if (!body) body = noop;
f12f522016-09-25Stephen R. van den Berg  #if constant(Gz.deflate)
920d092016-09-26Stephen R. van den Berg  if (gzfile && sizeof(body) >= _options->compressionThreshold
3074172016-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");
920d092016-09-26Stephen R. van den Berg  gzfile.setparams(_options->compressionLevel, _options->compressionStrategy, _options->compressionWindowSize);
3074172016-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, "type":mimetype||"text/plain;charset=UTF-8", "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);
f12f522016-09-25Stephen R. van den Berg  #if constant(Gz.deflate)
920d092016-09-26Stephen R. van den Berg  if (_options->compressionLevel)
f12f522016-09-25Stephen R. van den Berg  gzfile = Gz.File();
3074172016-09-23Stephen R. van den Berg  #endif t::create(_req); 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;
a8ed1c2016-09-22Stephen R. van den Berg  type = ci->read_int8();
3074172016-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); }
a8ed1c2016-09-22Stephen R. van den Berg  }
3074172016-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"])]));
4b99e12016-09-19Stephen R. van den Berg  } }
3074172016-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; if (forcebinary && !forceascii) foreach (tosend;; m) if (!stringp(m[1])) { anybinary = 1; break; } foreach (tosend;; m) { type = m[0]; msg = m[1]; 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);
06210f2016-09-22Stephen R. van den Berg  }
3074172016-09-23Stephen R. van den Berg  c->add_int8(type)->add(msg);
06210f2016-09-22Stephen R. van den Berg  }
3074172016-09-23Stephen R. van den Berg  if (sizeof(c)) wrapfinish(myreq, c->read()); sendqempty(); } else lock = 0; }
a8ed1c2016-09-22Stephen R. van den Berg  }
4b99e12016-09-19Stephen R. van den Berg  }
3074172016-09-23Stephen R. van den Berg  class XHR { inherit Polling:p; constant forcebinary = 1;
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  final protected void getbody(Protocols.WebSocket.Request _req) { ci->add(_req.body_raw); }
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  final protected void wrapfinish(Protocols.WebSocket.Request req, string body) { respfinish(req, body, String.range(body)[1]==0xff ? "application/octet-stream" : 0); }
4b99e12016-09-19Stephen R. van den Berg  }
3074172016-09-23Stephen R. van den Berg  class JSONP { inherit Polling:p;
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  private string head;
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  protected void create(Protocols.WebSocket.Request req) { p::create(req); head = "___eio[" + (int)req.variables->j + "]("; noop = head+"\""+::noop+"\");"; }
4b99e12016-09-19Stephen R. van den Berg 
3074172016-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"}))); }
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  final protected void wrapfinish(Protocols.WebSocket.Request req, string body) { c->add(head)->add(Standards.JSON.encode(body))->add(");"); respfinish(req, c->read()); }
4b99e12016-09-19Stephen R. van den Berg  }
3074172016-09-23Stephen R. van den Berg  class WebSocket { inherit Transport:t;
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  private Protocols.WebSocket.Connection con; private Stdio.Buffer bb = Stdio.Buffer(); private String.Buffer sb = String.Buffer();
4b99e12016-09-19Stephen R. van den Berg 
508f482016-09-24Stephen R. van den Berg  final void close() { if (con) catch(con.close()); }
3074172016-09-23Stephen R. van den Berg  protected void create(Protocols.WebSocket.Request req, Protocols.WebSocket.Connection _con) { con = _con; con.onmessage = recv;
508f482016-09-24Stephen R. van den Berg  con.onclose = ::close;
3074172016-09-23Stephen R. van den Berg  t::create(req); }
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  final void flush(void|int type, void|string|Stdio.Buffer msg) { void sendit() { con.send_text(sprintf("%c%s",type,stringp(msg) ? msg : msg->read())); }; if (msg) sendit(); else { array tosend; while (sizeof(tosend = sendq.try_read_array())) { array m; foreach (tosend;; m) { type = m[0]; msg = m[1]; sendit(); }
06210f2016-09-22Stephen R. van den Berg  }
3074172016-09-23Stephen R. van den Berg  sendqempty();
a8ed1c2016-09-22Stephen R. van den Berg  } }
4b99e12016-09-19Stephen R. van den Berg 
3074172016-09-23Stephen R. van den Berg  private void recv(Protocols.WebSocket.Frame f) { kickwatchdog(); switch (f.opcode) { case Protocols.WebSocket.FRAME_TEXT:
85e9e12016-09-22Stephen R. van den Berg  sb.add(f.text);
3074172016-09-23Stephen R. van den Berg  break; case Protocols.WebSocket.FRAME_BINARY:
4b99e12016-09-19Stephen R. van den Berg  bb->add(f.data);
3074172016-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;
4b99e12016-09-19Stephen R. van den Berg  }
3074172016-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(); read_cb(type, bb->read_buffer(sizeof(bb))); } }
4b99e12016-09-19Stephen R. van den Berg  } //! Set initial argument on callbacks. final void set_id(mixed _id) { id = _id; }
a362df2016-09-22Stephen R. van den Berg  //! Retrieve initial argument on callbacks. Defaults to the Socket
3ad2e42016-09-20Stephen R. van den Berg  //! object itself.
4b99e12016-09-19Stephen R. van den Berg  final mixed query_id() {
3ad2e42016-09-20Stephen R. van den Berg  return id || this;
4b99e12016-09-19Stephen R. van den Berg  }
3ad2e42016-09-20Stephen R. van den Berg  //! As long as the read callback has not been set, all received messages
4b99e12016-09-19Stephen R. van den Berg  //! will be buffered.
3ad2e42016-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
4b99e12016-09-19Stephen R. van den Berg  flushrecvq(); }
3074172016-09-23Stephen R. van den Berg  //! Send text @[string] or binary @[Stdio.Buffer] messages.
4b99e12016-09-19Stephen R. van den Berg  final void write(string|Stdio.Buffer ... msgs) { if (state >= SCLOSING)
85e9e12016-09-22Stephen R. van den Berg  DUSERERROR("Socket already shutting down");
4b99e12016-09-19Stephen R. van den Berg  foreach (msgs;; string|Stdio.Buffer msg) { PD("Queue %s %c:%O\n", sid, MESSAGE, (string)msg);
85e9e12016-09-22Stephen R. van den Berg  sendq.write(({MESSAGE, msg}));
4b99e12016-09-19Stephen R. van den Berg  } if (state == RUNNING)
06210f2016-09-22Stephen R. van den Berg  flush();
4b99e12016-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 || ""));
85e9e12016-09-22Stephen R. van den Berg  sendq.write(({type, msg || ""}));
3ad2e42016-09-20Stephen R. van den Berg  switch (state) { case RUNNING: case SCLOSING:
06210f2016-09-22Stephen R. van den Berg  flush();
3ad2e42016-09-20Stephen R. van den Berg  }
4b99e12016-09-19Stephen R. van den Berg  }
06210f2016-09-22Stephen R. van den Berg  private void flush() {
508f482016-09-24Stephen R. van den Berg  if(catch(conn.flush())) {
0d1daa2016-09-23Stephen R. van den Berg  catch(conn.close());
508f482016-09-24Stephen R. van den Berg  if (upgtransport) catch(upgtransport.close()); }
06210f2016-09-22Stephen R. van den Berg  }
4b99e12016-09-19Stephen R. van den Berg  private void flushrecvq() {
85e9e12016-09-22Stephen R. van den Berg  while (read_cb && !recvq.is_empty())
d325422016-09-26Stephen R. van den Berg  read_cb(query_id(), recvq.get());
4b99e12016-09-19Stephen R. van den Berg  }
3ad2e42016-09-20Stephen R. van den Berg  //! Close the socket signalling the other side. final void close() {
4b99e12016-09-19Stephen R. van den Berg  if (state < SCLOSING) {
3ad2e42016-09-20Stephen R. van den Berg  if (close_cb) close_cb(query_id());
4b99e12016-09-19Stephen R. van den Berg  PT("Send close, state %O\n", state); state = SCLOSING;
3ad2e42016-09-20Stephen R. van den Berg  catch(send(CLOSE));
4b99e12016-09-19Stephen R. van den Berg  } }
3ad2e42016-09-20Stephen R. van den Berg  private void rclose() { close(); state = RCLOSING; m_delete(clients, sid); }
4b99e12016-09-19Stephen R. van den Berg  private void clearcallback() { close_cb = 0; read_cb = 0; // Sort of a race, if multithreading
a362df2016-09-22Stephen R. van den Berg  id = 0; // Delete all references to this Socket
1f3ef52016-09-23Stephen R. van den Berg  upgtransport = conn = 0;
4b99e12016-09-19Stephen R. van den Berg  }
3ad2e42016-09-20Stephen R. van den Berg  private void recv(int type, void|string|Stdio.Buffer msg) {
4b99e12016-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
3ad2e42016-09-20Stephen R. van den Berg  close();
4b99e12016-09-19Stephen R. van den Berg  break; case CLOSE:
3ad2e42016-09-20Stephen R. van den Berg  rclose();
a8ed1c2016-09-22Stephen R. van den Berg  if (!sendq.size())
4b99e12016-09-19Stephen R. van den Berg  clearcallback(); break; case SENDQEMPTY: if (state == RCLOSING) clearcallback(); break; case FORCECLOSE:
3ad2e42016-09-20Stephen R. van den Berg  rclose();
4b99e12016-09-19Stephen R. van den Berg  clearcallback(); break; case PING: send(PONG, msg); break; case MESSAGE:
85e9e12016-09-22Stephen R. van den Berg  if (read_cb && recvq.is_empty())
3ad2e42016-09-20Stephen R. van den Berg  read_cb(query_id(), msg);
4b99e12016-09-19Stephen R. van den Berg  else {
d325422016-09-26Stephen R. van den Berg  recvq.put(msg);
4b99e12016-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;
a362df2016-09-22Stephen R. van den Berg  if(conn)
06210f2016-09-22Stephen R. van den Berg  flush();
4b99e12016-09-19Stephen R. van den Berg  break; case PING: state = PAUSED;
a8ed1c2016-09-22Stephen R. van den Berg  if (!sendq.size())
4b99e12016-09-19Stephen R. van den Berg  send(NOOP);
06210f2016-09-22Stephen R. van den Berg  flush();
85e9e12016-09-22Stephen R. van den Berg  upgtransport.flush(PONG, msg);
4b99e12016-09-19Stephen R. van den Berg  break; case UPGRADE: {
85e9e12016-09-22Stephen R. van den Berg  upgtransport.read_cb = recv;
a362df2016-09-22Stephen R. van den Berg  conn = upgtransport;
4b99e12016-09-19Stephen R. van den Berg  curtransport = "websocket"; if (state == PAUSED) state = RUNNING; upgtransport = 0;
06210f2016-09-22Stephen R. van den Berg  flush();
4b99e12016-09-19Stephen R. van den Berg  break; } } } protected void destroy() {
3ad2e42016-09-20Stephen R. van den Berg  close();
4b99e12016-09-19Stephen R. van den Berg  }
920d092016-09-26Stephen R. van den Berg  //! @param options
3074172016-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 //! is terminated, unit in @expr{ms}. //! @member int "pingInterval" //! The browser-client will send a small ping message every //! @expr{pingInterval ms}. //! @member int "allowUpgrades" //! When @expr{true} (default), it allows the server to upgrade //! the connection to a real @[Protocol.WebSocket] connection. //! @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,
920d092016-09-26Stephen R. van den Berg  void|mapping options) {
a362df2016-09-22Stephen R. van den Berg  request = req;
920d092016-09-26Stephen R. van den Berg  _options = .EngineIO.options; if (options && sizeof(options)) _options += options;
a362df2016-09-22Stephen R. van den Berg  switch (curtransport = req.variables->transport) {
4b99e12016-09-19Stephen R. van den Berg  default:
85e9e12016-09-22Stephen R. van den Berg  req.response_and_finish((["data":"Unsupported transport",
4b99e12016-09-19Stephen R. van den Berg  "error":Protocols.HTTP.HTTP_UNSUPP_MEDIA])); return; case "websocket":
a362df2016-09-22Stephen R. van den Berg  conn = WebSocket(req, req.websocket_accept(0));
4b99e12016-09-19Stephen R. van den Berg  break; case "polling":
a362df2016-09-22Stephen R. van den Berg  conn = req.variables.j ? JSONP(req) : XHR(req);
4b99e12016-09-19Stephen R. van den Berg  break; }
a362df2016-09-22Stephen R. van den Berg  conn.read_cb = recv;
4b99e12016-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());
3ad2e42016-09-20Stephen R. van den Berg  clients[sid] = this;
4b99e12016-09-19Stephen R. van den Berg  send(OPEN, Standards.JSON.encode( (["sid":sid, "upgrades":
920d092016-09-26Stephen R. van den Berg  _options->allowUpgrades ? ({"websocket"}) : ({}), "pingInterval":_options->pingInterval, "pingTimeout":_options->pingTimeout
4b99e12016-09-19Stephen R. van den Berg  ]))); PD("New EngineIO sid: %O\n", sid); }
a362df2016-09-22Stephen R. van den Berg  //! Handle request, and returns a new Socket object if it's a new
4b99e12016-09-19Stephen R. van den Berg  //! connection. final void onrequest(Protocols.WebSocket.Request req) { string s;
a362df2016-09-22Stephen R. van den Berg  request = req;
4b99e12016-09-19Stephen R. van den Berg  if ((s = req.variables->transport) == curtransport)
a362df2016-09-22Stephen R. van den Berg  conn.onrequest(req);
4b99e12016-09-19Stephen R. van den Berg  else switch (s) { default:
85e9e12016-09-22Stephen R. van den Berg  req.response_and_finish((["data":"Invalid transport",
4b99e12016-09-19Stephen R. van den Berg  "error":Protocols.HTTP.HTTP_UNSUPP_MEDIA])); return 0; case "websocket":
85e9e12016-09-22Stephen R. van den Berg  upgtransport = WebSocket(req, req.websocket_accept(0)); upgtransport.read_cb = upgrecv;
4b99e12016-09-19Stephen R. van den Berg  } } private string _sprintf(int type, void|mapping flags) { string res=UNDEFINED;
3ad2e42016-09-20Stephen R. van den Berg  switch (type) {
4b99e12016-09-19Stephen R. van den Berg  case 'O':
3ad2e42016-09-20Stephen R. van den Berg  res = sprintf(DRIVERNAME"(%s.%d,%s,%d,%d,%d,%d)",
85e9e12016-09-22Stephen R. van den Berg  sid, protocol, curtransport, state, sendq.size(), recvq.is_empty(),sizeof(clients));
4b99e12016-09-19Stephen R. van den Berg  break; } return res; } }