Skip to content

Commit

Permalink
Added security/performance featues: max. clients, max. requests per
Browse files Browse the repository at this point in the history
minute.
Added hybi-13 origin check to support chrome 16.
  • Loading branch information
nekudo committed Dec 15, 2011
1 parent 69eb43b commit 5c08967
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 15 deletions.
15 changes: 9 additions & 6 deletions README.md
@@ -1,21 +1,24 @@
PHP WebSocket
=============
A websocket server implemented in php.

A simple PHP WebSocket server and client.
ATTENTION: Totally beta and just for testing/development!

- Supports draft hybi-10
- Application module, the server can be extended by custom behaviors
- Supports websocket draft hybi-10 (Currently tested with Chrome 16 and Firefox 8).
- Supports origin-check.
- Supports various security/performance settings.
- Application module, the server can be extended by custom behaviors.

## Server example

This creates a server on localhost:8000 with one Application that listens on `ws://localhost:8000/demo`:

$server = new \WebSocket\Server('localhost', 8000);

// server settings:
// server settings:
$server->setCheckOrigin(true);
$server->setAllowedOrigin('foo.lh');
$server->setMaxClients(20);
$server->setMaxConnectionsPerIp(5);
$server->setMaxRequestsPerMinute(50);

$server->registerApplication('demo', \WebSocket\Application\DemoApplication::getInstance());
$server->run();
Expand Down
11 changes: 6 additions & 5 deletions server/lib/WebSocket/Connection.php
Expand Up @@ -77,28 +77,29 @@ private function handshake($data)
// check origin:
if($this->server->getCheckOrigin() === true)
{
if(isset($headers['Sec-WebSocket-Origin']) === false)
$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->close(1002);
return false;
}

if(empty($headers['Sec-WebSocket-Origin']))
if(empty($origin))
{
$this->log('Empty origin provided.');
$this->close(1002);
return false;
}

if($this->server->checkOrigin($headers['Sec-WebSocket-Origin']) === false)
if($this->server->checkOrigin($origin) === false)
{
$this->log('Invalid origin provided.');
$this->close(1002);
return false;
}
}

}

// do handyshake: (hybi-10)
$secKey = $headers['Sec-WebSocket-Key'];
Expand Down
70 changes: 66 additions & 4 deletions server/lib/WebSocket/Server.php
Expand Up @@ -12,12 +12,14 @@ class Server extends Socket
private $clients = array();
private $applications = array();
private $_ipStorage = array();
private $_requestStorage = array();

// server settings:
private $_checkOrigin = true;
private $_allowedOrigins = array();
private $_maxClients = 30;
private $_maxConnectionsPerIp = 5;
private $_maxRequestsPerMinute = 20; // @todo
private $_maxRequestsPerMinute = 50;

public function __construct($host = 'localhost', $port = 8000, $max = 100)
{
Expand Down Expand Up @@ -45,23 +47,31 @@ public function run()
$client = new Connection($this, $ressource);
$this->clients[(int)$ressource] = $client;
$this->allsockets[] = $ressource;

if(count($this->clients) > $this->_maxClients)
{
$client->onDisconnect();
continue;
}

$this->_addIpToStoragee($client->getClientIp());
if($this->_checkMaxConnectionsPerIp($client->getClientIp()) === false)
{
$client->onDisconnect();
continue;
}
}
}
else
{
$client = $this->clients[(int)$socket];
$bytes = socket_recv($socket, $data, 4096, 0);
if($bytes === 0)
if($bytes === 0 || $this->_checkRequestLimit($client->getClientId()) === false)
{
$client->onDisconnect();
}
else
{
{
$client->onData($data);
}
}
Expand Down Expand Up @@ -94,11 +104,16 @@ public function log($message, $type = 'info')
public function removeClient($resource)
{
$client = $this->clients[(int)$resource];
$clientId = $client->getClientId();
$this->_removeIpFromStorage($client->getClientIp());
if(isset($this->_requestStorage[$clientId]))
{
unset($this->_requestStorage[$clientId]);
}
unset($this->clients[(int)$resource]);
$index = array_search($resource, $this->allsockets);
unset($this->allsockets[$index]);
unset($client);
unset($client, $clientId);
}

public function checkOrigin($domain)
Expand Down Expand Up @@ -150,6 +165,38 @@ private function _checkMaxConnectionsPerIp($ip)
}
return ($this->_ipStorage[$ip] > $this->_maxConnectionsPerIp) ? false : true;
}

private function _checkRequestLimit($clientId)
{
// no data in storage - no danger:
if(!isset($this->_requestStorage[$clientId]))
{
$this->_requestStorage[$clientId] = array(
'lastRequest' => time(),
'totalRequests' => 1
);
return true;
}

// time since last request > 1min - no danger:
if(time() - $this->_requestStorage[$clientId]['lastRequest'] > 60)
{
$this->_requestStorage[$clientId] = array(
'lastRequest' => time(),
'totalRequests' => 1
);
return true;
}

// did requests in last minute - check limits:
if($this->_requestStorage[$clientId]['totalRequests'] > $this->_maxRequestsPerMinute)
{
return false;
}

$this->_requestStorage[$clientId]['totalRequests']++;
return true;
}

// Getter/Setter Methods...

Expand Down Expand Up @@ -206,4 +253,19 @@ public function setMaxRequestsPerMinute($limit)
$this->_maxRequestsPerMinute = $limit;
return true;
}

public function setMaxClients($max)
{
if((int)$max === 0)
{
return false;
}
$this->_maxClients = (int)$max;
return true;
}

public function getMaxClients()
{
return $this->_maxClients;
}
}
2 changes: 2 additions & 0 deletions server/server.php
Expand Up @@ -10,9 +10,11 @@
$server = new \WebSocket\Server('localhost', 8000);

// server settings:
$server->setMaxClients(20);
$server->setCheckOrigin(true);
$server->setAllowedOrigin('foo.lh');
$server->setMaxConnectionsPerIp(5);
$server->setMaxRequestsPerMinute(50);


$server->registerApplication('demo', \WebSocket\Application\DemoApplication::getInstance());
Expand Down

0 comments on commit 5c08967

Please sign in to comment.