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());
}
});
}
});
});
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:
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.
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`.
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:
composer create-project laravel/laravel stresstest2
composer require beyondcode/laravel-websockets
php artisan vendor:publish
and select the laravel-websockets package, should be one of the first onesphp artisan migrate
composer require pusher/pusher-php-server
config/websockets.php
add '*' to allowed origins.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
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.http://localhost:3000/laravel-websockets
and click on the dashboard connect button -> nothing happensphp artisan websockets:serve --port=3000
and go to the dashboard -> dashboard now doesn't open and network log on browser displays error 404Interestingly 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
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 over ws://
or wss://
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 one php artisan serve --port=3000
. Then you can access the websockets dashboard on http://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.