menu

Engine - Notifikasi/chat laravel websocket using Ratchet part-1


Tidak terasa sudah lebih dari 2 bulan tidak menulis akhirnya diberi kesempatan lagi untuk berkarya. Pada artikel kali ini codedoct akan membagikan cara membuat sebuah chat engine menggunakan websocket dengan teknologi ratchet yang akan dikombinasikan dengan laravel.

Sebelum memulai codedoct akan memberitahu teknologi apa saja yang akan digunakan secara detail,
  1. Laravel versi 5.4
  2. PHP versi 5.6
  3. Ratchet cboden versi 0.4
Oke sekarang saatnya kita mulai, Pertama install terlebih dahulu ratchetnya pada laravel dengan cara,
$ composer require cboden/ratchet

Selanjutnya, buat file command laravel untuk dijalankan di server dengan cara,
$ php artisan make:command WebSocketServer --command=websocket:init

Syntax di atas akan secara otomatis membuat file baru dengan nama WebSocketServer.php pada path app/Console/Commands/ edit file tersebut sehingga akan tampak seperti ini,
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React\EventLoop\Factory;
use App\Http\Controllers\WebSocketController;

class WebSocketServer extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'websocket:init';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->info("start server");
        $server = IoServer::factory(
             new HttpServer(
                 new WsServer(
                     new WebSocketController()
                 )
             ),
             8080
        );
        $server->run();
    }
}

Terlihat pada code diatas kita menambahkan file WebSocketController.php terserah kalian meletakkannya dipath mana saja, dalam hal ini codedoct meletakkan pada path app\Http\Controllers\, silahkan edit file terbut sehingga akan tampak seperti ini,
<?php

namespace App\Http\Controllers\Thirdparty;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
/**
 * @author Mihawk | codedoct.com
 */
class WebSocketController implements MessageComponentInterface
{
    protected $clients;
    private $users;
    private $userresources;
    public function __construct()
    {
        $this->clients = new \SplObjectStorage;
        $this->users = [];
        $this->userresources = [];
    }
    /**
     * [onOpen description]
     * @method onOpen
     * @param  ConnectionInterface $conn [description]
     * @return [JSON]                    [description]
     * @example connection               var conn = new WebSocket('ws://localhost:8090');
     */
    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn);
        $this->users[$conn->resourceId] = $conn;
    }
    /**
     * [onMessage description]
     * @method onMessage
     * @param  ConnectionInterface $conn [description]
     * @param  [JSON.stringify]          $msg  [description]
     * @return [JSON]                    [description]
     * @example sendAll                  conn.send(JSON.stringify({command: "sendAll", data: {message:"halo global"}, from: "3"}));
     * @example sendConnection           conn.send(JSON.stringify({command: "sendConnection", data: "halo kampret - kampret", to: [1,2]}));
     * @example message                  conn.send(JSON.stringify({command: "message", to: "1", from: "9", message: "it needs xss protection"}));
     * @example register                 conn.send(JSON.stringify({command: "register", userId: 9}));
     */
    public function onMessage(ConnectionInterface $conn, $msg)
    {
        $data = json_decode($msg);
        if (isset($data->command)) {
            switch ($data->command) {
                case "sendAll":
                    if ($data->from) {
                        foreach ($this->userresources as $key => $resources) {
                            if ($key != $data->from) {
                                foreach ($resources as $resourceId) { //setiap user ID bisa buka di banyak browser
                                    if (isset($this->users[$resourceId])) {
                                        $this->users[$resourceId]->send(json_encode($data->data));
                                    }
                                }
                            }
                        }
                    }
                break;
                case "sendConnection":
                    if (count($data->to)>0) {
                        foreach ($this->userresources as $key => $resources) {
                            if (in_array($key, $data->to)) {
                                foreach ($resources as $resourceId) { //setiap user ID bisa buka di banyak browser
                                    if (isset($this->users[$resourceId])) {
                                        $this->users[$resourceId]->send(json_encode($data->data));
                                    }
                                }
                            }
                        }
                    }
                break;
                case "message":
                    if ($data->from && $data->to) {
                        if ( isset($this->userresources[$data->to]) ) {
                            foreach ($this->userresources[$data->to] as $key => $resourceId) {
                                if ( isset($this->users[$resourceId]) ) {
                                    $this->users[$resourceId]->send(json_encode($data->data));
                                }
                            }
                        }
                        if (isset($this->userresources[$data->from])) {
                            foreach ($this->userresources[$data->from] as $key => $resourceId) {
                                if ( isset($this->users[$resourceId])  && $conn->resourceId != $resourceId ) { //jika buka di browser yg berbeda
                                    $this->users[$resourceId]->send(json_encode($data->data));
                                }
                            }
                        }
                    }
                break;
                case "register":
                    if (isset($data->userId)) {
                        if (isset($this->userresources[$data->userId])) {
                            if (!in_array($conn->resourceId, $this->userresources[$data->userId]))
                            {
                                $this->userresources[$data->userId][] = $conn->resourceId;
                            }
                        }else{
                            $this->userresources[$data->userId] = [];
                            $this->userresources[$data->userId][] = $conn->resourceId;
                        }
                    }
                    // $conn->send(json_encode($this->users));
                    $conn->send(json_encode($this->userresources));
                break;
                default:
                    $example = array(
                        'methods' => [
                                    "send to all" => '{command: "sendAll", data: {message:"halo global"}, from: "3"}',
                                    "send to group" => '{command: "sendConnection", data: "halo kampret - kampret", to: [1,2]}',
                                    "message" => '{command: "message", to: "1", from: "2", data: {message:"halo gan"}',
                                    "register" => '{command: "register", userId: 9}',
                                ],
                    );
                    $conn->send(json_encode($example));
                break;
            }
        }
    }
    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);
        echo "Connection {$conn->resourceId} has disconnected\n";
        unset($this->users[$conn->resourceId]);
        foreach ($this->userresources as &$userId) {
            foreach ($userId as $key => $resourceId) {
                if ($resourceId==$conn->resourceId) {
                    unset( $userId[ $key ] );
                }
            }
        }
    }
    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }
}

Code websocket server diatas akan kita panggil melalui command php artisan untuk itu kita harus menambahkannya pada kernel laravel, yang terletak pada path app/Console/ dengan nama file Kernel.php edit bagian command menjadi seperti ini,
/**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        Commands\WebSocketServer::class,
    ];

Oke jika semua sudah selesai saatnya kita coba run WebSocketServer nya dengan cara,
$ php artisan websocket:init

Sehingga akan tampak seperti ini tampilannya,


===DONE!===