Error processing request

Parameters

CONTENT_LENGTH0
REQUEST_METHODGET
REQUEST_URI/revision/WebSocket?V=53
QUERY_STRINGV=53
CONTENT_TYPE
DOCUMENT_URI/revision/WebSocket
DOCUMENT_ROOT/var/www/nikit/nikit/nginx/../docroot
SCGI1
SERVER_PROTOCOLHTTP/1.1
HTTPSon
REMOTE_ADDR172.70.178.52
REMOTE_PORT34290
SERVER_PORT4443
SERVER_NAMEwiki.tcl-lang.org
HTTP_HOSTwiki.tcl-lang.org
HTTP_CONNECTIONKeep-Alive
HTTP_ACCEPT_ENCODINGgzip, br
HTTP_X_FORWARDED_FOR3.143.245.137
HTTP_CF_RAY883cb488dcc662fd-ORD
HTTP_X_FORWARDED_PROTOhttps
HTTP_CF_VISITOR{"scheme":"https"}
HTTP_ACCEPT*/*
HTTP_USER_AGENTMozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; [email protected])
HTTP_REFERERhttp://tcl.wiki/revision/WebSocket?V=53
HTTP_CF_CONNECTING_IP3.143.245.137
HTTP_CDN_LOOPcloudflare
HTTP_CF_IPCOUNTRYUS

Body


Error

Unknow state transition: LINE -> END

-code

1

-level

0

-errorstack

INNER {returnImm {Unknow state transition: LINE -> END} {}} CALL {my render_wikit WebSocket WebSockets\ are\ a\ nice\ alternative\ to\ XMLHTTPRequest\ for\ bi-directional\ communication\ between\ a\ web\ browser\ and\ a\ server\ application.\ They\ need\ a\ browser,\ e.g.\ Chrome,\ that\ supports\ the\ WebSocket\ API\ and\ a\ server\ that\ supports\ the\ WebSocket\ protocol.\n\n**See\ also**\n\n\ \ \ *\ https://github.com/bovine/tclwebsockets\ —\ Tcl\ bindings\ for\ a\ fork\ of\ \[https://libwebsockets.org/trac/libwebsockets%|%libwebsockets\].\n\ \ \ *\ https://bitbucket.org/naviserver/websocket\ —\ WebSockets\ for\ \[NaviServer\]\n\ \ \ *\ http://websocketd.com/\ —\ A\ \[CGI\]-style\ WebSocket\ wrapper\ for\ scripts\ that\ read\ from\ STDIN\ and\ write\ to\ STDOUT.\n\n**WebSocket\ for\ \[Wibble\]**\n\n''\[AMG\]:\ I\ moved\ the\ older\ discussion\ to\ the\ bottom\ of\ the\ page\ \[http://wiki.tcl.tk/26556#pagetoc6334fd30\]\ so\ the\ current\ code\ can\ be\ right\ at\ the\ top.''\n\n\[AMG\]:\ Here's\ the\ code.\ \ It\ requires\ the\ latest\ Wibble\ \[http://wiki.tcl.tk/_/revision?N=27377.code&V=28\].\n\n\[EF\]:\ Note\ that\ a\ complete\ client\ library,\ which\ code\ inherits\ from\ the\ implementation\ below,\ is\ available\ in\ \[WebSocket\ Client\ Library\].\ \ The\ library\ is\ also\ able\ to\ provide\ protocol\ framing\ support\ within\ a\ Web\ server,\ if\ necessary.\n\n\[dzach\]:\ 2013-05-03\ Any\ reason\ why\ the\ ''query''\ key\ is\ not\ included\ in\ the\ ''request''\ dict?\n\n\[AMG\]:\ I\ don't\ see\ why\ it\ wouldn't\ be.\ \ ''query''\ and\ ''rawquery''\ are\ set\ very\ near\ the\ top\ of\ \[\[getrequest\]\].\ \[dzach\]\ You\ are\ right.\ Strangely\ enough,\ my\ working\ copy\ of\ wibble,\ which\ I\ thought\ was\ \"clean\",\ doesn't,\ but\ a\ fresh\ one\ I\ just\ ran\ ''does''\ include\ the\ query\ in\ the\ request\ dict.\n\n======\npackage\ require\ sha1\n\n#\ Define\ the\ ::wibble::ws\ namespace.\nnamespace\ eval\ ::wibble::ws\ \{\n\ \ \ \ namespace\ path\ ::wibble\n\}\n\n#\ Send\ a\ WebSocket\ frame.\nproc\ ::wibble::ws::send\ \{type\ \{msg\ \"\"\}\ \{final\ 1\}\}\ \{\n\ \ \ \ log\ \"\[info\ coroutine\]\ Tx\ \$type\ \$msg\"\n\n\ \ \ \ #\ Compute\ the\ opcode.\ \ The\ opcode\ is\ zero\ for\ continuation\ frames.\n\ \ \ \ upvar\ #1\ fragment\ fragment\n\ \ \ \ if\ \{\[info\ exists\ fragment\]\}\ \{\n\ \ \ \ \ \ \ \ set\ opcode\ 0\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ set\ opcode\ \[dict\ get\ \{text\ 1\ binary\ 2\ ping\ 9\}\ \$type\]\n\ \ \ \ \}\n\ \ \ \ if\ \{!\$final\}\ \{\n\ \ \ \ \ \ \ \ set\ fragment\ \"\"\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ unset\ -nocomplain\ fragment\n\ \ \ \ \}\n\n\ \ \ \ #\ Encode\ text.\n\ \ \ \ if\ \{\$type\ eq\ \"text\"\}\ \{\n\ \ \ \ \ \ \ \ set\ msg\ \[encoding\ convertto\ utf-8\ \$msg\]\n\ \ \ \ \}\n\n\ \ \ \ #\ Assemble\ the\ header.\n\ \ \ \ set\ header\ \[binary\ format\ c\ \[expr\ \{!!\$final\ <<\ 7\ |\ \$opcode\}\]\]\n\ \ \ \ if\ \{\[string\ length\ \$msg\]\ <\ 126\}\ \{\n\ \ \ \ \ \ \ \ append\ header\ \[binary\ format\ c\ \[string\ length\ \$msg\]\]\n\ \ \ \ \}\ elseif\ \{\[string\ length\ \$msg\]\ <\ 65536\}\ \{\n\ \ \ \ \ \ \ \ append\ header\ \\x7e\[binary\ format\ Su\ \[string\ length\ \$msg\]\]\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ append\ header\ \\x7f\[binary\ format\ Wu\ \[string\ length\ \$msg\]\]\n\ \ \ \ \}\n\n\ \ \ \ #\ Send\ the\ frame.\n\ \ \ \ set\ socket\ \[namespace\ tail\ \[info\ coroutine\]\]\n\ \ \ \ chan\ puts\ -nonewline\ \$socket\ \$header\$msg\n\ \ \ \ chan\ flush\ \$socket\n\}\n\n#\ Close\ the\ current\ WebSocket\ connection.\nproc\ ::wibble::ws::close\ \{\{reason\ \"\"\}\ \{description\ \"\"\}\}\ \{\n\ \ \ \ icc\ put\ ::wibble::\[namespace\ tail\ \[info\ coroutine\]\]\ exception\ close\\\n\ \ \ \ \ \ \ \ \$reason\ \$description\n\}\n\n#\ WebSocket\ analogue\ of\ \[::wibble::process\]\nproc\ ::wibble::ws::process\ \{state\ socket\ request\ response\}\ \{\n\ \ \ \ #\ Get\ configuration\ options.\n\ \ \ \ if\ \{!\[dict\ exists\ \$state\ options\ maxlength\]\}\ \{\n\ \ \ \ \ \ \ \ set\ maxlength\ 16777216\n\ \ \ \ \}\ elseif\ \{\[dict\ get\ \$state\ options\ maxlength\]\ eq\ \"\"\}\ \{\n\ \ \ \ \ \ \ \ set\ maxlength\ 18446744073709551615\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ set\ maxlength\ \[dict\ get\ \$state\ options\ maxlength\]\n\ \ \ \ \}\n\n\ \ \ \ #\ Create\ WebSocket\ event\ handler\ wrapper\ coroutine.\n\ \ \ \ set\ fid\ \[namespace\ current\]::\$socket\n\ \ \ \ cleanup\ ws_unset_feed\ \[list\ icc\ destroy\ \$fid\]\n\ \ \ \ icc\ configure\ \$fid\ accept\ connect\ disconnect\ text\ binary\ close\n\ \ \ \ coroutine\ \$socket\ apply\ \{\{handler\ state\}\ \{\n\ \ \ \ \ \ \ \ try\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ while\ \{1\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ foreach\ event\ \[icc\ get\ \[info\ coroutine\]\ *\]\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{*\}\$handler\ \$state\ \{*\}\$event\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[lindex\ \$event\ 0\]\ eq\ \"disconnect\"\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \}\ on\ error\ \{\"\"\ options\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ panic\ \$options\ \"\"\ \"\"\ \"\"\ \"\"\ \[dict\ get\ \$state\ request\]\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \[dict\ get\ \$state\ response\]\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\ ::wibble::zone\}\ \[dict\ get\ \$state\ options\ handler\]\ \$state\n\n\ \ \ \ #\ Respond\ to\ WebSocket\ handshake.\n\ \ \ \ chan\ puts\ \$socket\ \"HTTP/1.1\ 101\ WebSocket\ Protocol\ Handshake\"\n\ \ \ \ chan\ puts\ \$socket\ \"upgrade:\ websocket\"\n\ \ \ \ chan\ puts\ \$socket\ \"connection:\ upgrade\"\n\ \ \ \ chan\ puts\ \$socket\ \"sec-websocket-accept:\ \[binary\ encode\ base64\ \[sha1::sha1\ -bin\\\n\ \ \ \ \ \ \ \ \[dict\ get\ \$request\ header\ sec-websocket-key\\\n\ \ \ \ \ \ \ \ \]258EAFA5-E914-47DA-95CA-C5AB0DC85B11\]\]\"\n\ \ \ \ chan\ puts\ \$socket\ \"\"\n\ \ \ \ chan\ flush\ \$socket\n\ \ \ \ chan\ configure\ \$socket\ -translation\ binary\n\n\ \ \ \ #\ It's\ necessary\ to\ bypass\ \[icc\ put\]\ in\ this\ one\ case,\ because\ it\ defers\n\ \ \ \ #\ event\ delivery\ when\ called\ from\ a\ coroutine.\ \ Consequentially,\ before\ it\n\ \ \ \ #\ would\ attempt\ to\ send\ the\ event,\ the\ feed\ will\ be\ destroyed.\n\ \ \ \ cleanup\ ws_disconnect\ \[list\ \$fid\ disconnect\]\n\n\ \ \ \ #\ Invoke\ connect\ handler.\n\ \ \ \ icc\ put\ \$fid\ connect\n\n\ \ \ \ set\ reason\ 1000\n\ \ \ \ try\ \{\n\ \ \ \ \ \ \ \ foreach\ event\ \[icc\ catch\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ #\ Main\ loop.\n\ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ set\ mode\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ while\ \{1\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Get\ basic\ header.\ \ Abort\ if\ reserved\ bits\ are\ set,\ mask\ bit\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ isn't\ set,\ unexpected\ continuation\ frame,\ fragmented\ or\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ oversized\ control\ frame,\ or\ the\ opcode\ is\ unrecognized.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[getblock\ 2\]\ Su\ header\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ opcode\ \[expr\ \{\$header\ >>\ 8\ &\ 0xf\}\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ len\ \[expr\ \{\$header\ &\ 0x7f\}\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{(\$header\ &\ 0x7080\ ^\ 0x80)\ ||\ (\$opcode\ ==\ 0\ &&\ \$mode\ eq\ \"\")\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ||\ (\$opcode\ >\ 7\ &&\ (!(\$header\ &\ 0x8000)\ ||\ \$len\ >\ 125))\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ||\ \$opcode\ ni\ \{0\ 1\ 2\ 8\ 9\ 10\}\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Send\ close\ frame,\ reason\ 1002:\ protocol\ error.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ reason\ 1002\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ break\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Determine\ the\ effective\ opcode\ for\ this\ frame.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$mode\ eq\ \"\"\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ mode\ \$opcode\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ elseif\ \{\$opcode\ ==\ 0\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ opcode\ \$mode\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Get\ the\ extended\ length,\ if\ present.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$len\ ==\ 126\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[getblock\ 2\]\ Su\ len\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ elseif\ \{\$len\ ==\ 127\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[getblock\ 8\]\ Wu\ len\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Limit\ the\ maximum\ message\ length.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[string\ length\ \$msg\]\ +\ \$len\ >\ \$maxlength\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Send\ close\ frame,\ reason\ 1009:\ frame\ too\ big.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ reason\ \[list\ 1009\ \"limit\ \$maxlength\ bytes\"\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ break\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Use\ an\ alternate\ message\ buffer\ for\ control\ frames.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$opcode\ >\ 7\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ oldmsg\ \$msg\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Get\ mask\ and\ data.\ \ Format\ data\ as\ a\ list\ of\ 32-bit\ integer\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ words\ and\ list\ of\ 8-bit\ integer\ byte\ leftovers.\ \ Then\ unmask\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ data,\ recombine\ the\ words\ and\ bytes,\ and\ append\ to\ the\ buffer.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[getblock\ \[expr\ \{4\ +\ \$len\}\]\]\ II*c*\ mask\ words\ bytes\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ for\ \{set\ i\ 0\}\ \{\$i\ <\ \[llength\ \$words\]\}\ \{incr\ i\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ lset\ words\ \$i\ \[expr\ \{\[lindex\ \$words\ \$i\]\ ^\ \$mask\}\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ for\ \{set\ i\ 0\}\ \{\$i\ <\ \[llength\ \$bytes\]\}\ \{incr\ i\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ lset\ bytes\ \$i\ \[expr\ \{\[lindex\ \$bytes\ \$i\]\ ^\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (\$mask\ >>\ (24\ -\ 8\ *\ \$i))\}\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ append\ msg\ \[binary\ format\ I*c*\ \$words\ \$bytes\]\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ If\ FIN\ bit\ is\ set,\ process\ the\ frame.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$header\ &\ 0x8000\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ switch\ \$opcode\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 1\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Text:\ decode\ and\ notify\ handler.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ icc\ put\ \$fid\ text\ \[encoding\ convertfrom\ utf-8\ \$msg\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ 2\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Binary:\ notify\ handler\ without\ decoding.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ icc\ put\ \$fid\ binary\ \$msg\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ 8\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Close:\ decode,\ handle,\ send\ close\ frame,\ terminate.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[string\ length\ \$msg\]\ >=\ 2\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[string\ range\ \$msg\ 0\ 1\]\ Su\ reason\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ icc\ put\ \$fid\ close\ \$reason\ \[encoding\ convertfrom\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ utf-8\ \[string\ range\ \$msg\ 2\ end\]\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ icc\ put\ \$fid\ close\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ reason\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ break\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ 9\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Ping:\ send\ pong\ to\ client,\ don't\ notify\ handler.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ chan\ puts\ -nonewline\ \$socket\ \\x8a\[binary\ format\ c\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \[string\ length\ \$msg\]\]\$msg\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ chan\ flush\ \$socket\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Prepare\ for\ the\ next\ frame.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$opcode\ <\ 8\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Data\ frame:\ reinitialize\ parser.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ mode\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Control\ frame:\ restore\ previous\ message\ buffer.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \$oldmsg\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \}\]\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ #\ Catch\ exception\ events\ and\ translate\ them\ into\ close\ reason\ codes.\n\ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[lrange\ \$event\ 0\ 1\]\ eq\ \{exception\ close\}\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ reason\ \[lrange\ \$event\ 2\ 3\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ break\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\ on\ error\ \{result\ options\}\ \{\n\ \ \ \ \ \ \ \ #\ Default\ error\ close\ reason.\n\ \ \ \ \ \ \ \ set\ reason\ \{1001\ \"internal\ server\ error\"\}\n\ \ \ \ \ \ \ \ return\ -options\ \$options\ \$result\n\ \ \ \ \}\ finally\ \{\n\ \ \ \ \ \ \ \ #\ Send\ close\ frame\ with\ reason,\ if\ one\ was\ given.\n\ \ \ \ \ \ \ \ catch\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[llength\ \$reason\]\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \[string\ range\ \[binary\ format\ Su\ \[lindex\ \$reason\ 0\]\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \]\[encoding\ convertto\ utf-8\ \[lindex\ \$reason\ 1\]\]\ 0\ 124\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ chan\ puts\ -nonewline\ \$socket\ \\x88\[binary\ format\ c\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \[string\ length\ \$msg\]\]\$msg\n\ \ \ \ \ \ \ \ \ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ chan\ puts\ -nonewline\ \$socket\ \\x88\\x00\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\n\n\ \ \ \ #\ Ask\ Wibble\ to\ close\ the\ connection.\n\ \ \ \ return\ 0\n\}\n\n#\ WebSocket\ upgrade\ zone\ handler.\nproc\ ::wibble::zone::websocket\ \{state\}\ \{\n\ \ \ \ set\ header\ \[dict\ get\ \$state\ request\ header\]\n\ \ \ \ if\ \{\[dict\ exists\ \$header\ sec-websocket-key\]\n\ \ \ \ \ &&\ (\[dict\ exists\ \$header\ sec-websocket-origin\]\ ||\ \[dict\ exists\ \$header\ origin\])\n\ \ \ \ \ &&\ \[dict\ exists\ \$header\ sec-websocket-version\]\n\ \ \ \ \ &&\ \[dict\ exists\ \$header\ connection\]\n\ \ \ \ \ &&\ \[dict\ exists\ \$header\ upgrade\]\n\ \ \ \ \ &&\ \[lsearch\ -nocase\ \[dict\ get\ \$header\ connection\]\ upgrade\]\ !=\ -1\n\ \ \ \ \ &&\ \[string\ equal\ -nocase\ \[dict\ get\ \$header\ upgrade\]\ websocket\]\}\ \{\n\ \ \ \ \ \ \ \ sendresponse\ \[list\ nonhttp\ 1\ sendcommand\ \[list\ ws::process\ \$state\]\]\n\ \ \ \ \}\n\}\n======\n\[sbron\]\ 2013-10-27:\ I\ have\ removed\ the\ dependency\ on\ the\ base64\ package.\ Wibble\ requires\ Tcl\ 8.6\ to\ run\ and\ 8.6\ has\ base64\ encoding/decoding\ built-in.\ So\ it\ seemed\ silly\ to\ use\ an\ external\ package\ for\ that.\n\n\[AMG\]:\ Agreed,\ thank\ you.\n\n\[dzach\]\ 26-12-2012:\ It\ looks\ like\ Firefox\ does\ not\ send\ the\ \"`sec-websocket-origin`\"\ header.\ I\ have\ not\ dug\ into\ the\ details\ but\ it\ seems\ to\ me\ that\ the\ \"`origin`\"\ header\ should\ serve\ the\ same\ purpose,\ so,\ for\ the\ above\ WebSocket\ zone\ handler\ to\ work\ with\ Firefox\ (and\ Opera),\ a\ check\ for\ the\ \"`origin`\"\ header\ can\ be\ added:\n\n======\nproc\ ::wibble::zone::websocket\ state\ \{\n\ \ set\ header\ \[dict\ get\ \$state\ request\ header\]\n\ \ if\ \{\[dict\ exists\ \$header\ sec-websocket-key\]\n\ \ \ \ &&\ (\[dict\ exists\ \$header\ sec-websocket-origin\]\ ||\ \[dict\ exists\ \$header\ origin\])\n\ \ \ \ &&\ \[dict\ exists\ \$header\ sec-websocket-version\]\n\ \ \ \ &&\ \[dict\ exists\ \$header\ connection\]\n\ \ \ \ &&\ \[dict\ exists\ \$header\ upgrade\]\n\ \ \ \ &&\ \[lsearch\ -nocase\ \[dict\ get\ \$header\ connection\]\ upgrade\]\ !=\ -1\n\ \ \ \ &&\ \[string\ equal\ -nocase\ \[dict\ get\ \$header\ upgrade\]\ websocket\]\}\ \{\n\ \ \ \ \ \ sendresponse\ \[list\ nonhttp\ 1\ sendcommand\ \[list\ ws::process\ \$state\]\]\n\ \ \}\n\}\n======\n\n\[UKo\]\ 2015-11-22:\ `sec-websocket-origin`\ is\ no\ longer\ part\ of\ the\ WebSocket\ standard\ (now\ part\ of\ the\ living\ html\ standard\ \[https://html.spec.whatwg.org/multipage/comms.html#network\]).\ The\ last\ version\ it\ was\ mentioned\ is\ 10\ \[https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10\]\ and\ it\ was\ removed\ in\ 11\ \[https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-11\].\n\nHere's\ an\ example\ WebSocket\ handler:\n\n======\nproc\ ::wibble::zone::ws-demo\ \{state\ event\ args\}\ \{\n\ \ \ \ upvar\ #1\ cancel\ cancel\n\n\ \ \ \ log\ \"\[info\ coroutine\]\ Rx\ \$event\ \$args\"\n\n\ \ \ \ #\ Perform\ initialization\ and\ cleanup.\n\ \ \ \ if\ \{\$event\ eq\ \"connect\"\}\ \{\n\ \ \ \ \ \ \ \ icc\ configure\ \[info\ coroutine\]\ accept\ tick\n\ \ \ \ \}\ elseif\ \{\$event\ eq\ \"disconnect\"\}\ \{\n\ \ \ \ \ \ \ \ after\ cancel\ \$cancel\n\ \ \ \ \}\n\n\ \ \ \ #\ Process\ timer\ and\ text\ events.\n\ \ \ \ if\ \{\$event\ in\ \{connect\ tick\}\}\ \{\n\ \ \ \ \ \ \ \ set\ cancel\ \[after\ 1000\ \[list\ ::wibble::icc\ put\ \[info\ coroutine\]\ tick\]\]\n\ \ \ \ \ \ \ \ ws::send\ text\ \"time\ \[clock\ format\ \[clock\ seconds\]\]\"\n\ \ \ \ \}\ elseif\ \{\$event\ eq\ \"text\"\}\ \{\n\ \ \ \ \ \ \ \ if\ \{\[lindex\ \$args\ 0\]\ eq\ \"close\"\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ ws::close\n\ \ \ \ \ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ ws::send\ text\ \"expr\ \[expr\ \[lindex\ \$args\ 0\]\]\"\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\n\}\n======\n\nAlso,\ put\ \"`::wibble::handle\ /ws-demo\ websocket\ handler\ ws-demo`\"\ at\ the\ top\ of\ your\ zone\ handler\ list.\ \ And\ here's\ a\ demo\ HTML5\ file,\ usable\ in\ \[Chrome\]:\n\n\[UKo\]\ 2015-11-23:\ For\ me\ this\ demo\ works\ in\ all\ tested\ modern\ browsers:\n\n\ \ \ *\ Firefox\ 42\n\ \ \ *\ Chromium\ 42\n\ \ \ *\ Safari\ (iOS\ 7)\n\ \ \ *\ Android\ Chrome\n\n======html\n<!DOCTYPE\ html>\n<html\ lang=\"en\">\n<body>\n\ \ <table>\n\ \ <tr><th>State</th><td\ id=\"state\">initializing</td></tr>\n\ \ <tr><th>Time</th><td\ id=\"time\">???</td></tr>\n\ \ <tr><th>Result</th><td\ id=\"result\">???</td></tr>\n\ \ <tr><th>Expression</th><td><form>\n\ \ \ \ <input\ type=\"text\"\ id=\"expression\"\ />\n\ \ </form></td></tr>\n<script>\nvar\ ws\ =\ new\ WebSocket(\"ws://\"+window.location.host+\"/ws-demo\")\;\nvar\ state\ =\ document.getElementById(\"state\")\;\nvar\ time\ =\ document.getElementById(\"time\")\;\nvar\ result\ =\ document.getElementById(\"result\")\;\nvar\ expression\ =\ document.getElementById(\"expression\")\;\nvar\ sendcount\ =\ 0\;\nvar\ receivecount\ =\ 0\;\n\nws.onopen\ =\ function\ ()\ \{\n\ \ state.innerHTML\ =\ \"connected\"\;\n\}\;\n\nws.onmessage\ =\ function\ (evt)\ \{\n\ \ receivecount\ +=\ 1\;\n\ \ state.innerHTML\ =\ \"sent\ \"\ +\ sendcount\ +\ \",\ received\ \"\ +\ receivecount\;\n\ \ if\ (evt.data.substr(0,\ 4)\ ==\ \"time\")\ \{\n\ \ \ \ time.innerHTML\ =\ evt.data.substr(5)\;\n\ \ \}\ else\ if\ (evt.data.substr(0,\ 4)\ ==\ \"expr\")\ \{\n\ \ \ \ result.innerHTML\ =\ evt.data.substr(5)\;\n\ \ \}\n\}\;\n\nws.onclose\ =\ function\ (evt)\ \{\n\ \ state.innerHTML\ =\ \"disconnected\"\;\n\}\;\n\nexpression.form.addEventListener(\"submit\",\ function\ (evt)\ \{\n\ \ evt.preventDefault()\;\n\ \ if\ (ws.readyState\ ===\ 1)\ \{\n\ \ \ \ ws.send(expression.value)\;\n\ \ \ \ expression.value\ =\ \"\"\;\n\ \ \ \ sendcount\ +=\ 1\;\n\ \ \ \ state.innerHTML\ =\ \"sent\ \"\ +\ sendcount\ +\ \",\ received\ \"\ +\ receivecount\;\n\ \ \}\n\ \ return\ false\;\n\})\;\n</script>\n</body>\n</html>\n======\n\nAs\ promised,\ this\ demo\ incorporates\ both\ a\ clock\ and\ a\ calculator.\ \ I\ did\ this\ to\ better\ exercise\ the\ asynchronous\ nature\ of\ WebSocket.\ \ Events\ come\ both\ from\ the\ client\ and\ from\ the\ timer.\n\nNow\ that\ I\ know\ the\ \[\[\[tailcall\]\]\]\ trick\ \[http://wiki.tcl.tk/1507#pagetocc0434a60\],\ I'm\ considering\ writing\ an\ \[\[icc\]\]\ utility\ \[proc\]\ or\ two\ for\ the\ purpose\ of\ easing\ event\ handler\ dispatch.\ \ It/they\ would\ wrap\ around\ this\ idiom,\ which\ I've\ had\ to\ write\ more\ than\ once\ in\ my\ own\ code:\n\n======\nforeach\ event\ \[icc\ get\ \$feed\ \{*\}\$filters\]\ \{\n\ \ \ switch\ -glob\ \[lindex\ \$event\ 0\]\ \{\n\ \ \ \ \ \ filter/*/whatever\ \{puts\ 1\}\n\ \ \ \ \ \ filter2/whatever/*\ \{puts\ 2\}\n\ \ \ \ \ \ etc\ \{puts\ 3\}\n\ \ \ \}\n\}\n======\n\nOr\ in\ the\ case\ of\ the\ above\ WebSocket\ code,\ replace\ `\[\[icc\ get\ \$feed\ \{*\}\$filters\]\]`\ with\ `\[\[icc\ catch\ \$script\]\]`.\n\n======\nicc\ get2\ \$feed\ \$filters\\\n\ \ \ \ filter/*/whatever\ event\ \{puts\ \[lindex\ \$event\ 1\]\}\\\n\ \ \ \ filter2/whatever/*\ \"\"\ \{puts\ 2\}\\\n\ \ \ \ etc\ \"\"\ \{puts\ 3\}\n\nicc\ catch2\ \$script\\\n\ \ \ \ filter/*/whatever\ event\ \{puts\ \[lindex\ \$event\ 1\]\}\\\n\ \ \ \ filter2/whatever/*\ \"\"\ \{puts\ 2\}\\\n\ \ \ \ etc\ \"\"\ \{puts\ 3\}\\\n\ \ \ \ exception\ \"\"\ \{puts\ 4\}\n======\n\nThe\ name(s)\ and\ syntax\ are\ definitely\ subject\ to\ change.\ \ Suggestions?\ \ Here\ are\ some\ ideas:\n\n\ \ \ *\ Move\ the\ event\ variable\ name\ from\ each\ handler\ to\ a\ common\ option.\n\ \ \ *\ Add\ an\ option\ to\ split\ the\ event\ identifier\ to\ a\ list.\n\ \ \ **\ \[\[\[split\]\]\]\ characters?\ \ Good\ for\ the\ \"/\"\ convention\ I\ used\ in\ another\ project.\n\ \ \ **\ Regular\ expression?\ \ Good\ for\ more\ complicated\ conventions.\n\ \ \ *\ Combine\ the\ above\ \[\[icc\ get2\]\]\ and\ \[\[icc\ catch2\]\]\ into\ a\ single\ command.\n\ \ \ **\ Operation\ would\ be\ determined\ by\ options.\n\ \ \ **\ One\ mode\ takes\ a\ list\ of\ feeds\ and\ a\ list\ of\ filters.\n\ \ \ **\ Other\ mode\ takes\ a\ script.\n\ \ \ **\ The\ name\ could\ be\ \[\[icc\ switch\]\].\n\ \ \ *\ Like\ \[\[\[switch\]\]\],\ allow\ the\ cases\ to\ be\ closed\ in\ a\ single\ brace-quoted\ word.\n\nPulling\ all\ this\ together\ might\ yield:\n\n======\nicc\ switch\ -feeds\ \$feed\ -filters\ \$filters\ -eventvar\ event\\\n-identvar\ ident\ -identsplit\ /\ \{\n\ \ \ \ filter/*/whatever\ \{puts\ \[lindex\ \$event\ 1\]\}\n\ \ \ \ filter2/whatever/*\ \{puts\ \[lindex\ \$ident\ 2\]\}\n\ \ \ \ etc\ \{puts\ 3\}\n\}\nicc\ switch\ -script\ \$script\ -eventvar\ event\\\n-identvar\ ident\ -identregexp\ \{(.*)/(.*)/(.*)\}\ \{\n\ \ \ \ filter/*/whatever\ \{puts\ \[lindex\ \$event\ 1\]\}\n\ \ \ \ filter2/whatever/*\ \{puts\ \[lindex\ \$ident\ 2\]\}\n\ \ \ \ etc\ \{puts\ 3\}\n\ \ \ \ exception\ \{puts\ 4\}\n\}\n======\n\nBut\ now\ I\ think\ that's\ too\ many\ pairs\ of\ options\ which\ must\ go\ together,\ and\ that\ complicates\ parsing.\ \ Maybe\ replace\ \"`-feeds\ \$feed\ -filters\ \$filters`\"\ with\ \"`-get\ \[\[list\ \$feed\ \$filters\]\]`\".\ \ Hmm,\ that\ \[\[\[list\]\]\]\ in\ there\ makes\ it\ seem\ even\ less\ clean,\ though.\ \ I\ dunno.\n\n----\n**Archived\ discussion**\n\n\[agb\]\ Dec.\ 2010.\ \ Chrome\ now\ supports\ an\ http://blog.chromium.org/2010/06/websocket-protocol-updated.html%|%updated\ version\ of\ the\ websocket\ protocol\ %|%\ so\ the\ \[wibble\]\ example\ previously\ here\ no\ longer\ works\ (to\ see\ it\ have\ a\ look\ in\ this\ page's\ history)\ .\ \ The\ changes\ to\ get\ the\ current\ version\ working\ are\ non-trivial.\n\n\[jbr\]\ 2010-12-20\ -\ Here\ is\ code\ that\ will\ allow\ \[wibble\]\ to\ handshake\ with\ the\ new\ spec.\ \ The\ version\ of\ Chrome\ that\ I\ have\ (8.0.552.231)\ has\ the\ new\ handshake\ but\ sends\ the\ old\ data\ framing.\ \ I\ can\ send\ data\ from\ the\ client,\ but,\ I\ haven't\ gotten\ it\ to\ accept\ data\ messages\ from\ the\ server.\ \ Wibble.tcl\ needs\ to\ be\ patched\ to\ add\ a\ way\ for\ it\ to\ release\ the\ socket\ that\ will\ be\ the\ websocket\ channel\ without\ responding\ and\ closing\ it:\n\n\[agb\]\ 2010-12-21\ -\ I\ made\ a\ small\ change\ to\ ::wibble::ws-handle\ to\ check\ that\ chan\ read\ actually\ reads\ a\ byte.\ \ With\ this\ change\ I\ have\ successful,\ bi-directional\ messages\ over\ the\ web\ socket\ with\ chrome\ 8.0.552.224.\ Thanks\ for\ updating\ \[wibble\].\n\n======\n\ #\ Abort\ processing\ on\ this\ client.\n\ proc\ wibble::abortclient\ \{\}\ \{\n\ \ \ \ return\ -code\ 7\n\ \}\n======\n\n\[AMG\]:\ I\ take\ it\ that\ this\ command\ is\ to\ be\ called\ by\ a\ zone\ handler\ in\ order\ to\ get\ Wibble\ to\ terminate\ the\ coroutine\ ''without''\ closing\ the\ socket.\ \ Correct?\n\nAlso,\ see\ my\ comments\ on\ \[Wibble\ wish\ list\]\ (\[http://wiki.tcl.tk/27380#pagetoc701442f2\])\ for\ an\ alternative,\ less\ invasive\ approach.\n\n\[jbr\]:\ Set\ keepalive\ 0\ at\ the\ top\ of\ ::wibble::process\ and\ then\ change\ the\ exceptional\ return\ handling\ like\ this:\n\n======\n\ \ \ \ \ \}\ on\ 7\ outcome\ \{\n\ \ \ \ \ \ \ \ set\ keepalive\ 1\n\ \ \ \ \ \}\ finally\ \{\n\ \ \ \ \ \ \ \ if\ \{\ !\$keepalive\ \}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ catch\ \{chan\ close\ \$socket\}\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \}\n======\n\n\[AMG\]:\ Some\ time\ after\ you\ wrote\ the\ above,\ I\ have\ changed\ Wibble\ to\ have\ customizable\ cleanup\ handlers.\ \ With\ the\ latest\ version\ of\ Wibble,\ instead\ of\ modifying\ the\ finally\ block,\ change\ the\ initialization\ of\ the\ cleanup\ list\ (top\ of\ \[\[process\]\])\ to\ the\ following:\n\n======\n\ \ \ \ set\ cleanup\ \{\n\ \ \ \ \ \ \ \ \{chan\ close\ \$file\}\n\ \ \ \ \ \ \ \ \{if\ \{!\$keepalive\}\ \{chan\ close\ \$socket\}\}\n\ \ \ \ \ \ \ \ \{dict\ unset\ ::wibble::icc::feeds\ \$coro\}\n\ \ \ \ \}\n======\n\n\[jbr\]:\ Add\ this\ to\ the\ zone\ handlers:\n\n======\n\ \ wibble::handle\ /ws\ websocket\ handler\ ws-demo\n======\n\nThis\ is\ your\ server\ side\ callback:\n\n======\n\ proc\ ::ws-demo\ \{\ event\ sock\ \{\ data\ \{\}\ \}\ \}\ \{\n\ \ \ \ switch\ \$event\ \{\n\ \ \ \ \ \ \ \ connect\ \{\}\n\ \ \ \ \ \ \ \ message\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ puts\ \"WS-Demo:\ \$event\ \$sock\ \$data\"\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\n\ \ \n\ \ ::wibble::ws-send\ \$sock\ \"Hello\"\n\ \}\n======\n\n\[AMG\]:\ Are\ `connect`\ and\ `message`\ the\ only\ two\ events\ that\ can\ happen?\n\n\[jbr\]:\ Connect\ and\ message\ are\ the\ only\ two\ events.\ \ WebSockets\ is\ a\ very\ low\ level\ thing\ (data\ packets)\ with\ the\ application\ specific\ messaging\ completely\ undefined.\n\n\[jbr\]:\ Utility\ to\ help\ the\ server\ send\ data\ frames,\ doesn't\ work\ yet!!\n\n======\n\ proc\ ::wibble::ws-send\ \{\ sock\ message\ \}\ \{\n\ \ \ \ #\ New\ data\ framing?\n\ \ \ \ #puts\ -nonewline\ \$sock\ \[binary\ format\ cc\ 4\ \[string\ length\ \$message\]\]\$message\n\n\ \ \ \ #\ Old\ data\ framing?\n\ \ \ \ puts\ \ -nonewline\ \$sock\ \"\\x00\"\n\ \ \ \ puts\ \ -nonewline\ \$sock\ \$message\n\ \ \ \ puts\ \ -nonewline\ \$sock\ \"\\xFF\"\n\n\ \ \ \ flush\ \$sock\n\ \}\n======\n\nHandler\ to\ accept\ data\ from\ browser.\ \ Uses\ old\ data\ framing.\n\n======\n\ proc\ ::wibble::ws-handle\ \{\ handler\ sock\ \}\ \{\n\ \ \ \ if\ \{\ \[chan\ eof\ \$sock\]\ \}\ \{\n\ \ \ \ \ \ \ \ puts\ \"Closed\ \$sock\"\n\ \ \ \ \ \ \ \ close\ \$sock\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ set\ code\ \[read\ \$sock\ 1\]\n\ \ \ \ \ \ \ \ if\ \{\[binary\ scan\ \$code\ c\ code\]\}\ \{\ \ \ \ \ \ \ \ \;\ #\ Do\ I\ need\ this?\ I\ think\ so.\n\ \ \ \ \ \ \ \ \ \ switch\ \$code\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ 0\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ message\ \{\}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ while\ \{\ \[set\ c\ \[read\ \$sock\ 1\]\]\ !=\ \"\\xFF\"\ \}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ append\ message\ \$c\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \$handler\ message\ \$sock\ \$message\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ default\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ puts\ \"Bad\ Blocking:\ \$c\"\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \}\n\ \ \ \ \}\n\ \}\n======\n\nThe\ Zone\ Handler\ \n\n======\n\ package\ require\ md5\n\ \ \n\ proc\ ::wibble::websocket\ \{\ state\ \}\ \{\n\ \ \ \ set\ upgrade\ \ \ \ \{\}\n\ \ \ \ set\ connection\ \{\}\n\ \ \ \ dict\ with\ state\ request\ header\ \{\}\n\n\ \ \ \ if\ \{\ \$connection\ ne\ \"Upgrade\"\ ||\ \$upgrade\ ne\ \"WebSocket\"\ \}\ \{\n\ \ \ \ \ \ \ \ return\n\ \ \ \ \}\n\n\ \ \ \ set\ sock\ \[dict\ get\ \$state\ request\ socket\]\n\n\ \ \ \ puts\ \"WebSocket\ Connect:\ \$sock\"\n\n\ \ \ \ set\ key1\ \[regsub\ -all\ \{\[^0-9\]\}\ \$\{sec-websocket-key1\}\ \{\}\]\n\ \ \ \ set\ spc1\ \[string\ length\ \[regsub\ -all\ \{\[^\ \]\}\ \ \ \$\{sec-websocket-key1\}\ \{\}\]\]\n\ \ \ \ set\ key2\ \[regsub\ -all\ \{\[^0-9\]\}\ \ \ \$\{sec-websocket-key2\}\ \{\}\]\n\ \ \ \ set\ spc2\ \[string\ length\ \[regsub\ -all\ \{\[^\ \]\}\ \$\{sec-websocket-key2\}\ \{\}\]\]\n\n\ \ \ \ set\ key3\ \[read\ \$sock\ 8\]\n\n\ \ \ \ set\ handler\ \[dict\ get\ \$state\ options\ handler\]\n\ \ \ \ chan\ event\ \$sock\ readable\ \[list\ ::wibble::ws-handle\ \$handler\ \$sock\]\n\n\ \ \ \ set\ key1\ \[expr\ \$key1/\$spc1\]\n\ \ \ \ set\ key2\ \[expr\ \$key2/\$spc2\]\n\n\ \ \ \ set\ challenge\ \[binary\ format\ II\ \$key1\ \$key2\]\$key3\n\ \ \ \ set\ response\ \ \[md5\ \$challenge\]\n\n\ \ \ \ puts\ \$sock\ \"HTTP/1.1\ 101\ WebSocket\ Protocol\ Handshake\"\n\ \ \ \ puts\ \$sock\ \"Connection:\ Upgrade\"\n\ \ \ \ puts\ \$sock\ \"Upgrade:\ WebSocket\"\n\ \ \ \ puts\ \$sock\ \"Sec-WebSocket-Origin:\ http://localhost:8080\"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \;\ #\ This\ shouldn't\ be\ hard\ coded!!\n\ \ \ \ puts\ \$sock\ \"Sec-WebSocket-Location:\ ws://localhost:8080/ws/demo\"\n\ \ \ \ puts\ \$sock\ \"\"\n\n\ \ \ \ chan\ configure\ \$sock\ -translation\ binary\n\ \ \ \ puts\ \$sock\ \$response\n\ \ \ \ chan\ flush\ \$sock\n\n\ \ \ \ \$handler\ connect\ \$sock\ \ \;\ #\ There\ should\ be\ an\ option\ to\ pass\ a\ session\ Id\ here.\n\n\ \ \ \ abortclient\n\ \}\n======\n\n----\n\[AMG\]:\ Thanks\ for\ the\ code,\ guys.\ \ I\ will\ need\ to\ ponder\ some\ more\ before\ integrating\ this\ into\ Wibble,\ but\ I\ do\ think\ I\ want\ this\ feature.\ \ However,\ I\ think\ it\ would\ benefit\ from\ tighter\ integration.\ \ As\ far\ as\ I\ can\ tell,\ it\ leverages\ Wibble\ for\ establishing\ the\ connection\ but\ then\ takes\ over\ all\ I/O.\ \ This\ concept\ is\ quite\ similar\ to\ something\ \[JCW\]\ shared\ with\ me\ the\ other\ day,\ namely\ an\ implementation\ of\ Server-Sent\ Events\ \[http://jeelabs.net/projects/jeerev/wiki/Web_server_events\]\ \[http://sapid.sourceforge.net/ssetest/\].\ \ Whatever\ I\ do,\ I\ would\ like\ it\ to\ support\ both\ protocols,\ or\ at\ least\ their\ common\ requirements.\n\nIf\ you're\ wondering\ why\ I\ haven't\ integrated\ all\ this\ sooner,\ it's\ because\ \[AJAX\]\ was\ my\ priority.\ \ It\ may\ be\ terribly\ clumsy\ compared\ to\ WebSockets\ and\ Server-Sent\ Events,\ but\ it\ also\ has\ the\ most\ browser\ support.\n\n\[jcw\]\ Neat...\ \[jbr\]'s\ return\ 7\ and\ keepalive\ idea\ look\ like\ a\ very\ useful\ tweak:\n\n\[jbr\]\ 2011-05-01\ Andy\ has\ offered\ a\ better\ way\ to\ handle\ this\ by\ removing\ the\ socket\ from\ the\ coroutines\ list\ and\ returning\ an\ uncaught\ error.\ \ No\ need\ to\ hack\ Wibble's\ main\ zone\ handler\ body.\n\n======\n\ \ \ \ \ \}\ on\ 7\ outcome\ \{\n\ \ \ \ \ \ \ \ set\ keepalive\ 1\n\ \ \ \ \ \}\ finally\ \{\n\ \ \ \ \ \ \ \ if\ \{\ !\$keepalive\ \}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ catch\ \{chan\ close\ \$socket\}\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \}\n======\n\nBetter\ than\ what\ I'm\ doing\ right\ now,\ which\ is\ to\ do\ an\ \"icc\ get\"\ to\ grab\ control\ over\ the\ socket\ by\ suspending\ the\ co-routine\ indefinitely.\ The\ problem\ with\ that\ is\ that\ I\ always\ get\ an\ error\ on\ socket\ close,\ as\ wibble\ tries\ to\ resume\ and\ send\ a\ response\ to\ the\ (now\ closed)\ socket.\nWhat's\ not\ clear\ to\ me\ is\ whether\ the\ \"return\ 7\"\ also\ causes\ the\ request's\ co-routine\ to\ be\ cleaned\ up\ right\ away\ (seems\ like\ a\ good\ idea).\n\n\[AMG\]:\ The\ \[coroutine\]\ will\ always\ be\ cleaned\ up,\ thanks\ to\ the\ \"finally\"\ clause\ inside\ \[\[process\]\].\ \ The\ only\ way\ to\ avoid\ the\ \"finally\"\ clause\ is\ to\ delete\ the\ current\ coroutine\ command\ (`\[rename\]\ \[\[\[info\ coroutine\]\]\]\ \"\"`)\ then\ `\[yield\]`.\n\nA\ few\ days\ ago\ I\ came\ up\ with\ another\ approach\ that\ I\ prefer\ to\ any\ presented\ on\ this\ page\ or\ the\ \[Wibble\ wish\ list\]:\ define\ a\ new\ key\ in\ the\ response\ \[dict\]\ that\ defines\ a\ custom\ I/O\ handler\ that\ \[\[process\]\]\ will\ execute\ instead\ of\ doing\ its\ normal\ post-\[\[getresponse\]\]\ activities.\ \ This\ way,\ more\ of\ the\ Wibble\ infrastructure\ is\ available\ to\ the\ custom\ code:\ error\ handling,\ automatic\ cleanup,\ and\ the\ ability\ to\ loop\ again\ and\ get\ another\ HTTP\ request\ from\ the\ same\ socket.\n\n----\n2011.1013\ \[jbr\]\ Here\ we\ are\ almost\ a\ year\ later\ with\ an\ update.\n\nWebSocket\ has\ again\ moved\ to\ a\ new\ handshake\ &\ framing.\ \ Here\ is\ a\ zone\ handler\ for\ Andy's\ newest\ wibble\ and\ chrome\ 14\ websockets.\n\n======\n\ package\ require\ sha1\n\ package\ require\ base64\n\n\ #\ Utility\ proc\ to\ frame\ and\ send\ short\ strings\ up\ to\ 126\ chars\n\ #\n\ proc\ ::wibble::ws-send\ \{\ sock\ message\ \}\ \{\n\ \ \ \ puts\ -nonewline\ \$sock\ \[binary\ format\ cc\ 0x81\ \[string\ length\ \$message\]\]\$message\n\ \ \ \ flush\ \$sock\n\ \}\ \n\n\ #\ WebSocket\ handler\ proc\ to\ receive\ short\ (up\ to\ 126\ chars)\ text\ format\ frames\n\ #\n\ proc\ ::wibble::ws-handle\ \{\ handler\ sock\ \}\ \{\n\n\ \ \ \ if\ \{\ \[chan\ eof\ \$sock\]\ \}\ \{\n\ \ \ \ \ \ \ \ close\ \$sock\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ binary\ scan\ \[read\ \$sock\ 1\]\ c\ opcode\n\ \ \ \ \ \ \ \ binary\ scan\ \[read\ \$sock\ 1\]\ c\ length\n\n\ \ \ \ \ \ \ \ set\ opcode\ \[expr\ \$opcode\ &\ 0x0F\]\n\ \ \ \ \ \ \ \ set\ length\ \[expr\ \$length\ &\ 0x7F\]\n\n\ \ \ \ \ \ \ \ binary\ scan\ \[read\ \$sock\ 4\]\ \ \ \ \ \ \ c*\ mask\n\ \ \ \ \ \ \ \ binary\ scan\ \[read\ \$sock\ \$length\]\ c*\ data\n\n\ \ \ \ \ \ \ \ set\ msg\ \{\}\n\ \ \ \ \ \ \ \ set\ i\ \ \ \ 0\n\ \ \ \ \ \ \ \ foreach\ char\ \$data\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ append\ msg\ \[binary\ format\ c\ \[expr\ \{\ \$char^\[lindex\ \$mask\ \[expr\ \{\ \$i%4\ \}\]\]\ \}\]\]\n\ \ \ \ \ \ \ \ \ \ \ \ incr\ i\n\ \ \ \ \ \ \ \ \}\ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \$handler\ message\ \$sock\ \$msg\n\ \ \ \ \}\n\ \}\n\n\ #\ Zone\ handler\n\ #\n\ proc\ ::wibble::websocket\ \{\ state\ \}\ \{\n\ \ \ \ set\ upgrade\ \ \ \ \{\}\n\ \ \ \ set\ connection\ \{\}\n\n\ \ \ \ dict\ with\ state\ request\ header\ \{\}\n\n\ \ \ \ if\ \{\ \$connection\ ne\ \"Upgrade\"\ ||\ \$upgrade\ ne\ \"websocket\"\ \}\ \{\n\ \ \ \ \ \ \ \ return\n\ \ \ \ \}\n\n\ \ \ \ set\ sock\ \[dict\ get\ \$state\ request\ socket\]\n\n\ \ \ \ puts\ \"WebSocket\ Connect:\ \$sock\"\n\n\ \ \ \ set\ response\ \[base64::encode\ \[sha1::sha1\ -bin\ \$\{sec-websocket-key\}258EAFA5-E914-47DA-95CA-C5AB0DC85B11\]\]\n\ \ \ \ set\ handler\ \ \[dict\ get\ \$state\ options\ handler\]\n\n\ \ \ \ puts\ \$sock\ \"HTTP/1.1\ 101\ WebSocket\ Protocol\ Handshake\"\n\ \ \ \ puts\ \$sock\ \"Upgrade:\ \ \ \ websocket\"\n\ \ \ \ puts\ \$sock\ \"Connection:\ Upgrade\"\n\ \ \ \ puts\ \$sock\ \"Sec-WebSocket-Accept:\ \$response\"\n\ \ \ \ puts\ \$sock\ \"\"\n\n\ \ \ \ chan\ configure\ \$sock\ -translation\ binary\n\ \ \ \ chan\ event\ \ \ \ \ \$sock\ readable\ \[list\ ::wibble::ws-handle\ \$handler\ \$sock\]\n\n\ \ \ \ \$handler\ connect\ \$sock\n\n\ \ \ \ return\ -code\ 7\n\ \}\n======\n\nAt\ the\ top\ of\ the\ ::wibble::process\ proc\ I\ initialize\ the\ keepsock\ variable\ and\ modified\ the\ cleanup\ procs\ like\ this:\n\n======\n\ \ \ \ set\ keepsock\ 0\n\ \ \ \ set\ cleanup\ \{\n\ \ \ \ \ \ \ \ \{chan\ close\ \$file\}\n\ \ \ \ \ \ \ \ \{\ if\ \{\ !\$keepsock\ \}\ \{\ chan\ close\ \$socket\ \}\ \}\n\ \ \ \ \ \ \ \ \{dict\ unset\ ::wibble::icc::feeds\ \$coro\}\n\ \ \ \ \}\n======\n\nThen\ I\ added\ a\ clause\ in\ the\ try\ structure\ to\ catch\ the\ return\ -code\ 7\ from\ the\ zone\ handler:\n\n======\n\ \ \ \ \}\ on\ 7\ outcome\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ set\ keepsock\ 1\n\ \ \ \ \}\ finally\ \{\n======\n\nWorks\ for\ me.\ \ \ Thanks\ Andy.\n\n----\n\[AMG\]:\ I\ made\ an\ update\ of\ the\ above\ for\ version\ 2011-11-24\ \[http://wiki.tcl.tk/27384#pagetoc58b50302\].\ \ Thanks\ to\ some\ spiffy\ new\ features\ in\ Wibble,\ it\ doesn't\ require\ any\ core\ modifications.\ \ However,\ I'm\ not\ quite\ ready\ to\ post\ it\ here,\ since\ I\ need\ to\ test\ it.\ \ I\ grabbed\ a\ copy\ of\ Chrome,\ but\ I\ don't\ know\ how\ to\ get\ it\ to\ talk\ to\ WebSocket\ anything.\ \ If\ you\ could\ please\ post\ an\ example\ HTML/JavaScript\ file\ that\ makes\ use\ of\ the\ above,\ I'll\ debug\ what\ I've\ got\ and\ post\ the\ combined\ demo\ here.\n\nA\ more\ flexible\ alternative\ to\ the\ WebSocket\ handler\ proc\ receiving\ the\ event\ (connect\ or\ message)\ as\ its\ first\ argument\ is\ to\ support\ separate\ connect\ and\ message\ handler\ command\ prefixes\ in\ the\ options\ dict.\ \ For\ compatibility\ with\ existing\ handlers,\ the\ connecthandler\ and\ messagehandler\ could\ name\ the\ same\ proc,\ but\ with\ an\ extra\ connect\ or\ message\ argument.\ \ Does\ this\ sound\ like\ a\ worthwhile\ change?\ \ Also,\ how\ about\ a\ disconnect\ handler?\ \ Would\ that\ ever\ have\ any\ value?\ \ The\ ICC\ feed\ mechanism\ already\ has\ a\ lapse\ system\ which\ is\ designed\ to\ detect\ application-level\ user\ disconnects,\ as\ opposed\ to\ protocol-level\ TCP\ socket\ disconnects.\n\nIn\ my\ development\ code,\ I\ got\ rid\ of\ the\ socket\ arguments\ since\ the\ socket\ name\ is\ always\ \[\[\[namespace\ tail\]\ \[\[\[info\ coroutine\]\]\]\]\].\n\nIn\ addition\ to\ the\ max-126-character\ string\ protocol,\ is\ there\ a\ way\ to\ send\ and\ receive\ binary\ data?\ \ Should\ we\ support\ it?\n\n----\n\[AMG\]:\ Disregard\ the\ above,\ it's\ overcome\ by\ events.\ \ I\ redid\ my\ update\;\ it's\ totally\ new\ code\ now.\ \ It\ still\ doesn't\ require\ any\ core\ modifications,\ which\ is\ good,\ and\ I'm\ still\ not\ ready\ to\ post\ it\ here,\ which\ is\ bad.\ \ However,\ I\ did\ test\ it,\ and\ it\ works\ so\ far.\ \ I\ ran\ into\ some\ fundamental\ design\ issues,\ which\ I'll\ discuss\ below.\ \ I\ decided\ against\ separate\ connecthandler\ and\ messagehandler\;\ a\ single\ handler\ works\ well\ enough,\ and\ it\ can\ dispatch\ to\ separate\ procs\ if\ needed.\ \ I\ kept\ the\ disconnect\ event,\ on\ the\ theory\ that\ a\ handler\ might\ want\ an\ opportunity\ to\ clean\ up\ after\ itself.\ \ The\ socket\ arguments\ are\ still\ gone.\ \ I\ added\ support\ for\ binary\ data,\ frames\ longer\ than\ 126\ bytes,\ ping/pong,\ close\ frames,\ fragmentation\ and\ continuation\ frames,\ basically\ everything\ I\ could\ find\ in\ the\ WebSocket\ protocol\ draft\ document\ \[http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17\].\n\nThe\ big\ design\ problem\ derives\ from\ the\ fact\ that\ WebSocket\ doesn't\ require\ the\ client\ and\ server\ to\ take\ turns\ the\ way\ HTTP\ does.\ \ Wibble\ successfully\ models\ HTTP\ with\ this\ main\ loop,\ running\ inside\ a\ \[coroutine\]:\n\n======\nwhile\ \{1\}\ \{\n\ \ \ \ set\ request\ \[getrequest\ \$port\ \$socket\ \$peerhost\ \$peerport\]\n\ \ \ \ set\ response\ \[getresponse\ \$request\]\n\ \ \ \ if\ \{!\[\{*\}\$sendcommand\ \$socket\ \$request\ \$response\]\}\ \{\n\ \ \ \ \ \ \ \ chan\ close\ \$socket\n\ \ \ \ \ \ \ \ break\n\ \ \ \ \}\n\}\n======\n\nWith\ WebSocket,\ both\ sides\ can\ talk\ simultaneously\;\ yet\ this\ is\ done\ with\ only\ a\ single\ TCP\ connection.\ \ This\ does\ not\ map\ well\ to\ Wibble's\ one-coroutine-per-socket\ model,\ shown\ above.\ \ The\ Wibble\ coroutine\ cycles\ between\ three\ states:\ get\ the\ client\ data,\ generate\ the\ response,\ send\ the\ response,\ then\ repeat\ or\ terminate.\n\nLet's\ take\ a\ simple\ example:\ a\ calculator\ with\ a\ clock.\ \ Implement\ these\ two\ features\ as\ separate\ connections,\ and\ there's\ little\ challenge.\ \ But\ multiplex\ them\ together,\ and\ you've\ got\ problems.\ \ The\ client\ should\ be\ able\ to\ send\ a\ new\ math\ expression\ at\ any\ time,\ to\ which\ the\ server\ should\ rapidly\ respond\ with\ the\ result.\ \ Every\ second,\ the\ server\ should\ also\ send\ the\ new\ time,\ without\ the\ client\ asking.\n\nMy\ first\ inclination\ is\ to\ use\ \[\[icc\]\]\ to\ wait\ for\ either\ readability\ or\ timeout.\ \ (\[\[icc\]\]\ can\ also\ be\ used\ to\ wait\ for\ events\ coming\ from\ other\ coroutines,\ but\ that's\ not\ important\ here.)\ \ That\ would\ work,\ if\ not\ for\ the\ problem\ of\ incomplete\ reads.\ \ Reading\ the\ WebSocket\ data\ is\ nontrivial.\ \ You\ have\ to\ read\ the\ first\ two\ bytes\ for\ the\ basic\ header,\ then\ read\ zero,\ two,\ or\ eight\ more\ for\ the\ extended\ length\ (depending\ on\ the\ length\ given\ in\ the\ basic\ header),\ plus\ four\ more\ for\ the\ mask,\ and\ then\ you\ have\ to\ read\ the\ actual\ data.\ \ For\ extra\ joy,\ consider\ that\ a\ message\ can\ be\ fragmented\ across\ multiple\ frames,\ and\ that\ control\ frames\ can\ be\ interleaved\ with\ the\ fragmented\ data\ frames.\ \ Also\ consider\ that\ each\ frame\ can\ be\ up\ to\ 14+2**64\ bytes\ in\ size.\ \ Socket\ readability\ certainly\ does\ not\ guarantee\ that\ the\ entire\ frame\ is\ immediately\ available\ for\ reading.\ \ This\ isn't\ \[UDP\].\ \;^)\n\nSince\ reading\ the\ data\ isn't\ atomic,\ it's\ a\ stateful\ process,\ with\ a\ potential\ wait\ state\ everywhere\ a\ read\ happens.\ \ At\ every\ wait\ state\ the\ system\ needs\ to\ return\ to\ the\ event\ loop\ so\ that\ a\ different\ socket\ can\ be\ serviced.\ \ Sounds\ like\ a\ job\ for\ coroutines.\ \;^)\ \ But...\ what\ if\ the\ other\ operation\ waiting\ to\ be\ processed\ exists\ in\ the\ same\ coroutine\ as\ the\ read?\ \ That\ doesn't\ work.\n\nHere's\ what\ I'm\ considering.\ \ Create\ a\ second\ coroutine\ for\ each\ WebSocket\ connection\ which\ doesn't\ directly\ fit\ the\ HTTP\ turn-taking\ model.\ \ Name\ the\ coroutine\ the\ same\ as\ the\ socket,\ but\ put\ it\ in\ the\ ::wibble::ws\ namespace,\ such\ that\ it's\ separate\ from\ the\ one\ in\ the\ ::wibble\ namespace\ yet\ \[\[namespace\ tail\ \[\[info\ coroutine\]\]\]\]\ is\ still\ the\ socket\ name.\ \ The\ original\ Wibble\ coroutine\ is\ in\ charge\ of\ reading\ the\ client\ data,\ and\ whenever\ a\ message\ is\ received,\ it\ sends\ it\ to\ the\ new\ coroutine\ via\ \[\[icc\]\].\ \ The\ new\ coroutine\ also\ roughly\ follows\ the\ HTTP\ model\ shown\ above,\ but\ with\ \[\[getrequest\]\]\ replaced\ with\ \[\[icc\ get\]\].\ \ This\ way\ it\ aggregates\ messages\ from\ multiple\ sources\ and\ gets\ them\ atomically.\ \ When\ it\ wants\ to\ send,\ it\ writes\ to\ the\ socket.\ \ Meanwhile,\ the\ first\ coroutine\ only\ writes\ to\ the\ socket\ when\ initiating\ and\ tearing\ down\ the\ connection.\n\nTo\ make\ this\ efficient,\ I\ would\ have\ to\ modify\ \[\[icc\]\]\ to\ not\ concatenate\ the\ event\ identification\ and\ data\ together\ into\ a\ single\ string.\ \ Instead,\ the\ payload\ would\ need\ to\ be\ a\ separate\ \[Tcl_Obj\].\ \ (\[AMG\],\ update:\ \[\[icc\]\]\ already\ supports\ arbitrary\ payloads\ in\ addition\ to\ the\ event\ identifier!\ \ No\ change\ needed.)\n\nNow\ I'm\ gonna\ digress\ from\ WebSocket\ in\ the\ interest\ of\ searching\ for\ a\ unified\ architecture,\ since\ I'm\ always\ looking\ for\ ideas.\ :^)\n\nThis\ two-coroutine\ model\ isn't\ necessary\ for\ any\ of\ the\ pure-HTTP\ concepts,\ since\ the\ communication\ direction\ strictly\ alternates.\ \ Imagine\ if\ I\ went\ ahead\ and\ made\ two\ coroutines\ anyway.\ \ They'd\ always\ be\ taking\ turns.\ \ Each\ one\ would\ do\ its\ thing,\ notify\ the\ other\ that\ it's\ done,\ then\ wait\ for\ the\ other\ to\ finish.\ \ Therefore\ they\ might\ as\ well\ just\ be\ a\ single\ coroutine,\ which\ is\ exactly\ what\ I\ have.\n\nActually,\ there\ is\ one\ major\ exception,\ one\ time\ when\ the\ HTTP\ client\ can\ do\ something\ that's\ of\ interest\ to\ the\ server\ even\ though\ it's\ the\ server's\ turn\ to\ talk.\ \ That\ one\ thing\ is:\ disconnect.\ \ In\ an\ AJAX\ long\ polling\ situation,\ the\ server\ can\ hang\ for\ minutes\ at\ a\ time\ waiting\ for\ there\ to\ be\ something\ worthwhile\ to\ report.\ \ During\ that\ time,\ the\ client\ can\ disconnect,\ but\ the\ server\ wouldn't\ be\ able\ to\ tell\ until\ it\ tries\ to\ read\ from\ the\ client.\ \ Of\ course,\ the\ server\ ''can't''\ try\ to\ read\ until\ after\ it\ sends\ something.\ \ My\ current\ solution\ is\ to\ have\ the\ server\ periodically\ send\ a\ no-op,\ just\ as\ an\ excuse\ to\ check\ if\ the\ client\ is\ still\ alive.\ \ If\ I\ had\ a\ two-coroutine\ model\ for\ AJAX\ long\ polling,\ the\ client\ read\ coroutine\ could\ always\ be\ on\ the\ lookout\ for\ client\ disconnect\;\ when\ it\ happens,\ it\ can\ use\ \[\[icc\]\]\ to\ let\ the\ other\ coroutine\ know\ it's\ quitting\ time.\ \ (More\ likely,\ it'll\ use\ \[\[icc\]\]\ to\ let\ all\ the\ other\ coroutines\ know\ that\ the\ one\ client\ died,\ and\ it'll\ just\ directly\ terminate\ the\ coroutines\ associated\ with\ the\ socket.)\n\nHowever,\ there's\ still\ a\ problem:\ unclean\ disconnects.\ \ If\ the\ client\ makes\ a\ request,\ it's\ now\ the\ server's\ turn\ to\ respond.\ \ While\ the\ server\ is\ hanging\ out\ waiting\ for\ something\ worthwhile\ to\ happen,\ the\ client\ sends\ some\ more\ data.\ \ The\ only\ way\ the\ read\ coroutine\ can\ tell\ the\ difference\ between\ the\ client\ sending\ data\ and\ the\ client\ disconnecting\ is\ to\ try\ to\ read\ from\ the\ client,\ so\ that's\ what\ it\ does.\ \ It\ can't\ do\ this\ without\ bound,\ so\ eventually\ it'll\ have\ to\ stop\ reading.\ \ At\ this\ point,\ the\ read\ coroutine\ again\ loses\ its\ ability\ to\ know\ when\ the\ client\ disconnects.\ \ One\ possible\ solution\ is\ to\ read\ without\ bound,\ but\ that\ opens\ up\ a\ \[DoS\]\ attack.\ \ Another\ possible\ solution\ is\ to\ forcibly\ disconnect\ the\ client\ if\ it\ sends\ too\ much\ data\ out-of-turn,\ but\ this\ interferes\ with\ HTTP\ pipelining.\ \ Everything\ comes\ down\ to\ the\ fact\ that\ the\ portable\ C\ I/O\ API\ doesn't\ have\ an\ out-of-band\ notification\ of\ client\ disconnects.\ \ So,\ I\ can't\ think\ a\ solution\ solid\ enough\ to\ justify\ the\ complexity\ of\ adding\ another\ coroutine,\ and\ I\ strongly\ lean\ against\ adopting\ a\ two-coroutine\ model\ for\ the\ Wibble\ core.\ \ But\ I'm\ definitely\ open\ to\ suggestions!\n\n----\n\[jbr\]\ 2011-11-26\ Andy,\ my\ thinking\ here\ is\ that\ once\ you've\ gotten\ an\ accept\ via\ the\ websocket\ zone\ handler\ then\ you\ just\ have\ a\ socket.\ \ You\ can\ use\ icc\ if\ you\ like\ or\ you\ can\ just\ register\ a\ fileevent.\ \ There\ is\ no\ reason\ to\ tie\ it\ to\ any\ other\ wibble\ infrastructure\ or\ HTTP\ concepts,\ its\ just\ a\ socket\ connected\ to\ some\ javascript\ in\ a\ web\ browser.\ \ The\ wibble\ co-routine\ should\ just\ return\ without\ closing\ the\ socket.\n\nP.S.\ \ -\ Thanks\ for\ coding\ up\ the\ rest\ of\ the\ message\ encoding.\ \ I've\ just\ been\ passing\ simple\ events\ to\ my\ server\ from\ user\ interactions\ with\ forms,\ but\ completeness\ is\ a\ good\ thing.\n\n\[AMG\]:\ But\ be\ careful,\ the\ WebSocket\ code\ is\ running\ inside\ the\ same\ thread\ of\ execution\ as\ the\ rest\ of\ the\ web\ server.\ \ The\ code\ you\ posted\ above\ is\ extremely\ easy\ to\ \[DoS\].\ \ All\ you\ have\ to\ do\ is\ perform\ a\ valid\ WebSocket\ handshake,\ then\ send\ one\ byte\ and\ one\ byte\ only.\ \ The\ code\ will\ block\ indefinitely\ waiting\ for\ the\ rest\ of\ the\ WebSocket\ header.\ \ During\ that\ time,\ the\ rest\ of\ the\ server\ is\ blocked\ too.\ \ Using\ \[\[getblock\]\]\ instead\ of\ \[\[\[read\]\]\]\ solves\ the\ problem,\ since\ it\ \[yield\]s\ rather\ than\ block\ the\ whole\ process.\ \ So\ hey,\ there's\ a\ case\ where\ the\ Wibble\ infrastructure\ is\ useful\ inside\ WebSocket\ code.\n\n\[jbr\]\ -\ I\ can't\ argue\ with\ that\ using\ \[\[getblock\]\]\ (or\ something\ like\ it)\ isn't\ a\ good\ idea.\ \ I'm\ just\ suggesting\ that\ the\ web\ socket\ zone\ handler\ allow\ the\ application\ specific\ code\ do\ its\ own\ thing\ an\ not\ impose\ a\ framework\ that\ might\ not\ fit\ so\ well.\ \ What\ if\ I\ want\ to\ hand\ the\ socket\ to\ a\ 3rd\ party\ library\ that\ already\ handles\ a\ web\ socket\ connection\ in\ its\ own\ code?\n\n\[AMG\]:\ Tonight\ I\ hope\ to\ post\ the\ new\ code\ I've\ got\ so\ you\ can\ better\ see\ what\ I\ have\ in\ mind.\ \ I\ don't\ plan\ for\ WebSocket\ to\ be\ integral\ to\ Wibble,\ especially\ if\ that\ means\ its\ design\ can\ constrain\ the\ application.\ \ Rather,\ WebSocket\ should\ be\ an\ add-on\ product,\ mostly\ as\ a\ demonstration\ of\ Wibble's\ flexibility.\ \ If\ implementing\ WebSocket\ cleanly\ requires\ a\ change\ to\ Wibble,\ I'll\ make\ that\ change,\ but\ I'll\ do\ so\ in\ a\ generic\ enough\ fashion\ that\ it'll\ also\ work\ for\ \[Server-Sent\ Events\]\ or\ other\ connection\ upgrades.\ \ If\ an\ interesting\ design\ idea\ crops\ up\ in\ the\ course\ of\ implementing\ WebSocket\ or\ any\ other\ zone\ handler\ or\ application,\ I'll\ consider\ adopting\ it\ (or\ its\ cousin)\ into\ Wibble\ proper.\ \ But\ I'm\ not\ going\ to\ change\ Wibble's\ scope\ in\ the\ process.\ \ \[\[::wibble::process\]\]\ shall\ forever\ deal\ only\ in\ HTTP,\ and\ if\ an\ application\ wants\ to\ switch\ from\ HTTP\ to\ something\ else,\ it\ should\ do\ so\ using\ the\ sendcommand\ response\ dict\ entry,\ to\ which\ \[\[process\]\]\ will\ hand\ over\ control.\n\nHere's\ the\ relationship\ between\ Wibble\ and\ WebSocket.\ \ This\ is\ the\ same\ relationship\ as\ between\ Wibble\ and\ other\ zone\ handlers,\ except\ (1)\ Wibble\ doesn't\ attempt\ to\ send\ anything\ to\ the\ client,\ and\ (2)\ the\ WebSocket\ code\ does\ all\ reads\ after\ the\ initial\ HTTP-like\ handshake.\n\n\ \ \ *\ Wibble\ handles\ all\ the\ HTTP\ parts,\ especially\ the\ rest\ of\ the\ web\ site.\n\ \ \ *\ All\ the\ coroutines\ exist\ in\ the\ same\ process\ and\ have\ to\ take\ care\ to\ not\ block\ each\ other.\n\ \ \ *\ Everything\ leading\ up\ to\ the\ initial\ upgrade\ is\ handled\ by\ Wibble.\n\ \ \ *\ The\ socket\ and\ its\ control\ coroutine\ are\ created\ by\ Wibble.\n\ \ \ *\ The\ Wibble\ error\ logging\ system\ wraps\ around\ the\ user\ code.\n\ \ \ *\ Wibble\ closes\ the\ socket\ when\ everything's\ done.\n\ \ \ *\ The\ Wibble\ I/O,\ log,\ panic,\ formatting,\ cleanup,\ and\ \[\[icc\]\]\ procedures\ are\ available\ for\ use.\n\nI\ do\ have\ a\ framework\ in\ mind\ for\ WebSocket.\ \ It\ does\ resemble\ Wibble\ in\ some\ ways,\ but\ only\ because\ I\ think\ it's\ a\ sensible\ approach.\ :^)\ \ The\ fact\ is\ that\ it\ also\ resembles\ the\ code\ you\ started\ with\;\ I\ just\ beefed\ it\ up\ for\ completeness\ and\ to\ avoid\ the\ DoS\ problem\ mentioned\ above.\n\n<<categories>>\ Webserver regexp2} CALL {my render WebSocket WebSockets\ are\ a\ nice\ alternative\ to\ XMLHTTPRequest\ for\ bi-directional\ communication\ between\ a\ web\ browser\ and\ a\ server\ application.\ They\ need\ a\ browser,\ e.g.\ Chrome,\ that\ supports\ the\ WebSocket\ API\ and\ a\ server\ that\ supports\ the\ WebSocket\ protocol.\n\n**See\ also**\n\n\ \ \ *\ https://github.com/bovine/tclwebsockets\ —\ Tcl\ bindings\ for\ a\ fork\ of\ \[https://libwebsockets.org/trac/libwebsockets%|%libwebsockets\].\n\ \ \ *\ https://bitbucket.org/naviserver/websocket\ —\ WebSockets\ for\ \[NaviServer\]\n\ \ \ *\ http://websocketd.com/\ —\ A\ \[CGI\]-style\ WebSocket\ wrapper\ for\ scripts\ that\ read\ from\ STDIN\ and\ write\ to\ STDOUT.\n\n**WebSocket\ for\ \[Wibble\]**\n\n''\[AMG\]:\ I\ moved\ the\ older\ discussion\ to\ the\ bottom\ of\ the\ page\ \[http://wiki.tcl.tk/26556#pagetoc6334fd30\]\ so\ the\ current\ code\ can\ be\ right\ at\ the\ top.''\n\n\[AMG\]:\ Here's\ the\ code.\ \ It\ requires\ the\ latest\ Wibble\ \[http://wiki.tcl.tk/_/revision?N=27377.code&V=28\].\n\n\[EF\]:\ Note\ that\ a\ complete\ client\ library,\ which\ code\ inherits\ from\ the\ implementation\ below,\ is\ available\ in\ \[WebSocket\ Client\ Library\].\ \ The\ library\ is\ also\ able\ to\ provide\ protocol\ framing\ support\ within\ a\ Web\ server,\ if\ necessary.\n\n\[dzach\]:\ 2013-05-03\ Any\ reason\ why\ the\ ''query''\ key\ is\ not\ included\ in\ the\ ''request''\ dict?\n\n\[AMG\]:\ I\ don't\ see\ why\ it\ wouldn't\ be.\ \ ''query''\ and\ ''rawquery''\ are\ set\ very\ near\ the\ top\ of\ \[\[getrequest\]\].\ \[dzach\]\ You\ are\ right.\ Strangely\ enough,\ my\ working\ copy\ of\ wibble,\ which\ I\ thought\ was\ \"clean\",\ doesn't,\ but\ a\ fresh\ one\ I\ just\ ran\ ''does''\ include\ the\ query\ in\ the\ request\ dict.\n\n======\npackage\ require\ sha1\n\n#\ Define\ the\ ::wibble::ws\ namespace.\nnamespace\ eval\ ::wibble::ws\ \{\n\ \ \ \ namespace\ path\ ::wibble\n\}\n\n#\ Send\ a\ WebSocket\ frame.\nproc\ ::wibble::ws::send\ \{type\ \{msg\ \"\"\}\ \{final\ 1\}\}\ \{\n\ \ \ \ log\ \"\[info\ coroutine\]\ Tx\ \$type\ \$msg\"\n\n\ \ \ \ #\ Compute\ the\ opcode.\ \ The\ opcode\ is\ zero\ for\ continuation\ frames.\n\ \ \ \ upvar\ #1\ fragment\ fragment\n\ \ \ \ if\ \{\[info\ exists\ fragment\]\}\ \{\n\ \ \ \ \ \ \ \ set\ opcode\ 0\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ set\ opcode\ \[dict\ get\ \{text\ 1\ binary\ 2\ ping\ 9\}\ \$type\]\n\ \ \ \ \}\n\ \ \ \ if\ \{!\$final\}\ \{\n\ \ \ \ \ \ \ \ set\ fragment\ \"\"\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ unset\ -nocomplain\ fragment\n\ \ \ \ \}\n\n\ \ \ \ #\ Encode\ text.\n\ \ \ \ if\ \{\$type\ eq\ \"text\"\}\ \{\n\ \ \ \ \ \ \ \ set\ msg\ \[encoding\ convertto\ utf-8\ \$msg\]\n\ \ \ \ \}\n\n\ \ \ \ #\ Assemble\ the\ header.\n\ \ \ \ set\ header\ \[binary\ format\ c\ \[expr\ \{!!\$final\ <<\ 7\ |\ \$opcode\}\]\]\n\ \ \ \ if\ \{\[string\ length\ \$msg\]\ <\ 126\}\ \{\n\ \ \ \ \ \ \ \ append\ header\ \[binary\ format\ c\ \[string\ length\ \$msg\]\]\n\ \ \ \ \}\ elseif\ \{\[string\ length\ \$msg\]\ <\ 65536\}\ \{\n\ \ \ \ \ \ \ \ append\ header\ \\x7e\[binary\ format\ Su\ \[string\ length\ \$msg\]\]\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ append\ header\ \\x7f\[binary\ format\ Wu\ \[string\ length\ \$msg\]\]\n\ \ \ \ \}\n\n\ \ \ \ #\ Send\ the\ frame.\n\ \ \ \ set\ socket\ \[namespace\ tail\ \[info\ coroutine\]\]\n\ \ \ \ chan\ puts\ -nonewline\ \$socket\ \$header\$msg\n\ \ \ \ chan\ flush\ \$socket\n\}\n\n#\ Close\ the\ current\ WebSocket\ connection.\nproc\ ::wibble::ws::close\ \{\{reason\ \"\"\}\ \{description\ \"\"\}\}\ \{\n\ \ \ \ icc\ put\ ::wibble::\[namespace\ tail\ \[info\ coroutine\]\]\ exception\ close\\\n\ \ \ \ \ \ \ \ \$reason\ \$description\n\}\n\n#\ WebSocket\ analogue\ of\ \[::wibble::process\]\nproc\ ::wibble::ws::process\ \{state\ socket\ request\ response\}\ \{\n\ \ \ \ #\ Get\ configuration\ options.\n\ \ \ \ if\ \{!\[dict\ exists\ \$state\ options\ maxlength\]\}\ \{\n\ \ \ \ \ \ \ \ set\ maxlength\ 16777216\n\ \ \ \ \}\ elseif\ \{\[dict\ get\ \$state\ options\ maxlength\]\ eq\ \"\"\}\ \{\n\ \ \ \ \ \ \ \ set\ maxlength\ 18446744073709551615\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ set\ maxlength\ \[dict\ get\ \$state\ options\ maxlength\]\n\ \ \ \ \}\n\n\ \ \ \ #\ Create\ WebSocket\ event\ handler\ wrapper\ coroutine.\n\ \ \ \ set\ fid\ \[namespace\ current\]::\$socket\n\ \ \ \ cleanup\ ws_unset_feed\ \[list\ icc\ destroy\ \$fid\]\n\ \ \ \ icc\ configure\ \$fid\ accept\ connect\ disconnect\ text\ binary\ close\n\ \ \ \ coroutine\ \$socket\ apply\ \{\{handler\ state\}\ \{\n\ \ \ \ \ \ \ \ try\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ while\ \{1\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ foreach\ event\ \[icc\ get\ \[info\ coroutine\]\ *\]\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \{*\}\$handler\ \$state\ \{*\}\$event\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[lindex\ \$event\ 0\]\ eq\ \"disconnect\"\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \}\ on\ error\ \{\"\"\ options\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ panic\ \$options\ \"\"\ \"\"\ \"\"\ \"\"\ \[dict\ get\ \$state\ request\]\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \[dict\ get\ \$state\ response\]\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\ ::wibble::zone\}\ \[dict\ get\ \$state\ options\ handler\]\ \$state\n\n\ \ \ \ #\ Respond\ to\ WebSocket\ handshake.\n\ \ \ \ chan\ puts\ \$socket\ \"HTTP/1.1\ 101\ WebSocket\ Protocol\ Handshake\"\n\ \ \ \ chan\ puts\ \$socket\ \"upgrade:\ websocket\"\n\ \ \ \ chan\ puts\ \$socket\ \"connection:\ upgrade\"\n\ \ \ \ chan\ puts\ \$socket\ \"sec-websocket-accept:\ \[binary\ encode\ base64\ \[sha1::sha1\ -bin\\\n\ \ \ \ \ \ \ \ \[dict\ get\ \$request\ header\ sec-websocket-key\\\n\ \ \ \ \ \ \ \ \]258EAFA5-E914-47DA-95CA-C5AB0DC85B11\]\]\"\n\ \ \ \ chan\ puts\ \$socket\ \"\"\n\ \ \ \ chan\ flush\ \$socket\n\ \ \ \ chan\ configure\ \$socket\ -translation\ binary\n\n\ \ \ \ #\ It's\ necessary\ to\ bypass\ \[icc\ put\]\ in\ this\ one\ case,\ because\ it\ defers\n\ \ \ \ #\ event\ delivery\ when\ called\ from\ a\ coroutine.\ \ Consequentially,\ before\ it\n\ \ \ \ #\ would\ attempt\ to\ send\ the\ event,\ the\ feed\ will\ be\ destroyed.\n\ \ \ \ cleanup\ ws_disconnect\ \[list\ \$fid\ disconnect\]\n\n\ \ \ \ #\ Invoke\ connect\ handler.\n\ \ \ \ icc\ put\ \$fid\ connect\n\n\ \ \ \ set\ reason\ 1000\n\ \ \ \ try\ \{\n\ \ \ \ \ \ \ \ foreach\ event\ \[icc\ catch\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ #\ Main\ loop.\n\ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ set\ mode\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ while\ \{1\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Get\ basic\ header.\ \ Abort\ if\ reserved\ bits\ are\ set,\ mask\ bit\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ isn't\ set,\ unexpected\ continuation\ frame,\ fragmented\ or\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ oversized\ control\ frame,\ or\ the\ opcode\ is\ unrecognized.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[getblock\ 2\]\ Su\ header\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ opcode\ \[expr\ \{\$header\ >>\ 8\ &\ 0xf\}\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ len\ \[expr\ \{\$header\ &\ 0x7f\}\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{(\$header\ &\ 0x7080\ ^\ 0x80)\ ||\ (\$opcode\ ==\ 0\ &&\ \$mode\ eq\ \"\")\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ||\ (\$opcode\ >\ 7\ &&\ (!(\$header\ &\ 0x8000)\ ||\ \$len\ >\ 125))\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ||\ \$opcode\ ni\ \{0\ 1\ 2\ 8\ 9\ 10\}\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Send\ close\ frame,\ reason\ 1002:\ protocol\ error.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ reason\ 1002\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ break\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Determine\ the\ effective\ opcode\ for\ this\ frame.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$mode\ eq\ \"\"\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ mode\ \$opcode\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ elseif\ \{\$opcode\ ==\ 0\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ opcode\ \$mode\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Get\ the\ extended\ length,\ if\ present.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$len\ ==\ 126\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[getblock\ 2\]\ Su\ len\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ elseif\ \{\$len\ ==\ 127\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[getblock\ 8\]\ Wu\ len\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Limit\ the\ maximum\ message\ length.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[string\ length\ \$msg\]\ +\ \$len\ >\ \$maxlength\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Send\ close\ frame,\ reason\ 1009:\ frame\ too\ big.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ reason\ \[list\ 1009\ \"limit\ \$maxlength\ bytes\"\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ break\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Use\ an\ alternate\ message\ buffer\ for\ control\ frames.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$opcode\ >\ 7\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ oldmsg\ \$msg\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Get\ mask\ and\ data.\ \ Format\ data\ as\ a\ list\ of\ 32-bit\ integer\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ words\ and\ list\ of\ 8-bit\ integer\ byte\ leftovers.\ \ Then\ unmask\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ data,\ recombine\ the\ words\ and\ bytes,\ and\ append\ to\ the\ buffer.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[getblock\ \[expr\ \{4\ +\ \$len\}\]\]\ II*c*\ mask\ words\ bytes\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ for\ \{set\ i\ 0\}\ \{\$i\ <\ \[llength\ \$words\]\}\ \{incr\ i\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ lset\ words\ \$i\ \[expr\ \{\[lindex\ \$words\ \$i\]\ ^\ \$mask\}\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ for\ \{set\ i\ 0\}\ \{\$i\ <\ \[llength\ \$bytes\]\}\ \{incr\ i\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ lset\ bytes\ \$i\ \[expr\ \{\[lindex\ \$bytes\ \$i\]\ ^\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (\$mask\ >>\ (24\ -\ 8\ *\ \$i))\}\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ append\ msg\ \[binary\ format\ I*c*\ \$words\ \$bytes\]\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ If\ FIN\ bit\ is\ set,\ process\ the\ frame.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$header\ &\ 0x8000\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ switch\ \$opcode\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 1\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Text:\ decode\ and\ notify\ handler.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ icc\ put\ \$fid\ text\ \[encoding\ convertfrom\ utf-8\ \$msg\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ 2\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Binary:\ notify\ handler\ without\ decoding.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ icc\ put\ \$fid\ binary\ \$msg\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ 8\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Close:\ decode,\ handle,\ send\ close\ frame,\ terminate.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[string\ length\ \$msg\]\ >=\ 2\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ binary\ scan\ \[string\ range\ \$msg\ 0\ 1\]\ Su\ reason\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ icc\ put\ \$fid\ close\ \$reason\ \[encoding\ convertfrom\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ utf-8\ \[string\ range\ \$msg\ 2\ end\]\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ icc\ put\ \$fid\ close\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ reason\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ break\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ 9\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Ping:\ send\ pong\ to\ client,\ don't\ notify\ handler.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ chan\ puts\ -nonewline\ \$socket\ \\x8a\[binary\ format\ c\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \[string\ length\ \$msg\]\]\$msg\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ chan\ flush\ \$socket\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Prepare\ for\ the\ next\ frame.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if\ \{\$opcode\ <\ 8\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Data\ frame:\ reinitialize\ parser.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ mode\ \"\"\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Control\ frame:\ restore\ previous\ message\ buffer.\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \$oldmsg\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \}\]\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ #\ Catch\ exception\ events\ and\ translate\ them\ into\ close\ reason\ codes.\n\ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[lrange\ \$event\ 0\ 1\]\ eq\ \{exception\ close\}\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ reason\ \[lrange\ \$event\ 2\ 3\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ break\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\ on\ error\ \{result\ options\}\ \{\n\ \ \ \ \ \ \ \ #\ Default\ error\ close\ reason.\n\ \ \ \ \ \ \ \ set\ reason\ \{1001\ \"internal\ server\ error\"\}\n\ \ \ \ \ \ \ \ return\ -options\ \$options\ \$result\n\ \ \ \ \}\ finally\ \{\n\ \ \ \ \ \ \ \ #\ Send\ close\ frame\ with\ reason,\ if\ one\ was\ given.\n\ \ \ \ \ \ \ \ catch\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ if\ \{\[llength\ \$reason\]\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ msg\ \[string\ range\ \[binary\ format\ Su\ \[lindex\ \$reason\ 0\]\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \]\[encoding\ convertto\ utf-8\ \[lindex\ \$reason\ 1\]\]\ 0\ 124\]\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ chan\ puts\ -nonewline\ \$socket\ \\x88\[binary\ format\ c\\\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \[string\ length\ \$msg\]\]\$msg\n\ \ \ \ \ \ \ \ \ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ chan\ puts\ -nonewline\ \$socket\ \\x88\\x00\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\n\n\ \ \ \ #\ Ask\ Wibble\ to\ close\ the\ connection.\n\ \ \ \ return\ 0\n\}\n\n#\ WebSocket\ upgrade\ zone\ handler.\nproc\ ::wibble::zone::websocket\ \{state\}\ \{\n\ \ \ \ set\ header\ \[dict\ get\ \$state\ request\ header\]\n\ \ \ \ if\ \{\[dict\ exists\ \$header\ sec-websocket-key\]\n\ \ \ \ \ &&\ (\[dict\ exists\ \$header\ sec-websocket-origin\]\ ||\ \[dict\ exists\ \$header\ origin\])\n\ \ \ \ \ &&\ \[dict\ exists\ \$header\ sec-websocket-version\]\n\ \ \ \ \ &&\ \[dict\ exists\ \$header\ connection\]\n\ \ \ \ \ &&\ \[dict\ exists\ \$header\ upgrade\]\n\ \ \ \ \ &&\ \[lsearch\ -nocase\ \[dict\ get\ \$header\ connection\]\ upgrade\]\ !=\ -1\n\ \ \ \ \ &&\ \[string\ equal\ -nocase\ \[dict\ get\ \$header\ upgrade\]\ websocket\]\}\ \{\n\ \ \ \ \ \ \ \ sendresponse\ \[list\ nonhttp\ 1\ sendcommand\ \[list\ ws::process\ \$state\]\]\n\ \ \ \ \}\n\}\n======\n\[sbron\]\ 2013-10-27:\ I\ have\ removed\ the\ dependency\ on\ the\ base64\ package.\ Wibble\ requires\ Tcl\ 8.6\ to\ run\ and\ 8.6\ has\ base64\ encoding/decoding\ built-in.\ So\ it\ seemed\ silly\ to\ use\ an\ external\ package\ for\ that.\n\n\[AMG\]:\ Agreed,\ thank\ you.\n\n\[dzach\]\ 26-12-2012:\ It\ looks\ like\ Firefox\ does\ not\ send\ the\ \"`sec-websocket-origin`\"\ header.\ I\ have\ not\ dug\ into\ the\ details\ but\ it\ seems\ to\ me\ that\ the\ \"`origin`\"\ header\ should\ serve\ the\ same\ purpose,\ so,\ for\ the\ above\ WebSocket\ zone\ handler\ to\ work\ with\ Firefox\ (and\ Opera),\ a\ check\ for\ the\ \"`origin`\"\ header\ can\ be\ added:\n\n======\nproc\ ::wibble::zone::websocket\ state\ \{\n\ \ set\ header\ \[dict\ get\ \$state\ request\ header\]\n\ \ if\ \{\[dict\ exists\ \$header\ sec-websocket-key\]\n\ \ \ \ &&\ (\[dict\ exists\ \$header\ sec-websocket-origin\]\ ||\ \[dict\ exists\ \$header\ origin\])\n\ \ \ \ &&\ \[dict\ exists\ \$header\ sec-websocket-version\]\n\ \ \ \ &&\ \[dict\ exists\ \$header\ connection\]\n\ \ \ \ &&\ \[dict\ exists\ \$header\ upgrade\]\n\ \ \ \ &&\ \[lsearch\ -nocase\ \[dict\ get\ \$header\ connection\]\ upgrade\]\ !=\ -1\n\ \ \ \ &&\ \[string\ equal\ -nocase\ \[dict\ get\ \$header\ upgrade\]\ websocket\]\}\ \{\n\ \ \ \ \ \ sendresponse\ \[list\ nonhttp\ 1\ sendcommand\ \[list\ ws::process\ \$state\]\]\n\ \ \}\n\}\n======\n\n\[UKo\]\ 2015-11-22:\ `sec-websocket-origin`\ is\ no\ longer\ part\ of\ the\ WebSocket\ standard\ (now\ part\ of\ the\ living\ html\ standard\ \[https://html.spec.whatwg.org/multipage/comms.html#network\]).\ The\ last\ version\ it\ was\ mentioned\ is\ 10\ \[https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10\]\ and\ it\ was\ removed\ in\ 11\ \[https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-11\].\n\nHere's\ an\ example\ WebSocket\ handler:\n\n======\nproc\ ::wibble::zone::ws-demo\ \{state\ event\ args\}\ \{\n\ \ \ \ upvar\ #1\ cancel\ cancel\n\n\ \ \ \ log\ \"\[info\ coroutine\]\ Rx\ \$event\ \$args\"\n\n\ \ \ \ #\ Perform\ initialization\ and\ cleanup.\n\ \ \ \ if\ \{\$event\ eq\ \"connect\"\}\ \{\n\ \ \ \ \ \ \ \ icc\ configure\ \[info\ coroutine\]\ accept\ tick\n\ \ \ \ \}\ elseif\ \{\$event\ eq\ \"disconnect\"\}\ \{\n\ \ \ \ \ \ \ \ after\ cancel\ \$cancel\n\ \ \ \ \}\n\n\ \ \ \ #\ Process\ timer\ and\ text\ events.\n\ \ \ \ if\ \{\$event\ in\ \{connect\ tick\}\}\ \{\n\ \ \ \ \ \ \ \ set\ cancel\ \[after\ 1000\ \[list\ ::wibble::icc\ put\ \[info\ coroutine\]\ tick\]\]\n\ \ \ \ \ \ \ \ ws::send\ text\ \"time\ \[clock\ format\ \[clock\ seconds\]\]\"\n\ \ \ \ \}\ elseif\ \{\$event\ eq\ \"text\"\}\ \{\n\ \ \ \ \ \ \ \ if\ \{\[lindex\ \$args\ 0\]\ eq\ \"close\"\}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ ws::close\n\ \ \ \ \ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ ws::send\ text\ \"expr\ \[expr\ \[lindex\ \$args\ 0\]\]\"\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\n\}\n======\n\nAlso,\ put\ \"`::wibble::handle\ /ws-demo\ websocket\ handler\ ws-demo`\"\ at\ the\ top\ of\ your\ zone\ handler\ list.\ \ And\ here's\ a\ demo\ HTML5\ file,\ usable\ in\ \[Chrome\]:\n\n\[UKo\]\ 2015-11-23:\ For\ me\ this\ demo\ works\ in\ all\ tested\ modern\ browsers:\n\n\ \ \ *\ Firefox\ 42\n\ \ \ *\ Chromium\ 42\n\ \ \ *\ Safari\ (iOS\ 7)\n\ \ \ *\ Android\ Chrome\n\n======html\n<!DOCTYPE\ html>\n<html\ lang=\"en\">\n<body>\n\ \ <table>\n\ \ <tr><th>State</th><td\ id=\"state\">initializing</td></tr>\n\ \ <tr><th>Time</th><td\ id=\"time\">???</td></tr>\n\ \ <tr><th>Result</th><td\ id=\"result\">???</td></tr>\n\ \ <tr><th>Expression</th><td><form>\n\ \ \ \ <input\ type=\"text\"\ id=\"expression\"\ />\n\ \ </form></td></tr>\n<script>\nvar\ ws\ =\ new\ WebSocket(\"ws://\"+window.location.host+\"/ws-demo\")\;\nvar\ state\ =\ document.getElementById(\"state\")\;\nvar\ time\ =\ document.getElementById(\"time\")\;\nvar\ result\ =\ document.getElementById(\"result\")\;\nvar\ expression\ =\ document.getElementById(\"expression\")\;\nvar\ sendcount\ =\ 0\;\nvar\ receivecount\ =\ 0\;\n\nws.onopen\ =\ function\ ()\ \{\n\ \ state.innerHTML\ =\ \"connected\"\;\n\}\;\n\nws.onmessage\ =\ function\ (evt)\ \{\n\ \ receivecount\ +=\ 1\;\n\ \ state.innerHTML\ =\ \"sent\ \"\ +\ sendcount\ +\ \",\ received\ \"\ +\ receivecount\;\n\ \ if\ (evt.data.substr(0,\ 4)\ ==\ \"time\")\ \{\n\ \ \ \ time.innerHTML\ =\ evt.data.substr(5)\;\n\ \ \}\ else\ if\ (evt.data.substr(0,\ 4)\ ==\ \"expr\")\ \{\n\ \ \ \ result.innerHTML\ =\ evt.data.substr(5)\;\n\ \ \}\n\}\;\n\nws.onclose\ =\ function\ (evt)\ \{\n\ \ state.innerHTML\ =\ \"disconnected\"\;\n\}\;\n\nexpression.form.addEventListener(\"submit\",\ function\ (evt)\ \{\n\ \ evt.preventDefault()\;\n\ \ if\ (ws.readyState\ ===\ 1)\ \{\n\ \ \ \ ws.send(expression.value)\;\n\ \ \ \ expression.value\ =\ \"\"\;\n\ \ \ \ sendcount\ +=\ 1\;\n\ \ \ \ state.innerHTML\ =\ \"sent\ \"\ +\ sendcount\ +\ \",\ received\ \"\ +\ receivecount\;\n\ \ \}\n\ \ return\ false\;\n\})\;\n</script>\n</body>\n</html>\n======\n\nAs\ promised,\ this\ demo\ incorporates\ both\ a\ clock\ and\ a\ calculator.\ \ I\ did\ this\ to\ better\ exercise\ the\ asynchronous\ nature\ of\ WebSocket.\ \ Events\ come\ both\ from\ the\ client\ and\ from\ the\ timer.\n\nNow\ that\ I\ know\ the\ \[\[\[tailcall\]\]\]\ trick\ \[http://wiki.tcl.tk/1507#pagetocc0434a60\],\ I'm\ considering\ writing\ an\ \[\[icc\]\]\ utility\ \[proc\]\ or\ two\ for\ the\ purpose\ of\ easing\ event\ handler\ dispatch.\ \ It/they\ would\ wrap\ around\ this\ idiom,\ which\ I've\ had\ to\ write\ more\ than\ once\ in\ my\ own\ code:\n\n======\nforeach\ event\ \[icc\ get\ \$feed\ \{*\}\$filters\]\ \{\n\ \ \ switch\ -glob\ \[lindex\ \$event\ 0\]\ \{\n\ \ \ \ \ \ filter/*/whatever\ \{puts\ 1\}\n\ \ \ \ \ \ filter2/whatever/*\ \{puts\ 2\}\n\ \ \ \ \ \ etc\ \{puts\ 3\}\n\ \ \ \}\n\}\n======\n\nOr\ in\ the\ case\ of\ the\ above\ WebSocket\ code,\ replace\ `\[\[icc\ get\ \$feed\ \{*\}\$filters\]\]`\ with\ `\[\[icc\ catch\ \$script\]\]`.\n\n======\nicc\ get2\ \$feed\ \$filters\\\n\ \ \ \ filter/*/whatever\ event\ \{puts\ \[lindex\ \$event\ 1\]\}\\\n\ \ \ \ filter2/whatever/*\ \"\"\ \{puts\ 2\}\\\n\ \ \ \ etc\ \"\"\ \{puts\ 3\}\n\nicc\ catch2\ \$script\\\n\ \ \ \ filter/*/whatever\ event\ \{puts\ \[lindex\ \$event\ 1\]\}\\\n\ \ \ \ filter2/whatever/*\ \"\"\ \{puts\ 2\}\\\n\ \ \ \ etc\ \"\"\ \{puts\ 3\}\\\n\ \ \ \ exception\ \"\"\ \{puts\ 4\}\n======\n\nThe\ name(s)\ and\ syntax\ are\ definitely\ subject\ to\ change.\ \ Suggestions?\ \ Here\ are\ some\ ideas:\n\n\ \ \ *\ Move\ the\ event\ variable\ name\ from\ each\ handler\ to\ a\ common\ option.\n\ \ \ *\ Add\ an\ option\ to\ split\ the\ event\ identifier\ to\ a\ list.\n\ \ \ **\ \[\[\[split\]\]\]\ characters?\ \ Good\ for\ the\ \"/\"\ convention\ I\ used\ in\ another\ project.\n\ \ \ **\ Regular\ expression?\ \ Good\ for\ more\ complicated\ conventions.\n\ \ \ *\ Combine\ the\ above\ \[\[icc\ get2\]\]\ and\ \[\[icc\ catch2\]\]\ into\ a\ single\ command.\n\ \ \ **\ Operation\ would\ be\ determined\ by\ options.\n\ \ \ **\ One\ mode\ takes\ a\ list\ of\ feeds\ and\ a\ list\ of\ filters.\n\ \ \ **\ Other\ mode\ takes\ a\ script.\n\ \ \ **\ The\ name\ could\ be\ \[\[icc\ switch\]\].\n\ \ \ *\ Like\ \[\[\[switch\]\]\],\ allow\ the\ cases\ to\ be\ closed\ in\ a\ single\ brace-quoted\ word.\n\nPulling\ all\ this\ together\ might\ yield:\n\n======\nicc\ switch\ -feeds\ \$feed\ -filters\ \$filters\ -eventvar\ event\\\n-identvar\ ident\ -identsplit\ /\ \{\n\ \ \ \ filter/*/whatever\ \{puts\ \[lindex\ \$event\ 1\]\}\n\ \ \ \ filter2/whatever/*\ \{puts\ \[lindex\ \$ident\ 2\]\}\n\ \ \ \ etc\ \{puts\ 3\}\n\}\nicc\ switch\ -script\ \$script\ -eventvar\ event\\\n-identvar\ ident\ -identregexp\ \{(.*)/(.*)/(.*)\}\ \{\n\ \ \ \ filter/*/whatever\ \{puts\ \[lindex\ \$event\ 1\]\}\n\ \ \ \ filter2/whatever/*\ \{puts\ \[lindex\ \$ident\ 2\]\}\n\ \ \ \ etc\ \{puts\ 3\}\n\ \ \ \ exception\ \{puts\ 4\}\n\}\n======\n\nBut\ now\ I\ think\ that's\ too\ many\ pairs\ of\ options\ which\ must\ go\ together,\ and\ that\ complicates\ parsing.\ \ Maybe\ replace\ \"`-feeds\ \$feed\ -filters\ \$filters`\"\ with\ \"`-get\ \[\[list\ \$feed\ \$filters\]\]`\".\ \ Hmm,\ that\ \[\[\[list\]\]\]\ in\ there\ makes\ it\ seem\ even\ less\ clean,\ though.\ \ I\ dunno.\n\n----\n**Archived\ discussion**\n\n\[agb\]\ Dec.\ 2010.\ \ Chrome\ now\ supports\ an\ http://blog.chromium.org/2010/06/websocket-protocol-updated.html%|%updated\ version\ of\ the\ websocket\ protocol\ %|%\ so\ the\ \[wibble\]\ example\ previously\ here\ no\ longer\ works\ (to\ see\ it\ have\ a\ look\ in\ this\ page's\ history)\ .\ \ The\ changes\ to\ get\ the\ current\ version\ working\ are\ non-trivial.\n\n\[jbr\]\ 2010-12-20\ -\ Here\ is\ code\ that\ will\ allow\ \[wibble\]\ to\ handshake\ with\ the\ new\ spec.\ \ The\ version\ of\ Chrome\ that\ I\ have\ (8.0.552.231)\ has\ the\ new\ handshake\ but\ sends\ the\ old\ data\ framing.\ \ I\ can\ send\ data\ from\ the\ client,\ but,\ I\ haven't\ gotten\ it\ to\ accept\ data\ messages\ from\ the\ server.\ \ Wibble.tcl\ needs\ to\ be\ patched\ to\ add\ a\ way\ for\ it\ to\ release\ the\ socket\ that\ will\ be\ the\ websocket\ channel\ without\ responding\ and\ closing\ it:\n\n\[agb\]\ 2010-12-21\ -\ I\ made\ a\ small\ change\ to\ ::wibble::ws-handle\ to\ check\ that\ chan\ read\ actually\ reads\ a\ byte.\ \ With\ this\ change\ I\ have\ successful,\ bi-directional\ messages\ over\ the\ web\ socket\ with\ chrome\ 8.0.552.224.\ Thanks\ for\ updating\ \[wibble\].\n\n======\n\ #\ Abort\ processing\ on\ this\ client.\n\ proc\ wibble::abortclient\ \{\}\ \{\n\ \ \ \ return\ -code\ 7\n\ \}\n======\n\n\[AMG\]:\ I\ take\ it\ that\ this\ command\ is\ to\ be\ called\ by\ a\ zone\ handler\ in\ order\ to\ get\ Wibble\ to\ terminate\ the\ coroutine\ ''without''\ closing\ the\ socket.\ \ Correct?\n\nAlso,\ see\ my\ comments\ on\ \[Wibble\ wish\ list\]\ (\[http://wiki.tcl.tk/27380#pagetoc701442f2\])\ for\ an\ alternative,\ less\ invasive\ approach.\n\n\[jbr\]:\ Set\ keepalive\ 0\ at\ the\ top\ of\ ::wibble::process\ and\ then\ change\ the\ exceptional\ return\ handling\ like\ this:\n\n======\n\ \ \ \ \ \}\ on\ 7\ outcome\ \{\n\ \ \ \ \ \ \ \ set\ keepalive\ 1\n\ \ \ \ \ \}\ finally\ \{\n\ \ \ \ \ \ \ \ if\ \{\ !\$keepalive\ \}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ catch\ \{chan\ close\ \$socket\}\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \}\n======\n\n\[AMG\]:\ Some\ time\ after\ you\ wrote\ the\ above,\ I\ have\ changed\ Wibble\ to\ have\ customizable\ cleanup\ handlers.\ \ With\ the\ latest\ version\ of\ Wibble,\ instead\ of\ modifying\ the\ finally\ block,\ change\ the\ initialization\ of\ the\ cleanup\ list\ (top\ of\ \[\[process\]\])\ to\ the\ following:\n\n======\n\ \ \ \ set\ cleanup\ \{\n\ \ \ \ \ \ \ \ \{chan\ close\ \$file\}\n\ \ \ \ \ \ \ \ \{if\ \{!\$keepalive\}\ \{chan\ close\ \$socket\}\}\n\ \ \ \ \ \ \ \ \{dict\ unset\ ::wibble::icc::feeds\ \$coro\}\n\ \ \ \ \}\n======\n\n\[jbr\]:\ Add\ this\ to\ the\ zone\ handlers:\n\n======\n\ \ wibble::handle\ /ws\ websocket\ handler\ ws-demo\n======\n\nThis\ is\ your\ server\ side\ callback:\n\n======\n\ proc\ ::ws-demo\ \{\ event\ sock\ \{\ data\ \{\}\ \}\ \}\ \{\n\ \ \ \ switch\ \$event\ \{\n\ \ \ \ \ \ \ \ connect\ \{\}\n\ \ \ \ \ \ \ \ message\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ puts\ \"WS-Demo:\ \$event\ \$sock\ \$data\"\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \}\n\ \ \n\ \ ::wibble::ws-send\ \$sock\ \"Hello\"\n\ \}\n======\n\n\[AMG\]:\ Are\ `connect`\ and\ `message`\ the\ only\ two\ events\ that\ can\ happen?\n\n\[jbr\]:\ Connect\ and\ message\ are\ the\ only\ two\ events.\ \ WebSockets\ is\ a\ very\ low\ level\ thing\ (data\ packets)\ with\ the\ application\ specific\ messaging\ completely\ undefined.\n\n\[jbr\]:\ Utility\ to\ help\ the\ server\ send\ data\ frames,\ doesn't\ work\ yet!!\n\n======\n\ proc\ ::wibble::ws-send\ \{\ sock\ message\ \}\ \{\n\ \ \ \ #\ New\ data\ framing?\n\ \ \ \ #puts\ -nonewline\ \$sock\ \[binary\ format\ cc\ 4\ \[string\ length\ \$message\]\]\$message\n\n\ \ \ \ #\ Old\ data\ framing?\n\ \ \ \ puts\ \ -nonewline\ \$sock\ \"\\x00\"\n\ \ \ \ puts\ \ -nonewline\ \$sock\ \$message\n\ \ \ \ puts\ \ -nonewline\ \$sock\ \"\\xFF\"\n\n\ \ \ \ flush\ \$sock\n\ \}\n======\n\nHandler\ to\ accept\ data\ from\ browser.\ \ Uses\ old\ data\ framing.\n\n======\n\ proc\ ::wibble::ws-handle\ \{\ handler\ sock\ \}\ \{\n\ \ \ \ if\ \{\ \[chan\ eof\ \$sock\]\ \}\ \{\n\ \ \ \ \ \ \ \ puts\ \"Closed\ \$sock\"\n\ \ \ \ \ \ \ \ close\ \$sock\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ set\ code\ \[read\ \$sock\ 1\]\n\ \ \ \ \ \ \ \ if\ \{\[binary\ scan\ \$code\ c\ code\]\}\ \{\ \ \ \ \ \ \ \ \;\ #\ Do\ I\ need\ this?\ I\ think\ so.\n\ \ \ \ \ \ \ \ \ \ switch\ \$code\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ 0\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ set\ message\ \{\}\n\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ while\ \{\ \[set\ c\ \[read\ \$sock\ 1\]\]\ !=\ \"\\xFF\"\ \}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ append\ message\ \$c\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \$handler\ message\ \$sock\ \$message\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \ \ default\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ puts\ \"Bad\ Blocking:\ \$c\"\n\ \ \ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \ \ \}\n\ \ \ \ \}\n\ \}\n======\n\nThe\ Zone\ Handler\ \n\n======\n\ package\ require\ md5\n\ \ \n\ proc\ ::wibble::websocket\ \{\ state\ \}\ \{\n\ \ \ \ set\ upgrade\ \ \ \ \{\}\n\ \ \ \ set\ connection\ \{\}\n\ \ \ \ dict\ with\ state\ request\ header\ \{\}\n\n\ \ \ \ if\ \{\ \$connection\ ne\ \"Upgrade\"\ ||\ \$upgrade\ ne\ \"WebSocket\"\ \}\ \{\n\ \ \ \ \ \ \ \ return\n\ \ \ \ \}\n\n\ \ \ \ set\ sock\ \[dict\ get\ \$state\ request\ socket\]\n\n\ \ \ \ puts\ \"WebSocket\ Connect:\ \$sock\"\n\n\ \ \ \ set\ key1\ \[regsub\ -all\ \{\[^0-9\]\}\ \$\{sec-websocket-key1\}\ \{\}\]\n\ \ \ \ set\ spc1\ \[string\ length\ \[regsub\ -all\ \{\[^\ \]\}\ \ \ \$\{sec-websocket-key1\}\ \{\}\]\]\n\ \ \ \ set\ key2\ \[regsub\ -all\ \{\[^0-9\]\}\ \ \ \$\{sec-websocket-key2\}\ \{\}\]\n\ \ \ \ set\ spc2\ \[string\ length\ \[regsub\ -all\ \{\[^\ \]\}\ \$\{sec-websocket-key2\}\ \{\}\]\]\n\n\ \ \ \ set\ key3\ \[read\ \$sock\ 8\]\n\n\ \ \ \ set\ handler\ \[dict\ get\ \$state\ options\ handler\]\n\ \ \ \ chan\ event\ \$sock\ readable\ \[list\ ::wibble::ws-handle\ \$handler\ \$sock\]\n\n\ \ \ \ set\ key1\ \[expr\ \$key1/\$spc1\]\n\ \ \ \ set\ key2\ \[expr\ \$key2/\$spc2\]\n\n\ \ \ \ set\ challenge\ \[binary\ format\ II\ \$key1\ \$key2\]\$key3\n\ \ \ \ set\ response\ \ \[md5\ \$challenge\]\n\n\ \ \ \ puts\ \$sock\ \"HTTP/1.1\ 101\ WebSocket\ Protocol\ Handshake\"\n\ \ \ \ puts\ \$sock\ \"Connection:\ Upgrade\"\n\ \ \ \ puts\ \$sock\ \"Upgrade:\ WebSocket\"\n\ \ \ \ puts\ \$sock\ \"Sec-WebSocket-Origin:\ http://localhost:8080\"\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \;\ #\ This\ shouldn't\ be\ hard\ coded!!\n\ \ \ \ puts\ \$sock\ \"Sec-WebSocket-Location:\ ws://localhost:8080/ws/demo\"\n\ \ \ \ puts\ \$sock\ \"\"\n\n\ \ \ \ chan\ configure\ \$sock\ -translation\ binary\n\ \ \ \ puts\ \$sock\ \$response\n\ \ \ \ chan\ flush\ \$sock\n\n\ \ \ \ \$handler\ connect\ \$sock\ \ \;\ #\ There\ should\ be\ an\ option\ to\ pass\ a\ session\ Id\ here.\n\n\ \ \ \ abortclient\n\ \}\n======\n\n----\n\[AMG\]:\ Thanks\ for\ the\ code,\ guys.\ \ I\ will\ need\ to\ ponder\ some\ more\ before\ integrating\ this\ into\ Wibble,\ but\ I\ do\ think\ I\ want\ this\ feature.\ \ However,\ I\ think\ it\ would\ benefit\ from\ tighter\ integration.\ \ As\ far\ as\ I\ can\ tell,\ it\ leverages\ Wibble\ for\ establishing\ the\ connection\ but\ then\ takes\ over\ all\ I/O.\ \ This\ concept\ is\ quite\ similar\ to\ something\ \[JCW\]\ shared\ with\ me\ the\ other\ day,\ namely\ an\ implementation\ of\ Server-Sent\ Events\ \[http://jeelabs.net/projects/jeerev/wiki/Web_server_events\]\ \[http://sapid.sourceforge.net/ssetest/\].\ \ Whatever\ I\ do,\ I\ would\ like\ it\ to\ support\ both\ protocols,\ or\ at\ least\ their\ common\ requirements.\n\nIf\ you're\ wondering\ why\ I\ haven't\ integrated\ all\ this\ sooner,\ it's\ because\ \[AJAX\]\ was\ my\ priority.\ \ It\ may\ be\ terribly\ clumsy\ compared\ to\ WebSockets\ and\ Server-Sent\ Events,\ but\ it\ also\ has\ the\ most\ browser\ support.\n\n\[jcw\]\ Neat...\ \[jbr\]'s\ return\ 7\ and\ keepalive\ idea\ look\ like\ a\ very\ useful\ tweak:\n\n\[jbr\]\ 2011-05-01\ Andy\ has\ offered\ a\ better\ way\ to\ handle\ this\ by\ removing\ the\ socket\ from\ the\ coroutines\ list\ and\ returning\ an\ uncaught\ error.\ \ No\ need\ to\ hack\ Wibble's\ main\ zone\ handler\ body.\n\n======\n\ \ \ \ \ \}\ on\ 7\ outcome\ \{\n\ \ \ \ \ \ \ \ set\ keepalive\ 1\n\ \ \ \ \ \}\ finally\ \{\n\ \ \ \ \ \ \ \ if\ \{\ !\$keepalive\ \}\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ catch\ \{chan\ close\ \$socket\}\n\ \ \ \ \ \ \ \ \}\n\ \ \ \ \ \}\n======\n\nBetter\ than\ what\ I'm\ doing\ right\ now,\ which\ is\ to\ do\ an\ \"icc\ get\"\ to\ grab\ control\ over\ the\ socket\ by\ suspending\ the\ co-routine\ indefinitely.\ The\ problem\ with\ that\ is\ that\ I\ always\ get\ an\ error\ on\ socket\ close,\ as\ wibble\ tries\ to\ resume\ and\ send\ a\ response\ to\ the\ (now\ closed)\ socket.\nWhat's\ not\ clear\ to\ me\ is\ whether\ the\ \"return\ 7\"\ also\ causes\ the\ request's\ co-routine\ to\ be\ cleaned\ up\ right\ away\ (seems\ like\ a\ good\ idea).\n\n\[AMG\]:\ The\ \[coroutine\]\ will\ always\ be\ cleaned\ up,\ thanks\ to\ the\ \"finally\"\ clause\ inside\ \[\[process\]\].\ \ The\ only\ way\ to\ avoid\ the\ \"finally\"\ clause\ is\ to\ delete\ the\ current\ coroutine\ command\ (`\[rename\]\ \[\[\[info\ coroutine\]\]\]\ \"\"`)\ then\ `\[yield\]`.\n\nA\ few\ days\ ago\ I\ came\ up\ with\ another\ approach\ that\ I\ prefer\ to\ any\ presented\ on\ this\ page\ or\ the\ \[Wibble\ wish\ list\]:\ define\ a\ new\ key\ in\ the\ response\ \[dict\]\ that\ defines\ a\ custom\ I/O\ handler\ that\ \[\[process\]\]\ will\ execute\ instead\ of\ doing\ its\ normal\ post-\[\[getresponse\]\]\ activities.\ \ This\ way,\ more\ of\ the\ Wibble\ infrastructure\ is\ available\ to\ the\ custom\ code:\ error\ handling,\ automatic\ cleanup,\ and\ the\ ability\ to\ loop\ again\ and\ get\ another\ HTTP\ request\ from\ the\ same\ socket.\n\n----\n2011.1013\ \[jbr\]\ Here\ we\ are\ almost\ a\ year\ later\ with\ an\ update.\n\nWebSocket\ has\ again\ moved\ to\ a\ new\ handshake\ &\ framing.\ \ Here\ is\ a\ zone\ handler\ for\ Andy's\ newest\ wibble\ and\ chrome\ 14\ websockets.\n\n======\n\ package\ require\ sha1\n\ package\ require\ base64\n\n\ #\ Utility\ proc\ to\ frame\ and\ send\ short\ strings\ up\ to\ 126\ chars\n\ #\n\ proc\ ::wibble::ws-send\ \{\ sock\ message\ \}\ \{\n\ \ \ \ puts\ -nonewline\ \$sock\ \[binary\ format\ cc\ 0x81\ \[string\ length\ \$message\]\]\$message\n\ \ \ \ flush\ \$sock\n\ \}\ \n\n\ #\ WebSocket\ handler\ proc\ to\ receive\ short\ (up\ to\ 126\ chars)\ text\ format\ frames\n\ #\n\ proc\ ::wibble::ws-handle\ \{\ handler\ sock\ \}\ \{\n\n\ \ \ \ if\ \{\ \[chan\ eof\ \$sock\]\ \}\ \{\n\ \ \ \ \ \ \ \ close\ \$sock\n\ \ \ \ \}\ else\ \{\n\ \ \ \ \ \ \ \ binary\ scan\ \[read\ \$sock\ 1\]\ c\ opcode\n\ \ \ \ \ \ \ \ binary\ scan\ \[read\ \$sock\ 1\]\ c\ length\n\n\ \ \ \ \ \ \ \ set\ opcode\ \[expr\ \$opcode\ &\ 0x0F\]\n\ \ \ \ \ \ \ \ set\ length\ \[expr\ \$length\ &\ 0x7F\]\n\n\ \ \ \ \ \ \ \ binary\ scan\ \[read\ \$sock\ 4\]\ \ \ \ \ \ \ c*\ mask\n\ \ \ \ \ \ \ \ binary\ scan\ \[read\ \$sock\ \$length\]\ c*\ data\n\n\ \ \ \ \ \ \ \ set\ msg\ \{\}\n\ \ \ \ \ \ \ \ set\ i\ \ \ \ 0\n\ \ \ \ \ \ \ \ foreach\ char\ \$data\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ append\ msg\ \[binary\ format\ c\ \[expr\ \{\ \$char^\[lindex\ \$mask\ \[expr\ \{\ \$i%4\ \}\]\]\ \}\]\]\n\ \ \ \ \ \ \ \ \ \ \ \ incr\ i\n\ \ \ \ \ \ \ \ \}\ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \$handler\ message\ \$sock\ \$msg\n\ \ \ \ \}\n\ \}\n\n\ #\ Zone\ handler\n\ #\n\ proc\ ::wibble::websocket\ \{\ state\ \}\ \{\n\ \ \ \ set\ upgrade\ \ \ \ \{\}\n\ \ \ \ set\ connection\ \{\}\n\n\ \ \ \ dict\ with\ state\ request\ header\ \{\}\n\n\ \ \ \ if\ \{\ \$connection\ ne\ \"Upgrade\"\ ||\ \$upgrade\ ne\ \"websocket\"\ \}\ \{\n\ \ \ \ \ \ \ \ return\n\ \ \ \ \}\n\n\ \ \ \ set\ sock\ \[dict\ get\ \$state\ request\ socket\]\n\n\ \ \ \ puts\ \"WebSocket\ Connect:\ \$sock\"\n\n\ \ \ \ set\ response\ \[base64::encode\ \[sha1::sha1\ -bin\ \$\{sec-websocket-key\}258EAFA5-E914-47DA-95CA-C5AB0DC85B11\]\]\n\ \ \ \ set\ handler\ \ \[dict\ get\ \$state\ options\ handler\]\n\n\ \ \ \ puts\ \$sock\ \"HTTP/1.1\ 101\ WebSocket\ Protocol\ Handshake\"\n\ \ \ \ puts\ \$sock\ \"Upgrade:\ \ \ \ websocket\"\n\ \ \ \ puts\ \$sock\ \"Connection:\ Upgrade\"\n\ \ \ \ puts\ \$sock\ \"Sec-WebSocket-Accept:\ \$response\"\n\ \ \ \ puts\ \$sock\ \"\"\n\n\ \ \ \ chan\ configure\ \$sock\ -translation\ binary\n\ \ \ \ chan\ event\ \ \ \ \ \$sock\ readable\ \[list\ ::wibble::ws-handle\ \$handler\ \$sock\]\n\n\ \ \ \ \$handler\ connect\ \$sock\n\n\ \ \ \ return\ -code\ 7\n\ \}\n======\n\nAt\ the\ top\ of\ the\ ::wibble::process\ proc\ I\ initialize\ the\ keepsock\ variable\ and\ modified\ the\ cleanup\ procs\ like\ this:\n\n======\n\ \ \ \ set\ keepsock\ 0\n\ \ \ \ set\ cleanup\ \{\n\ \ \ \ \ \ \ \ \{chan\ close\ \$file\}\n\ \ \ \ \ \ \ \ \{\ if\ \{\ !\$keepsock\ \}\ \{\ chan\ close\ \$socket\ \}\ \}\n\ \ \ \ \ \ \ \ \{dict\ unset\ ::wibble::icc::feeds\ \$coro\}\n\ \ \ \ \}\n======\n\nThen\ I\ added\ a\ clause\ in\ the\ try\ structure\ to\ catch\ the\ return\ -code\ 7\ from\ the\ zone\ handler:\n\n======\n\ \ \ \ \}\ on\ 7\ outcome\ \{\n\ \ \ \ \ \ \ \ \ \ \ \ set\ keepsock\ 1\n\ \ \ \ \}\ finally\ \{\n======\n\nWorks\ for\ me.\ \ \ Thanks\ Andy.\n\n----\n\[AMG\]:\ I\ made\ an\ update\ of\ the\ above\ for\ version\ 2011-11-24\ \[http://wiki.tcl.tk/27384#pagetoc58b50302\].\ \ Thanks\ to\ some\ spiffy\ new\ features\ in\ Wibble,\ it\ doesn't\ require\ any\ core\ modifications.\ \ However,\ I'm\ not\ quite\ ready\ to\ post\ it\ here,\ since\ I\ need\ to\ test\ it.\ \ I\ grabbed\ a\ copy\ of\ Chrome,\ but\ I\ don't\ know\ how\ to\ get\ it\ to\ talk\ to\ WebSocket\ anything.\ \ If\ you\ could\ please\ post\ an\ example\ HTML/JavaScript\ file\ that\ makes\ use\ of\ the\ above,\ I'll\ debug\ what\ I've\ got\ and\ post\ the\ combined\ demo\ here.\n\nA\ more\ flexible\ alternative\ to\ the\ WebSocket\ handler\ proc\ receiving\ the\ event\ (connect\ or\ message)\ as\ its\ first\ argument\ is\ to\ support\ separate\ connect\ and\ message\ handler\ command\ prefixes\ in\ the\ options\ dict.\ \ For\ compatibility\ with\ existing\ handlers,\ the\ connecthandler\ and\ messagehandler\ could\ name\ the\ same\ proc,\ but\ with\ an\ extra\ connect\ or\ message\ argument.\ \ Does\ this\ sound\ like\ a\ worthwhile\ change?\ \ Also,\ how\ about\ a\ disconnect\ handler?\ \ Would\ that\ ever\ have\ any\ value?\ \ The\ ICC\ feed\ mechanism\ already\ has\ a\ lapse\ system\ which\ is\ designed\ to\ detect\ application-level\ user\ disconnects,\ as\ opposed\ to\ protocol-level\ TCP\ socket\ disconnects.\n\nIn\ my\ development\ code,\ I\ got\ rid\ of\ the\ socket\ arguments\ since\ the\ socket\ name\ is\ always\ \[\[\[namespace\ tail\]\ \[\[\[info\ coroutine\]\]\]\]\].\n\nIn\ addition\ to\ the\ max-126-character\ string\ protocol,\ is\ there\ a\ way\ to\ send\ and\ receive\ binary\ data?\ \ Should\ we\ support\ it?\n\n----\n\[AMG\]:\ Disregard\ the\ above,\ it's\ overcome\ by\ events.\ \ I\ redid\ my\ update\;\ it's\ totally\ new\ code\ now.\ \ It\ still\ doesn't\ require\ any\ core\ modifications,\ which\ is\ good,\ and\ I'm\ still\ not\ ready\ to\ post\ it\ here,\ which\ is\ bad.\ \ However,\ I\ did\ test\ it,\ and\ it\ works\ so\ far.\ \ I\ ran\ into\ some\ fundamental\ design\ issues,\ which\ I'll\ discuss\ below.\ \ I\ decided\ against\ separate\ connecthandler\ and\ messagehandler\;\ a\ single\ handler\ works\ well\ enough,\ and\ it\ can\ dispatch\ to\ separate\ procs\ if\ needed.\ \ I\ kept\ the\ disconnect\ event,\ on\ the\ theory\ that\ a\ handler\ might\ want\ an\ opportunity\ to\ clean\ up\ after\ itself.\ \ The\ socket\ arguments\ are\ still\ gone.\ \ I\ added\ support\ for\ binary\ data,\ frames\ longer\ than\ 126\ bytes,\ ping/pong,\ close\ frames,\ fragmentation\ and\ continuation\ frames,\ basically\ everything\ I\ could\ find\ in\ the\ WebSocket\ protocol\ draft\ document\ \[http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17\].\n\nThe\ big\ design\ problem\ derives\ from\ the\ fact\ that\ WebSocket\ doesn't\ require\ the\ client\ and\ server\ to\ take\ turns\ the\ way\ HTTP\ does.\ \ Wibble\ successfully\ models\ HTTP\ with\ this\ main\ loop,\ running\ inside\ a\ \[coroutine\]:\n\n======\nwhile\ \{1\}\ \{\n\ \ \ \ set\ request\ \[getrequest\ \$port\ \$socket\ \$peerhost\ \$peerport\]\n\ \ \ \ set\ response\ \[getresponse\ \$request\]\n\ \ \ \ if\ \{!\[\{*\}\$sendcommand\ \$socket\ \$request\ \$response\]\}\ \{\n\ \ \ \ \ \ \ \ chan\ close\ \$socket\n\ \ \ \ \ \ \ \ break\n\ \ \ \ \}\n\}\n======\n\nWith\ WebSocket,\ both\ sides\ can\ talk\ simultaneously\;\ yet\ this\ is\ done\ with\ only\ a\ single\ TCP\ connection.\ \ This\ does\ not\ map\ well\ to\ Wibble's\ one-coroutine-per-socket\ model,\ shown\ above.\ \ The\ Wibble\ coroutine\ cycles\ between\ three\ states:\ get\ the\ client\ data,\ generate\ the\ response,\ send\ the\ response,\ then\ repeat\ or\ terminate.\n\nLet's\ take\ a\ simple\ example:\ a\ calculator\ with\ a\ clock.\ \ Implement\ these\ two\ features\ as\ separate\ connections,\ and\ there's\ little\ challenge.\ \ But\ multiplex\ them\ together,\ and\ you've\ got\ problems.\ \ The\ client\ should\ be\ able\ to\ send\ a\ new\ math\ expression\ at\ any\ time,\ to\ which\ the\ server\ should\ rapidly\ respond\ with\ the\ result.\ \ Every\ second,\ the\ server\ should\ also\ send\ the\ new\ time,\ without\ the\ client\ asking.\n\nMy\ first\ inclination\ is\ to\ use\ \[\[icc\]\]\ to\ wait\ for\ either\ readability\ or\ timeout.\ \ (\[\[icc\]\]\ can\ also\ be\ used\ to\ wait\ for\ events\ coming\ from\ other\ coroutines,\ but\ that's\ not\ important\ here.)\ \ That\ would\ work,\ if\ not\ for\ the\ problem\ of\ incomplete\ reads.\ \ Reading\ the\ WebSocket\ data\ is\ nontrivial.\ \ You\ have\ to\ read\ the\ first\ two\ bytes\ for\ the\ basic\ header,\ then\ read\ zero,\ two,\ or\ eight\ more\ for\ the\ extended\ length\ (depending\ on\ the\ length\ given\ in\ the\ basic\ header),\ plus\ four\ more\ for\ the\ mask,\ and\ then\ you\ have\ to\ read\ the\ actual\ data.\ \ For\ extra\ joy,\ consider\ that\ a\ message\ can\ be\ fragmented\ across\ multiple\ frames,\ and\ that\ control\ frames\ can\ be\ interleaved\ with\ the\ fragmented\ data\ frames.\ \ Also\ consider\ that\ each\ frame\ can\ be\ up\ to\ 14+2**64\ bytes\ in\ size.\ \ Socket\ readability\ certainly\ does\ not\ guarantee\ that\ the\ entire\ frame\ is\ immediately\ available\ for\ reading.\ \ This\ isn't\ \[UDP\].\ \;^)\n\nSince\ reading\ the\ data\ isn't\ atomic,\ it's\ a\ stateful\ process,\ with\ a\ potential\ wait\ state\ everywhere\ a\ read\ happens.\ \ At\ every\ wait\ state\ the\ system\ needs\ to\ return\ to\ the\ event\ loop\ so\ that\ a\ different\ socket\ can\ be\ serviced.\ \ Sounds\ like\ a\ job\ for\ coroutines.\ \;^)\ \ But...\ what\ if\ the\ other\ operation\ waiting\ to\ be\ processed\ exists\ in\ the\ same\ coroutine\ as\ the\ read?\ \ That\ doesn't\ work.\n\nHere's\ what\ I'm\ considering.\ \ Create\ a\ second\ coroutine\ for\ each\ WebSocket\ connection\ which\ doesn't\ directly\ fit\ the\ HTTP\ turn-taking\ model.\ \ Name\ the\ coroutine\ the\ same\ as\ the\ socket,\ but\ put\ it\ in\ the\ ::wibble::ws\ namespace,\ such\ that\ it's\ separate\ from\ the\ one\ in\ the\ ::wibble\ namespace\ yet\ \[\[namespace\ tail\ \[\[info\ coroutine\]\]\]\]\ is\ still\ the\ socket\ name.\ \ The\ original\ Wibble\ coroutine\ is\ in\ charge\ of\ reading\ the\ client\ data,\ and\ whenever\ a\ message\ is\ received,\ it\ sends\ it\ to\ the\ new\ coroutine\ via\ \[\[icc\]\].\ \ The\ new\ coroutine\ also\ roughly\ follows\ the\ HTTP\ model\ shown\ above,\ but\ with\ \[\[getrequest\]\]\ replaced\ with\ \[\[icc\ get\]\].\ \ This\ way\ it\ aggregates\ messages\ from\ multiple\ sources\ and\ gets\ them\ atomically.\ \ When\ it\ wants\ to\ send,\ it\ writes\ to\ the\ socket.\ \ Meanwhile,\ the\ first\ coroutine\ only\ writes\ to\ the\ socket\ when\ initiating\ and\ tearing\ down\ the\ connection.\n\nTo\ make\ this\ efficient,\ I\ would\ have\ to\ modify\ \[\[icc\]\]\ to\ not\ concatenate\ the\ event\ identification\ and\ data\ together\ into\ a\ single\ string.\ \ Instead,\ the\ payload\ would\ need\ to\ be\ a\ separate\ \[Tcl_Obj\].\ \ (\[AMG\],\ update:\ \[\[icc\]\]\ already\ supports\ arbitrary\ payloads\ in\ addition\ to\ the\ event\ identifier!\ \ No\ change\ needed.)\n\nNow\ I'm\ gonna\ digress\ from\ WebSocket\ in\ the\ interest\ of\ searching\ for\ a\ unified\ architecture,\ since\ I'm\ always\ looking\ for\ ideas.\ :^)\n\nThis\ two-coroutine\ model\ isn't\ necessary\ for\ any\ of\ the\ pure-HTTP\ concepts,\ since\ the\ communication\ direction\ strictly\ alternates.\ \ Imagine\ if\ I\ went\ ahead\ and\ made\ two\ coroutines\ anyway.\ \ They'd\ always\ be\ taking\ turns.\ \ Each\ one\ would\ do\ its\ thing,\ notify\ the\ other\ that\ it's\ done,\ then\ wait\ for\ the\ other\ to\ finish.\ \ Therefore\ they\ might\ as\ well\ just\ be\ a\ single\ coroutine,\ which\ is\ exactly\ what\ I\ have.\n\nActually,\ there\ is\ one\ major\ exception,\ one\ time\ when\ the\ HTTP\ client\ can\ do\ something\ that's\ of\ interest\ to\ the\ server\ even\ though\ it's\ the\ server's\ turn\ to\ talk.\ \ That\ one\ thing\ is:\ disconnect.\ \ In\ an\ AJAX\ long\ polling\ situation,\ the\ server\ can\ hang\ for\ minutes\ at\ a\ time\ waiting\ for\ there\ to\ be\ something\ worthwhile\ to\ report.\ \ During\ that\ time,\ the\ client\ can\ disconnect,\ but\ the\ server\ wouldn't\ be\ able\ to\ tell\ until\ it\ tries\ to\ read\ from\ the\ client.\ \ Of\ course,\ the\ server\ ''can't''\ try\ to\ read\ until\ after\ it\ sends\ something.\ \ My\ current\ solution\ is\ to\ have\ the\ server\ periodically\ send\ a\ no-op,\ just\ as\ an\ excuse\ to\ check\ if\ the\ client\ is\ still\ alive.\ \ If\ I\ had\ a\ two-coroutine\ model\ for\ AJAX\ long\ polling,\ the\ client\ read\ coroutine\ could\ always\ be\ on\ the\ lookout\ for\ client\ disconnect\;\ when\ it\ happens,\ it\ can\ use\ \[\[icc\]\]\ to\ let\ the\ other\ coroutine\ know\ it's\ quitting\ time.\ \ (More\ likely,\ it'll\ use\ \[\[icc\]\]\ to\ let\ all\ the\ other\ coroutines\ know\ that\ the\ one\ client\ died,\ and\ it'll\ just\ directly\ terminate\ the\ coroutines\ associated\ with\ the\ socket.)\n\nHowever,\ there's\ still\ a\ problem:\ unclean\ disconnects.\ \ If\ the\ client\ makes\ a\ request,\ it's\ now\ the\ server's\ turn\ to\ respond.\ \ While\ the\ server\ is\ hanging\ out\ waiting\ for\ something\ worthwhile\ to\ happen,\ the\ client\ sends\ some\ more\ data.\ \ The\ only\ way\ the\ read\ coroutine\ can\ tell\ the\ difference\ between\ the\ client\ sending\ data\ and\ the\ client\ disconnecting\ is\ to\ try\ to\ read\ from\ the\ client,\ so\ that's\ what\ it\ does.\ \ It\ can't\ do\ this\ without\ bound,\ so\ eventually\ it'll\ have\ to\ stop\ reading.\ \ At\ this\ point,\ the\ read\ coroutine\ again\ loses\ its\ ability\ to\ know\ when\ the\ client\ disconnects.\ \ One\ possible\ solution\ is\ to\ read\ without\ bound,\ but\ that\ opens\ up\ a\ \[DoS\]\ attack.\ \ Another\ possible\ solution\ is\ to\ forcibly\ disconnect\ the\ client\ if\ it\ sends\ too\ much\ data\ out-of-turn,\ but\ this\ interferes\ with\ HTTP\ pipelining.\ \ Everything\ comes\ down\ to\ the\ fact\ that\ the\ portable\ C\ I/O\ API\ doesn't\ have\ an\ out-of-band\ notification\ of\ client\ disconnects.\ \ So,\ I\ can't\ think\ a\ solution\ solid\ enough\ to\ justify\ the\ complexity\ of\ adding\ another\ coroutine,\ and\ I\ strongly\ lean\ against\ adopting\ a\ two-coroutine\ model\ for\ the\ Wibble\ core.\ \ But\ I'm\ definitely\ open\ to\ suggestions!\n\n----\n\[jbr\]\ 2011-11-26\ Andy,\ my\ thinking\ here\ is\ that\ once\ you've\ gotten\ an\ accept\ via\ the\ websocket\ zone\ handler\ then\ you\ just\ have\ a\ socket.\ \ You\ can\ use\ icc\ if\ you\ like\ or\ you\ can\ just\ register\ a\ fileevent.\ \ There\ is\ no\ reason\ to\ tie\ it\ to\ any\ other\ wibble\ infrastructure\ or\ HTTP\ concepts,\ its\ just\ a\ socket\ connected\ to\ some\ javascript\ in\ a\ web\ browser.\ \ The\ wibble\ co-routine\ should\ just\ return\ without\ closing\ the\ socket.\n\nP.S.\ \ -\ Thanks\ for\ coding\ up\ the\ rest\ of\ the\ message\ encoding.\ \ I've\ just\ been\ passing\ simple\ events\ to\ my\ server\ from\ user\ interactions\ with\ forms,\ but\ completeness\ is\ a\ good\ thing.\n\n\[AMG\]:\ But\ be\ careful,\ the\ WebSocket\ code\ is\ running\ inside\ the\ same\ thread\ of\ execution\ as\ the\ rest\ of\ the\ web\ server.\ \ The\ code\ you\ posted\ above\ is\ extremely\ easy\ to\ \[DoS\].\ \ All\ you\ have\ to\ do\ is\ perform\ a\ valid\ WebSocket\ handshake,\ then\ send\ one\ byte\ and\ one\ byte\ only.\ \ The\ code\ will\ block\ indefinitely\ waiting\ for\ the\ rest\ of\ the\ WebSocket\ header.\ \ During\ that\ time,\ the\ rest\ of\ the\ server\ is\ blocked\ too.\ \ Using\ \[\[getblock\]\]\ instead\ of\ \[\[\[read\]\]\]\ solves\ the\ problem,\ since\ it\ \[yield\]s\ rather\ than\ block\ the\ whole\ process.\ \ So\ hey,\ there's\ a\ case\ where\ the\ Wibble\ infrastructure\ is\ useful\ inside\ WebSocket\ code.\n\n\[jbr\]\ -\ I\ can't\ argue\ with\ that\ using\ \[\[getblock\]\]\ (or\ something\ like\ it)\ isn't\ a\ good\ idea.\ \ I'm\ just\ suggesting\ that\ the\ web\ socket\ zone\ handler\ allow\ the\ application\ specific\ code\ do\ its\ own\ thing\ an\ not\ impose\ a\ framework\ that\ might\ not\ fit\ so\ well.\ \ What\ if\ I\ want\ to\ hand\ the\ socket\ to\ a\ 3rd\ party\ library\ that\ already\ handles\ a\ web\ socket\ connection\ in\ its\ own\ code?\n\n\[AMG\]:\ Tonight\ I\ hope\ to\ post\ the\ new\ code\ I've\ got\ so\ you\ can\ better\ see\ what\ I\ have\ in\ mind.\ \ I\ don't\ plan\ for\ WebSocket\ to\ be\ integral\ to\ Wibble,\ especially\ if\ that\ means\ its\ design\ can\ constrain\ the\ application.\ \ Rather,\ WebSocket\ should\ be\ an\ add-on\ product,\ mostly\ as\ a\ demonstration\ of\ Wibble's\ flexibility.\ \ If\ implementing\ WebSocket\ cleanly\ requires\ a\ change\ to\ Wibble,\ I'll\ make\ that\ change,\ but\ I'll\ do\ so\ in\ a\ generic\ enough\ fashion\ that\ it'll\ also\ work\ for\ \[Server-Sent\ Events\]\ or\ other\ connection\ upgrades.\ \ If\ an\ interesting\ design\ idea\ crops\ up\ in\ the\ course\ of\ implementing\ WebSocket\ or\ any\ other\ zone\ handler\ or\ application,\ I'll\ consider\ adopting\ it\ (or\ its\ cousin)\ into\ Wibble\ proper.\ \ But\ I'm\ not\ going\ to\ change\ Wibble's\ scope\ in\ the\ process.\ \ \[\[::wibble::process\]\]\ shall\ forever\ deal\ only\ in\ HTTP,\ and\ if\ an\ application\ wants\ to\ switch\ from\ HTTP\ to\ something\ else,\ it\ should\ do\ so\ using\ the\ sendcommand\ response\ dict\ entry,\ to\ which\ \[\[process\]\]\ will\ hand\ over\ control.\n\nHere's\ the\ relationship\ between\ Wibble\ and\ WebSocket.\ \ This\ is\ the\ same\ relationship\ as\ between\ Wibble\ and\ other\ zone\ handlers,\ except\ (1)\ Wibble\ doesn't\ attempt\ to\ send\ anything\ to\ the\ client,\ and\ (2)\ the\ WebSocket\ code\ does\ all\ reads\ after\ the\ initial\ HTTP-like\ handshake.\n\n\ \ \ *\ Wibble\ handles\ all\ the\ HTTP\ parts,\ especially\ the\ rest\ of\ the\ web\ site.\n\ \ \ *\ All\ the\ coroutines\ exist\ in\ the\ same\ process\ and\ have\ to\ take\ care\ to\ not\ block\ each\ other.\n\ \ \ *\ Everything\ leading\ up\ to\ the\ initial\ upgrade\ is\ handled\ by\ Wibble.\n\ \ \ *\ The\ socket\ and\ its\ control\ coroutine\ are\ created\ by\ Wibble.\n\ \ \ *\ The\ Wibble\ error\ logging\ system\ wraps\ around\ the\ user\ code.\n\ \ \ *\ Wibble\ closes\ the\ socket\ when\ everything's\ done.\n\ \ \ *\ The\ Wibble\ I/O,\ log,\ panic,\ formatting,\ cleanup,\ and\ \[\[icc\]\]\ procedures\ are\ available\ for\ use.\n\nI\ do\ have\ a\ framework\ in\ mind\ for\ WebSocket.\ \ It\ does\ resemble\ Wibble\ in\ some\ ways,\ but\ only\ because\ I\ think\ it's\ a\ sensible\ approach.\ :^)\ \ The\ fact\ is\ that\ it\ also\ resembles\ the\ code\ you\ started\ with\;\ I\ just\ beefed\ it\ up\ for\ completeness\ and\ to\ avoid\ the\ DoS\ problem\ mentioned\ above.\n\n<<categories>>\ Webserver} CALL {my revision WebSocket} CALL {::oo::Obj1382911 process revision/WebSocket} CALL {::oo::Obj1382909 process}

-errorcode

NONE

-errorinfo

Unknow state transition: LINE -> END
    while executing
"error $msg"
    (class "::Wiki" method "render_wikit" line 6)
    invoked from within
"my render_$default_markup $N $C $mkup_rendering_engine"
    (class "::Wiki" method "render" line 8)
    invoked from within
"my render $name $C"
    (class "::Wiki" method "revision" line 31)
    invoked from within
"my revision $page"
    (class "::Wiki" method "process" line 56)
    invoked from within
"$server process [string trim $uri /]"

-errorline

4