SEEDS Creator's Blog

読者です 読者をやめる 読者になる 読者になる

WebSocketでルータ越しの通信を行う

概要

弊社ではコミュニケーションツールとして、 チャットサービスの「Slack」を使用しています。

https://slack.com/

Slackでは、 チャット内で動作するBotを簡単に作成できるような仕組みが用意されています。

とても簡単なので、色々とBotを作成していますが、 今回はその中でも「Kanasanコマンド」を作成した時に行ったテクニックをご紹介します。

Kanasan-API

社内には、Mac miniにつながっている大きなスピーカーがあります。

参考:http://seeds-std-pr.blogspot.jp/2014/09/kana-san.html

このMac miniで再生すると、 スピーカーから音が出て社内全体に響き渡るようになっています。

普段は、朝礼の時間にラジオ体操を流したり、 がんばるタイムの始めと終わりに学校のチャイムを鳴らしたりしています。

また、SayKana (http://www.a-quest.com/quickware/saykana/)というソフトを使用して、 任意の日本語文字列を喋らせたりもしてます。 単純なものですが、このシステムを「Kanasan」と名づけました。

先日、このツールと連携するKanasan-APIというWebAPIも作りました。 https://github.com/memememomo/p5-KanaSan-API

このKanasan-APIをSlackと連携させたいなと考えました。

Kanasanコマンド

Slackは、(噂では)バックエンドがIRCということもあり、 メッセージの最初にスラッシュ(/)をつけると、コマンドとして認識されます。

Slackには、新しくコマンドを作成できる仕組みも用意されています。

ということで、「/kanasan ほげほげ」とSlack上で入力したら、 社内のスピーカーに「ほげほげ」と喋らせることができるKanasanコマンドを作成しようと考えました。

メッセージ反応系botの仕組みと問題点

任意のURLを設定すると、Slack上でメッセージが投稿されたタイミングで、 設定したURLにリクエストを飛ばすことができます。

メッセージ反応系botでは、 このリクエストを処理して反応を返します。

Kanasanコマンドもこの仕組を利用します。 しかし、今回のKanasan-APIはオフィスのルータ下にあるため、 Slackに直接叩いてもらうことができません。

Websocketでルータ越しの通信

上記の問題点を解消するため、 まずはSlackに叩いてもらうエンドポイントをHeroku上に置くことにしました。 そして、Heroku上のエンドポイントとオフィス内のサーバをWebsocketでつなぎ、 なるべくリアルタイムに反応できるようにしました。

Heroku上のエンドポイントのソースは以下のようになります。

heroku.coffee
HTTP_PORT = process.env.PORT || 8000

app = require("express")()
socketio = require 'socket.io'
server = require('http').Server(app)
io = socketio.listen(server)

# 社内サーバにデータを送るWebSocket
s = undefined
io.sockets.on 'connection', (socket) ->
  s = socket
  console.log 'connected'

# Slackからのリクエストを処理
app.get "/api", (req, res) ->
  t = req.param 'text'
  param =
    text:t
  if s != undefined
    s.emit 'news', param
    res.send 'ok'
  else
    res.send 'ng'

server.listen(process.env.PORT)
console.log HTTP_PORT

express と Socket.io を使用しています。 expressでSlackからのリクエストを受け付ける処理を記述し、 Socket.ioで社内サーバにWebsocket経由でデータを送る処理を記述しています。 expressとSocket.ioを連携させる機能があったので比較的簡単に書けました。

社内のサーバのソースは以下のようになります。

local.coffee
WEBSOCKET_URL = 'http://hogehoge.herokuapp.com/'
API_URL = 'http://local-kanasan.net/api'

request = require('request')
io = require('socket.io-client')(WEBSOCKET_URL)

# Heroku上のサーバにWebSocketで接続する
socket = io.connect WEBSOCKET_URL

# Herokuからのデータを受信するWebSocket
socket.on 'news', (data) ->
  try
    # Kanasan-APIを叩く
    options =
      uri: API_URL,
      form: {text: data.text}
    request.post options, (error, response, body) ->
      console.log body
  catch e
    console.log 'error'

Socket.io と request を使用しています。 Socket.ioでHeroku上からのデータを受け付る処理を記述し、 requestでKanasanAPIを叩く処理を記述しています。

以上で、Kanasanコマンドを動作させることができました。

まとめ

WebSocketでルータ越しの通信を実現しました。 Node.jsを使用すると、今回のように双方向通信処理をカジュアルに書けていいですね。 ブラウザとの双方向通信以外の用途で色々使えそうな可能性が見えてきました。