June 15, 2011

WebSockets





UPDATE: THIS IS CODE FOR AN OLD VERSION OF THE WEBSOCKET PROTOCOL HANDSHAKE AND IS NO LONGER VALID.




Yes, long hiatus. Hi.

WebSockets have amazing potential for browser-based gaming. Unfortunately, they are also in a constant state of flux, partially because the draft spec keeps changing and partially because browser makers want to minimize the amount of dickery that internet doofuses can inflict on you. Firefox and Opera have temporarily dropped WebSocket support but they should be back in again in Firefox 6.

The last few nights I've been trying to fix a handshake bug with my implementation of draft76 handshaking. This is the version currently used by Chrome and Safari. Finally got it working again after writing the following test case:

#!/usr/bin/env python
#------------------------------------------------------------------------------
# netboa/websocket/sanity_check.py
# Copyright 2011 Jim Storch
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain a
# copy of the License at http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#------------------------------------------------------------------------------

"""
Wrote this to test that I'm correctly generating the responses for draft76
websockets.
"""

import struct
import hashlib


## From the draft76 docs:

REQUEST1 = (
'GET /demo HTTP/1.1\r\n'
'Host: example.com\r\n'
'Connection: Upgrade\r\n'
'Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n'
'Sec-WebSocket-Protocol: sample\r\n'
'Upgrade: WebSocket\r\n'
'Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n'
'Origin: http://example.com\r\n'
'\r\n'
'^n:ds[4U'
)

CORRECT_RESPONSE1 = "8jKS'y:G*Co,Wxa-"

REQUEST2 = (
'GET /demo HTTP/1.1\r\n'
'Host: example.com\r\n'
'Connection: Upgrade\r\n'
'Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0\r\n'
'Sec-WebSocket-Protocol: sample\r\n'
'Upgrade: WebSocket\r\n'
'Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8\r\n'
'Origin: http://example.com\r\n'
'\r\n'
'Tm[K T2u'
)

CORRECT_RESPONSE2 = 'fQJ,fN/4F4!~K~MH'

def parse_request(request):
req = {}
segments = request.split('\r\n\r\n', 1)
assert(len(segments) == 2)
header, payload = segments
lines = header.split('\r\n')
line = lines.pop(0)
items = line.split('\x20',2)
assert(len(items) == 3)
req['method'] = items[0]
req['request_uri'] = items[1]
req['http_version'] = items[2]
for line in lines:
parts = line.split(':\x20', 1)
assert(len(parts) == 2)
req[parts[0].lower()] = parts[1]
req['payload'] = payload
return req

def get_word(key):
digits=''
spaces=0
for char in key:
if char.isdigit():
digits += char
elif char == '\x20':
spaces += 1
assert(spaces)
assert(digits)
keynum = int(digits)
assert(keynum % spaces == 0)
wordval = keynum / spaces
assert(wordval < 2 ** 32)
return struct.pack("!I", wordval)

def create_token(req):
key1 = req.get('sec-websocket-key1', None)
assert(key1 is not None)
word1 = get_word(key1)
key2 = req.get('sec-websocket-key2', None)
assert(key2 is not None)
word2 = get_word(key2)
salt = req.get('payload', None)
assert(salt is not None)
assert(len(salt)==8)
return hashlib.md5(word1 + word2 + salt).digest()

req = parse_request(REQUEST1)
print create_token(req) == CORRECT_RESPONSE1

req = parse_request(REQUEST2)
print create_token(req) == CORRECT_RESPONSE2



Please note that this version is scheduled to be replaced by a draft called Hybi-08 which features a way simpler algorithm (which I *think* I have it nearly ready to drop in).

My interests are clearly taking Netboa towards being a browser-based game server. The combination of HTTP + WebSockets is quite appealing. It's really time to knuckle down and learn some Javascript -- or, more likely, lots of JQuery.

4 comments:

Richard said...

I'm headed in the same direction. Although I am really reticent to architect my own solution so am looking to use socket.io on the client-side and gevent socket.io support on the server-side.

I'd love to see a google code hosted project where mud developers collaborated on a web-based MUD client based on socket.io (or whatever) and they could just have their own game framework plugged in on the back end. Something like jsterm but allowing all the fancy graphical markup that Kavir does these days, and not using canvas.

I've written a simplistic terminal type interface just to experiment and it reminds me of how much cross-browser compatibility sucks. What is actually quite simple becomes convoluted and confusingly undocumented. Hmm, actually playing with my terminal it works okay with chrome which is good.

georgek said...

There's always Decaf, https://bitbucket.org/stendec/decafmud

I've been thinking too of a browser game and mud hybrid. Basically a browser game in presentation but with real-time synchronous components via sockets. Have you all looked at Echo Bazaar at all? They have a pretty good head start on the space but I think they're miles above where the typical browser games are at the moment.

Jim said...

@Richard

I agree that browser compatibility issues are a drain on programmer motivation. Same thing killed my enthusiasm for Telnet. That's probably why I'm looking at JQuery because it's trod much of that ground (ignoring that I'm pursuing a feature that only works on two browsers).

Do you think a generic client is feasible? My guess is that each author's needs would be too diverse.

@George

I hadn't seen Echo Bazaar before. Looks interesting. Thanks!

Richard said...

Yes, I think a generic client is possible. Although writing that, I am picturing mushclient and the sorts of things Kavir is doing with it. But I think better would be solid browser-client-side and server-side logic liberally licensed that any MUD developer could wrap and use with the minimal amount of work to get their MUD on the web.