diff --git a/src/WebSocket/Adapter.php b/src/WebSocket/Adapter.php index e05e7d1..540e8e3 100644 --- a/src/WebSocket/Adapter.php +++ b/src/WebSocket/Adapter.php @@ -82,6 +82,13 @@ abstract public function onOpen(callable $callback): self; */ abstract public function onMessage(callable $callback): self; + /** + * Is called when an HTTP request is received. + * @param callable $callback + * @return self + */ + abstract public function onRequest(callable $callback): self; + /** * Is called when a connection is closed. * @param callable $callback diff --git a/src/WebSocket/Adapter/Swoole.php b/src/WebSocket/Adapter/Swoole.php index 696f3e4..c39efe7 100644 --- a/src/WebSocket/Adapter/Swoole.php +++ b/src/WebSocket/Adapter/Swoole.php @@ -3,6 +3,7 @@ namespace Utopia\WebSocket\Adapter; use Swoole\Http\Request; +use Swoole\Http\Response; use Swoole\Process; use Swoole\WebSocket\Frame; use Swoole\WebSocket\Server; @@ -125,6 +126,15 @@ public function onClose(callable $callback): self return $this; } + public function onRequest(callable $callback): self + { + $this->server->on('request', function (Request $request, Response $response) use ($callback) { + call_user_func($callback, $request, $response); + }); + + return $this; + } + public function setPackageMaxLength(int $bytes): self { $this->config['package_max_length'] = $bytes; diff --git a/src/WebSocket/Adapter/Workerman.php b/src/WebSocket/Adapter/Workerman.php index edfb02b..b95234f 100644 --- a/src/WebSocket/Adapter/Workerman.php +++ b/src/WebSocket/Adapter/Workerman.php @@ -5,6 +5,7 @@ use Utopia\WebSocket\Adapter; use Workerman\Connection\TcpConnection; use Workerman\Worker; +use Workerman\Protocols\Http\Request; class Workerman extends Adapter { @@ -16,6 +17,10 @@ class Workerman extends Adapter private mixed $callbackOnStart; + private mixed $callbackOnMessage; + + private mixed $callbackOnRequest; + public function __construct(string $host = '0.0.0.0', int $port = 80) { parent::__construct($host, $port); @@ -88,9 +93,8 @@ public function onOpen(callable $callback): self public function onMessage(callable $callback): self { - $this->server->onMessage = function (TcpConnection $connection, string $data) use ($callback): void { - call_user_func($callback, $connection->id, $data); - }; + $this->callbackOnMessage = $callback; + $this->setupMessageHandler(); return $this; } @@ -104,6 +108,32 @@ public function onClose(callable $callback): self return $this; } + public function onRequest(callable $callback): self + { + $this->callbackOnRequest = $callback; + $this->setupMessageHandler(); + + return $this; + } + + /** + * Sets up the unified message handler that routes between WebSocket messages and HTTP requests + */ + private function setupMessageHandler(): void + { + $this->server->onMessage = function (TcpConnection $connection, $data) { + if ($data instanceof Request) { + if (is_callable($this->callbackOnRequest)) { + call_user_func($this->callbackOnRequest, $connection, $data); + } + } elseif (is_string($data)) { + if (is_callable($this->callbackOnMessage)) { + call_user_func($this->callbackOnMessage, $connection->id, $data); + } + } + }; + } + public function setPackageMaxLength(int $bytes): self { return $this; diff --git a/src/WebSocket/Server.php b/src/WebSocket/Server.php index 133684f..984eb34 100644 --- a/src/WebSocket/Server.php +++ b/src/WebSocket/Server.php @@ -196,6 +196,24 @@ public function onClose(callable $callback): self return $this; } + /** + * Is called when an HTTP request is received. + * @param callable $callback + * @return self + */ + public function onRequest(callable $callback): self + { + try { + $this->adapter->onRequest($callback); + } catch (Throwable $error) { + foreach ($this->errorCallbacks as $errorCallback) { + $errorCallback($error, 'onRequest'); + } + } + + return $this; + } + /** * Returns all connections. * diff --git a/tests/servers/Swoole/server.php b/tests/servers/Swoole/server.php index 49179f5..54c88d1 100644 --- a/tests/servers/Swoole/server.php +++ b/tests/servers/Swoole/server.php @@ -3,6 +3,7 @@ require_once __DIR__.'/../../../vendor/autoload.php'; use Swoole\Http\Request; +use Swoole\Http\Response; use Utopia\WebSocket; $adapter = new WebSocket\Adapter\Swoole(); @@ -42,4 +43,24 @@ break; } }) + ->onRequest(function (Request $request, Response $response) use ($server) { + echo 'HTTP request received: ', $request->server['request_uri'], PHP_EOL; + + if ($request->server['request_uri'] === '/health') { + $response->header('Content-Type', 'application/json'); + $response->status(200); + $response->end(json_encode(['status' => 'ok', 'message' => 'WebSocket server is running'])); + } elseif ($request->server['request_uri'] === '/info') { + $response->header('Content-Type', 'application/json'); + $response->status(200); + $response->end(json_encode([ + 'server' => 'Swoole WebSocket', + 'connections' => count($server->getConnections()), + 'timestamp' => time() + ])); + } else { + $response->status(404); + $response->end('Not Found'); + } + }) ->start(); diff --git a/tests/servers/Workerman/server.php b/tests/servers/Workerman/server.php index 276043d..3333300 100644 --- a/tests/servers/Workerman/server.php +++ b/tests/servers/Workerman/server.php @@ -3,6 +3,8 @@ require_once __DIR__.'/../../../vendor/autoload.php'; use Utopia\WebSocket; +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http\Request; $adapter = new WebSocket\Adapter\Workerman(); $adapter->setWorkerNumber(1); // Important for tests @@ -41,4 +43,31 @@ break; } }) + ->onRequest(function (TcpConnection $connection, Request $request) use ($server) { + $path = $request->path(); + if (!is_string($path)) { + throw new \Exception('Invalid path ' . $path . ' for request: ' . json_encode($request, JSON_PRETTY_PRINT)); + } + echo 'HTTP request received: ', $path, PHP_EOL; + + if ($path === '/health') { + $connection->send('HTTP/1.1 200 OK' . "\r\n" . + 'Content-Type: application/json' . "\r\n" . + 'Connection: close' . "\r\n\r\n" . + json_encode(['status' => 'ok', 'message' => 'WebSocket server is running'])); + } elseif ($path === '/info') { + $connection->send('HTTP/1.1 200 OK' . "\r\n" . + 'Content-Type: application/json' . "\r\n" . + 'Connection: close' . "\r\n\r\n" . + json_encode([ + 'server' => 'Workerman WebSocket', + 'connections' => count($server->getConnections()), + 'timestamp' => time() + ])); + } else { + $connection->send('HTTP/1.1 404 Not Found' . "\r\n" . + 'Connection: close' . "\r\n\r\n" . + 'Not Found'); + } + }) ->start();