Skip to content

Instantly share code, notes, and snippets.

@Mevrael
Created March 14, 2017 10:40
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save Mevrael/6855dd47d45fa34ee7161c8e0d2d0e88 to your computer and use it in GitHub Desktop.
Save Mevrael/6855dd47d45fa34ee7161c8e0d2d0e88 to your computer and use it in GitHub Desktop.
Laravel + WebSocket (Ratchet/ReactPHP) integration
<?php
// add this to web routes
Route::get('/websocket/open', 'WebSocketController@onOpen');
Route::get('/websocket/message', 'WebSocketController@onMessage');
Route::get('/websocket/close', 'WebSocketController@onClose');
Route::get('/websocket/error', 'WebSocketController@onError');
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Ratchet\WebSocket\Version\RFC6455\Connection;
class WebSocketController
{
/**
* @var Connection
*/
protected $connection = null;
/**
* @var array|mixed
*/
protected $data = [];
/**
* @var Connection
*/
protected $currentClient = null;
/**
* @var Connection[]
*/
protected $otherClients = [];
public function __construct(Request $request)
{
$this->connection = $request->get('connection');
$this->data = $request->get('data');
$this->currentClient = $request->get('current_client');
$this->otherClients = $request->get('other_clients');
}
public function sendToOthers(array $data) {
foreach ($this->otherClients as $client) {
$client->send(json_encode($data));
}
}
public function onOpen(Request $request)
{
if (!Auth::check()) {
$this->currentClient->close();
return;
}
echo 'Opened' . PHP_EOL;
echo $request->ip() . PHP_EOL;
$this->sendToOthers([
'type' => 'USER_CONNECTED',
]);
}
public function onMessage(Request $request)
{
if (!Auth::check()) {
$this->currentClient->close();
return;
}
echo 'New message' . PHP_EOL;
echo $request->ip() . PHP_EOL;
$this->sendToOthers([
'type' => 'MESSAGE_RECEIVED',
'data' => [
'user' => Auth::user()->name,
'message' => $this->data->message,
]
]);
}
public function onClose(Request $request)
{
echo 'Closed' . PHP_EOL;
echo $request->ip() . PHP_EOL;
$this->sendToOthers([
'type' => 'USER_DISCONNECTED',
]);
}
public function onError(Request $request)
{
echo 'Error' . PHP_EOL;
}
}
<?php
// in Laravel project root create also wsserver.php
// use 'php wsserver' to launch Web Socket server
// Make sure composer dependencies have been installed
require __DIR__ . '/vendor/autoload.php';
use App\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
/**
* chat.php
* Send any incoming messages to all connected clients (except sender)
*/
class WebSocketLaravelServer implements MessageComponentInterface
{
protected $clients;
public function __construct() {
echo 'Creating app...' . PHP_EOL;
$this->clients = new \SplObjectStorage;
}
protected function handleLaravelRequest(ConnectionInterface $con, $route, $data = null)
{
/**
* @var \Ratchet\WebSocket\Version\RFC6455\Connection $con
* @var \Guzzle\Http\Message\Request $wsrequest
* @var \Illuminate\Http\Response $response
*/
$params = [
'connection' => $con,
'other_clients' => [],
];
if ($data !== null) {
if (is_string($data)) {
$params = ['data' => json_decode($data)];
} else {
$params = ['data' => $data];
}
}
foreach ($this->clients as $client) {
if ($con != $client) {
$params['other_clients'][] = $client;
} else {
$params['current_client'] = $client;
}
}
$wsrequest = $con->WebSocket->request;
$app = require __DIR__.'/bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::create($route, 'GET', $params, $wsrequest->getCookies())
);
//var_dump(Auth::id());
$controllerResult = $response->getContent();
$kernel->terminate($request, $response);
return json_encode($controllerResult);
}
public function onOpen(ConnectionInterface $con)
{
$this->clients->attach($con);
$this->handleLaravelRequest($con, '/websocket/open');
}
public function onMessage(ConnectionInterface $con, $msg)
{
$this->handleLaravelRequest($con, '/websocket/message', $msg);
}
public function onClose(ConnectionInterface $con)
{
$this->handleLaravelRequest($con, '/websocket/close');
$this->clients->detach($con);
}
public function onError(ConnectionInterface $con, \Exception $e)
{
$this->handleLaravelRequest($con, '/websocket/error');
echo 'Error: ' . $e->getMessage() . PHP_EOL;
$con->close();
}
}
// Run the server application through the WebSocket protocol on port 8080
$app = new Ratchet\App('localhost', 8080);
$app->route('/echo', new WebSocketLaravelServer);
$app->run();
@joshmanders
Copy link

Think that wssocket.php can be migrated into a Artisan command instead of executed by itself?

@ftcvlad
Copy link

ftcvlad commented Feb 19, 2018

as mentioned at http://socketo.me/docs/migration-3 Guzzle Http API changed, so, getting cookies could be smth like

 $wsrequest = $con->httpRequest ;
 $cookies = Cookies::fromRequest($wsrequest)->getAll();
 $allCookies = array();
 foreach ($cookies as $cookie){
        $allCookies[$cookie->getName()] = $cookie->getValue();
 }

$request = \Illuminate\Http\Request::create($route, 'GET', $params, $allCookies);

@jenrenia
Copy link

jenrenia commented Feb 10, 2020

This kind of code didn't work for me immediately. BUT!
There are few changes to make it working with Bearer token.
Change handleLaravelRequest() method to:

`$params = [
'connection' => $con,
'other_clients' => [],
];
if ($data !== null) {
if (is_string($data)) {
$params = ['data' => json_decode($data)];
} else {
$params = ['data' => $data];
}
}

    foreach ($this->clients as $client) {
        if ($con != $client) {
            $params['other_clients'][] = $client;
        } else {
            $params['current_client'] = $client;
        }
    }

	$wsrequest = $con->httpRequest;
	$cookies = $wsrequest->getHeader("Cookie");
	$cookies = \GuzzleHttp\Psr7\parse_header($cookies)[0];

    $app = App::getInstance();
    $kernel = $app->make(\Illuminate\Contracts\Http\Kernel::class);
    $request = \Illuminate\Http\Request::create($route, 'GET', $params, $cookies);
    $request->headers->set('Authorization', 'Bearer '.$cookies['X-Authorization'],);
    $response = $kernel->handle(
        $request
    );

    $response->getContent();
    $controllerResult = $response->getContent();

    // var_dump($controllerResult);

    $kernel->terminate($request, $response);

    return json_encode($controllerResult);`

X-Authorization Cookie on the client side must contain actual Bearer token given by Laravel during auth.
Without this line $request->headers->set('Authorization', 'Bearer '.$cookies['X-Authorization'],); it's unable to make working 'auth:api' middleware properly. If header "Authorization" isn't present, you'll continiously get "Unauthorized". You'll even have no ability to use Auth::user()->id inside controller, because you'll get "Unauthorized message" before.

@grabson101
Copy link

I've used this code and added few changes, but whatever I do, code

$response = $kernel->handle( $request );

is causing memory leak.

@jenrenia
Copy link

I've used this code and added few changes, but whatever I do, code

$response = $kernel->handle( $request );

is causing memory leak.

Yes, it causes memory leak.
Does anyone has solution?

@grabson101
Copy link

grabson101 commented Jul 21, 2020

I've used this code and added few changes, but whatever I do, code
$response = $kernel->handle( $request );
is causing memory leak.

Yes, it causes memory leak.
Does anyone has solution?

Maybe this code will help you

<?php
use \League\OAuth2\Server\ResourceServer;
use \Laravel\Passport\TokenRepository;
use \Laravel\Passport\Guards\TokenGuard;
use \Laravel\Passport\ClientRepository;
use \Illuminate\Support\Facades\Auth;
use \Illuminate\Http\Request;

class UserGetter 
{
    public static function getUser($bearerToken) 
    {
        $tokenguard = new TokenGuard(
            \App::make(ResourceServer::class), 
            \Auth::createUserProvider('users'), 
            \App::make(TokenRepository::class), 
            \App::make(ClientRepository::class), 
            \App::make('encrypter')
        );
        $request = Request::create('/');
        $request->headers->set('Authorization', $bearerToken);
        
        return $tokenguard->user($request);
    }
}

Also you have to use garbage collector like:
gc_collect_cycles();
after all because it still takes a lot of memory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment