views:

277

answers:

1

I'm using python to implement a simple websocket server. The handshake I'm using comes from http://en.wikipedia.org/w/index.php?title=WebSockets&oldid=372387414.

The handshake itself seems to work, but when I hit send, I get a javascript error:

Uncaught Error: INVALID_STATE_ERR: DOM Exception 11

Here's the html:

<!doctype html>
<html>
    <head>
        <title>ws_json</title>

    </head>
    <body onload="handleLoad();" onunload="handleUnload();">
        <input type="text" id='input' />
        <input type="button" value="submit" onclick="handleSubmit()" />
        <div id="display"></div>

        <script type="text/javascript">
            function showmsg(str){
                display = document.getElementById("display");
                display.innerHTML += "<p>" + str + "</p>";
            }

            function send(str){
                ws.send(str.length);
                ws.send(str);
            }

            function handleSubmit(){
                input = document.getElementById('input');
                send(input.value);
                input.focus();
                input.value = '';
            }

            function handleLoad(){
                ws = new WebSocket("ws://localhost:8888/");
                ws.onopen = function(){
                    showmsg("websocket opened.");
                }

                ws.onclose = function(){
                    showmsg("websocket closed.");
                }
            }

            function handleUnload(){
                ws.close();
            }
        </script>
    </body>
</html>

And here's the python code:

import socket
import threading
import json

PORT = 8888
LOCATION = "localhost:8888"

def handler(s):

    print " in handler "

    ip, _ = s.getpeername()
    print "New connection from %s" % ip
    request = s.recv(1024)

    print "\n%s\n" % request
    print s.getpeername()

    # send response
    response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
    response += "Upgrade: WebSocket\r\n"
    response += "Connection: Upgrade\r\n"
    try:
        peername = s.getpeername()
        response += "Sec-WebSocket-Origin: http://%s\r\n" % peername[0] # % request[request.index("Origin: ")+8:-4]
    except ValueError:
        print "Bad Request"
        raise socket.error
    response += "Sec-WebSocket-Location: ws://%s\r\n" % LOCATION
    response += "Sec-WebSocket-Protocol: sample"
    response = response.strip() + "\r\n\r\n"

    print response
    s.send(response)

    while True:
        length = s.recv(1)
        print length
        if not length:
            break
        length = int(length)
        print "Length: %i" % length
        data = s.recv(length)
        print "Received: %s" % data
        print ""

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', PORT))
s.listen(5)

print "server is running..."
while True:
    sock, addr = s.accept()
    threading.Thread(target=handler, args=(sock, )).start()

Does anyone know what I'm doing wrong here?

+4  A: 

I tested your code on Firefox 4 and got the same error upon hitting send, however before that I got

Firefox can't establish a connection to the server at ws://localhost:8888/.

which is probably why the WebSocket object was destroyed. I suspect your handshake response is missing something, so Firefox is closing the socket.

From the Wikipedia article on Websockets:

The Sec-WebSocket-Key1 and Sec-WebSocket-Key2 fields and the eight bytes after the fields are random tokens which the server uses to construct a 16 byte token at the end of its handshake to prove that it has read the client's handshake.

Your server's response does not have this special number at the bottom, So I think we need to figure out how to generate it, and include it.

EDIT: How to generate that number

Lets start with key1, key2, and the 8 bytes at the end of the handshake

key1 = "18x 6]8vM;54 *(5:  {   U1]8  z [  8"
key2 = "1_ tx7X d  <  nw  334J702) 7]o}` 0"
end8 = "Tm[K T2u"

We make a number for each key by ignoring every character that is not a digit 0-9. In Python:

def numFromKey(key):
    return int(filter(lambda c: c in map(str,range(10)),key))

next we divide that number by the number of spaces in the original key string, so here is a is a function that counts the spaces in a string.

def spacesIn(key):
    return len(filter(lambda c: c==' ',key))

The two numbers resulting from the keys are:

pkey1 = numFromKey(key1)/spacesIn(key1)
pkey2 = numFromKey(key2)/spacesIn(key2)

Now we need to concatenate the bytes of pkey1, pkey2, and end8. The processed keys need to be represented as 32 bit Big-Endian numbers.

from struct import pack
catstring = pack('>L',pkey1) + pack('>L',pkey2) + end8

Then we take the md5 hash of those bytes to get the magic number that we tack on the end of the handshake

import md5
magic = md5.new(catstring).digest()

Thats how I think it works at least

Nathan
Thanks for the info, never would have figured that out myself. I found this on google: http://golang.org/src/pkg/websocket/server.go it describes how to generate a key. Working on understanding it as we speak.
Joshua Moore
Here's a better description of the handshake: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#page-7
Joshua Moore
I came to a similar solution, but I'm having problems encoding the results of the md5 algorithm in utf-8.
Joshua Moore
http://pastebin.com/Q4GPcM09 Here is my code for the handshake, but as I said there's a problem with the utf-8 encoding.
Joshua Moore
There is a ruby implementation here: http://github.com/igrigorik/em-websocket/blob/master/lib/em-websocket/handler76.rb As to encoding an MD5 hash as UTF-8; that should not be needed because ASCII is a subset of UTF-8. So an ASCII string (MD5 hashes only contain [0-9a-f]) is automatically valid UTF-8.
igorw