There’s no doubt about it, websockets are awesome. They give your application that live kick to react to events without page loads, just pushed data. To add that to our favourite stack, Laravel as an API back end and ember.js as our interactive front end isn’t the easiest thing to take on but once it all makes sense you’re good to go. It’s also worth mentioning that this is just a starting point to get you up and running, there are much more fine-grained changes you can take on to make this work with better security, but it will get you started with immediate efficiency and relieve the pressure that javascript polling for updates adds to your back end infrastructure.
First things first, we’re going to use Laravel’s Broadcast support and for that we need to broadcast to somewhere, I really like working with redis so we’ll use that.
Install predis into your Laravel project
composer require predis/predis
in your .env file we’ll set redis as the default broadcasting driver
BROADCAST_DRIVER=redis
and we need to un-comment the BroadcastServiceProvider in config/app.php
App\Providers\BroadcastServiceProvider::class,
So now that our project has the beginnings of some interaction we need to install and start redis
# yum install redis
# systemctl enable redis
# systemctl start redis
By default it listens on localhost, you can go nuts and set up clustering as well.
So let’s create an event to broadcast to redis from our controller with a model as it’s property.
event(new JobUpdated($job));
And we’ll create an event in app/Events/JobUpdated.php
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use App\Job;
class JobUpdated implements ShouldBroadcast
{
use InteractsWithSockets, SerializesModels;
public $job;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Job $job)
{
$this->job = $job;
}
/**
* Get the channels the event should broadcast on.
*
* @return Channel|array
*/
public function broadcastOn()
{
return new Channel('mychannel');
}
}
So now we have an event that will fire when something happens with our Job model, and when it’s done whatever it’s doing (updating the model), it will broadcast the updated model on our channel, “mychannel” to redis.
That’s cool but how does it get to ember now? Well, we need to build something to take that redis data and throw it out there.
So let’s install some prerequisits for node and use a little node script to handle things for us:
# npm install express --save
# npm install ioredis --save
# npm install socket.io --save
That will create/edit package.json
so we can run npm install
on the server to install those things from the repository and now we’ll create our socket.js
script to do the socket.io broadcasting of our redis events.
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.subscribe('mychannel', function(err, count) {
});
redis.on('message', function(channel, message) {
console.log('Message Recieved: ' + message);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
http.listen(3000, function(){
console.log('Listening on Port 3000');
});
I like to run this in a screen
so I can just detach from it and come back to it when I need to check on things.
# screen -d -m -S socket node socket.js
You can use screen -r socket
to reconnect and then Ctrl+A Ctrl+D
to detach.
We’re going to need nginx to handle making an https connection for our socket.io. Like everything else, it does this effortlessly just by adding a config file.
server {
listen 80;
server_name socketio.mydomain.tld;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name socketio.mydomain.tld;
ssl_certificate /etc/letsencrypt/live/socketio.mydomain.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/socketio.mydomain.tld/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
keepalive_timeout 60;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_set_header Accept-Encoding "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Front-End-Https on;
proxy_redirect off;
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
proxy_read_timeout 3600;
send_timeout 3600;
}
}
Okay, so now we have an https:// connection to our socket.io node script which will relay all of our events from our redis broadcasted Laravel events.
Now we just need to add socket.io to our ember.js app so that it can recieve those events.
# ember install ember-websockets
And we just need to configure the socket.io use by adding to our config/environment.js
var ENV = {
'ember-websockets': {
socketIO: true
}
};
And then make some code to create our connection and handle the incoming socket.io events:
import { inject as service } from '@ember/service';
import Controller from '@ember/controller';
export default Controller.extend({
init() {
this._super(...arguments);
var _this = this;
var socket = this.get("socketio").socketFor("https://socketio.mydomain.tld/");
socket.on("connect", function() {
socket.on("mychannel:App\\Events\\JobUpdated", _this.updateJob, _this);
});
},
socketio: service("socket-io"),
updateJob: function(data) {
console.log(data);
}
});
Leave a Reply