Skip Navigation
Show nav
Dev Center
  • Get Started
  • ドキュメント
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
    • .NET
  • ドキュメント
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
View categories

Categories

  • Heroku のアーキテクチャ
    • Dyno (アプリコンテナ)
      • Dyno Management
      • Dyno Concepts
      • Dyno Behavior
      • Dyno Reference
      • Dyno Troubleshooting
    • スタック (オペレーティングシステムイメージ)
    • ネットワーキングと DNS
    • プラットフォームポリシー
    • プラットフォームの原則
  • Developer Tools
    • コマンドライン
    • Heroku VS Code Extension
  • デプロイ
    • Git を使用したデプロイ
    • Docker によるデプロイ
    • デプロイ統合
  • 継続的デリバリーとインテグレーション
    • 継続的統合
  • 言語サポート
    • Node.js
      • Working with Node.js
      • Node.js Behavior in Heroku
      • Troubleshooting Node.js Apps
    • Ruby
      • Rails のサポート
      • Bundler の使用
      • Working with Ruby
      • Ruby Behavior in Heroku
      • Troubleshooting Ruby Apps
    • Python
      • Working with Python
      • Python でのバックグランドジョブ
      • Python Behavior in Heroku
      • Django の使用
    • Java
      • Java Behavior in Heroku
      • Working with Java
      • Maven の使用
      • Spring Boot の使用
      • Troubleshooting Java Apps
    • PHP
      • PHP Behavior in Heroku
      • Working with PHP
    • Go
      • Go の依存関係管理
    • Scala
    • Clojure
    • .NET
      • Working with .NET
  • データベースとデータ管理
    • Heroku Postgres
      • Postgres の基礎
      • Postgres スターターガイド
      • Postgres のパフォーマンス
      • Postgres のデータ転送と保持
      • Postgres の可用性
      • Postgres の特別なトピック
      • Migrating to Heroku Postgres
    • Heroku Data For Redis
    • Apache Kafka on Heroku
    • その他のデータストア
  • AI
    • Working with AI
    • Heroku Inference
      • Inference API
      • Quick Start Guides
      • AI Models
      • Inference Essentials
    • Vector Database
    • Model Context Protocol
  • モニタリングとメトリクス
    • ログ記録
  • アプリのパフォーマンス
  • アドオン
    • すべてのアドオン
  • 共同作業
  • セキュリティ
    • アプリのセキュリティ
    • ID と認証
      • シングルサインオン (SSO)
    • Private Space
      • インフラストラクチャネットワーキング
    • コンプライアンス
  • Heroku Enterprise
    • Enterprise Accounts
    • Enterprise Team
    • Heroku Connect (Salesforce 同期)
      • Heroku Connect の管理
      • Heroku Connect のリファレンス
      • Heroku Connect のトラブルシューティング
  • パターンとベストプラクティス
  • Heroku の拡張
    • Platform API
    • アプリの Webhook
    • Heroku Labs
    • アドオンのビルド
      • アドオン開発のタスク
      • アドオン API
      • アドオンのガイドラインと要件
    • CLI プラグインのビルド
    • 開発ビルドパック
    • Dev Center
  • アカウントと請求
  • トラブルシューティングとサポート
  • Salesforce とのインテグレーション
  • 言語サポート
  • Ruby
  • Working with Ruby
  • Ruby で Heroku の WebSocket を使用する

Ruby で Heroku の WebSocket を使用する

日本語 — Switch to English

この記事の英語版に更新があります。ご覧の翻訳には含まれていない変更点があるかもしれません。

最終更新日 2023年09月29日(金)

Table of Contents

  • 前提条件
  • WebSocket アプリの作成
  • 機能
  • ローカル実行
  • デプロイ
  • スケーリング
  • セキュリティ
  • Rails での使用

このクイックスタートでは、Heroku にデプロイされた WebSocket を使用する Sinatra アプリケーションについて説明します。

デモアプリケーション​のサンプルコードは GitHub で入手できます。編集や機能強化を歓迎します。単にリポジトリをフォークし、変更を加えて、プルリクエストを送信してください。

前提条件

  • Heroku ユーザーアカウント。無料ですぐにサインアップできます​。
  • Ruby 2.1.2 以降および Heroku CLI​ (基本的な Ruby スターターガイド)​で説明しているとおり)
  • Rack ミドルウェア​に関する実践的な知識

WebSocket アプリの作成

このサンプルアプリケーションは、Ruby で WebSocket を使用する単純な例を提供します。サンプルを複製し、読みながらコードを追っていくことができます。

オプション 1。サンプルアプリを複製する

$ git clone git@github.com:heroku-examples/ruby-websockets-chat-demo.git
Cloning into 'ruby-websockets-chat-demo'...
remote: Counting objects: 31, done.
remote: Compressing objects: 100% (24/24), done.
remote: Total 31 (delta 0), reused 31 (delta 0)
Receiving objects: 100% (31/31), 38.33 KiB | 0 bytes/s, done.

オプション 2。新しいアプリを作成する

$ mkdir ruby-websockets-chat-demo
$ cd ruby-websockets-chat-demo

機能

このデモアプリは、バックエンドへの WebSocket を開く単純なチャットアプリケーションです。ブラウザからチャットメッセージが送信されると常に、そのメッセージはサーバーに送信された後、接続している各クライアントにブロードキャストされ、ページに表示されます。

この実装にとって重要なものがいくつかあります。ここでは、標準の WebSocket API​ を提供する Faye の WebSocket 実装​を使用します。これにより、WebSocket に応答する Rack ミドルウェアをビルドできるようになります。

ブラウザ上の JavaScript は、サーバーへの WebSocket 接続を開き、サーバーから受信された WebSocket メッセージに応答してチャットメッセージを表示します。では、バックエンドとフロントエンドの両方をさらに詳細に見ていきましょう。

バックエンド

サンプルアプリでは、ChatBackend​ という名前の WebSocket ロジックをカプセル化する Rack ミドルウェア​を作成します。組織の目的のために、すべての Rack ミドルウェアを middlewares​ ディレクトリに配置します。

ChatBackend​ ミドルウェアについて見ていきましょう。Web アプリに接続しているすべてのクライアントを追跡する必要があるため、基本的なボイラープレートコードを使用して @clients​ 配列を設定しましょう。

# middlewares/chat_backend.rb
require 'faye/websocket'

module ChatDemo
  class ChatBackend
    KEEPALIVE_TIME = 15 # in seconds

    def initialize(app)
      @app     = app
      @clients = []
    end

    def call(env)
    end
  end
end

call​ メソッド内の Faye::Websocket​ では、env​ を検査することで、受信リクエストが WebSocket リクエストであるかどうかを検出できます。そうであれば、WebSocket ロジックを実行しましょう。そうでない場合は、スタックの残りの部分を実行する必要があります (この場合、これは Sinatra アプリ)。

# middlewares/chat_backend.rb
def call(env)
  if Faye::WebSocket.websocket?(env)
    # WebSockets logic goes here

    # Return async Rack response
    ws.rack_response
  else
    @app.call(env)
  end
end

ここで、WebSocket コードを追加しましょう。まず、env​ に基づいて新しい WebSocket オブジェクトを作成する必要があります。これは Faye::WebSocket​ イニシャライザを使用して行うことができます。オプションハッシュでは、X 秒ごとにアクティブな各接続に ping を送信する ping​ オプションを指定できます。この例では、以前に 15 秒に設定した KEEPALIVE_TIME​ 定数を使用します。これを行うのは、接続がアイドル状態になってから 55 秒が経過すると Heroku によって接続が終了される​ためです。

ミドルウェアの call​ メソッドで ping​ オプションを使用して WebSocket を初期化します。

# middlewares/chat_backend.rb#call
ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME })

このアプリにとって重要な 3 つの WebSocket イベントは open​、message​、close​ です。それぞれのイベントで、WebSocket のライフサイクルを確認するために、デモの目的で STDOUT​ にメッセージを出力します。

open

open​ は、サーバーへの新しい接続が発生すると呼び出されます。open​に関しては、クライアントが接続したことを、以前に設定した @clients​ 配列に格納するだけです。

# middlewares/chat_backend.rb#call
ws.on :open do |event|
  p [:open, ws.object_id]
  @clients << ws
end

message

message​ は、サーバーで WebSocket メッセージを受信すると呼び出されます。message​に関しては、接続された各クライアントにメッセージをブロードキャストする必要があります。渡される event​ オブジェクトには data​ 属性があり、これがメッセージです。

# middlewares/chat_backend.rb#call
ws.on :message do |event|
  p [:message, event.data]
  @clients.each {|client| client.send(event.data) }
end

close

close​ は、クライアントが接続を閉じると呼び出されます。close​に関しては、クライアントのストアからクライアントを削除することによってクリーンアップする必要があるだけです。

# middlewarces/chat_backend.rb#call
ws.on :close do |event|
  p [:close, ws.object_id, event.code, event.reason]
  @clients.delete(ws)
  ws = nil
end

フロントエンド

この記事の 2 番目の部分は、サーバーとの WebSocket 接続を実際に開くようにクライアント側を設定することです。まず、使用するページをレンダリングするための Sinatra アプリのセットアップを完了する必要があります。

ビュー

Sinatra アプリでは、インデックスビューをレンダリングする必要があるだけです。これには、組み込みの ERB​ を使用します。

# app.rb
require 'sinatra/base'

module ChatDemo
  class App < Sinatra::Base
    get "/" do
      erb :"index.html"
    end
  end
end

Sinatra では、そのビューは views​ ディレクトリに格納されます。基本的なチャットフォームを備えた index.html.erb​ を作成します。

メッセージの受信

ここで、WebSocket に戻ります。メインページによってロードされ、バックエンドへの WebSocket 接続を確立する public/assets/js/application.js​ を記述しましょう。

var scheme   = "ws://";
var uri      = scheme + window.document.location.host + "/";
var ws       = new WebSocket(uri);

WebSocket を開くと、ブラウザはメッセージを受信します。これらのメッセージは、handle​ (ユーザーのハンドル) と text​ (ユーザーのメッセージ) の 2 つのキーを含む JSON 応答として構造化されます。メッセージが受信されたら、そのメッセージを新しいエントリとしてページに挿入する必要があります。

// public/assets/js/application.js
ws.onmessage = function(message) {
  var data = JSON.parse(message.data);
  $("#chat-text").append("<div class='panel panel-default'><div class='panel-heading'>" + data.handle + "</div><div class='panel-body'>" + data.text + "</div></div>");
  $("#chat-text").stop().animate({
    scrollTop: $('#chat-text')[0].scrollHeight
  }, 800);
};

メッセージを送信する

これでページにサーバーからのメッセージを受信できるようになったので、次はメッセージを実際に送信する方法が必要です。フォームからこの値を取得し、JSON メッセージとして WebSocket 経由でサーバーに送信するように、フォーム送信ボタンを上書きします。

// public/assets/js/application.js
$("#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 = "";
});

これで、最終的な application.js​ ファイルにより、完全な送受信機能が定義​されます。

ローカル実行

アプリケーションを接続するために、Rack::Builder​ をセットアップする config.ru​ を使用します。これは、アプリケーションをロードする方法を Rack およびサーバーに指示します。セットアップするのは WebSocket ミドルウェアなので、リクエストはまずこれを通過します。

# config.ru
require './app'
require './middlewares/chat_backend'

use ChatDemo::ChatBackend

run ChatDemo::App

依存関係をダウンロードおよび取得する必要があるため、Gemfile​ を設定する必要があります。

# Gemfile
source "https://rubygems.org"

ruby "2.0.0"

gem "faye-websocket"
gem "sinatra"
gem "puma"

そして、すべての依存関係をインストールします。

$ bundle install

ここで Procfile​ を設定するので、Web サービスを実行する方法を文書化しました。faye-websocket​でサポートされている Web サーバーの 1 つ​である puma Web サーバー​を使用します。

web: bundle exec puma -p $PORT

アプリをローカルで実行します。

$ heroku local --port 5001
Puma starting in single mode...
* Version 2.6.0, codename: Pantsuit Party
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:5001
Use Ctrl-C to stop

http://localhost:5001​ でアプリを開き、チャットメッセージを入力します。サーバー出力は次のようになります。

127.0.0.1 - - [03/Oct/2013 17:16:25] "GET / HTTP/1.1" 200 1430 0.0115
127.0.0.1 - - [03/Oct/2013 17:16:25] "GET /assets/css/application.css HTTP/1.1" 304 - 0.0164
127.0.0.1 - - [03/Oct/2013 17:16:25] "GET /assets/css/bootstrap.min.css HTTP/1.1" 304 - 0.0046
127.0.0.1 - - [03/Oct/2013 17:16:25] "GET /assets/js/jquery-2.0.3.min.js HTTP/1.1" 304 - 0.0007
127.0.0.1 - - [03/Oct/2013 17:16:25] "GET /assets/js/application.js HTTP/1.1" 200 716 0.0063
127.0.0.1 - - [03/Oct/2013 17:16:25] "GET / HTTP/1.1" HIJACKED -1 0.0059
[:open, 7612540]
127.0.0.1 - - [03/Oct/2013 17:16:25] "GET /favicon.ico HTTP/1.1" 404 490 0.0012
[:message, "{\"handle\":\"hone\",\"text\":\"test\"}"]

デプロイ

アプリをローカルで実行した後は、Heroku にデプロイします。まだ実行していない場合は、そのアプリケーションを Git リポジトリに配置します。

$ git init
$ git add .
$ git commit -m "Ready to deploy"

Heroku にデプロイするアプリを作成します。

$ heroku create
Creating limitless-ocean-5045... done, stack is heroku-18
http://limitless-ocean-5045.herokuapp.com/ | git@heroku.com:limitless-ocean-5045.git
Git remote heroku added

git push​ を使用してアプリを Heroku にデプロイします。

$ git push heroku master
Counting objects: 9, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 557 bytes, done.
Total 5 (delta 3), reused 0 (delta 0)

-----> Ruby/Rack app detected
-----> Using Ruby version: ruby-2.1.2
-----> Installing dependencies using Bundler version 1.6.3
       Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin --deployment
       Installing eventmachine (1.0.3)
       Installing websocket-driver (0.3.0)
       Installing faye-websocket (0.7.0)
       Installing rack (1.5.2)
       Installing puma (2.6.0)
       Installing rack-protection (1.5.0)
       Installing redis (3.0.4)
       Installing tilt (1.4.1)
       Installing sinatra (1.4.3)
       Installing bundler (1.3.2)
       Your bundle is complete! It was installed into ./vendor/bundle
       Cleaning up the bundler cache.
-----> Discovering process types
       Procfile declares types     -> web
       Default types for Ruby/Rack -> console, rake

-----> Compiled slug size: 26.8MB
-----> Launching... done, v3
       http://limitless-ocean-5045.herokuapp.com deployed to Heroku

以上で、Web アプリが Heroku で稼働するようになりました。heroku open​ を使用してブラウザでアプリを開きます。

スケーリング

これでアプリをデプロイし、基本中の基本を理解できたので、アプリをもう少し堅牢になるように拡張したいと思います。これには、公開する前にやっておきたいことのすべては含まれていませんが、正しい判断を行うという方向性は間違っていません。

現時点では、アプリを複数の dyno にスケーリングしても、全員にすべてのメッセージが届くわけではありません。現在のアプリのステートレスな性質のため、1 番目の dyno に接続されたクライアントには、2 番目の dyno に接続されたクライアントから送信されたメッセージは届きません。チャットの例では、メッセージの状態を Redis などのグローバルストレージシステムに格納することによってこれを解決できます。これにより、Redis に接続されたすべての dyno にメッセージを送信できるようになります。WebSocket アプリケーションアーキテクチャに関するセクション​で詳しく説明しています。

Redis アドオン​を追加します。

$ heroku addons:create rediscloud:20
Adding rediscloud:20 on limitless-ocean-5045... v4 (free)

ローカルで Redis がセットアップされている​ことを確認します。redis​gem を使用して、Ruby アプリから Redis とインターフェース接続します。前述したように、Gemfile​ を編集してアプリの依存関係を設定する必要があります。次の行を Gemfile​ の末尾に追加します。

gem 'redis'

依存関係をインストールします。

$ bundle install

Redis の pubsub システム​を使用するように各 dyno を設定する必要があります。すべての dyno が同じチャネル chat-demo​ にサブスクライブしてメッセージを待機します。各サーバーでメッセージを取得したら、接続されたクライアントにそのメッセージを発行できます。

require 'redis'

module ChatDemo
  class ChatBackend
    ...
    CHANNEL        = "chat-demo"

    def initialize(app)
      ...
      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

サブスクライブはブロック関数であり、実行フローを停止してメッセージを待機するため、別のスレッドでサブスクライブを実行する必要があります。さらに、接続に対してサブスクライブコマンドを実行すると、その接続ではサブスクライブ解除またはメッセージの受信しかできなくなるため、2 番目の Redis 接続が必要です。

ここでは、サーバーでメッセージを受信したら、ブラウザに送信する代わりに Redis を使用してチャネルに発行します。このようにすると、すべての dyno にその通知が届くため、すべてのクライアントでメッセージを受信できます。middlewares/chat_backend#call​を変更します。

ws.on :message do |event|
  p [:message, event.data]
  @redis.publish(CHANNEL, event.data)
end

以上の手順はすべて、このコミット​で確認できます。

セキュリティ

この時点で、アプリケーションは公開されており、多くの攻撃に対して脆弱です。WebSocket アプリケーションのセキュリティ保護のガイドライン全般については、「WebSocket のセキュリティ​」を参照してください。github​ のサンプルアプリでは、WSS​ をセットアップし、入力をサニタイズ​します。

Rails での使用

このサンプルアプリでは、Sinatra アプリと並行して Rack ミドルウェアを作成しました。Rails アプリで同じミドルウェアを使用するのは簡単です。

既存の ChatBackend​ ミドルウェアを Rails プロジェクトの app/middleware/chat_backend.rb​ にコピーします。次に、このミドルウェアをスタックに挿入します。これは config/application.rb​ で定義されます。

require 'chat_backend'
config.middleware.use ChatDemo::ChatBackend

Rails アプリ内に埋め込まれたカスタムのチャットミドルウェアによって WebSocket リクエストが処理されるようになります。

関連カテゴリー

  • Working with Ruby
Windows で生成された Ruby プロジェクトのデプロイ Ruby バージョンの指定

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure
  • .NET

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing
  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Github
  • LinkedIn
  • © 2025 Salesforce, Inc. All rights reserved. Various trademarks held by their respective owners. Salesforce Tower, 415 Mission Street, 3rd Floor, San Francisco, CA 94105, United States
  • heroku.com
  • Legal
  • Terms of Service
  • Privacy Information
  • Responsible Disclosure
  • Trust
  • Contact
  • Cookie Preferences
  • Your Privacy Choices