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 AppEvents;
use IlluminateBroadcastingChannel;
use IlluminateBroadcastingInteractsWithSockets;
use IlluminateBroadcastingPresenceChannel;
use IlluminateBroadcastingPrivateChannel;
use IlluminateContractsBroadcastingShouldBroadcast;
use IlluminateFoundationEventsDispatchable;
use IlluminateQueueSerializesModels;
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 IlluminateBroadcastingChannel|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 AppHttpControllers;
use RatchetConnectionInterface;
use RatchetWebSocketMessageComponentInterface;
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:
BeyondCodeLaravelWebSocketsExceptionsInvalidWebSocketController
Invalid WebSocket Controller provided. Expected instance of `RatchetWebSocketMessageComponentInterface`, but received `RatchetWebSocketMessageComponentInterface`.
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
2
Answers
Why don’t you try that with soketi?
Not sure what you did here, but you can easily setup soketi and use laravels configuration for pusher to use it. Not sure will you app work with web sockets on the same port tho.
When you run
php artisan serve --port=3000
this starts up the development webserver. This runs all your normal web and api routes, including the websockets dashboard which in itself is not using websockets.When you run
php artisan websockets:serve --port=3000
this starts the websockets server, but this only server websockets overws://
orwss://
but not the dashboard.The websocket dashboard however does connect to the websocket server from the client/browser.
You should open 2 terminals and run bith servers on different ports. For example, in the first one run
php artisan serve --port=8000
and on the second onephp artisan serve --port=3000
. Then you can access the websockets dashboard onhttp://localhost:8000/laravel-websockets
but you have to make sure that you set the websockets url to use port 3000. By default it uses port 6001 for the websockets, so it might be easier to use that port instead of 3000 to make sure it works before you switch ports to 3000.