How to fix Websocket Handshake code?

Kirk picture Kirk · Jul 10, 2012 · Viewed 14.1k times · Source

This is likely a familiar sob story. But there are so many of them out there, and I'm such a n00b I can't find the answer, so I'd like your help if you can help me.

So, I'm using phpwebsocket by lemmingzshadow (google brings this up pretty easily if you are unfamiliar). As far as I can tell the version he has out has a bug where it doesn't follow the standards that Chrome 20.+ now uses. Its got something to do with the hand shake & security keys but that's where I'm stuck at. I know I need to provide the following based on other questions, hopefully you can help me understand and fix this issue:

The header Chrome receives is (Edited; I apparently posted the message to the server twice.):

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: aWXrpLOnEm15mE8+w1zG05ad01k=
Sec-WebSocket-Protocol: QuatroDuo

The header my server receives is:

Upgrade: websocket
Connection: Upgrade
Host: gumonshoe.net:8000
Origin: http://gumonshoe.net
Sec-WebSocket-Key: v3+iw0U78qkwZnp+RWTu3A
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame

I don't think the cookies are necessary, correct me if I'm wrong though.

I hate to do this next part, but I figure pasting it all is better than doing nothing and needing to come back later. Here is the portion of code that reads & interprets the handshake and sends the new one.

Help is appreaciated:

<?PHP
private function handshake($data)
    {   
        $this->log('Performing handshake\r\n\r\n' . $data);  
        $lines = preg_split("/\r\n/", $data);

        // check for valid http-header:
        if(!preg_match('/\AGET (\S+) HTTP\/1.1\z/', $lines[0], $matches)) {
            $this->log('Invalid request: ' . $lines[0]);
            $this->sendHttpResponse(400);
            stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
            return false;
        }

        // check for valid application:
        $path = $matches[1];
        $this->application = $this->server->getApplication(substr($path, 1));
            if(!$this->application) {
                $this->log('Invalid application: ' . $path);
                $this->sendHttpResponse(404);           
                stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                $this->server->removeClientOnError($this);
                return false;
            }

        // generate headers array:
        $headers = array();
        foreach($lines as $line)
        {
            $line = chop($line);
            if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
            {
                $headers[$matches[1]] = $matches[2];
            }
        }

        // check for supported websocket version:       
        if(!isset($headers['Sec-WebSocket-Version']) || $headers['Sec-WebSocket-Version'] < 6)
        {
            $this->log('Unsupported websocket version.');
            $this->sendHttpResponse(501);
            stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
            $this->server->removeClientOnError($this);
            return false;
        }

        // check origin:
        if($this->server->getCheckOrigin() === true)
        {
            $origin = (isset($headers['Sec-WebSocket-Origin'])) ? $headers['Sec-WebSocket-Origin'] : false;
            $origin = (isset($headers['Origin'])) ? $headers['Origin'] : $origin;
            if($origin === false)
            {
                $this->log('No origin provided.');
                $this->sendHttpResponse(401);
                stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                $this->server->removeClientOnError($this);
                return false;
            }

            if(empty($origin))
            {
                $this->log('Empty origin provided.');
                $this->sendHttpResponse(401);
                stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                $this->server->removeClientOnError($this);
                return false;
            }

            if($this->server->checkOrigin($origin) === false)
            {
                $this->log('Invalid origin provided. : ' . $origin . ' Legal options were:');
                $gumk = 0;
                foreach(array_keys($this->server->getAllowedOrigins()) as $lo) {
                    $this->log( '[' . $gumk++ . '] : ' . $lo);
                }
                $this->sendHttpResponse(401);
                stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                $this->server->removeClientOnError($this);
                return false;
            }
        }       

        // do handyshake: (hybi-10)
        $secKey = $headers['Sec-WebSocket-Key'];
        $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
        $response = "HTTP/1.1 101 Switching Protocols\r\n";
        $response.= "Upgrade: websocket\r\n";
        $response.= "Connection: Upgrade\r\n";
        $response.= "Sec-WebSocket-Accept: " . $secAccept . "\r\n";
        $response.= "Sec-WebSocket-Protocol: " . substr($path, 1) . "\r\n\r\n";     
        if(false === ($this->server->writeBuffer($this->socket, $response)))
        {
            return false;
        }
        $this->handshaked = true;
        $this->log('Handshake sent');
        $this->application->onConnect($this);

        // trigger status application:
        if($this->server->getApplication('status') !== false)
        {
            $this->server->getApplication('status')->clientConnected($this->ip, $this->port);
        }

        return true;            
    }

Receiving the following error,

Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch

As I'm largely inexperienced in this level of server debugging, a more detailed answer than linking me to documentation/specifications would be appreciated.

Answer

Kirk picture Kirk · Jul 10, 2012

If any of you are beating your head against a wall, this is the offending piece of code:

$response.= "Sec-WebSocket-Protocol: " . substr($path, 1) .

I am sure there is a way to actually set the desired/possible protocols, but I'm not sure yet what they are; and I'm not sure if its necessary for my purposes. If someone has an explanation of what the protocol switching is even for, I'd love to read it, but for now I'm just taking it out of my code.

Lots of googling to find this small problem.

I also dumped the pack(H*) code in the handshake which didn't seem to be necessary based on what I was reading. I'm not sure if that did anything or not, but it wasn't necessary to get the program to work.