diff --git a/README.md b/README.md index ad1b7ee..a913e6d 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,13 @@ $server->registerApplication('chat', \Bloatless\WebSocket\Examples\Application\C $server->run(); ``` -Assuming this code is in a file called `server.php` you can than start your server with the following command: +Assuming this code is in a file called `server.php` you can then start your server with the following command: ```shell php server.php ``` -The websocket server will than listen for new connection on the provided host and port. By default, this will be +The websocket server will then listen for new connections on the provided host and port. By default, this will be `localhost:8000`. This repositoy also includes a working example in [examples/server.php](examples/server.php) @@ -94,15 +94,15 @@ interface ApplicationInterface } ``` -`onConnet` and `onDisconnect` can be used to keep track of all the clients connected to your application. `onData` will -be called whenever the websocket server receives new data from one of the clients connect to the application. +`onConnect` and `onDisconnect` can be used to keep track of all the clients connected to your application. `onData` will +be called whenever the websocket server receives new data from one of the clients connected to the application. `onIPCData` will be called if data is provided by another process on your machine. (See [Push-Client (IPC)](#push-client-ipc)) A working example of an application can be found in [examples/Application/Chat.php](examples/Application/Chat.php) ### Timers -A common requirement to long-running processes such as a websocket server is to execute tasks periodically. This can +A common requirement for long-running processes such as a websocket server is to execute tasks periodically. This can be done using timers. Timers can execute methods within your server or application periodically. Here is an example: ```php @@ -119,12 +119,12 @@ This example would call the method `someMethod` within your chat application eve ### Push-Client (IPC) It is often required to push data into the websocket-server process from another application. Let's assume you run a -website containg a chat and an area containing news or a blog. Now every time a new article is published in your blog +website containing a chat and an area containing news or a blog. Now every time a new article is published in your blog you want to notify all users currently in your chat. To achieve this you somehow need to push data from your blog logic into the websocket server. This is where the Push-Client comes into play. When starting the websocket server, it opens a unix-domain-socket and listens for new messages. The Push-Client can -than be used to send these messages. Here is an example: +then be used to send these messages. Here is an example: ```php $pushClient = new \Bloatless\WebSocket\PushClient('//tmp/phpwss.sock'); @@ -134,17 +134,16 @@ $pushClient->sendToApplication('chat', [ ]); ``` -This code pushes data into your running websocket-server process. In this case the `echo` Method within the -chat-application is called and sends the provided message to all connected clients. +This code pushes data into your running websocket-server process. In this case the `echo` method within the +chat-application is called and it sends the provided message to all connected clients. You can find the full working example in: [examples/push.php](examples/push.php) -**Important Hint:** Push messages can be not larger than 64kb! +**Important Hint:** Push messages cannot be larger than 64kb! ### Client (Browser/JS) -Everything above this point was related to the server-side of things. But how to connect to the server from your -browser? +Everything above this point was related to the server-side of things. But how to connect to the server from your browser? Here is a simple example: @@ -168,8 +167,8 @@ A better example of the chat client can be found in: [examples/public/chat.html] ## Intended use and limitations -This project was mainly build for educational purposes. The code is relatively simple and easy to understand. This -server was **not tested in production**, so I strongly recommand to not use it on a live project. It should be totally +This project was mainly built for educational purposes. The code is relatively simple and easy to understand. This +server was **not tested in production**, so I strongly recommend not to use it in a live project. It should be totally fine for small educational projects or internal tools, but most probably will not handle huge amounts of traffic or connections very well. diff --git a/src/Application/Application.php b/src/Application/Application.php index dcbafda..8700415 100644 --- a/src/Application/Application.php +++ b/src/Application/Application.php @@ -16,7 +16,7 @@ protected function __construct() // singleton construct required this method to be protected/private } - final private function __clone() + protected function __clone() { // singleton construct required this method to be protected/private } diff --git a/src/Connection.php b/src/Connection.php index 38b091b..cbbae6d 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -49,6 +49,11 @@ class Connection * @var string $dataBuffer */ private string $dataBuffer = ''; + + /** + * @var array $headers + */ + private array $headers = []; /** * @param Server $server @@ -104,16 +109,15 @@ private function handshake(string $data): bool $this->application = $this->server->getApplication($applicationKey); // generate headers array: - $headers = []; foreach ($lines as $line) { $line = chop($line); if (preg_match('/\A(\S+): (.*)\z/', $line, $matches)) { - $headers[ strtolower($matches[1])] = $matches[2]; + $this->headers[ strtolower($matches[1])] = $matches[2]; } } // check for supported websocket version: - if (!isset($headers['sec-websocket-version']) || $headers['sec-websocket-version'] < 6) { + if (!isset($this->headers['sec-websocket-version']) || $this->headers['sec-websocket-version'] < 6) { $this->log('Unsupported websocket version.'); $this->sendHttpResponse(501); stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR); @@ -123,8 +127,8 @@ private function handshake(string $data): bool // check origin: if ($this->server->getCheckOrigin() === true) { - $origin = (isset($headers['sec-websocket-origin'])) ? $headers['sec-websocket-origin'] : ''; - $origin = (isset($headers['origin'])) ? $headers['origin'] : $origin; + $origin = (isset($this->headers['sec-websocket-origin'])) ? $this->headers['sec-websocket-origin'] : ''; + $origin = (isset($this->headers['origin'])) ? $this->headers['origin'] : $origin; if (empty($origin)) { $this->log('No origin provided.'); $this->sendHttpResponse(401); @@ -143,13 +147,13 @@ private function handshake(string $data): bool } // do handyshake: (hybi-10) - $secKey = $headers['sec-websocket-key']; + $secKey = $this->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"; - if (isset($headers['sec-websocket-protocol']) && !empty($headers['sec-websocket-protocol'])) { + if (isset($this->headers['sec-websocket-protocol']) && !empty($this->headers['sec-websocket-protocol'])) { $response .= "Sec-WebSocket-Protocol: " . substr($path, 1) . "\r\n"; } $response .= "\r\n"; @@ -588,6 +592,15 @@ public function getClientSocket() { return $this->socket; } + + /** + * Return the headers of the connection + * @return array + */ + public function getClientHeaders(): array + { + return $this->headers; + } /** * Returns the application the client is connected to. diff --git a/src/Server.php b/src/Server.php index 3f7579d..916d710 100644 --- a/src/Server.php +++ b/src/Server.php @@ -37,9 +37,9 @@ class Server private $icpSocket; /** - * @var string $ipcSocketPath + * @var null|string $ipcSocketPath */ - private string $ipcSocketPath; + private ?string $ipcSocketPath; /** * @var string $ipcOwner If set, owner of the ipc socket will be changed to this value. @@ -114,16 +114,17 @@ class Server /** * @param string $host * @param int $port - * @param string $ipcSocketPath + * @param null|string $ipcSocketPath */ public function __construct( string $host = 'localhost', int $port = 8000, - string $ipcSocketPath = '/tmp/phpwss.sock' + ?string $ipcSocketPath = '/tmp/phpwss.sock' ) { $this->host = $host; $this->port = $port; $this->ipcSocketPath = $ipcSocketPath; + $this->timers = new TimerCollection(); } /** @@ -136,13 +137,14 @@ public function run(): void { ob_implicit_flush(); $this->createSocket($this->host, $this->port); - $this->openIPCSocket($this->ipcSocketPath); - $this->timers = new TimerCollection(); + if ($this->ipcSocketPath) { + $this->openIPCSocket($this->ipcSocketPath); + } $this->log('Server created'); while (true) { $this->timers->runAll(); - + $changed_sockets = $this->allsockets; @stream_select($changed_sockets, $write, $except, 0, 5000); foreach ($changed_sockets as $socket) { @@ -598,10 +600,15 @@ public function writeBuffer($resource, string $string): int */ private function openIPCSocket(string $ipcSocketPath): void { - if (file_exists($ipcSocketPath)) { - unlink($ipcSocketPath); - } - $this->icpSocket = socket_create(AF_UNIX, SOCK_DGRAM, 0); + if (substr(php_uname(), 0, 7) == "Windows"){ + $this->icpSocket = socket_create(AF_INET, SOCK_DGRAM, 0); + $ipcSocketPath = $this->host; + } else { + if (file_exists($ipcSocketPath)) { + unlink($ipcSocketPath); + } + $this->icpSocket = socket_create(AF_UNIX, SOCK_DGRAM, 0); + } if ($this->icpSocket === false) { throw new \RuntimeException('Could not open ipc socket.'); } @@ -630,7 +637,13 @@ private function openIPCSocket(string $ipcSocketPath): void private function handleIPC(): void { $buffer = ''; - $bytesReceived = socket_recvfrom($this->icpSocket, $buffer, 65536, 0, $this->ipcSocketPath); + if (substr(php_uname(), 0, 7) == "Windows") { + $from = ''; + $port = 0; + $bytesReceived = socket_recvfrom($this->icpSocket, $buffer, 65536, 0, $from, $port); + } else { + $bytesReceived = socket_recvfrom($this->icpSocket, $buffer, 65536, 0, $this->ipcSocketPath); + } if ($bytesReceived === false) { return; }