Skip to content

Websockets support #121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 83 commits into from
Jul 5, 2025
Merged

Websockets support #121

merged 83 commits into from
Jul 5, 2025

Conversation

thekid
Copy link
Member

@thekid thekid commented Jan 12, 2025

This pull request adds websockets to the web server.

In a nutshell

use web\Application;
use web\handler\WebSocket;

class Ws extends Application {

  public function routes() {
    return [
      '/ws/echo' => new WebSocket(function($conn, $payload) {
        $conn->send('You said: '.$payload);
      }),
      '/'   => function($request, $response) {
        $html= ... // Shortened for brevity
        $res->send($html, 'text/html; charset=utf-8');
      }
    ];
  }
}

Complete implemenation at https://gist.github.com/thekid/f933e8ffbc59b35af5e639bdd6f538c2

Backwards compatibility

This pull request breaks BC and thus institutes a 5.0-RELEASE, with the necessity to create compatibility releases for these libraries:

frontend:  "xp-forge/web": "^4.2",
lambda-ws: "xp-forge/web": "^4.0 | ^3.0",
rest-api:  "xp-forge/web": "^4.0 | ^3.0 | ^2.0 | ^1.0",
sessions:  "xp-forge/web": "^4.0 | ^3.0 | ^2.0 | ^1.0",
web-auth:  "xp-forge/web": "^4.0 | ^3.0 | ^2.0 | ^1.0",

All libraries except lambda-ws continue to work as intended. For the AWS Lambda web adapter, see xp-forge/lambda-ws#13

TODO

  • Multiplex protocol handling both http and websocket
  • Integration tests for WebSocket protocol
  • Extend logging API to handle both websocket messages and HTTP requests
  • System testing with various server implementations
  • Solution for development webserver (reverse proxy w/ websocket <-> SSE translation)

See also

@thekid thekid marked this pull request as draft January 12, 2025 09:39
@thekid thekid changed the title Feature/websockets Websockets support Jan 12, 2025
@thekid
Copy link
Member Author

thekid commented Jan 12, 2025

Integration tests for WebSocket protocol

Here's the test:

diff --git a/src/it/php/web/unittest/IntegrationTest.class.php b/src/it/php/web/unittest/IntegrationTest.class.php
index a546932..678dca0 100755
--- a/src/it/php/web/unittest/IntegrationTest.class.php
+++ b/src/it/php/web/unittest/IntegrationTest.class.php
@@ -1,6 +1,7 @@
 <?php namespace web\unittest;
 
 use test\{Assert, After, Test, Values};
+use websocket\WebSocket;
 
 #[StartServer(TestingServer::class)]
 class IntegrationTest {
@@ -161,4 +162,17 @@ class IntegrationTest {
     $r= $this->send('GET', '/cookie', '1.0', ['Cookie' => $header]);
     Assert::equals((string)strlen($header), $r['body']);
   }
+
+  #[Test]
+  public function websocket_message() {
+    try {
+      $ws= new WebSocket($this->server->connection, '/ws');
+      $ws->connect();
+      $ws->send('Test');
+      $echo= $ws->receive();
+    } finally {
+      $ws->close();
+    }
+    Assert::equals('Echo: Test', $echo);
+  }
 }

This institutes a dependency on the websocket client implementation - xp-forge/websockets#6 - and the ability to pass paths - xp-forge/websockets#7 - and would thus bump the minimum version requirement for the respective library to ^4.0, which in turn would cause this library to become PHP 7.4+, resulting in a BC break. Alternatives would be to hardwire websocket handshake and protocol into the integration test, greatly reducint its readability, or to backport the PR to the websocket library's 3.0-SERIES.

@thekid
Copy link
Member Author

thekid commented Jan 12, 2025

System testing with various server implementations

Developed on async server. Verified this works with prefork API, see the PID change for the requests:

image

@thekid
Copy link
Member Author

thekid commented Jan 13, 2025

Solution for development webserver

This could be done by starting the development webserver as a backend and then:

  • Proxying all HTTP requests
  • Translating websocket messages to server-sent events

A POC shows this is viable.

thekid added 10 commits January 18, 2025 09:42
…o it

To start, this is a simple reverse proxy setup, which is exactly what happens
for HTTP request. Websockets, on the other hand, are kept alive in this server,
and its messages are translated to server-sent events, before being passed to
the backend, which doesn't support this protocol.

This enables the highly effective "save-rerun" developer experience with no
intermediary steps like restarting the server (and potentially losing state),
or compiling (even with the XP Compiler in place, this will happen "just in
time").

We could have chosen any wire format to send the messages back and forth but
chose to go with something well-established and standardized, in this case
opting for https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
@thekid
Copy link
Member Author

thekid commented Feb 2, 2025

Tests are failing on PHP 7.4 due to missing support for ephemeral ports, see php/php-src@a61a9fe

@thekid thekid mentioned this pull request Feb 2, 2025
@thekid
Copy link
Member Author

thekid commented Apr 11, 2025

Created a branch named four to be able to easily make changes to 4.X series.

'headers' => $request->headers(),
'listener' => $this->listener,
]];
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first array element will become the value of the Upgrade header, e.g.:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

The second argument is then passed to the handleSwitch() method's $context parameter.

Copy link
Member Author

@thekid thekid Apr 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTTP/2 cleartext uses the following:

HTTP/1.1 101 Switching Protocols
Upgrade: h2c
Connection: Upgrade

@thekid thekid merged commit ac0d4c3 into master Jul 5, 2025
14 checks passed
@thekid thekid deleted the feature/websockets branch July 5, 2025 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add websocket support
1 participant