リアルタイムなWebアプリを実現する4の方法

こんにちはTakahiRoyteです!今回はWebアプリにてリアルタイムを実現する以下の方法4つについてコード例を交えて説明していきます。

  • AJAXポーリング
  • AJAXロングポーリング
  • Server Sent Events
  • WebSocket

サーバーはexpress、クライアントからのリクエストはaxiosやJavaScriptの標準APIを利用しています。
WebSocket版のみHerokuにデモを公開しているので是非試してみてください!複数デバイスや複数タブでリアルタイム通信してるのが確認できます。(100コンボすると何かが起きるかも……?)

■WebSocketデモ

AJAXポーリング

realtime01.png

一般的なポーリングと同じで、定期的にWebAPIに対しリクエストを投げてその結果を取得します。定期的な間隔で取得するので、厳密にはリアルタイムとは言えませんが、ポーリングの間隔でクライアントとサーバーそれぞれがデータを送り合うことができます。

サーバー側

サーバーサイドは特筆することはない普通のREST APIです。
下の例で言えばgetLatestData()が新しいデータを取得するメソッドであればOK。

const express = require('express')
const app = express()

app.get('/api', (req, res) => {
// 最新データを取得
const data = getLatestData()
res.send(data)
})
app.listen(3000)

クライアント側

クライアント側もsetIntervalでAPIを定期で叩くだけです。
コード例では1秒周期でAPI叩きに行きます。

// 1秒間隔で sendRequest を実行する
const intervalId = setInterval(await sendRequest, 1000)

async function sendRequest() {
const response = await axios.get('http://api-url.com/api')
doAnythingWithData(response)
}

AJAXロングポーリング

realtime02.png

ロングポーリングはクライアントのリクエストのタイムアウト時間を長く設定し、その間にサーバー側で更新があればそのリクエストに対してレスポンスを返す仕組みです。レスポンスが返り次第、再度リクエストを投げることでリアルタイム性を確保しています。こちらもポーリングの間隔で双方向にデータの送受信をしています。

サーバー側

基本ポーリングと変わりません。getLatestData()メソッドの中でデータを返すタイミングをコントロールする感じです。

const express = require('express')
const app = express()

app.get('/api', async(req, res) => {
// 最新データを取得
const data = await getLatestData()
res.send(data)
})
app.listen(3000)

クライアント側

axiosの設定でtimeoutをなが~く設定しています。
レスポンスが返ってきたら、再度メソッドを実行し直す仕組みです。

async function sendRequest() {
// タイムアウトする時間を長くする
const response = await axios.get('http://api-url.com/api', {timeout: 99999999})
doAnythingWithData(response)

// 再度自身を呼び出し
sendRequest()
}
sendRequest()

Server-Sent Events

realtime03.png

Server-sent Events(SSE)ではクライアントとサーバーでコネクションを張り、レスポンスを閉じずstreamとしてサーバーからデータを返す形式です。SSEではサーバーからクライアントへの一方向のデータ送信のみ可能です。

サーバー側

expressのSSE用ミドルウェアがあるのでそれを利用します。

const express = require('express')
const sseMiddleware = require('express-sse-middleware')
const app = express()

app.use(sseMiddleware)
app.get('/path', (req, res) => {
const sendMessage = res.sse()
const data = getLatestData()
setInterval(() => {
sendMessage(data)
}, 1000) // 1秒おきに数値を送信する
})
app.listen(3000)

クライアント側

クライアントは標準APIのEventSourceを使います。
メッセージが来るたびに処理が実行されます。

const eventSource = new EventSource('http://api-url.com/api')
eventSource.onmessage = (event) => {
// event.data で送られてきたデータにアクセスできる
}

WebSocket

realtime04.png

WebSocketは双方向性リアルタイムを実現するために作られたAPIです。サーバーからのイベントもクライアントからのイベントもリアルタイムで処理することが出来ます。以下のデモでは複数ユーザー間でのリアルタイムな通信を実現しています。WebSocketはHTTPベースのWSというプロトコルで通信をするのですが、HTTP通信のように毎回ヘッダ込で通信する必要がないのでオーバーヘッド≒通信量が少ないです。さすが専用API!

■WebSocketデモ

サーバー側

expressとWebSocketライブラリwsを組み合わせて使います。

const express = require('express')
const SocketServer = require('ws').Server
const app = express().listen(3000)
const wss = new SocketServer({ app })

wss.on('connection', (ws) => {
// メッセージ受信時、クライアントに送信する
ws.on('message', (message) => {
wss.clients.forEach((client) => {
client.send(message)
})
})

ws.on('close', () => {
// クライアント接続終了時の処理
})
})

クライアント側

クライアントでは標準APIのWebSocketを使います。
双方向なのでsendMessage()にあるようにクライアントからサーバーへデータを送れます。

const webSocket = new WebSocket('ws://api-url.com/api')
webSocket.onmessage = (event) => {
// event.data で送られてきたデータにアクセスできる
}

function sendMessage(message) {
// サーバーにデータを送る時はsendを使う
webSocket.send(message)
}

コードを見て分かる通り、クライアントからサーバーに送信 ⇒ サーバーが全クライアントに送信 という手順を取ります。クライアントからクライアントへの通信は基本的に対応していないので、そこは注意が必要です。

まとめ

いかがでしたでしょうか。同じWebアプリで同じようなリアルタイム性を実現するにしても色々な方法があります。ぜひ目的に合った実装方法でリアルタイム性を実現してみてくださいね。

伊藤 貴洋 について

フルスタックエンジニア目指して勉強中。アジャイル開発の推進も担当しています。