Skip to content
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

Websockets support #121

Open
wants to merge 59 commits into
base: master
Choose a base branch
from
Open

Websockets support #121

wants to merge 59 commits into from

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' => 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
// \util\cmd\Console::writeLine('>>> ', $message);
$this->backend->write($message."\r\n");
foreach ($this->transmit($request->incoming(), $this->backend) as $step) {
// yield 'read' => $socket;
Copy link
Member Author

Choose a reason for hiding this comment

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

This is commented to guarantee synchronous execution of this method due to the backend server only being able to handle one request at a time. We could consider using a worker pool in order to speed things up in a future pull request, or handling requests to static resources inside the proxy itself.

Copy link
Member Author

@thekid thekid left a comment

Choose a reason for hiding this comment

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

A couple smaller things to be addressed

src/main/php/xp/web/srv/ForwardRequests.class.php Outdated Show resolved Hide resolved
src/main/php/xp/web/srv/ForwardRequests.class.php Outdated Show resolved Hide resolved
src/main/php/xp/web/srv/Input.class.php Outdated Show resolved Hide resolved
src/main/php/xp/web/srv/Protocol.class.php Outdated Show resolved Hide resolved
@thekid thekid linked an issue Jan 19, 2025 that may be closed by this pull request
5 tasks
@thekid thekid marked this pull request as ready for review January 19, 2025 12:22
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