diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..7746407 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,11 @@ +--- +name: Pull Request +about: Pull Request +title: '' +labels: '' +assignees: '' +--- + +Thanks for contributing! + +Before you create a Pull Request, make sure to read the [Contributing](../../docs/Contributing.md) specification. diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 0695c98..67e7ebd 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -12,7 +12,7 @@ jobs: name: Unit test steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 with: @@ -24,7 +24,7 @@ jobs: id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} @@ -39,11 +39,11 @@ jobs: name: Code standard steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: "8.2" + php-version: "8.3" coverage: none env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -51,7 +51,7 @@ jobs: id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} @@ -66,11 +66,11 @@ jobs: name: Code coverage steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: "8.2" + php-version: "8.3" coverage: xdebug env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -78,7 +78,7 @@ jobs: id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} diff --git a/COPYING.md b/LICENSE.md similarity index 100% rename from COPYING.md rename to LICENSE.md diff --git a/composer.json b/composer.json index fcd8163..9183825 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ }, "require-dev": { "php-coveralls/php-coveralls": "^2.0", - "phpunit/phpunit": "^9.0 | ^10.0", + "phpunit/phpunit": "^9.0 | ^10.0 | ^11.0", "phrity/net-mock": "1.3", "squizlabs/php_codesniffer": "^3.5" } diff --git a/docs/Changelog.md b/docs/Changelog.md index 218d80d..c9e09e8 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,11 +1,24 @@ -[Documentation](Index.md) > Changelog +[Documentation](Index.md) / Changelog # Websocket: Changelog +## `v2.2` + + > PHP version `^8.0` + +### `2.2.0` + + * Documentation review (@sirn-se) + * Dependency updates (@sirn-se) + ## `v2.1` > PHP version `^8.0` +### `2.1.2` + + * Allow repeated headers when pulling HTTP messages (@sirn-se) + ### `2.1.1` * Fix issue with falsy but valid HTTP headers (@axklim) diff --git a/docs/Client.md b/docs/Client.md index 0564991..c351705 100644 --- a/docs/Client.md +++ b/docs/Client.md @@ -1,4 +1,4 @@ -[Documentation](Index.md) > Client +[Documentation](Index.md) / Client # Websocket: Client diff --git a/docs/Contributing.md b/docs/Contributing.md index 1458ea9..a3a1ab0 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -1,21 +1,30 @@ -[Documentation](Index.md) > Contributing +[Documentation](Index.md) / Contributing # Websocket: Contributing Everyone is welcome to help out! But to keep this project sustainable, please ensure your contribution respects the requirements below. -## PR Requirements +## Pull Request requirements Requirements on pull requests; * All tests **MUST** pass. * Code coverage **MUST** remain at 100%. * Code **MUST** adhere to PSR-1 and PSR-12 code standards. +## SemVer, versions, and target branches + +This repo uses strict [Semantic Versioning](https://semver.org). + +* MAJOR version when introducing breaking changes +* MINOR version when adding features +* PATCH version when fixing bugs and equivalent changes + Base your patch on corresponding version branch, and target that version branch in your pull request. | Version | Branch | PHP | Status | | --- | --- | --- | --- | +| [`2.2`](https://github.com/sirn-se/websocket-php/tree/2.2.0) | `v2.2-main` | `^8.1` | Future version | | [`2.1`](https://github.com/sirn-se/websocket-php/tree/2.1.0) | `v2.1-main` | `^8.0` | Current version | | [`2.0`](https://github.com/sirn-se/websocket-php/tree/2.0.0) | `v2.0-main` | `^8.0` | Bug fixes only | | [`1.7`](https://github.com/sirn-se/websocket-php/tree/1.7.0) | `v1.7-master` | `^7.4\|^8.0` | Bug fixes only | @@ -27,7 +36,6 @@ Base your patch on corresponding version branch, and target that version branch | [`1.1`](https://github.com/sirn-se/websocket-php/tree/1.1.0) | - | - | Not supported | | [`1.0`](https://github.com/sirn-se/websocket-php/tree/1.0.0) | - | - | Not supported | - ## Dependency management Install or update dependencies using [Composer](https://getcomposer.org/). diff --git a/docs/Examples.md b/docs/Examples.md index 3e7e0aa..591b5ea 100644 --- a/docs/Examples.md +++ b/docs/Examples.md @@ -1,4 +1,4 @@ -[Documentation](Index.md) > Examples +[Documentation](Index.md) / Examples # Websocket: Examples diff --git a/docs/Index.md b/docs/Index.md index 86f2b01..8c1c2f9 100644 --- a/docs/Index.md +++ b/docs/Index.md @@ -16,3 +16,4 @@ * [Changelog](Changelog.md) - The changelog of this repo * [Contributing](Contributing.md) - Contributors and requirements * [Examples](Examples.md) - Examples +* [License](../LICENCE.md) - License diff --git a/docs/Listener.md b/docs/Listener.md index fa40449..b9d5f9d 100644 --- a/docs/Listener.md +++ b/docs/Listener.md @@ -1,4 +1,4 @@ -[Documentation](Index.md) > Listener +[Documentation](Index.md) / Listener # Websocket: Listener diff --git a/docs/Message.md b/docs/Message.md index 20b7830..b129018 100644 --- a/docs/Message.md +++ b/docs/Message.md @@ -1,4 +1,4 @@ -[Documentation](Index.md) > Message +[Documentation](Index.md) / Message # Websocket: Message diff --git a/docs/Middleware.md b/docs/Middleware.md index 45b8080..fc75444 100644 --- a/docs/Middleware.md +++ b/docs/Middleware.md @@ -1,4 +1,4 @@ -[Documentation](Index.md) > Middleware +[Documentation](Index.md) / Middleware # Websocket: Middleware @@ -24,172 +24,19 @@ $server These two middlewares provide standard operability according to WebSocket protocol, and should be added unless you write your own implementation of close and ping/pong handling. -* `CloseHandler` - Automatically acts on incoming and outgoing Close requests, as specified in WebSocket protocol -* `PingResponder` - Responds with Pong message when receiving a Ping message, as specified in WebSocket protocol +* [CloseHandler](Middleware/CloseHandler.md) - Automatically acts on incoming and outgoing Close requests, as specified in WebSocket protocol +* [PingResponder](Middleware/PingResponder.md) - Responds with Pong message when receiving a Ping message, as specified in WebSocket protocol ## Optional middlewares These middlewares are included in library and can be added to provide additional functionality. -* `PingInterval` - Used to automatically send Ping messages at specified interval -* `Callback` - Apply provided callback function on specified actions +* [PingInterval](Middleware/PingInterval.md) - Used to automatically send Ping messages at specified interval +* [Callback](Middleware/Callback.md) - Apply provided callback function on specified actions -## Middleware descriptions +## Creating your own middleware -Description of included middlewares. +You can create your own middleware by implementing relevant interfaces. +A middleware may handle WebSocket message transfers, HTTP handshake operations, and Tick operability. -### The CloseHandler middleware - -* When a Close message is received, CloseHandler will respond with a Close confirmation message -* When a Close confirmation message is received, CloseHandler will close the connection -* When a Close message is sent, CloseHandler will block further messages from being sent - -```php -$client_or_server->addMiddleware(new WebSocket\Middleware\CloseHandler()); -``` - -### The PingResponder middleware - -* When a Ping message is received, PingResponder will respond with a Pong message - -```php -$client_or_server->addMiddleware(new WebSocket\Middleware\PingResponder()); -``` - -### The PingInterval middleware - -* Sends Ping messages on Connection at interval, typically used as "heartbeat" to keep connection open -* If `interval` is not specified, it will use connection timeout configuration as interval - -```php -$client_or_server->addMiddleware(new WebSocket\Middleware\PingInterval(interval: 10)); -``` - -### The Callback middleware - -This middleware will apply callback functions on certain actions. - -```php -$client_or_server - ->addMiddleware(new WebSocket\Middleware\Callback( - incoming: function (WebSocket\Middleware\ProcessStack $stack, WebSocket\Connection $connection): WebSocket\Message\Message { - $message = $stack->handleIncoming(); // Get incoming message from next middleware - $message->setContent("Changed message"); - return $message; - }, - outgoing: function (WebSocket\Middleware\ProcessStack $stack, WebSocket\Connection $connection, WebSocket\Message\Message $message): WebSocket\Message\Message { - $message->setContent('Changed message'); - $message = $stack->handleOutgoing($message); // Forward outgoing message to next middleware - return $message; - }, - httpIncoming: function (WebSocket\Middleware\ProcessHttpStack $stack, WebSocket\Connection $connection): WebSocket\Http\Message { - $message = $stack->handleHttpIncoming(); // Get incoming message from next middleware - return $message; - }, - httpOutgoing: function (WebSocket\Middleware\ProcessHttpStack $stack, WebSocket\Connection $connection, WebSocket\Http\Message $message): WebSocket\Http\Message { - $message = $stack->handleHttpOutgoing($message); // Forward outgoing message to next middleware - return $message; - }, - tick: function (WebSocket\Middleware\ProcessTickStack $stack, WebSocket\Connection $connection): void { - $stack->handleTick(); // Forward tick to next middleware - } - )); -``` - -* The `incoming` and `outgoing` callbacks **MUST** return a [Message](Message.md) instance -* The `httpIncoming` and `httpOutgoing` callbacks **MUST** return a HTTP request/response instance respectively -* The `tick` callback returns nothing - -The `handleIncoming`, `handleOutgoing`, `handleHttpIncoming`, `handleHttpOutgoing` and `handleTick` methods will pass initiative further down the middleware stack. - -## Writing your own middleware - -A middleware **MUST** implement the `MiddlewareInterface`. - -```php -interface WebSocket\Middleware\MiddlewareInterface -{ - public function __toString(): string; -} -``` - -A middleware that wants to handle incoming messages **MUST** implement the `ProcessIncomingInterface`. - -```php -interface WebSocket\Middleware\ProcessIncomingInterface extends WebSocket\Middleware\MiddlewareInterface -{ - public function processIncoming( - WebSocket\Middleware\ProcessStack $stack, - WebSocket\Connection $connection - ): WebSocket\Message\Message; -} -``` - -A middleware that wants to handle outgoing messages **MUST** implement the `ProcessOutgoingInterface`. - -```php -interface WebSocket\Middleware\ProcessOutgoingInterface extends WebSocket\Middleware\MiddlewareInterface -{ - public function processOutgoing( - WebSocket\Middleware\ProcessStack $stack, - WebSocket\Connection $connection, - WebSocket\Message\Message $message - ): WebSocket\Message\Message; -} -``` - -A middleware that wants to handle incoming HTTP messages **MUST** implement the `ProcessHttpIncomingInterface`. - -```php -interface WebSocket\Middleware\ProcessHttpIncomingInterface extends WebSocket\Middleware\MiddlewareInterface -{ - public function processHttpIncoming( - WebSocket\Middleware\ProcessHttpStack $stack, - WebSocket\Connection $connection - ): WebSocket\Http\Message; -} -``` - -A middleware that wants to handle outgoing HTTP messages **MUST** implement the `ProcessHttpOutgoingInterface`. - -```php -interface WebSocket\Middleware\ProcessHttpOutgoingInterface extends WebSocket\Middleware\MiddlewareInterface -{ - public function processHttpOutgoing( - WebSocket\Middleware\ProcessHttpStack $stack, - WebSocket\Connection $connection, - WebSocket\Http\Message $message - ): WebSocket\Http\Message; -} -``` - -A middleware that wants to handle Tick operation **MUST** implement the `ProcessTickInterface`. - -```php -interface WebSocket\Middleware\ProcessTickInterface extends WebSocket\Middleware\MiddlewareInterface -{ - public function processTick( - WebSocket\Middleware\ProcessTickStack $stack, - WebSocket\Connection $connection - ): void; -} -``` - -The `ProcessStack`, `ProcessHttpStack` and `ProcessTickStack` classes are used to hand over initiative to the next middleware in middleware stack. - -```php -// Get the received Message, possibly handled by other middlewares -$message = $stack->handleIncoming(); - -// Forward the Message to be sent, possibly handled by other middlewares -$message = $stack->handleOutgoing($message); - -// Get the received HTTP request/response message, possibly handled by other middlewares -$message = $stack->handleHttpIncoming(); - -// Forward the HTTP request/response message to be sent, possibly handled by other middlewares -$message = $stack->handleHttpOutgoing($message); - -// Forward the Tick operation, possibly handled by other middlewares -$stack->handleTick(); -``` +* [Creating](Middleware/Creating.md) - How to create a Middleware diff --git a/docs/Middleware/Callback.md b/docs/Middleware/Callback.md new file mode 100644 index 0000000..b34412f --- /dev/null +++ b/docs/Middleware/Callback.md @@ -0,0 +1,39 @@ +[Documentation](../Index.md) / [Middleware](../Middleware.md) / Callback + +# Websocket: Callback middleware + +This middleware will apply callback functions on certain actions. +This middlewares is included in library and can be added to provide additional functionality. + +```php +$client_or_server + ->addMiddleware(new WebSocket\Middleware\Callback( + incoming: function (WebSocket\Middleware\ProcessStack $stack, WebSocket\Connection $connection): WebSocket\Message\Message { + $message = $stack->handleIncoming(); // Get incoming message from next middleware + $message->setContent("Changed message"); + return $message; + }, + outgoing: function (WebSocket\Middleware\ProcessStack $stack, WebSocket\Connection $connection, WebSocket\Message\Message $message): WebSocket\Message\Message { + $message->setContent('Changed message'); + $message = $stack->handleOutgoing($message); // Forward outgoing message to next middleware + return $message; + }, + httpIncoming: function (WebSocket\Middleware\ProcessHttpStack $stack, WebSocket\Connection $connection): WebSocket\Http\Message { + $message = $stack->handleHttpIncoming(); // Get incoming message from next middleware + return $message; + }, + httpOutgoing: function (WebSocket\Middleware\ProcessHttpStack $stack, WebSocket\Connection $connection, WebSocket\Http\Message $message): WebSocket\Http\Message { + $message = $stack->handleHttpOutgoing($message); // Forward outgoing message to next middleware + return $message; + }, + tick: function (WebSocket\Middleware\ProcessTickStack $stack, WebSocket\Connection $connection): void { + $stack->handleTick(); // Forward tick to next middleware + } + )); +``` + +* The `incoming` and `outgoing` callbacks **MUST** return a [Message](Message.md) instance +* The `httpIncoming` and `httpOutgoing` callbacks **MUST** return a HTTP request/response instance respectively +* The `tick` callback returns nothing + +The `handleIncoming`, `handleOutgoing`, `handleHttpIncoming`, `handleHttpOutgoing` and `handleTick` methods will pass initiative further down the middleware stack. diff --git a/docs/Middleware/CloseHandler.md b/docs/Middleware/CloseHandler.md new file mode 100644 index 0000000..eace0ef --- /dev/null +++ b/docs/Middleware/CloseHandler.md @@ -0,0 +1,14 @@ +[Documentation](../Index.md) / [Middleware](../Middleware.md) / CloseHandler + +# Websocket: CloseHandler middleware + +Thid middleware provide standard operability according to WebSocket protocol, +and should be added unless you write your own implementation of close handling. + +* When a Close message is received, CloseHandler will respond with a Close confirmation message +* When a Close confirmation message is received, CloseHandler will close the connection +* When a Close message is sent, CloseHandler will block further messages from being sent + +```php +$client_or_server->addMiddleware(new WebSocket\Middleware\CloseHandler()); +``` diff --git a/docs/Middleware/Creating.md b/docs/Middleware/Creating.md new file mode 100644 index 0000000..ae09878 --- /dev/null +++ b/docs/Middleware/Creating.md @@ -0,0 +1,104 @@ +[Documentation](../Index.md) / [Middleware](../Middleware.md) / Creating + +# Websocket: Creating your own middleware + +You can create your own middleware by implementing relevant interfaces. +A middleware may handle WebSocket message transfers, HTTP handshake operations, and Tick operability. + +A middleware **MUST** implement the `MiddlewareInterface`. + +```php +interface WebSocket\Middleware\MiddlewareInterface +{ + public function __toString(): string; +} +``` + +## WebSocket message transfer + +A middleware that wants to handle incoming messages **MUST** implement the `ProcessIncomingInterface`. + +```php +interface WebSocket\Middleware\ProcessIncomingInterface extends WebSocket\Middleware\MiddlewareInterface +{ + public function processIncoming( + WebSocket\Middleware\ProcessStack $stack, + WebSocket\Connection $connection + ): WebSocket\Message\Message; +} +``` + +A middleware that wants to handle outgoing messages **MUST** implement the `ProcessOutgoingInterface`. + +```php +interface WebSocket\Middleware\ProcessOutgoingInterface extends WebSocket\Middleware\MiddlewareInterface +{ + public function processOutgoing( + WebSocket\Middleware\ProcessStack $stack, + WebSocket\Connection $connection, + WebSocket\Message\Message $message + ): WebSocket\Message\Message; +} +``` + +## HTTP handshake operations + +A middleware that wants to handle incoming HTTP messages **MUST** implement the `ProcessHttpIncomingInterface`. + +```php +interface WebSocket\Middleware\ProcessHttpIncomingInterface extends WebSocket\Middleware\MiddlewareInterface +{ + public function processHttpIncoming( + WebSocket\Middleware\ProcessHttpStack $stack, + WebSocket\Connection $connection + ): WebSocket\Http\Message; +} +``` + +A middleware that wants to handle outgoing HTTP messages **MUST** implement the `ProcessHttpOutgoingInterface`. + +```php +interface WebSocket\Middleware\ProcessHttpOutgoingInterface extends WebSocket\Middleware\MiddlewareInterface +{ + public function processHttpOutgoing( + WebSocket\Middleware\ProcessHttpStack $stack, + WebSocket\Connection $connection, + WebSocket\Http\Message $message + ): WebSocket\Http\Message; +} +``` + +## Tick operability + +A middleware that wants to handle Tick operation **MUST** implement the `ProcessTickInterface`. + +```php +interface WebSocket\Middleware\ProcessTickInterface extends WebSocket\Middleware\MiddlewareInterface +{ + public function processTick( + WebSocket\Middleware\ProcessTickStack $stack, + WebSocket\Connection $connection + ): void; +} +``` + +## Working with middleware stacks + +The `ProcessStack`, `ProcessHttpStack` and `ProcessTickStack` classes are used to hand over initiative to the next middleware in middleware stack. + +```php +// Get the received Message, possibly handled by other middlewares +$message = $stack->handleIncoming(); + +// Forward the Message to be sent, possibly handled by other middlewares +$message = $stack->handleOutgoing($message); + +// Get the received HTTP request/response message, possibly handled by other middlewares +$message = $stack->handleHttpIncoming(); + +// Forward the HTTP request/response message to be sent, possibly handled by other middlewares +$message = $stack->handleHttpOutgoing($message); + +// Forward the Tick operation, possibly handled by other middlewares +$stack->handleTick(); +``` diff --git a/docs/Middleware/PingInterval.md b/docs/Middleware/PingInterval.md new file mode 100644 index 0000000..c11ff47 --- /dev/null +++ b/docs/Middleware/PingInterval.md @@ -0,0 +1,12 @@ +[Documentation](../Index.md) / [Middleware](../Middleware.md) / PingInterval + +# Websocket: PingInterval middleware + +This middlewares is included in library and can be added to provide additional functionality. + +* Sends Ping messages on Connection at interval, typically used as "heartbeat" to keep connection open +* If `interval` is not specified, it will use connection timeout configuration as interval + +```php +$client_or_server->addMiddleware(new WebSocket\Middleware\PingInterval(interval: 10)); +``` diff --git a/docs/Middleware/PingResponder.md b/docs/Middleware/PingResponder.md new file mode 100644 index 0000000..266f539 --- /dev/null +++ b/docs/Middleware/PingResponder.md @@ -0,0 +1,12 @@ +[Documentation](../Index.md) / [Middleware](../Middleware.md) / PingResponder + +# Websocket: PingResponder middleware + +Thid middleware provide standard operability according to WebSocket protocol, +and should be added unless you write your own implementation of ping/pong handling. + +* When a Ping message is received, PingResponder will respond with a Pong message + +```php +$client_or_server->addMiddleware(new WebSocket\Middleware\PingResponder()); +``` diff --git a/docs/Server.md b/docs/Server.md index b57973a..564adfd 100644 --- a/docs/Server.md +++ b/docs/Server.md @@ -1,4 +1,4 @@ -[Documentation](Index.md) > Sever +[Documentation](Index.md) / Server # Websocket: Server diff --git a/examples/echoserver.php b/examples/echoserver.php index 99db3ac..922f37b 100644 --- a/examples/echoserver.php +++ b/examples/echoserver.php @@ -1,5 +1,10 @@ diff --git a/src/Client.php b/src/Client.php index 65df2ec..7ee1116 100644 --- a/src/Client.php +++ b/src/Client.php @@ -1,10 +1,8 @@ withHeader($parts[0], $parts[1]); + $message = $message->withAddedHeader($parts[0], $parts[1]); } } if ($message instanceof Request) { diff --git a/src/Http/Message.php b/src/Http/Message.php index 1a4ccca..cd20e3f 100644 --- a/src/Http/Message.php +++ b/src/Http/Message.php @@ -1,10 +1,8 @@