How to fix Websocket Handshake code? - php

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.

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.

Related

Socket.io 3 and PHP integration

I using PHP SocketIO class to connect NodeJS application and send messages.
Everything worked wonderfully with Socket.io 2 but after upgrade to version 3 the PHP integration is stopped working.
When I send request I am getting this response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hNcappwZIQEbMz7ZGWS71lNcROc=
But I don't see anything on NodeJS side, even when I tried to log any connection to the server by using "connection" event.
This is the PHP class:
class SocketIO
{
/**
* #param null $host - $host of socket server
* #param null $port - port of socket server
* #param string $action - action to execute in sockt server
* #param null $data - message to socket server
* #param string $address - addres of socket.io on socket server
* #param string $transport - transport type
* #return bool
*/
public function send($host = null, $port = null, $action= "message", $data = null, $address = "/socket.io/?EIO=2", $transport = 'websocket')
{
$fd = fsockopen($host, $port, $errno, $errstr);
if (!$fd) {
return false;
} //Can't connect tot server
$key = $this->generateKey();
$out = "GET $address&transport=$transport HTTP/1.1\r\n";
$out.= "Host: https://$host:$port\r\n";
$out.= "Upgrade: WebSocket\r\n";
$out.= "Connection: Upgrade\r\n";
$out.= "Sec-WebSocket-Key: $key\r\n";
$out.= "Sec-WebSocket-Version: 13\r\n";
$out.= "Origin: https://$host\r\n\r\n";
fwrite($fd, $out);
// 101 switching protocols, see if echoes key
$result= fread($fd,10000);
preg_match('#Sec-WebSocket-Accept:\s(.*)$#mU', $result, $matches);
$keyAccept = trim($matches[1]);
$expectedResonse = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
$handshaked = ($keyAccept === $expectedResonse) ? true : false;
if ($handshaked){
fwrite($fd, $this->hybi10Encode('42["' . $action . '", "' . addslashes($data) . '"]'));
fread($fd,1000000);
return true;
} else {return false;}
}
private function generateKey($length = 16)
{
$c = 0;
$tmp = '';
while ($c++ * 16 < $length) { $tmp .= md5(mt_rand(), true); }
return base64_encode(substr($tmp, 0, $length));
}
private function hybi10Encode($payload, $type = 'text', $masked = true)
{
$frameHead = array();
$payloadLength = strlen($payload);
switch ($type) {
case 'text':
$frameHead[0] = 129;
break;
case 'close':
$frameHead[0] = 136;
break;
case 'ping':
$frameHead[0] = 137;
break;
case 'pong':
$frameHead[0] = 138;
break;
}
if ($payloadLength > 65535) {
$payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8);
$frameHead[1] = ($masked === true) ? 255 : 127;
for ($i = 0; $i < 8; $i++) {
$frameHead[$i + 2] = bindec($payloadLengthBin[$i]);
}
if ($frameHead[2] > 127) {
$this->close(1004);
return false;
}
} elseif ($payloadLength > 125) {
$payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8);
$frameHead[1] = ($masked === true) ? 254 : 126;
$frameHead[2] = bindec($payloadLengthBin[0]);
$frameHead[3] = bindec($payloadLengthBin[1]);
} else {
$frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength;
}
foreach (array_keys($frameHead) as $i) {
$frameHead[$i] = chr($frameHead[$i]);
}
if ($masked === true) {
$mask = array();
for ($i = 0; $i < 4; $i++) {
$mask[$i] = chr(rand(0, 255));
}
$frameHead = array_merge($frameHead, $mask);
}
$frame = implode('', $frameHead);
for ($i = 0; $i < $payloadLength; $i++) {
$frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
}
return $frame;
}
}
Thank you for help!
I was having the same problem with all the libraries that exists on github, the problem is that they are abandoned or not updated to socket.io V3.
In socket.io documentation says:
TL;DR: due to several breaking changes, a v2 client will not be able to connect to a v3 server (and vice versa)
To solve this problem, you need to learn how socket.io client works, this is easy because is in the protocol documentation, in the sample-session section.
Socket.Io protocol documentation
To solve this, you will need to forget the fsockopen and fwrite functions, you need to use CURL directly doing the requests mentioned in the protocol documentation.
Request n°1
GET
url: /socket.io/?EIO=4&transport=polling&t=N8hyd7H
Open packet: Open the connection between php and socket.io server. The server will return a "session id" named "sid", you will be adding this to the url query for the subsecuent queries.
Request n°2
POST
url: /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1
post body: '40'
Namespace connection request: You need to send in the body the number 40, as a string, this means that you want to connect to socket.io "message" type
Request n°3
GET
url: /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1
Namespace connection approval : This will return if the connection is successful or if there is an error, here is when the socket.io server authorizes your connection if you need a token.
Request n°4
POST
url: /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=sessionIdFromRequest1
post body: 42[event,data]
For example 42["notifications","Hi, Im a notification"] and is equivalent to socket.emit(event,data)
Emit message to server: Send your message to the socket.io server.
Here is a BASIC example using Symfony 5.2 and HttpClientInterface:
<?php
// install dependencies before: composer require symfony/http-client
use Symfony\Component\HttpClient\CurlHttpClient;
include('vendor/autoload.php');
$client = new CurlHttpClient();
sendToSocket($client);
function sendToSocket(HttpClientInterface $client)
{
$first = $client->request('GET', 'http://localhost:3000/socket.io/?EIO=4&transport=polling&t=N8hyd6w');
$res = ltrim($first->getContent(), '0');
$res = json_decode($res, true);
$sid = $res['sid'];
$second = $client->request('POST', 'http://localhost:3000/socket.io/?EIO=4&transport=polling&sid='.$sid, [
'body' => '40'
]);
$third = $client->request('GET', 'http://localhost:3000/socket.io/?EIO=4&transport=polling&sid='.$sid);
$fourth = $client->request('POST', 'http://localhost:3000/socket.io/?EIO=4&transport=polling&sid='.$sid, [
'body' => '42["notifications","Hi, Im a notification"]'
]);
}
As you can see, is very easy, and you dont need the troubling "copy-pasted" libraries out there. I said "copy-pasted" because all use the same code to open de socket and send the information, but no one is compatible with socket.io V3.
Here is an image, proving that the given code works as January 4 2021 with php 7.4, symfony 5.2 and socket.io V3.
This is my test server in node
// Install dependencies before: npm i express socket.io
const app = require('express')();
const http = require('http').createServer(app);
const io = require('socket.io')(http, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
io.on('connection', function (socket) {
console.log("New Connection with transport", socket.conn.transport.name);
socket.on('notifications', function (data) {
console.log(data);
});
});
http.listen(3000, () => {
console.log('Server started port 3000');
});
I need to say that this solution works excellent if you want to send "one direction" messages to your socket.io server, like a new notification or whatever that doesn't need a permanent connection, is just "one shot" and nothing else.
Happy coding and greetings from Mexico.
Here is another example:
First column is Postman making a request to the php server, simulating a server side event, like a new question created. In the response are the dumps of the response body from the 4 requests that you need to make.
Second column is the socket.IO node server running on port 3000
And the last column is the chrome console, simulating a user connected to the socket.IO server via websocket looking for notifications in 'questions' event.

PHP: write_socket() giving unexpected warning

I'm trying to set up a WebSocket application, but I'm stuck at the following.
After accepting the socket I received the client's headers as expected, but when I try to send the upgrade back socket_write() throws a warning:
Warning: socket_write(): unable to write to socket [10038]: An operation was attempted on something that is not a socket
This happens in the following piece of code:
var_dump($this->socket); //output: resource(2) of type (Socket)
socket_write($this->socket, $upgrade);
This happens inside a pthreads context.
What are possible reasons PHP is throwing me this warning?
The full code:
public function handshake($headers)
{
Main::console($headers);
Main::console("Getting client WebSocket version...");
Main::console("Headers: \r\n\r\n".$headers);
if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/", $headers, $match))
$version = $match[1];
else {
Main::console("The client doesn't support WebSocket");
return false;
}
Main::console("Client WebSocket version is {$version}, (required: 13)");
if($version == 13) {
// Extract header variables
Main::console("Getting headers...");
if(preg_match("/GET (.*) HTTP/", $headers, $match))
$root = $match[1];
if(preg_match("/Host: (.*)\r\n/", $headers, $match))
$host = $match[1];
if(preg_match("/Origin: (.*)\r\n/", $headers, $match))
$origin = $match[1];
if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $headers, $match))
$key = $match[1];
Main::console("Client headers are:\r\n\r\n".
"- Root: ".$root."\r\n".
"- Host: ".$host."\r\n".
"- Origin: ".$origin."\r\n".
"- Sec-WebSocket-Key: ".$key."\n");
Main::console("Generating Sec-WebSocket-Accept key...");
$acceptKey = $key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$acceptKey = base64_encode(sha1($acceptKey, true));
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n".
"Upgrade: websocket\r\n".
"Connection: Upgrade\r\n".
"Sec-WebSocket-Accept: $acceptKey".
"\r\n\r\n";
Main::console("Sending this response to the client #{$this->getId()}:\r\n\r\n".$upgrade);
var_dump($this->socket);
socket_write($this->socket, $upgrade, strlen($upgrade));
$this->setHandshake(true);
Main::console("Handshake is successfully done!");
return true;
}
else {
Main::console("WebSocket version 13 required (the client supports version {$version})");
return false;
}
}
public function run()
{
while($this->alive)
{
$bytes = #socket_recv($this->socket, $buffer, 4096, MSG_WAITALL);
if ($buffer)
{
if(!$this->handshake)
{
$this->handshake($buffer);
} else {
Main::console("Client {$this->getID()} says {$buffer}");
}
}
}
}
Try socket_last_error() and socket_strerror() for more information. If that doesn't help, show us how the socket is created. Source: http://www.php.net/manual/en/function.socket-last-error.php

WebSocket handshake not working

I'm making a simple WebSocket server in PHP. My websocket client connects to it fine, but any time I try to send data through it I get an "Error: INVALID_STATE_ERR: DOM Exception 11" thrown in JavaScript.
This and a couple other questions seem to describe the same problem I'm having but the WebSocket protocol has changed since.
I'm assuming the problem is that my script is handshaking incorrectly as stated in that question. I'm using Chromium 15 which uses WebSocket version 8.
Here's my handshake function (partially my code partially modified from an outdated example I found somewhere):
function dohandshake($user, $buffer)
{
server_log(1, 'Requesting handshake...');
// Determine which version of the WebSocket protocol the client is using
if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/ ", $buffer, $match))
$version = $match[1];
else
return false;
if($version == 8)
{
// Extract header variables
if(preg_match("/GET (.*) HTTP/" ,$buffer,$match)){ $r=$match[1]; }
if(preg_match("/Host: (.*)\r\n/" ,$buffer,$match)){ $h=$match[1]; }
if(preg_match("/Sec-WebSocket-Origin: (.*)\r\n/",$buffer,$match)){ $o=$match[1]; }
if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$buffer,$match)){ $k = $match[1]; }
// Generate our Socket-Accept key based on the IETF specifications
$accept_key = $k . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$accept_key = sha1($accept_key, true);
$accept_key = base64_encode($accept_key);
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: $accept_key";
socket_write($user->socket, $upgrade, strlen($upgrade));
$user->handshake = true;
return true;
}
else
{
server_log("Client is trying to use an unsupported WebSocket protocol ({$version})");
return false;
}
}
I tested the key generation code on several examples I found and it seems to have returned the correct key according to those examples
Dumb solution of the century, apparently two "\r\n" newlines are expected at the end of the handshake response.

php websocket problem

I'm new to web sockets.. i made my first web socket and i am having problems on running it now!
here is the code of the socket
// set some variables
$host = "127.0.0.1";
$port = 1234;
// don't timeout!
set_time_limit(0);
// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socket\n");
if($socket){
echo "socket created .... $socket\n";
}
// bind socket to port
$result = socket_bind($socket, $host, $port) or die("Could not bind to socket\n");
if($result){
echo "socket binded ... $result\n";
}
// start listening for connections
$result = socket_listen($socket, 3) or die("Could not set up socket listener\n");
if($result){
echo "socket is now listening ... $result";
}
// accept incoming connections
// spawn another socket to handle communication
$spawn = socket_accept($socket) or die("Could not accept incoming connection\n");
if($spawn){
echo $spawn."\n";
}
// read client input
$input = socket_read($spawn, 1024) or die("Could not read input\n");
if($input){
echo $input."\n";
}
// clean up input string
$input = trim($input);
// reverse client input and send back
$output = strrev($input) . "\n";
socket_write($spawn, $output, strlen ($output)) or die("Could not write output\n");
// close sockets
socket_close($spawn);
socket_close($socket);
now how can i run this code?? i wrote on my xampp shell the following code:
php htdocs/socket/server.php -q
it displays:
socket created....Resource id #4
socket binded... 1
socket is now listening...1 Resource is #5
GET socket/server.php HTTP 1.1
upgrade: WebSocket
connection: Upgrade
Host: http://localhost
sec-WebSocket-key1: 14 53 8501 z4 5R'
sec-WebSocket-key2: S 9\ 2s63, *8460!~MO#
now how can i run it.. how can i send input to it and how can i use it with JavaScript??
i made a JavaScript code but it connect for a second and then disconnect...
here is the javascipt code:
$(document).ready(function() {
if(!("WebSocket" in window)){
$('#chatLog, input, button, #examples').fadeOut("fast");
$('<p>Oh no, you need a browser that supports WebSockets. How about Google Chrome?</p>').appendTo('#container');
}else{
//The user has WebSockets
connect();
function connect(){
var socket;
var host = "ws://localhost:1234/websocket_source_files/myown.php";
try{
var socket = new WebSocket(host);
message('<p class="event">Socket Status: '+socket.readyState);
socket.onopen = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
}
socket.onmessage = function(msg){
message('<p class="message">Received: '+msg.data);
}
socket.onclose = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
}
} catch(exception){
message('<p>Error'+exception);
}
function send(){
var text = $('#text').val();
if(text==""){
message('<p class="warning">Please enter a message');
return ;
}
try{
socket.send(text);
message('<p class="event">Sent: '+text)
} catch(exception){
message('<p class="warning">');
}
$('#text').val("");
}
function message(msg){
$('#chatLog').append(msg+'</p>');
}//End message()
$('#text').keypress(function(event) {
if (event.keyCode == '13') {
send();
}
});
$('#disconnect').click(function(){
socket.close();
});
}
}//End connect()
});
</script>
<title>WebSockets Client</title>
</head>
<body>
<div id="wrapper">
<div id="container">
<h1>WebSockets Client</h1>
<div id="chatLog">
</div>
<p id="examples">e.g. try 'hi', 'name', 'age', 'today'</p>
<input id="text" type="text" />
<button id="disconnect">Disconnect</button>
</div>
</div>
</body>
</html>​
please help me run this code and learn web sockets.. i really need to use them with my school project.
The socket_accept-function will block (wait) until a client connects to it. That's it's standard behavior.
But the functions you execute after you've connected your socket don't block (unless you tell them to). So you'll want to tell your script to wait until it can read from the Socket.
To do so, the socket_set_block-function is used. Also, you might want to check for any possible errors using the socket_last_error-function.
Although, I think Java or C are way bedder suited for using Sockets.
Write another PHP script which would connect to it.
you are not doing the handshake propertly.
from what you posted, you are dealing with the ietf-00 implementation ( https://datatracker.ietf.org/doc/html/draft-ietf-hybi-thewebsocketprotocol-00 )
this is old and deprecated, the last one seems to be ietf-10 ( https://datatracker.ietf.org/doc/html/draft-ietf-hybi-thewebsocketprotocol-10 ).
a very basic description of the handshake you need can be found here: http://en.wikipedia.org/wiki/WebSockets
(you can find in there the links to the newer and official specifications).
The important part in your case is this:
The Sec-WebSocket-Key1 and Sec-WebSocket-Key2 fields and the 8 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.
The handshake is constructed by concatenating the numbers from the
first key, and dividing by the number of spaces. This is then repeated
for the second key. The two resulting numbers are concatenated with
each other, and with the last 8 bytes after the fields.
The final result is an MD5 sum of the concatenated string.[7] The
handshake looks like HTTP but actually isn't. It allows the server to
interpret part of the handshake request as HTTP and then switch to
WebSocket. Once established, WebSocket data frames can be sent back
and forth between the client and the server in full-duplex mode. Text
frames can be sent full-duplex, in either direction at the same time.
The data is minimally framed with just two bytes. Each frame starts
with a 0x00 byte, ends with a 0xFF byte, and contains UTF-8 data in
between. Binary frames are not supported yet in the API. WebSocket
text frames use a terminator, while binary frames use a length prefix.
Now, some code (this will accept one connection, receive a message, and then send a response, just like a very basic and raw example to show how it can be done):
// Just to log to console
function myLog($msg)
{
echo date('m/d/Y H:i:s ', time()) . $msg . "\n";
}
// This will actually read and process the key-1 and key-2 variables, doing the math for them
function getWebSocketKeyHash($key)
{
$digits = '';
$spaces = 0;
// Get digits
preg_match_all('/([0-9])/', $key, $digits);
$digits = implode('', $digits[0]);
// Count spaces
$spaces = preg_match_all("/\\s/ ", $key, $dummySpaces);
$div = (int)$digits / (int)$spaces;
myLog('key |' . $key . '|: ' . $digits . ' / ' . $spaces . ' = ' . $div);
return (int)$div;
}
// This will read one header: value from the request header
function getWebSocketHeader($buffer, &$lines, &$keys)
{
preg_match_all("/([a-zA-Z0-9\\-]*)(\\s)*:(\\s)*(.*)?\r\n/", $buffer, $headers);
$lines = explode("\r\n", $buffer);
$keys = array_combine($headers[1], $headers[4]);
}
// This is where the handshake gets done
function handshake($peer)
{
$buffer = socket_read($peer, 4096, PHP_BINARY_READ);
socket_getpeername($peer, $address, $port);
$peerName = $address . ':' . $port;
myLog('Got from: ' . $peerName . ': ' . $buffer);
getWebSocketHeader($buffer, $lines, $keys);
if (!isset($keys['Sec-WebSocket-Key1']) || !isset($keys['Sec-WebSocket-Key2'])) {
myLog('Invalid websocket handshake for: ' . $peerName);
return;
}
$key1 = getWebSocketKeyHash($keys['Sec-WebSocket-Key1']);
$key2 = getWebSocketKeyHash($keys['Sec-WebSocket-Key2']);
$code = array_pop($lines);
// Process the result from both keys and form the response header
$key = pack('N', $key1) . pack('N', $key2) . $code;
myLog('1:|' . $key1 . '|- 2:|' . $key2 . '|3:|' . $code . '|4: ' . $key);
$response = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n";
$response .= "Upgrade: WebSocket\r\n";
$response .= "Connection: Upgrade\r\n";
$response .= "Sec-WebSocket-Origin: " . trim($keys['Origin']) . "\r\n";
$response .= "Sec-WebSocket-Location: ws://" . trim($keys['Host']) . "/\r\n";
$response .= "\r\n" . md5($key, true); // this is the actual response including the hash of the result of processing both keys
myLog($response);
socket_write($peer, $response);
}
// This is where you can send a frame (delimited by 0x00 and 0xFF)
function send($peer, $message)
{
socket_write($peer, pack('c', (int)0) . utf8_encode($message) . pack('c', (int)255));
}
// This is where you receive a frame (delimited again by 0x00 and 0xFF)
function receive($peer)
{
$buffer = socket_read($peer, 4096, PHP_BINARY_READ);
if (empty($buffer)) {
myLog('Error receiving from peer');
return;
}
return substr($buffer, 1, -1);
}
// Now create a socket
$socket = socket_create_listen(1026);
$peer = socket_accept($socket);
// Do the handshake and wait for an incoming message from the client
handshake($peer);
myLog('Got ' . receive($peer));
// Respond!
send($peer, 'hi there');
socket_close($peer);
socket_close($socket);
EDIT:
this is a very basic html that works in chrome (mine at least):
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript">
function WebSocketTest()
{
if ("WebSocket" in window)
{
// Let us open a web socket
var ws = new WebSocket("ws://host:1026");
ws.onopen = function()
{
// Web Socket is connected, send data using send()
ws.send("Message to send");
console.log('send');
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
console.log(received_msg);
var txt = document.createTextNode(received_msg);
document.getElementById('messages').appendChild(txt);
};
ws.onclose = function()
{
// websocket is closed.
console.log('close');
};
}
else
{
// The browser doesn't support WebSocket
alert("WebSocket NOT supported by your Browser!");
}
}
</script>
</head>
<body>
<div id="sse">
Run WebSocket
</div>
<div id="messages">
</div>
</body>
</html>

Using PHP to read a web page with fsockopen(), but fgets is not working

Im using this code here: http://www.digiways.com/articles/php/httpredirects/
public function ReadHttpFile($strUrl, $iHttpRedirectMaxRecursiveCalls = 5)
{
// parsing the url getting web server name/IP, path and port.
$url = parse_url($strUrl);
// setting path to '/' if not present in $strUrl
if (isset($url['path']) === false)
$url['path'] = '/';
// setting port to default HTTP server port 80
if (isset($url['port']) === false)
$url['port'] = 80;
// connecting to the server]
// reseting class data
$this->success = false;
unset($this->strFile);
unset($this->aHeaderLines);
$this->strLocation = $strUrl;
$fp = fsockopen ($url['host'], $url['port'], $errno, $errstr, 30);
// Return if the socket was not open $this->success is set to false.
if (!$fp)
return;
$header = 'GET / HTTP/1.1\r\n';
$header .= 'Host: '.$url['host'].$url['path'];
if (isset($url['query']))
$header .= '?'.$url['query'];
$header .= '\r\n';
$header .= 'Connection: Close\r\n\r\n';
// sending the request to the server
echo "Header is: <br />".str_replace('\n', '\n<br />', $header)."<br />";
$length = strlen($header);
if($length != fwrite($fp, $header, $length))
{
echo 'error writing to header, exiting<br />';
return;
}
// $bHeader is set to true while we receive the HTTP header
// and after the empty line (end of HTTP header) it's set to false.
$bHeader = true;
// continuing untill there's no more text to read from the socket
while (!feof($fp))
{
echo "in loop";
// reading a line of text from the socket
// not more than 8192 symbols.
$good = $strLine = fgets($fp, 128);
if(!$good)
{
echo 'bad';
return;
}
// removing trailing \n and \r characters.
$strLine = ereg_replace('[\r\n]', '', $strLine);
if ($bHeader == false)
$this->strFile .= $strLine.'\n';
else
$this->aHeaderLines[] = trim($strLine);
if (strlen($strLine) == 0)
$bHeader = false;
echo "read: $strLine<br />";
return;
}
echo "<br />after loop<br />";
fclose ($fp);
}
This is all I get:
Header is:
GET / HTTP/1.1\r\n
Host: www.google.com/\r\n
Connection: Close\r\n\r\n
in loopbad
So it fails the fgets($fp, 128);
Is there a reason you aren't using PHP's built-in, enabled-by-default ability to fetch remote files using fopen?
$remote_page = file_get_contents('http://www.google.com/'); // <- Works!
There are also plenty of high-quality third-party libraries, if you need to do something like fetch headers without thinking too hard. Try Zend_Http_Client on for size.
The flaw is here:
$good = $strLine = fgets($fp, 128);
if(!$good)
{
echo 'bad';
return;
}
fgets() returns either a string on success, or FALSE on failure. However, if there was no more data to be returned, fgets() will return the empty string (''). So, both $good and $strLine are set to the empty string, which PHP will happily cast to FALSE in the if() test. You should rewrite as follows:
$strLine = fgets($fp, 128);
if ($strLine === FALSE) { // strict comparison - types and values must match
echo 'bad';
return;
}
There's no need for the double assignment, as you can test $strLine directly.

Categories