Edited at
LaravelDay 14

laravel4 websocket push

More than 5 years have passed since last update.

二回目の微妙な登場でございます。


websocket push!

websocketの代表と言えばchatでしょうか?

laravel4でのチャット実装のそのまんまなサンプルは

laravel4 cookbook

にありますので、

そちらを参考にしていただければと思います。

laravel4パッケージにしたLatchetもありますが、

ratchetを使います。

今回はlaravel4で実装するwebsocket pushについてです

pushをご存知でない方が一番分かりやすいのは、

iPhoneやandroidでニュースやメッセージを受信したときに通知されるnotificationの

web版だと思ってください。

つまり、アクセスしている人にサーバから送信する事ができます。

node.jsで広告やコンテンツを一斉に配信したり、

ブラウザゲーム系にも活用されている方は多いかもしれません。

Ratchetとzmqを使って実装します。(といってもリファレンス通りですが!)

Ratchet

まずサーバpushを実装するにあたっていくつか必要なものがありますので、

makeします。

(mac環境での構築方法なので他のOSの方はそれ用にあわせてください)


libzmq


libzmq

$ git clone git://github.com/zeromq/libzmq.git

$ cd libzmq/
$ ./autogen.sh
$ ./configure && make
$ make install

*errorが出る場合はmacPortsでlibtool autoconf automakeをinstallしてください。


php-zmq


php-zmq

$ git clone git://github.com/mkoppanen/php-zmq.git

$ cd php-zmq/
$ phpize
$ ./configure && make
$ make install

re2cあたりがエラー出る場合は同様にmacPortsでzmq.soをiniに忘れずに追記してください。

php -mで確認してください!

あとはいつもと同じです


composer.json

    "require": {

"laravel/framework": "4.0.*",
"cboden/Ratchet": "0.3.*",
"react/zmq": "0.2.*"
}


push実装


Wamp/Pusher.php

namespace Wamp;

use Ratchet\ConnectionInterface;
use Ratchet\Wamp\WampServerInterface;

class Pusher implements WampServerInterface
{
protected $subscribedTopics = array();

public function onSubscribe(ConnectionInterface $conn, $topic)
{
if (!array_key_exists($topic->getId(), $this->subscribedTopics))
{
$this->subscribedTopics[$topic->getId()] = $topic;
}
}

/**
* @param string JSON'ified string we'll receive from ZeroMQ
*/

public function onBlogEntry($entry) {
$entryData = json_decode($entry, true);

if (!array_key_exists($entryData['cat'], $this->subscribedTopics)) {
return;
}

$topic = $this->subscribedTopics[$entryData['cat']];
$topic->broadcast($entryData);
}

public function onUnSubscribe(ConnectionInterface $conn, $topic) {
}

public function onOpen(ConnectionInterface $conn) {
}

public function onClose(ConnectionInterface $conn) {
}

public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {
$conn->callError($id, $topic, 'You are not allowed to make calls')->close();
}

public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) {
$conn->close();
}

public function onError(ConnectionInterface $conn, \Exception $e) {
}
}


onBlogEntryが受け、

サーバから接続しているブラウザに送信する様にします。

実際にはDBで配信時間を見ながら自動で送信したりという実装ですが、

書くと長くなるので省きます


コマンド実装


app/commands/WampPushCommand.php


use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React\Socket;

class WampPushCommand extends Command {

/**
* @var string
*/

protected $name = 'wamp:push';

/**
* The console command description.
* @var string
*/

protected $description = "boot wamp server";

/**
* @return void
*/

public function fire()
{
$loop = React\EventLoop\Factory::create();
$pusher = new Wamp\Pusher;

$context = new \React\ZMQ\Context($loop);
$pull = $context->getSocket(ZMQ::SOCKET_PULL);
$pull->bind('tcp://127.0.0.1:5555');
$pull->on('message', array($pusher, 'onBlogEntry'));

$webSock = new \React\Socket\Server($loop);
$webSock->listen(10000, '0.0.0.0');
$webServer = new Ratchet\Server\IoServer(
new Ratchet\Http\HttpServer(
new Ratchet\WebSocket\WsServer(
new Ratchet\Wamp\WampServer($pusher
)
)
), $webSock);
$this->info('wamp boot');
$this->info('Listening on port ' . 10000);
$loop->run();
}
}


この例は

port 5555から受け付けて、port 10000にpushを行います。

実際にはコマンドの引数でport指定する様にしましょう。(サンプルなので手抜き)

ここでいうWampとは、The WebSocket Application Messaging Protocolで、

windows環境をさすものではありませんYo

artisanに登録します。


app/start/artisan.php

$artisan->add(new WampPushCommand);


これでwebsocketの準備が整いました。

$ php artisan wamp:push

wamp boot
Listening on port 10000

上記の様に起動します。

次にブラウザです。

折角なのでデフォルトのhello.phpに書いてしまいましょう

リファレンスと同じ様にwhen.js, autobahn.jsにしときます。


hello.phpに書いてみる

<script src="/js/when.js"></script>

<script src="http://autobahn.s3.amazonaws.com/js/autobahn.min.js"></script>
<script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
<script>
var conn = new ab.Session(
'ws://127.0.0.1:10000' , function() {
conn.subscribe('news', function(topic, data) {
console.log('New article published to category "' + topic + '" : ' + data.title);
$('.welcome').html(
'New article published to category "' + topic
+ '"<br /> title : ' + data.title
+ "<br /> article : " + data.article
);
});
}, function() {
console.warn('WebSocket connection closed');
}, {
'skipSubprotocolCheck': true
}
);
</script>

newsカテゴリーを購読して、受信したときにlaravel4ロゴを書き換えます。(わかりやすい!)

リソースでエントリーします


ResourceController

    public function create()

{
$entryData = [
'cat' => 'news',
'title' => 'ニュース!',
'article' => 'laravel4.1 でましたよ',
'when' => time()
];
$context = new ZMQContext();
$socket = $context->getSocket(ZMQ::SOCKET_PUSH, 'my pusher1');
$socket->connect("tcp://localhost:5555");
$socket->send(json_encode($entryData));
}

先ほどのlaravel4のデフォルトページをそのまま表示させておき、

他のブラウザかタブからリソースを叩いて送ってみましょう。

sample.png


じゃーん

今回はリソース直叩きなので、普通のwebsocketと変わらないじゃん!と言われそうですが、

実際にはこれをサーバのみで実装します。

webでできるなんて、ちょっと気持ち悪いですね!!!

慣れるまでちょっと長い道のりですが、

実装できる様になると色んなものに使えると思います。

laravel4でwebsocketアプリケーションを実装する事で、

ブラウザからごにょごにょしたデータをnode.jsに送って解析して云々などしなくても、

用意されているものでユーザーデータだったり、色んな情報も簡単に実装する事ができます。

Illuminate万歳!

明日はatijust さんです