socket.io-php-emitterを使ってsocket.ioの外(簡易API)からemitしてみる

# 何をするか
前回、[socket.ioでサンプルチャットアプリを作る記事](http://qiita.com/mito_log/items/a6207d2b43189687d750)をかいたのですが、今回はそれを少し拡張して、phpからsocket.ioにemitできるようにしてみます。備忘として書きます。

[公式ページ](http://socket.io/docs/rooms-and-namespaces/)で下記のように書いてありました。

> **Sending messages from the outside-world**
In some cases, you might want to emit events to sockets in Socket.IO namespaces / rooms from outside the context of your Socket.IO processes.

>There’s several ways to tackle this problem, like implementing your own channel to send messages into the process.

>To facilitate this use case, we created two modules:

> - [socket.io-redis](http://github.com/automattic/socket.io-redis)
> - [socket.io-emitter](http://github.com/automattic/socket.io-emitter)

簡単にいうと、
`socket.ioのプロセスの外部から、namespaceやroomにイベントをemitするには、下記の2つ(socket.io-redis/socket.io-emitter)を実装しましょう。`
ってことらしいです。ただ、これだけだと、jsからしか呼べないので、rubyとかphpでAPI書いてる場合だとちょっと保守性悪そうです。そこで、下記のツールが使えそうです。

> [socket.io-php-emitter](https://github.com/rase-/socket.io-php-emitter)
> *A PHP implementation of socket.io-emitter*

socket.io-emitterのphp実装版を書いてくれた方がいたので、今回はこれを使って、php経由でsocket.ioの特定のroomにemitしてみます。

# 今回の環境
 - ec2インスタンス (Amazon Linux AMI release 2014.09)
 - Apache (v2.2.29)
 - PHP (v5.3.29)
 - redis (v2.8.19)
 - phpredis (v2.2.5)
 - socket.io (v1.3.5)
 - socket.io-emitter (v0.2.0)
 - socket.io-redis (v0.1.4)

# 手順
※ httpd、php環境は予め準備されているものとします

1. redis関連ツールのインストールとredisサーバの起動
2. socket.io-php-emitterのインストール(途中必要に応じてcomposerもインストール)
3. socket.io-redis, socket.io-emitterのインストール
4. server.jsをアップデート & nodeサーバ再起動
5. 簡易APIを用意
6. iOS側のソースも少しアップデート
7. ready to go!

## 1. redis関連ツールのインストールとredisサーバの起動
### redis本体のインストール
[AWS EC2にredisをインストールする](http://qiita.com/stoshiya/items/b8c1d2eb41770f92ffcf)
以前はmakeしてたんですが、yumでinstallすると秒速且つ設定ファイルなども自動で配置してくれました...ありがとうございます。

### redis実行環境を整える
僕の環境だと、`/etc/redis.conf`にconfファイルが配置されました。中の設定を少し変更します。

```bash
37c37
< daemonize yes
---
> daemonize no
```

そのあと下記のようにコマンドを打っていきます。

```bash
# vm.overcommit_memory=1を現状で反映させる
sysctl vm.overcommit_memory=1

# 末尾に vm.overcommit_memory = 1 を追加
vi /etc/sysctl.conf

# redisログのgroup:userを変更
chown redis:redis /var/log/redis/redis.log

# 実行
/etc/init.d/redis start

# 実行確認
ps ax | grep [r]edis
29143 ?        Ssl    0:19 redis-server 127.0.0.1:6379 

# サーバ再起動で自動的に実行されるように
chkconfig redis on

# 2~5までonならOK
chkconfig --list redis
```

※ yumでいれた場合実行スクリプトは、既に `/etc/init.d/redis` に配置されていました
※ redisグループ/ユーザも勝手に追加されておりましたのでここでは省略(いつのまに?)
※ 参考その1: [Redisインストール](http://qiita.com/i2bskn/items/42cbf6bc52f9cfa406bb)
※ 参考その2：[さくらのVPSにredisをインストール](http://9ensan.com/blog/server/sakura-vps-redis/)

### phpredisのインストール

```bash
sudo yum -y install php-pecl-redis --enablerepo=epel
```
※ 参考: [PHPでRedisを使ってみる](http://d.hatena.ne.jp/yk5656/20140923/1411889810)

## 2. socket.io-php-emitterのインストール(途中必要に応じてcomposerもインストール)
### composerをインストール
今回つかうツールはcomposerパッケージとしてインストールするので、まずcomposerがないと始まりません。

```bash
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
# 必要であればpathをbashrcやzshrcに追加
# PATH=$PATH:/usr/local/bin
# vi ~.bashrc
```

参考：[vagrant+CentOSにcomposerを導入](http://qiita.com/TetsuTaka/items/1431bb202e1a00777f82)

### socket.io-php-emitterのインストール

```bash
# documentrootへ移動
cd /var/www/html

# ソースを落とす & ソースディレクトリへ移動
git clone https://github.com/rase-/socket.io-php-emitter.git
cd socket.io-php-emitter/

# composerで一括インストール
# (自分の環境では、the requested PHP extension dom is missing from your systemっていうエラーがでたので、一旦 yum install php-xml して再度 composer installしました)
composer install
```

## 3. socket.io-redis, socket.io-emitterのインストール

```bash
# server.jsがあるディレクトリまで移動
cd /path/to/server.js
# インストール
npm install socket.io-redis
npm install socket.io-emitter
```
## 4. server.jsをアップデート & nodeサーバ再起動

```bash
# 2,3行目に下記を追加
1a2,3
> var redis = require('socket.io-redis');
> var adapter = io.adapter(redis({ host: '127.0.0.1', port: 6379 }));

# node-server再度実行
node server

```

## 5. 簡易APIを用意
例えばこんな感じの簡易APIをサーバ側に用意しておきます。

```php:SIOChatEmitter.php
<?php
    header('Content-type: application/json');

    $json_string = file_get_contents('php://input');
    $json = json_decode($json_string);

    $sepStr = ',';

    $param = $json->param;
    $params = split($sepStr, $param);
    $room = $params[0];

    if(empty($room) || empty($param)){
        exit;
    }
    //echo 'post to' . $room;

    require('src/Emitter.php');
    $redis = new \Redis();
    $redis->connect('127.0.0.1', '6379');
    $emitter = new SocketIO\Emitter($redis);
    $emitter->in($room)->emit('update', $param);

    echo json_encode(array('param' => $param));
?>
```

## 6. iOS側のソースも少しアップデート
とりあえず、ニックネームを変更すると、http POSTポストが飛ぶようにしました。
該当箇所はこんな感じ。

```objc:ChatViewController.m(289行目)
AFHTTPRequestOperationManager* manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSString *postFilePath = [NSString stringWithFormat:@"%@/%@",
kHTTPHostName, kEmitFilePath];

[manager POST:postFilePath
   parameters:@{@"param":[param paramStr]}
      success:^(AFHTTPRequestOperation *operation, id responseObject)
 {
     NSLog(@"posted");
 } failure:^(AFHTTPRequestOperation *operation, NSError *error)
 {
     NSLog(@"Error: %@", error);
 }];
```

※ ソースは下記においてます。
[https://github.com/mitolog/SIOChat](https://github.com/mitolog/SIOChat)

## 7. ready to go!
6のソースを実行して、1つのroomに2名以上参加してみます。で、自分のnicknameを変更してみると、room内の他のメンバーに 「changed nickname from "hogehoge" to "fugafuga"」という感じでメッセが飛びます。

# 感想
 - 概念的にはなんとなく分かるのだけど、中身がそんなに理解できていないので、要勉強
 - ステキな勉強会があったら是非いきたい
 
# (話それるけど)勉強になりそうなもの
 - [今更だけどSocket.ioについてまとめてみる](http://blog.wnotes.net/blog/article/nodejs-socketio-summary)
 - [Socket.IO, Redisを使用し各ゲーム間でプッシュ通知するシステム](https://www.cyberagent.co.jp/techinfo/techreport/report/id=8858
)
 - [今日から始めるNode.jsコードリーディング - libuv / V8 JavaScriptエンジン / Node.jsによるスクリプトの実行](http://blog.otakumode.com/2014/08/14/nodejs-code-reading-startup-script/)
 
以上でした〜。