LoginSignup
5

More than 5 years have passed since last update.

Openshiftでrubyなwebsocketアプリを動かす

Posted at

前回までのあらすじ

herokuではwebsocketが公式サポートされていて、検索するとRubyでのwebsocketサンプルもいっぱいでてくる。一方OpenShiftの情報はあまりヒットせず、まだβサポートだから不安定だとか、node.jsでしか駄目だとか、いやいやできるよだとか、現状どうなっているのかさっぱりわからない。こうすればできるという情報もヒットしたのでそれを元にherokuでのwebsocketサンプルをOpenShift上で動かして実際に試すことにした。

結論

2013年12月現在、OpenShift上のruby1.9カートリッジでwebsocketの通信に成功。

やったこと

OpenShiftでwebsocketのruby実装を使うには、Ruby websockets on OpenShiftの情報がわりと詳しい。ただし、肝心のサンプルが今のOpenShiftでは動作させられなかった上に、独自ライブラリを使った実装なので動作しない原因を調べるのもかなりきつい。
そこで、比較的単純でわかりやすいherokuでのサンプル実装を、上記サイトに書いてある注意点を読みながらOpenShiftで動くように改変していった。

おおまかな手順

  1. herokuで動作するruby-websockets-chat-demoをOpenShiftに乗せる
  2. herokuで動かした経験を元にredisを使わないようにサンプルを改変する
  3. OpenShiftにあわせてGemfile等の設定を変える
  4. OpenShiftのrubyカートリッジでpumaを動作させる
  5. websocketのポートをOpenShiftの仕様にあわせる

ruby-websockets-chat-demoをOpenShiftに乗せる

OpenShiftで適当にアプリケーションを作成

$ rhc app create websocketchat -t ruby-1.9
Application Options
-------------------
  Domain:     l1s
  Cartridges: ruby-1.9
  Gear Size:  default
  Scaling:    no

Creating application 'websocketchat' ...
()
Run 'rhc show-app websocketchat' for more details about your app.

ruby-websockets-chat-demoをmergeする。Using WebSockets on Heroku with Rubyにサンプルのgithubへのリンクがあるので、How to Migrate Your Heroku Ruby Application to OpenShiftを参考にしてさっき作った空のアプリケーションにmerge。

$ cd websocketchat
$ git pull -s recursive -X theirs https://github.com/heroku-examples/ruby-websockets-chat-demo.git
warning: no common commits
remote: Counting objects: 48, done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 48 (delta 12), reused 47 (delta 12)
Unpacking objects: 100% (48/48), done.
From https://github.com/heroku-examples/ruby-websockets-chat-demo
 * branch            HEAD       -> FETCH_HEAD
Auto-merging config.ru
Auto-merging README.md
Merge made by the 'recursive' strategy.
 Gemfile                                               |    8 +
 Gemfile.lock                                          |   28 +
()
 views/index.html.erb                                  |   34 +
 21 files changed, 9615 insertions(+), 297 deletions(-)
 create mode 100644 Gemfile
 create mode 100644 Gemfile.lock
()
 create mode 100644 views/index.html.erb

もともとが起源の違う二つのリポジトリのマージなので、マージ後はコミットが妙なグラフにはなるが気にしない。

redisを使わないようにサンプルを改変する

この時点ではローカルでもrackupできない。サンプルでは複数ノードにスケールした時でも受けとったメッセージを共有するためにredisを使うコードになっていて、ローカルではredisの環境を設定してないからだ。今は複数ノードなんていらないのでredisの部分をがさっと削る。

以下変更したところだけ抜粋。

middlewares/chat_backend.rb
require 'faye/websocket'
require 'thread'
# require 'redis'
require 'json'
require 'erb'

module ChatDemo
  class ChatBackend
    KEEPALIVE_TIME = 15 # in seconds
    CHANNEL        = "chat-demo"

    def initialize(app)
      @app     = app
      @clients = []
=begin
      uri = URI.parse(ENV["REDISCLOUD_URL"])
      @redis = Redis.new(host: uri.host, port: uri.port, password: uri.password)
      Thread.new do
        redis_sub = Redis.new(host: uri.host, port: uri.port, password: uri.password)
        redis_sub.subscribe(CHANNEL) do |on|
          on.message do |channel, msg|
            @clients.each {|ws| ws.send(msg) }
          end
        end
      end
=end
    end

    def call(env)
      if Faye::WebSocket.websocket?(env)
        ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME })
        ws.on :open do |event|
          p [:open, ws.object_id]
          @clients << ws
        end

        ws.on :message do |event|
          p [:message, event.data]
          # @redis.publish(CHANNEL, sanitize(event.data))
          @clients.each {|c| c.send(sanitize(event.data))}
        end

これでローカルでもproductionオプションをつけると起動するようになる(productionにしないと何故かpumaがこける)。

$ bundle exec rackup -E production
Puma 2.6.0 starting...
* Min threads: 0, max threads: 16
* Environment: production
* Listening on tcp://0.0.0.0:9292

ようやくHTMLが表示されて喜ぶが、productionならhttpsだよね。だからwss使うね、というおせっかいコードのせいで、httpで立ちあげているとwebsocket接続ができない。さらにOpenShiftではwsの待受ポートが特殊なのでやはりwebsocket通信はまだできない。後でまとめて変更することにする。

OpenShiftにあわせてGemfile等の設定を変える

ローカルではそれなりに動いたのでOpenShiftにpushしてみると変なところでエラーがでている。
herokuではGemfileにrubyのバージョンを指定するrubyオプションが使えるが、OpenShiftでは使えないからのようだ。というわけで削る。ついでにもう使わないredisも削っておく。

Gemfile
source "https://rubygems.org"

# ruby "2.0.0"

gem "faye-websocket"
gem "sinatra"
gem "puma"
# gem "redis"

bundle installをしてリポジトリを更新してからgit push origin masterをするとOpenShiftにアップロードされる。ローカルと同様にHTMLは表示される。やったね。だがしかしwebsocketを使うチャット部分は何も動かない。

OpenShiftでpumaを動作させる

以下、ほぼRuby websockets on Openshiftの受け売り。

OpenShiftのruby環境ではapache+passengerが使われており、非常にトリッキーなことをしないとwebsocketの通信ができないらしい。ただしOpenShiftには抜け道が用意されていて、passengerの代わりに他のweb serverを利用する方法がある。それを使ってruby製のwebserver pumaを起動してやるとwebsocketの通信も比較的簡単にできるよ、ということのようだ。確かにherokuのサンプルでもpumaを起動するようになっていた。

具体的にはOpenShiftが用意しているフックがあるので、そこにpassengerの停止とpumaの起動、pumaの停止のスクリプトを置く。実行属性を付けるのを忘れないように。

/.openshift/action_hooks/post_start_ruby-1.9
#!/bin/bash
echo "Replacing the default Passenger server with Puma"
pushd ${OPENSHIFT_REPO_DIR} > /dev/null
${HOME}/ruby/bin/control stop
set -e
PUMA_PID_FILE="${OPENSHIFT_DATA_DIR}puma.pid"
PUMA_BIND_URL="tcp://${OPENSHIFT_RUBY_IP}:${OPENSHIFT_RUBY_PORT}"
PUMA_OPTS="-d --pidfile ${PUMA_PID_FILE} -e production --bind '${PUMA_BIND_URL}'"
bundle exec "puma $PUMA_OPTS"
exit 0
/.openshift/action_hooks/deploy
#!/bin/bash
if [ -f "${OPENSHIFT_DATA_DIR}puma.pid" ]; then
  echo "Stopping Puma..."
  PUMA_PID=$(cat "${OPENSHIFT_DATA_DIR}puma.pid")
  ps -p $PUMA_PID &> /dev/null
  [ "$?" == 0 ] && kill $PUMA_PID
fi

OpenShiftにpushして確認する。先程とは違いpush後の起動ログでPumaの起動メッセージがでているのが確認できる。

()
remote: Activating deployment
remote: Starting Ruby cartridge
remote: Replacing the default Passenger server with Puma
remote: Stopping Ruby cartridge
remote: Waiting for stop to finish
remote: Puma starting in single mode...
remote: * Version 2.6.0, codename: Pantsuit Party
remote: * Min threads: 0, max threads: 16
remote: * Environment: production
remote: * Listening on tcp://127.8.152.129:8080
remote: * Daemonizing...
remote: Result: success
remote: Activation status: success
remote: Deployment completed with status: success
()

もうひといきだ!さあ後はポートの設定だけだ。

websocketsのポートをOpenShiftの仕様にあわせる

OpenShiftでは外からのwebsocketのポートは8000と8443に割りあてられている(Try Out Low-Latency Connections with WebSockets参照)。前者がhttp用で後者がhttps用。

サンプルではwebsocketの通信も80と443で行うようになっているのでそこを変更する。テストなのでwsの8000ポートに固定しておく。

/views/application.js.erb
//var scheme   = "<%= @scheme %>";
var scheme   = "ws://";
//var uri      = scheme + window.document.location.host + "/";
var uri      = scheme + window.document.location.hostname + ":8000/";
var ws       = new WebSocket(uri);
ws.onmessage = function(message) {
  var data = JSON.parse(message.data);
  $("#chat-text").append("<div class='panel panel-default'><div class='panel-heading'>" + da\
ta.handle + "</div><div class='panel-body'>" + data.text + "</div></div>");
  $("#chat-text").stop().animate({
    scrollTop: $('#chat-text')[0].scrollHeight
  }, 800);
};

$("#input-form").on("submit", function(event) {
  event.preventDefault();
  var handle = $("#input-handle")[0].value;
  var text   = $("#input-text")[0].value;
  ws.send(JSON.stringify({ handle: handle, text: text }));
  $("#input-text")[0].value = "";
});

これでpushすればようやくチャットサンプルが動作する。複数ブラウザを立ちあげて1人チャットを楽しもう。おつかれさまでした。

この手順で作ったサンプルそのものを http://websocketchat-l1s.rhcloud.com/ で動かしているので、気になる方はどうぞ。しばらくは残しておくつもりですが無料枠が足りなくなったら消してしまうのであしからず。

まとめ

OpenShiftのwebsocketは一年ほど前にpreview versionとしてリリースされているが、その後のステータスがどうなっているかは確認できなかった。ただし、いろいろ調べると現状でもpreview version時の情報はそのまま生きていて、それに従うこと(+サーバ置き換えという荒技)をすればrubyでもwebsocketを楽しめる。現に、herokuのwebsocketなチャットサンプルをインターフェイスまわりだけ修正することで動作させることができた。
ポイントは、

  • Pumaを動かす
  • Portの指定に注意する

の2点。
これに気をつけていればherokuのwebsocketのコードがすこしの変更で動きそうで夢が広がる。

蛇足

そもそもこの記事を書こうとして、ブラウザ上で編集するにしても、エディタで書いた文章をブラウザにペーストするにしても、推敲や編集がやりにくくてたまらなかったので、先の投稿スクリプトを書いた。技術の力ですこし幸せになった。

参考情報

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5