目次


Node.js、Redis、Socket.io を使用して Bluemix 上で HTML5 チャット・アプリを作成する

Comments

最近まで、Web ベースのチャット・アプリケーションを作成するのは、簡単なことではありませんでした。HTTP プロトコルは Web ベースのチャットの実装を難しくします。なぜなら、HTTP プロトコルのリクエスト・レスポンス・アーキテクチャーは、チャットが持つ絶えず更新される性質にはあまり適していないからです。この記事では、こうした状況は HTML5 と WebSocket API のおかげで完全に変わったことを示すとともに、Socket.io ライブラリーを使用してリアルタイム・チャット・アプリケーションをあっという間に作成する方法を紹介します。

このアプリケーションは、アプリケーションを簡単に公開できるようにする Bluemix プラットフォームの上で実行されます。

この記事で取り上げるアプリケーションは、V8 JavaScript エンジンを使用したサーバー・サイド JavaScript ランタイムとして人気のある Node.js をサーバー・サイドで使用しています。このアプリケーションはまた、Express フレームワークと Jade テンプレート・エンジンを使用して、通常の HTTP リクエストを送信します。このアプリケーションの大変な処理のほとんどは Socket.io によって行われますが、Socket.io をサーバーとクライアントの両方に実装するのは簡単です。さらにこのアプリケーションは、最新のメッセージを Redis データ・ストアに100 個までキャッシュし、アプリケーションを簡単に公開できるようにする Bluemix プラットフォームの上で実行されます。

アプリを実行するコードを入手する

アプリケーションを作成するために必要なもの

  • HTML、CSS、JavaScript に関する基本的知識
  • Node.js
  • Redis
  • Bluemix アカウント
  • Bluemix のコマンドライン・インターフェース (cf)

: アプリケーションの完全なソース・コードは IBM DevOps Services から入手することができます。簡潔にするため、この記事には完全なソース・コードのリストを含めていません。ソース・コードをダウンロード (上掲の「コードを入手する」ボタンをクリック) すれば、そのコードを見ながら記事を読み進めることができます。

ステップ 1. アプリケーションを作成する

皆さんのコンピューターにアプリケーションのディレクトリーを作成し、package.json ファイルを追加します。

    {
      "name": "bluemixchat",
      "version": "0.0.1",
      "private": true,
      "scripts": {
        "start": "node server.js"
      },
      "dependencies": {
      }
}

必要な npm モジュールをインストールします。

  • Express
  • Jade
  • Redis
  • Socket.io

npm install --save express jade redis socket.io を実行して、上記のモジュールをインストールします。

このコマンドによって、プロジェクトの node_modules サブディレクトリーに npm モジュール (および npm モジュールが依存しているあらゆるモジュール) がインストールされ、インストールされたモジュールが package.json ファイルの依存関係として自動的に追加されます。下記に示すとおり、次のステップでは必要最小限の Express アプリケーション (server.js) を起動して、実行されている状態にします。

    // Startup Express App
    var express = require('express');
    var app = express();
    app.listen(process.env.PORT || 3000);

    // Configure Jade template engine
    var path = require('path');
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'jade');
    app.use(express.static(path.join(__dirname, 'public')));

    // handle HTTP GET request to the "/" URL
    app.get('/', function(req, res) {
      res.render('index');
    });

上記のコード・リストにおいて、アプリケーションは URL / 宛てに送信されるリクエストを処理し、index という名前のビューをレンダリングします。ここで、ビューを作成します。はじめに、views という名前のサブディレクトリーを作成し、そのサブディレクトリーにファイル index.jade を追加します。このファイルの内容は以下のコード・リストに示されています。

: このコードでは public/stylesheets サブディレクトリーからスタイルシートを読み込んで、アプリケーションのスタイルを定義しています。このスタイルシート・ファイルについては、この記事では説明しません。CSS コードを含むスタイルシート・ファイルは DevOps Services からダウンロードしたコードに入っています。

    doctype html
    html(lang='en')
      head
        meta(charset='utf-8')
        title Bluemix Chat
        meta(name='viewport', content='initial-scale=1.0, width=device-width, \
         height=device-height, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no')
        link(rel='stylesheet', href='//cdn.jsdelivr.net/normalize/3.0.1/normalize.min.css')
        link(rel='stylesheet', href='/stylesheets/style.css')    
      body
        h1 Bluemix Chat
        form
          input(id='msg', autocomplete='off', autofocus)
          button(type='submit') Send
        ul#messages

Jade テンプレート・エンジンは HTML ページを定義する簡潔な方法を提供します。このテンプレート・エンジンでは、文書の構造を判別するためにホワイト・スペースを使用していることに注意してください。スペースとタブを混在させないようにして、コードをインデントする方法について十分に注意してください。

これでアプリケーションを実行できるようになっているはずです。プロジェクト・ディレクトリーのコマンドラインで npm start を実行してください。

このコマンドはお使いのマシン上のポート 3000 で Web サーバーを実行します。実行中のアプリケーションを表示するには、ブラウザーで http://localhost:3000 を指定します。以下のスクリーン・ショットのようなウィンドウが表示されるはずです。

Bluemix チャット画面のスクリーン・キャプチャー
Bluemix チャット画面のスクリーン・キャプチャー

ステップ 2. アプリケーションを Bluemix へプッシュする

アプリケーションを Bluemix へデプロイするには、コマンドライン・インターフェースを使用したツールである cf を使用します。このツールがない場合は、Cloud Foundry プロジェクトの GitHub ページにアクセスして、インストール方法に関する情報を調べてください。cf ツールを使用せずに、Bluemix ダッシュボードからアプリケーションやサービスを作成することもできますが、アプリケーションのデプロイには cf ツールが必要です。そのため、この記事ではすべての作業に cf ツールを使用しています。

Bluemix API に接続し、Bluemix アカウントを使用してログインするには、以下のコマンドを実行してください。

    cf api https://api.bluemix.net
    cf login

2 番目のコマンドを入力した後に、e-メール・アドレスとパスワードを入力します。

: Bluemix にデプロイする際に、アプリケーションの一意の名前とホスト名を入力します。この例で示されている名前を使用すると、エラーになります。

cf push bluemixchat を使用して、このアプリケーションを Bluemix にデプロイできるようになりました。

アプリケーションがデプロイされると、アプリケーションが起動されたことを示すメッセージが表示され、アプリケーションに URL が渡されます。アプリケーションが動作していることをテストするには、ブラウザーで http://bluemixchat.mybluemix.net を指定します。次のステップでは、Socket.io を使用して、リアルタイムのメッセージングを有効にします。

ステップ 3. Socket.io を使用して、リアルタイムでクライアントを更新する

Socket.io は、サーバー上およびクライアント上で HTML5 WebSockets を使用するアプリケーションをとても簡単に構築できるようにする npm モジュールです。Socket.io を Node.js アプリケーションにバインドすると、この Node.js アプリケーションは、サーバーに対して新しいメッセージをプッシュしたり、サーバーからの新しいメッセージをリッスンしたりするために使用できる、クライアント・サイドの JavaScript ライブラリーを自動的に URL /socket.io/socket.io.js で提供するようになります。

Socket.io サーバーを Express アプリケーションに追加するには、server.js ファイルの中で app.listen(process.env.PORT || 3000); という行を見つけて、以下のコードに置き換えます。

    var http = require('http').Server(app);
    var io = require('socket.io')(http);
    http.listen(process.env.PORT || 3000);

このコードでは、HTTP モジュールを読み込んで、そのモジュールに Socket.io サーバーをバインドします。そして、ポート 3000 または現在のプロセスの PORT 環境変数で定義されているポートで、受信されるリクエストをリッスンします。アプリケーションが Bluemix にデプロイされると、どのポートをリッスンするかが PORT 設定によって定義されます。

次に、チャット・アプリケーションで各種イベントを処理するコードと、接続されているクライアントに対して Socket.io を使用してメッセージを送信するコードを追加します。そのために、server.js ファイルの末尾に以下のコードを追加します。

    // socket.io listen for messages
    io.on('connection', function(socket) {  
      // When a message is received, broadcast it
      // to all users except the originating client
      socket.on('msg', function(data) {        
        socket.broadcast.emit('msg', data);        
      });

      // When a user joins the chat, send a notice
      // to all users except the originating client
      socket.on('join', function(nickname) {
        // Attach the user's nickname to the socket
        socket.nickname = nickname;
        socket.broadcast.emit('notice', nickname + ' has joined the chat.');
      });

      // When a user disconnects, send a notice
      // to all users except the originating client
      socket.on('disconnect', function() {
        socket.broadcast.emit('notice', socket.nickname + ' has left the chat.');
      });
    });

これでサーバー・サイドのコードが完成したので、クライアント・サイドに Socket.io を扱うコードを追加します。ファイル views/index.jade の末尾に以下のコードを追加します。このコードは Jade テンプレートの body 要素内に挿入する必要があることに注意してください。挿入されていない場合、問題が発生する可能性があります。

    script(src='//cdn.jsdelivr.net/jquery/2.1.1/jquery.min.js')
    script(src='/socket.io/socket.io.js')
    script(src='/javascripts/client.js')

このコードでは、CDN (Content Delivery Network) から jQuery を読み込んだ後、サーバー上に公開されている標準的な場所から Socket.io を読み込み、最後にアプリケーションの public/javascripts ディレクトリーに格納する client.js ファイルを読み込んでいます。ここで、以下に示す内容のファイルを作成します。

    $(document).ready(function() {
      var socket = io(), nickname, msgList = $('#messages');

      // Check if nickname stored in localStorage
      if('localStorage' in window && localStorage.getItem('nickname')) {
        nickname = localStorage.getItem('nickname');
      } else {
        // If not in localStorage, prompt user for nickname
        nickname = prompt('Please enter your nickname');
        if('localStorage' in window) {
          localStorage.setItem('nickname', nickname);
        }
      }  

      // Send message to server that user has joined
      socket.emit('join', nickname);

      // Function to add a message to the page
      var newMessage = function(data) {
        var who = $('<div class="who">').text(data.nickname),
            when = $('<div class="when">').text(new Date().toString().substr(0, 24)),
            msg = $('<div class="msg">').text(data.msg),
            header = $('<div class="header clearfix">').append(who).append(when),
            li = $('<li>').append(header).append(msg);    

        msgList.prepend(li);
      };

      // Handle the form to submit a new message
      $('form').submit(function(e) {
        var msgField = $('#msg'),        
            data = { msg: msgField.val(), nickname: nickname, when: new Date() };

        e.preventDefault();
        // Send message to Socket.io server
        socket.emit('msg', data);
        // Add message to the page
        newMessage(data);
        // Clear the message field
        msgField.val('');    
      });  

      // When a message is received from the server
      // add it to the page using newMessage()
      socket.on('msg', function(data) { newMessage(data); });

      // When a notice is received from the server
      // (user joins or disconnects), add it to the page
      socket.on('notice', function(msg) {
        msgList.prepend($('<div class="notice">').text(msg));
      });
    });

このコードでは、ブラウザーの localStorage ストアを検索することで、ユーザーがすでにニックネームを入力したかどうかをチェックし、ニックネームが入力されていない場合は、ユーザーに入力を促すプロンプトを表示します。ニックネームは、後で使用するために localStorage に保管します。ページがロードされると、メッセージが Socket.io に送信されて、新しいユーザーが加わったことが明らかに示されます。メッセージ・フォームが送信されると、アプリケーションは現在開いているページにメッセージを追加して、そのメッセージをサーバーに送信します。最後のステップで、アプリケーションは 2 つの Socket.io イベント (msgnotice) をリッスンし、これらのイベントでデータを受信したときは、ページを更新します。

アプリケーションを再度実行してメッセージを入力し、それらのメッセージが画面に表示されることを確認します。その方法としては、別のブラウザーを起動すれば、一方のブラウザーでアプリケーションに接続してメッセージを送信し、もう一方のブラウザーでそれらのメッセージを確認することができます。いくつかのブラウザーまたは端末で試してみてください ― 正常に動作するチャット・アプリケーションが作成されました。

次のステップでは、Redis データ・ストアを使用してこれらのメッセージを保管する方法を説明します。

ステップ 4. メッセージ履歴を Redis に保管する

現状のままアプリケーションを使用すると、ページを最新の表示に更新したときにすべてのチャット履歴が消えることに注目してください。モバイル端末でアプリケーションを使用している場合、チャット履歴の消失はさらに問題となります。なぜなら、モバイル・ユーザーはチャットなどのアプリケーションを途中で中断したりしながら使用するからです。端末がロックされた後、再度ブラウザーを開いた際にページが最新の表示に更新される可能性があり、その場合履歴が消えてしまいます。

これを解決するために、チャット履歴をデータ・ストアに保管します。Redis ストアは、キーと値のペア、リストなどのデータを簡単に保管できるようにします。Redis では、データをディスクにではなく、メモリーに保管するため、通常のデータベースよりも格段に高速です。データをキャッシュするのに最適な Redis は、チャット・アプリケーションのメッセージ履歴を保管するのにも非常に適しています。

Redis がローカル・マシン上で実行されていることを確認してください (通常は、コマンド redis-server を実行して Redis を起動します)。次に、ファイル server.js で http.listen(process.env.PORT || 3000); という行を検索してください。この行の下に以下のコードを挿入します。

    // Configure Redis client connection
    var redis = require('redis');
    var credentials;
    // Check if we are in Bluemix or localhost
    if(process.env.VCAP_SERVICES) {
      // On Bluemix read connection settings from
      // VCAP_SERVICES environment variable
      var env = JSON.parse(process.env.VCAP_SERVICES);
      credentials = env['redis-2.6'][0]['credentials'];
    } else {
      // On localhost just hardcode the connection details
      credentials = { "host": "127.0.0.1", "port": 6379 }
    }
    // Connect to Redis
    var redisClient = redis.createClient(credentials.port, credentials.host);
    if('password' in credentials) {
      // On Bluemix we need to authenticate against Redis
      redisClient.auth(credentials.password);
    }

: 後で Bluemix にアプリケーションをデプロイする際に、アプリケーションは VCAP_SERVICES 環境変数から Redis の接続設定を読み込むことになります。このコードによって、Redis サーバーへの接続と認証が行われます。

新しいメッセージを受信したときにそれらのメッセージが Redis にプッシュされるように、サーバーのアプリケーションを更新します。それにはまず、以下のブロックを探します。

    socket.on('msg', function(data) {        
      socket.broadcast.emit('msg', data);        
    });

無名関数の最初に、以下の行を追加します。

    redisClient.lpush('messages', JSON.stringify(data));
    redisClient.ltrim('messages', 0, 99);

このコードでは、最新のメッセージを Redis の messages リストにプッシュした後、リストを最新の 100 アイテムのみにします (100 アイテムを超える場合)。チャット履歴が消える問題を解決するために最後に行うことは、クライアントが起動されたときに Redis 内の既存メッセージを検索することです。それにはまず、以下のブロックを探します。

    app.get('/', function(req, res) {
      res.render('index');
    });

このブロックを以下のコードに置き換えます。

    app.get('/', function(req, res) {
      // Get the 100 most recent messages from Redis
      var messages = redisClient.lrange('messages', 0, 99, function(err, reply) {
        if(!err) {        
          var result = [];
          // Loop through the list, parsing each item into an object
          for(var msg in reply) result.push(JSON.parse(reply[msg]));
          // Pass the message list to the view
          res.render('index', { messages: result });    
        } else res.render('index');
      });    
    });

このコードでは、Redis の messages リスト内にある最新の 100 アイテムを取得し、それらのアイテムをループ処理して JSON として構文解析し、JavaScript オブジェクトに代入します。次にこれらのオブジェクトの配列をビューに渡します。ファイル views/index.jade を開き、以下のコードを ul#messages 要素の中にネストします。

    - for(var i=0,ln=messages.length;i<ln;i++)      
      li
        .header.clearfix
          .who= messages[i].nickname
          .when= new Date(messages[i].when).toString().substr(0, 24)
        .msg= messages[i].msg

アプリケーションを再度起動し、いくつかのメッセージを送信します。ページを再度読み込んだときに、メッセージ履歴が復元されていることに注目してください。現在、アプリケーションはユーザーの join 通知や disconnect 通知を保管しませんが、これらを保管するように機能強化するのは簡単です。最後のステップとして、Redis を Bluemix 上でセットアップし、最終成果物を公開します。

ステップ 5. Bluemix 上で Redis サービスを作成し、Redis サービスにバインドする

最新の変更を Bluemix へプッシュするには、Redis サービス・インスタンスをセットアップし、アプリケーションにバインドします。サービスを作成するには、cf create-service redis 100 bmcredis を実行します。

データベース名 (前述のコマンドの bmcredis) を一意の名前に変更します。アプリケーションがデータベースに接続できるようにするには、cf bind-service bluemixchat bmcredis というコマンドを実行して、アプリケーションをバインドする必要があります。

バインドが有効になるように、アプリケーションを再起動します。cf push を実行して変更を Bluemix にプッシュします。

おめでとうございます! Node.js、Redis、Socket.io を使用してチャット・アプリケーションが作成され、Bluemix へデプロイされました。アプリケーションが実行されているのを確認することができます。

Bluemix のチャットが実行されていることを示す各画面のスクリーン・ショット
Bluemix のチャットが実行されていることを示す各画面のスクリーン・ショット

まとめ

作成したアプリケーションは完全に機能するチャット・アプリケーションですが、このアプリケーションを拡張する方法は数多くあります。ニックネームに変更を加えられるようにしたり、他に接続されているユーザーのリストを見られるようにしたりすることもできます。複数のルームや、プライベート・メッセージ、その他のサポートを追加することができます。このアプリケーションは Bluemix 上にデプロイされているため、追加した機能はすべて、単純な cf push コマンドでデプロイすることができます。これ以上簡単なことはありません。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Cloud computing
ArticleID=982640
ArticleTitle=Node.js、Redis、Socket.io を使用して Bluemix 上で HTML5 チャット・アプリを作成する
publish-date=09182014