As a Laravel beginning I'm having hard times grasping on how to create a simple "hello world" -like WebSocket interface, it seems as if authentication must be implemented, database logging must exist, and a certain WebSocket structure must be implemented in order for it to work. The problem is, the client cannot be modified to fit the server. How should this simple server be implemented in Laravel (preferably with beyondcode/laravel-websockets)? The websocket should be available at ws://{location}:3000/clock, whereas other already existing REST interfaces in other paths, such as http://{location}:3000/some-rest-thing. It's vital for the application to work with either commonly used Laravel frameworks, or default methods provided by Laravel.
Different parts of the application have also been attempted to create using this youtube tutorial, but it seems in many cases it's not possible to even access the /laravel-websockets dashboard and when it is accessible, pressing the connect button does nothing as the server returns HTTP status code 404.
The goal for the server is to collect WebSocket clients, and once receiving a "requestTime" request, broadcast the server time to all connected clients. The whole server application can be seen in the fully working NodeJS app below; this is the same structure the Laravel app should also have:
const wsServer = new ws.WebSocketServer({ server: server, path: "/clock" });
wsServer.on('connection', socket => {
socket.on('error', err => {
console.error(err);
});
socket.on('message', data => {
if(data.toString() === "requestTime") {
// broadcast time on requestTime event to all clients
wsServer.clients.forEach(client => {
if(client.readyState === ws.OPEN) {
client.send((new Date()).getMilliseconds());
}
});
}
});
});
Here's what has been implemented thus far:
Connecting a client to the implementation below the client connects, but almost directly disconnects as it receives a HTTP response code 200, which probably shouldn't happen in a WebSocket api. No events seem to be thrown.
SendTimeToClientEvent.php, the event that gets broadcasted to connected clients assuming that it only sends "<system time in ms>" with no other data
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class SendTimeToClientEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}
public function broadcastWith() {
return round(microtime(true) * 1000));
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new Channel('clock');
}
}
Route in api.php
Route::get('/clock', function(Request $request) {
$message = $request->input("message", null);
if($message == "requestTime") {
SendTimeToClientEvent::dispatch();
}
return null;
});
Pusher set to .env
BROADCAST_DRIVER=pusher
Laravel CMD output Note that the client works with some other provided frameworks
[Fri Mar 10 00:40:07 2023] 127.0.0.1:3617 Accepted
[Fri Mar 10 00:40:07 2023] 127.0.0.1:3609 Closing
[Fri Mar 10 00:40:07 2023] 127.0.0.1:3611 Invalid request (An existing connection was forcibly closed by the remote host.)
I've also attempted some alternative methods to do this, such as:
The first alternative method
WebSocketsRouter::webSocket('/clock', function ($webSocket) {
$webSocket->onMessage('requestTime', function ($client, $data) use ($webSocket) {
$webSocket->broadcast()->emit('systemTime', round(microtime(true) * 1000));
});
});
which will throw "invalid websocket controller provided". after running php artisan cache:clear
. Moving the function to an actual class that implements MessageComponentInterface seems to throw a similar error as well.
The second alternative method
create a new socket controller:
namespace App\Http\Controllers;
use Ratchet\ConnectionInterface;
use Ratchet\WebSocket\MessageComponentInterface;
class ClockWebSocketController implements MessageComponentInterface
{
protected $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
}
public function onMessage(ConnectionInterface $from, $msg)
{
if ($msg === 'requestTime') {
$now = round(microtime(true) * 1000);
$this->broadcast($now);
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
$conn->close();
}
protected function broadcast($msg)
{
foreach ($this->clients as $client) {
$client->send($msg);
}
}
}
bind it in AppServiceProvider.register()
App::bind(MessageComponentInterface::class, ClockWebSocketController::class);
and resolve and take the controller into use in api.php
$webSocketRouter = resolve(Router::class);
$webSocketRouter->webSocket('/clock', MessageComponentInterface::class);
unfortunately this throws the following peculiar error:
BeyondCode\LaravelWebSockets\Exceptions\InvalidWebSocketController
Invalid WebSocket Controller provided. Expected instance of `Ratchet\WebSocket\MessageComponentInterface`, but received `Ratchet\WebSocket\MessageComponentInterface`.
How to replicate with a fresh project
After trying to implement this in a whole new project, I still can't get this to work. I've added the steps I've taken below, which can be used to replicate the issue:
- Install Composer (2.5.4)
- Create a new Laravel Project
composer create-project laravel/laravel stresstest2
- Require laravel-websockets
composer require beyondcode/laravel-websockets
- Publish websockets config etc. packages
php artisan vendor:publish
and select the laravel-websockets package, should be one of the first ones - Run migration
php artisan migrate
- Require pusher-php-server
composer require pusher/pusher-php-server
- in
config/websockets.php
add '*' to allowed origins - in
.env
modify the following values:
BROADCAST_DRIVER=pusher
QUEUE_CONNECTION=sync
PUSHER_APP_ID=dummy
PUSHER_APP_KEY=dummy
PUSHER_APP_SECRET=dummy
# \/ are new
LARAVEL_WEBSOCKETS_PORT=3000
LARAVEL_WEBSOCKETS_HOST=127.0.0.1
- now launch laravel with
php artisan serve --port=3000
. websockets:serve is not used as in the future some REST API:s need to also be in the app. - Open
http://localhost:3000/laravel-websockets
and click on the dashboard connect button -> nothing happens - Alternatively try launching the app with
php artisan websockets:serve --port=3000
and go to the dashboard -> dashboard now doesn't open and network log on browser displays error 404
Interestingly if you first serve the website with php artisan serve --port=3000
, then stop the server (do not refresh the website!) and open the websocket server with php artisan websockets:serve
you can now press the Connect button, but the browser still displays error 404, even though the PHP server displays a new successful connection