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

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>