My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Friday, June 04, 2010

WebSocket Handshake 76 Simplified

update
there was a superfluous CR+LN with char 0x00 that was causing buffer troubles, now fixed



I am working during my free time (... recently extremely hard to have ...) over a little project that I'd like to show at the Front Trends event this October and WebSocket is the key of this project.

While 2 days ago I eventually found a way to communicate in few lines of php with a WebSocket, yesterday Chromium blog announced they "simply changed it", causing basically problems to all those projects based over the good old handshake75.

After I have found in Axod's Hack that somebody else had basically my same thoughts, I still could not find any valid example able to do the new handshake ... so here I am with the first draft-ietf-hybi-thewebsocketprotocol-00 php implementation I know, inspired somehow from the go version.


<?php

class WebSocketHandshake {

/*! Easy way to handshake a WebSocket via draft-ietf-hybi-thewebsocketprotocol-00
* @link http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt
* @author Andrea Giammarchi
* @blog webreflection.blogspot.com
* @date 4th June 2010
* @example
* // via function call ...
* $handshake = WebSocketHandshake($buffer);
* // ... or via class
* $handshake = (string)new WebSocketHandshake($buffer);
*
* socket_write($socket, $handshake, strlen($handshake));
*/

private $__value__;

public function __construct($buffer) {
$resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null;
preg_match('#GET (.*?) HTTP#', $buffer, $match) && $resource = $match[1];
preg_match("#Host: (.*?)\r\n#", $buffer, $match) && $host = $match[1];
preg_match("#Sec-WebSocket-Key1: (.*?)\r\n#", $buffer, $match) && $key1 = $match[1];
preg_match("#Sec-WebSocket-Key2: (.*?)\r\n#", $buffer, $match) && $key2 = $match[1];
preg_match("#Sec-WebSocket-Protocol: (.*?)\r\n#", $buffer, $match) && $protocol = $match[1];
preg_match("#Origin: (.*?)\r\n#", $buffer, $match) && $origin = $match[1];
preg_match("#\r\n(.*?)\$#", $buffer, $match) && $code = $match[1];
$this->__value__ =
"HTTP/1.1 101 WebSocket Protocol Handshake\r\n".
"Upgrade: WebSocket\r\n".
"Connection: Upgrade\r\n".
"Sec-WebSocket-Origin: {$origin}\r\n".
"Sec-WebSocket-Location: ws://{$host}{$resource}\r\n".
($protocol ? "Sec-WebSocket-Protocol: {$protocol}\r\n" : "").
"\r\n".
$this->_createHandshakeThingy($key1, $key2, $code)
;
}

public function __toString() {
return $this->__value__;
}

private function _doStuffToObtainAnInt32($key) {
return preg_match_all('#[0-9]#', $key, $number) && preg_match_all('# #', $key, $space) ?
implode('', $number[0]) / count($space[0]) :
''
;
}

private function _createHandshakeThingy($key1, $key2, $code) {
return md5(
pack('N', $this->_doStuffToObtainAnInt32($key1)).
pack('N', $this->_doStuffToObtainAnInt32($key2)).
$code,
true
);
}
}

// handshake headers strings factory
function WebSocketHandshake($buffer) {
return (string)new WebSocketHandshake($buffer);
}

?>


I am pretty sure above code does not need any other comment and methods are "as much semantic as possible", since I completely agree about the Axod point and the fact it's both over engineered and absolutely badly documented via those "specs" ... weird from a company famous for its simplicity concept that maybe this time forgot some KISS approach ...

18 comments:

Dr. Clue said...

I was merrily working on my HTML5
to Asterisk PBX AMI project when
the chrome update came through
and totally changed my priorities.

Hopefully with this little hint and
a bit of luck, I'll be able to get
my little HTML5 WebSocket server
back up and running soon.

I really wish hicks and them would make better documentation BEFORE flipping the switch next time. :)

Aadaam said...

I am pretty sure above code does not need any other comment and methods are "as much semantic as possible", since I completely agree about the Axod point and the fact it's both over engineered and absolutely badly documented via those "specs"


You haven't seen GData yet then...

Lessons learned: NOT everything is a list of things with an author and a last update date.

karlnorling said...

I wish someone would update the web-socket for nodeJS also.

http://github.com/andregoncalves/twitter-nodejs-websocket/blob/master/vendor/ws.js

Rob Flaherty said...

@karlnorling

This node.js websocket module works with the new spec implementation:
http://github.com/miksago/node-websocket-server

Anonymous said...

offtopic:

we andrea
hanno inserito il type hinting sugli scalar nell'ultimo trunk di php

www.ilia.ws

GOGOGOGO

Steven Roussey said...

Why PHP? Rather expensive way to handle things unless your daemon/server is built from PHP itself...

Andrea Giammarchi said...

PHP is one of the most popular server language for web content so ... why not? Anyway, you missed the point, how to do an handshake, it does not matter with which language ;)

Steven Roussey said...

I use PHP quite a bit, but it requires one process for every every websocket. Thus, it won't scale for many sites (for this purpose at least, for normal web pages, ajax, etc, it is fine). That is why you never see PHP mentioned in articles about comet connections between the client and server...

So I was definitely curious... :)

Andrea Giammarchi said...

I use Flash XML sockets with PHP since ages and I have never heard PHP requires a process for each socket ... and it would be nice if you can tell me where in this page you can read such thing, thanks.

Steven Roussey said...

I should have said, it requires a process for every socket, unless you have built your own server in PHP itself (actually, I did write that but the browser crashed and I was lazy the second time...).

I've never seen anything other than a rudimentary http/1.0 webserver written in PHP, so I'd love to know if you built a server in PHP itself, and your experiences with doing so.

PHP was never designed to run for long periods of time, though I know that there has been effort to fix that over the years. I'm most curious about how well it scales.

select/accept methods tend to fork so they can go back and handle another client concurrently

I think it was an engineer from meebo that said they started that way (in PHP) but had to abandon it. I got some interesting pointers along the way, but always worried about investing in something I would have to switch out of later.

There is no epoll or similar for php is there?

http://htmlimg3.scribdassets.com/98pvktwsad0nk0/images/18-c72c9747be/000.png

Andrea Giammarchi said...

Steven the point here is to show the handshake, PHP is just a simple way to it :)

the socket library is basically the native one, as the manual says, so socket programming a part which can be not that easy whatever language we use, I do believe PHP has not many problems there for what it offers, since socket "namespace" has been there since ages (true is that they did many nasty fixes during php 4 era)

concurrency should be handled by fixed stacks for each cycle and queued for the next iteration but I have never faced this inconvenient and I am sure if meebo guys said that there must be a valid reason (a link to that comment would be nice to better understand tho)

Regards

Steven Roussey said...

I really want to use PHP, and was hoping to find something on github, etc., that wasn't GPL like this:

http://sourceforge.net/projects/comet/

And was updated over time to include, oh the subject of your post, websockets! ;)

Andrea Giammarchi said...

comet and websocket are two completely different things ... where websocket wins under every aspect, imho ;)

Willy Tarreau said...

Unfortunately, draft 76 breaks HTTP compatibility by expecting the client to send 8 bytes that are not advertised in the header due to the lack of a proper Content-Length header. This results in the protocol not being able to pass reverse-proxies nor load balancers anymore. Nice :-/

And if you find a hacked reverse-proxy which lets them pass, then it means it can be abused by HTTP request smugling attacks because it lets data pass before the handshake is complete.

What is a real shame is that the protocol's author says he considers a mistake to try to make Websocket and HTTP coexist on the same port :-(

Unknown said...

With a simple modification to a script I was testing, I was able to implement your code into my test project, and successfully create a Websocket chat server via php. I couldn't figure out why Chrome 6 was failing, until I read your post. Thanks for the updates!

Steponas said...

Thanks! This has helper me a lot.

Andreas Rydberg said...

Thank you for giving me the last piece(the boolean in the md5()) to make the handshaking to work.

Zequez said...

I has been all night long searching for a solution because my old WebSocket server didn't work! I was missing the KeysAndCodeStuff xD

I love you man, thanks! I can go to sleep in peace now! ^^