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. [jbr] 2011-05-1 If someone is adding attributions to the wiki I wish that the would get it right. If you are not absolutely sure who added a comment then please leave it be. [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. [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: [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]. ====== # Abort processing on this client. proc wibble::abortclient {} { return -code 7 } ====== [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? Also, see my comments on [Wibble wish list] ([http://wiki.tcl.tk/27380#pagetoc701442f2]) for an alternative, less invasive approach. [jbr]: Set keepalive 0 at the top of ::wibble::process and then change the exceptional return handling like this: ====== } on 7 outcome { set keepalive 1 } finally { if { !$keepalive } { catch {chan close $socket} } } ====== [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: ====== set cleanup { {chan close $file} {if {!$keepalive} {chan close $socket}} {dict unset ::wibble::icc::feeds $coro} } ====== [jbr]: Add this to the zone handlers: ====== wibble::handle /ws websocket handler ws-demo ====== This is your server side callback: ====== proc ::ws-demo { event sock { data {} } } { switch $event { connect {} message { puts "WS-Demo: $event $sock $data" } } ::wibble::ws-send $sock "Hello" } ====== [AMG]: Are `connect` and `message` the only two events that can happen? [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. [jbr]: Utility to help the server send data frames, doesn't work yet!! ====== proc ::wibble::ws-send { sock message } { # New data framing? #puts -nonewline $sock [binary format cc 4 [string length $message]]$message # Old data framing? puts -nonewline $sock "\x00" puts -nonewline $sock $message puts -nonewline $sock "\xFF" flush $sock } ====== Handler to accept data from browser. Uses old data framing. ====== proc ::wibble::ws-handle { handler sock } { if { [chan eof $sock] } { puts "Closed $sock" close $sock } else { set code [read $sock 1] if {[binary scan $code c code]} { ; # Do I need this? I think so. switch $code { 0 { set message {} while { [set c [read $sock 1]] != "\xFF" } { append message $c } $handler message $sock $message } default { puts "Bad Blocking: $c" } } } } } ====== The Zone Handler ====== package require md5 proc ::wibble::websocket { state } { set upgrade {} set connection {} dict with state request header {} if { $connection ne "Upgrade" || $upgrade ne "WebSocket" } { return } set sock [dict get $state request socket] puts "WebSocket Connect: $sock" set key1 [regsub -all {[^0-9]} ${sec-websocket-key1} {}] set spc1 [string length [regsub -all {[^ ]} ${sec-websocket-key1} {}]] set key2 [regsub -all {[^0-9]} ${sec-websocket-key2} {}] set spc2 [string length [regsub -all {[^ ]} ${sec-websocket-key2} {}]] set key3 [read $sock 8] set handler [dict get $state options handler] chan event $sock readable [list ::wibble::ws-handle $handler $sock] set key1 [expr $key1/$spc1] set key2 [expr $key2/$spc2] set challenge [binary format II $key1 $key2]$key3 set response [md5 $challenge] puts $sock "HTTP/1.1 101 WebSocket Protocol Handshake" puts $sock "Connection: Upgrade" puts $sock "Upgrade: WebSocket" puts $sock "Sec-WebSocket-Origin: http://localhost:8080" ; # This shouldn't be hard coded!! puts $sock "Sec-WebSocket-Location: ws://localhost:8080/ws/demo" puts $sock "" chan configure $sock -translation binary puts $sock $response chan flush $sock $handler connect $sock ; # There should be an option to pass a session Id here. abortclient } ====== ---- [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. If 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. [jcw] Neat... [jbr]'s return 7 and keepalive idea look like a very useful tweak: [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. ====== } on 7 outcome { set keepalive 1 } finally { if { !$keepalive } { catch {chan close $socket} } } ====== Better 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. What'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). [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]`. A 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. <> Webserver