It looks like my previous answer was, at best, partially right. It
looks lke [ns_conn channel] does in fact give you a read/write channel.
The effect of the missing content-length header is to discard any
data that was read past the headers. Attached is a patch
(readahead.patch) to change that behavior. The patch does 2 things:
one, if the request includes 'upgrade' in the connection header, it
doesn't read past the header (and doesn't throw away what it has read).
Two, when [ns_conn channel] is called, it will insert into the channel
buffer any data that was already read (and not processed) on the socket.
Combined, it means that on an upgrade connection you can get the
entire body, even if there was no content-length header sent.
Also attached is a tcl file (index.tcl) that looks for an upgrade
header, and when it gets one it forks a background tcl thread that
echoes everything read back to the sender. Every new request will get a
new thread. It also probably needs some tweaking to get a second
request to work at all (since it's using shared channels to give the
socket to the background thread).
Importantly, this design of thread-per-connection is not scalable - your
server will fall down and start crying once you start up a few dozen
threads. (perhaps more; a really rough guess is that each tcl thread
consumes 2M of memory before doing anything). What you really want to
do is to transfer each new websocket to a single existing background
thread, but I'm not sure how to send stuff into another thread's event
loop using the aolserver thread tools. The standard tcl thread package
is more powerful (with thread::send and thread::wait) but I don't know
if those play nice inside AOLserver.
-J
wiwo wrote:
Hi!
I just tried the following:
I've been telneting to our server and was feeding it this request:
GET /test/ns_chan HTTP/1.1
Host: www.dcon.at
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
Sec-WebSocket-Protocol: sample
Content-Length: 8
Upgrade: WebSocket
Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
Origin: http://example.com
^n:ds[4U
Notice the "Content-Length" header. As Jeff told me, I've now been
able to output the 8 byte "body" of the request.
Then I was creating an ns_chan with:
set ch [ns_conn channel]
ns_chan create $ch $chan_name
and getting it back with:
set ch [ns_chan get $chan_name]
The I tried to write my WebSocket server handshake with:
puts $ch "
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://[util::host]
Sec-WebSocket-Location: ws://[util::host]/[ns_conn url]
Sec-WebSocket-Protocol: sample
$ret
"
which showed up in the telnet client.
Then I tried to read again
set str [read $x 10]
ns_log notice $str
and typed 10 characters in the telnet client which showed up in the
log.
So far so good! I could now write my own proposal for changing the
WebSocket Client and make Google and Apple change their
implementations of the protocol. Unfortunately my relationship with
Steve and Larry cooled down the last few months, so this could take a
while :-)
Or I could get my hands dirty and mess around with the code in
driver.c.
As far as I can see, the crucial code is in
static ReadErr SockReadLine(Driver *drvPtr, Ns_Sock *sock, Conn
*connPtr)
somewhere at line 1878 where the content length is read. I could
simply check for the "Upgrade: WebSocket" header and set the length to
the magic number of 8.
Of course I have no idea when I'm talking about AOLserver C code, but
maybe someone with a clue can give me his/her thoughts here.
wiwo
I'm pretty sure it's not possible to implement websockets in AOLserver
without some C-level work, and if you could you wouldn't want to.
You can't, because the driver reads the entire request, including
content, before handing it off to the connection thread. And it only
reads past the headers if there is a 'Content-length' header. (You can
hook into the content-reading with a read filter, but that won't help
because with no content-length it won't ever try to read it).
You wouldn't want to, because you would attach a conn thread to a single
persistent websocket, which would severely limit how many you could
handle. Doing it as a background tcl thread would be little better,
because you'd still need a thread per connection. Handling all
websockets with a single background thread using tcl event handling
would work, if you could get read/write handles to the sockets (which
currently you can't - I think the background delivery patch only creates
a writable channel).
So what else can you do?
For starters, you could check the current state of naviserver - I'm not
up to date on what has changed there lately, but a r/w version of
'ns_conn channel' might have been implemented.
Otherwise, you need to get your hands dirty in the C code and implement
'Upgrade' handling. If you do take this approach, I'd suggest
implementing just enough logic to transfer control elsewhere - that is,
in the driver, don't try to handle websockets, rather set it up to
recognize the upgrade protocol ("Connection: upgrade" and "Upgrade:
something" headers) and call a registered procedure and forget about the
connection (leaving it to the registered proc to actually do anything
with it).
-J
--
AOLserver -http://www.aolserver.com/
To Remove yourself from this list, simply send an email to
<[email protected]> with the
body of "SIGNOFF AOLSERVER" in the email message. You can leave the Subject:
field of your email blank.
--
AOLserver - http://www.aolserver.com/
To Remove yourself from this list, simply send an email to
<[email protected]> with the
body of "SIGNOFF AOLSERVER" in the email message. You can leave the Subject:
field of your email blank.
--
AOLserver - http://www.aolserver.com/
To Remove yourself from this list, simply send an email to
<[email protected]> with the
body of "SIGNOFF AOLSERVER" in the email message. You can leave the Subject:
field of your email blank.
? nsd
Index: conn.c
===================================================================
RCS file: /cvsroot/aolserver/aolserver/nsd/conn.c,v
retrieving revision 1.50
diff -c -r1.50 conn.c
*** conn.c 8 Dec 2009 04:12:19 -0000 1.50
--- conn.c 1 Nov 2010 22:31:07 -0000
***************
*** 1639,1644 ****
--- 1639,1647 ----
if (chan == NULL && spliceout) {
connPtr->sockPtr->sock = sock;
}
+ if (chan != NULL) {
+ Tcl_Ungets(chan,connPtr->content, connPtr->avail, 0);
+ }
return chan;
}
Index: driver.c
===================================================================
RCS file: /cvsroot/aolserver/aolserver/nsd/driver.c,v
retrieving revision 1.59
diff -c -r1.59 driver.c
*** driver.c 8 Dec 2009 04:12:19 -0000 1.59
--- driver.c 1 Nov 2010 22:31:07 -0000
***************
*** 1714,1720 ****
++sockPtr->nreads;
if ((connPtr->flags & NS_CONN_READHDRS)) {
! err = SockReadContent(drvPtr, sock, connPtr);
} else {
err = SockReadLine(drvPtr, sock, connPtr);
}
--- 1714,1726 ----
++sockPtr->nreads;
if ((connPtr->flags & NS_CONN_READHDRS)) {
! char *hdr = Ns_SetIGet(connPtr->headers, "connection");
! if (hdr != NULL && strstr(hdr, "upgrade") != NULL) {
! /* don't read past headers if this is an upgrade request */
! err = 0;
! } else {
! err = SockReadContent(drvPtr, sock, connPtr);
! }
} else {
err = SockReadLine(drvPtr, sock, connPtr);
}
if {[ns_set iget [ns_conn headers] upgrade] ne ""} {
set c [ns_conn channel]
ns_chan create $c ws
# ns_chan put ws
ns_thread begindetached {
set ch [ns_chan get ws]
fconfigure $ch -blocking 0 -buffering none
puts $ch "HTTP/1.0 200 OK\nContent-type: WebSocket\n\n"
proc doecho {ch} { puts $ch echo:[read $ch] }
fileevent $ch readable [list doecho $ch]
vwait forever
}
} else {
ns_write \
"HTTP/1.0 200 OK
Content-type: text/plain
Nothing
"
}