序章
サーバー送信イベント(SSE)は、HTTPに基づくテクノロジーです。 クライアント側では、と呼ばれるAPIを提供します EventSource
(HTML5標準の一部)サーバーに接続してサーバーから更新を受信できるようにします。
サーバー送信イベントを使用することを決定する前に、2つの非常に重要な側面を考慮する必要があります。
- サーバーからのデータ受信のみを許可します(単方向)
- イベントはUTF-8に制限されています(バイナリデータなし)
プロジェクトが株価や進行中の何かに関するテキスト情報などのみを受け取る場合は、WebSocketsのような代替手段の代わりにサーバー送信イベントを使用する候補です。
この記事では、サーバーからクライアントに流れるリアルタイムの情報を処理するために、バックエンドとフロントエンドの両方の完全なソリューションを構築します。 サーバーは、接続されているすべてのクライアントに新しい更新をディスパッチする役割を果たし、Webアプリはサーバーに接続し、これらの更新を受信して表示します。
前提条件
このチュートリアルを実行するには、次のものが必要です。
- Node.jsのローカル開発環境。 Node.jsをインストールしてローカル開発環境を作成する方法に従ってください。
- Expressに精通していること。
- React(およびフック)に精通していること。
- cURL は、エンドポイントを検証するために使用されます。 これは、ご使用の環境ですでに使用可能であるか、インストールする必要がある場合があります。 コマンドラインツールとオプションの使用にある程度精通していることも役立ちます。
このチュートリアルは、cURL v7.64.1、ノードv15.3.0、 npm
v7.4.0、 express
v4.17.1、 body-parser
v1.19.0、 cors
v2.8.5、および react
v17.0.1。
ステップ1-SSEExpressバックエンドの構築
このセクションでは、新しいプロジェクトディレクトリを作成します。 プロジェクトディレクトリ内には、サーバーのサブディレクトリがあります。 後で、クライアントのサブディレクトリも作成します。
まず、ターミナルを開き、新しいプロジェクトディレクトリを作成します。
- mkdir node-sse-example
新しく作成されたプロジェクトディレクトリに移動します。
- cd node-sse-example
次に、新しいサーバーディレクトリを作成します。
- mkdir sse-server
新しく作成されたサーバーディレクトリに移動します。
- cd sse-server
新しいを初期化します npm
事業:
- npm init -y
インストール express
, body-parser
、 と cors
:
- npm install express@4.17.1 body-parser@1.19.0 cors@2.8.5 --save
これで、バックエンドの依存関係の設定が完了しました。
このセクションでは、アプリケーションのバックエンドを開発します。 これらの機能をサポートする必要があります。
- 新しいファクトが追加されたときに開いている接続とブロードキャストの変更を追跡する
GET /events
更新を登録するエンドポイントPOST /facts
新しい事実のエンドポイントGET /status
接続したクライアントの数を知るためのエンドポイントcors
フロントエンドアプリからの接続を許可するミドルウェア
にある最初のターミナルセッションを使用します sse-server
ディレクトリ。 新しいを作成します server.js
ファイル:
を開きます server.js
コードエディタでファイルを作成します。 必要なモジュールを要求し、Expressアプリを初期化します。
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.get('/status', (request, response) => response.json({clients: clients.length}));
const PORT = 3000;
let clients = [];
let facts = [];
app.listen(PORT, () => {
console.log(`Facts Events service listening at http://localhost:${PORT}`)
})
次に、のミドルウェアを構築します GET
へのリクエスト /events
終点。 コードの次の行をに追加します server.js
:
// ...
function eventsHandler(request, response, next) {
const headers = {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache'
};
response.writeHead(200, headers);
const data = `data: ${JSON.stringify(facts)}\n\n`;
response.write(data);
const clientId = Date.now();
const newClient = {
id: clientId,
response
};
clients.push(newClient);
request.on('close', () => {
console.log(`${clientId} Connection closed`);
clients = clients.filter(client => client.id !== clientId);
});
}
app.get('/events', eventsHandler);
The eventsHandler
ミドルウェアは request
と response
Expressが提供するオブジェクト。
接続を開いたままにするには、ヘッダーが必要です。 The Content-Type
ヘッダーはに設定されています 'text/event-stream'
そしてその Connection
ヘッダーはに設定されています 'keep-alive'
. The Cache-Control
ヘッダーはオプションで、 'no-cache'
. さらに、HTTPステータスはに設定されます 200
-リクエストが成功した場合のステータスコード。
クライアントが接続を開いた後、 facts
文字列に変換されます。 これはテキストベースのトランスポートであるため、配列を stringify する必要があります。また、標準を満たすには、メッセージに特定の形式が必要です。 このコードは、というフィールドを宣言します data
それに文字列化された配列を設定します。 注意の最後の詳細は、二重末尾の改行です \n\n
イベントの終了を示すために必須です。
A clientId
タイムスタンプと response
Expressオブジェクト。 これらはに保存されます clients
配列。 いつ client
接続を閉じます、の配列 clients
に更新されます filter
それから client
.
次に、のミドルウェアを構築します POST
へのリクエスト /fact
終点。 コードの次の行をに追加します server.js
:
// ...
function sendEventsToAll(newFact) {
clients.forEach(client => client.response.write(`data: ${JSON.stringify(newFact)}\n\n`))
}
async function addFact(request, respsonse, next) {
const newFact = request.body;
facts.push(newFact);
respsonse.json(newFact)
return sendEventsToAll(newFact);
}
app.post('/fact', addFact);
サーバーの主な目的は、新しいファクトが追加されたときにすべてのクライアントに接続して通知を提供することです。 The addNest
ミドルウェアは事実を保存し、それを作成したクライアントに返します POST
リクエストし、を呼び出します sendEventsToAll
関数。
sendEventsToAll
を繰り返します clients
配列を使用し、 write
各エクスプレスの方法 response
更新を送信するオブジェクト。
ステップ2–バックエンドのテスト
Webアプリを実装する前に、cURLを使用してサーバーをテストできます。
ターミナルウィンドウで、 sse-server
プロジェクトディレクトリ内のディレクトリ。 そして、次のコマンドを実行します。
- node server.js
次のメッセージが表示されます。
- OutputFacts Events service listening at http://localhost:3001
2番目のターミナルウィンドウで、次のコマンドを使用して更新を待機している接続を開きます。
- curl -H Accept:text/event-stream http://localhost:3001/events
これにより、次の応答が生成されます。
- Outputdata: []
空の配列。
3番目のターミナルウィンドウで、次のコマンドを使用して、POST後のリクエストを作成して新しいファクトを追加します。
- curl -X POST \
- -H "Content-Type: application/json" \
- -d '{"info": "Shark teeth are embedded in the gums rather than directly affixed to the jaw, and are constantly replaced throughout life.", "source": "https://en.wikipedia.org/wiki/Shark"}'\
- -s http://localhost:3001/fact
後に POST
リクエストすると、2番目のターミナルウィンドウが新しいファクトで更新されます。
- Outputdata: {"info": "Shark teeth are embedded in the gums rather than directly affixed to the jaw, and are constantly replaced throughout life.", "source": "https://en.wikipedia.org/wiki/Shark"}
今、 facts
2番目のタブで通信を閉じて再度開くと、配列に1つの項目が入力されます。
- curl -H Accept:text/event-stream http://localhost:3001/events
空の配列の代わりに、次の新しいアイテムを含むメッセージを受信するはずです。
- Outputdata: [{"info": "Shark teeth are embedded in the gums rather than directly affixed to the jaw, and are constantly replaced throughout life.", "source": "https://en.wikipedia.org/wiki/Shark"}]
この時点で、バックエンドは完全に機能しています。 今度はを実装する時が来ました EventSource
フロントエンドのAPI。
ステップ3–ReactWebアプリのフロントエンドを構築する
プロジェクトのこの部分では、を使用するReactアプリを作成します EventSource
API。
Webアプリには、次の一連の機能があります。
- 以前に開発したサーバーを開いて接続を維持します
- 初期データを含むテーブルをレンダリングします
- SSEを介してテーブルを最新の状態に保つ
次に、新しいターミナルウィンドウを開き、プロジェクトディレクトリに移動します。 create-react-appを使用してReactアプリを生成します。
- npx create-react-app sse-client
新しく作成されたクライアントディレクトリに移動します。
- cd sse-client
クライアントアプリケーションを実行します。
- npm start
これにより、新しいReactアプリケーションで新しいブラウザウィンドウが開きます。 これで、フロントエンドの依存関係の設定が完了しました。
スタイリングについては、 App.css
コードエディタでファイルを作成します。 そして、次のコード行でコンテンツを変更します。
body {
color: #555;
font-size: 25px;
line-height: 1.5;
margin: 0 auto;
max-width: 50em;
padding: 4em 1em;
}
.stats-table {
border-collapse: collapse;
text-align: center;
width: 100%;
}
.stats-table tbody tr:hover {
background-color: #f5f5f5;
}
次に、 App.js
コードエディタでファイルを作成します。 そして、次のコード行でコンテンツを変更します。
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [ facts, setFacts ] = useState([]);
const [ listening, setListening ] = useState(false);
useEffect( () => {
if (!listening) {
const events = new EventSource('http://localhost:3001/events');
events.onmessage = (event) => {
const parsedData = JSON.parse(event.data);
setFacts((facts) => facts.concat(parsedData));
};
setListening(true);
}
}, [listening, facts]);
return (
<table className="stats-table">
<thead>
<tr>
<th>Fact</th>
<th>Source</th>
</tr>
</thead>
<tbody>
{
facts.map((fact, i) =>
<tr key={i}>
<td>{fact.info}</td>
<td>{fact.source}</td>
</tr>
)
}
</tbody>
</table>
);
}
export default App;
The useEffect
関数の引数には重要な部分が含まれています。 EventSource
オブジェクトと /events
エンドポイントと onmessage
方法 data
イベントのプロパティが解析されます。
とは異なり cURL
応答、これでイベントがオブジェクトになりました。 これで、 data
プロパティを解析して、結果として有効なJSONオブジェクトを取得します。
最後に、このコードは新しいファクトをファクトのリストにプッシュし、テーブルが再レンダリングされます。
ステップ4–フロントエンドのテスト
ここで、新しいファクトを追加してみてください。
ターミナルウィンドウで、次のコマンドを実行します。
- curl -X POST \
- -H "Content-Type: application/json" \
- -d '{"info": "Shark teeth are embedded in the gums rather than directly affixed to the jaw, and are constantly replaced throughout life.", "source": "https://en.wikipedia.org/wiki/Shark"}'\
- -s http://localhost:3001/fact
The POST
リクエストにより新しいファクトが追加され、接続されているすべてのクライアントがそれを受信するはずです。 ブラウザでアプリケーションをチェックすると、この情報を含む新しい行が表示されます。
結論
この記事は、サーバーから送信されたイベントの概要として役立ちました。 この記事では、サーバーからクライアントに流れるリアルタイムの情報を処理するために、バックエンドとフロントエンドの両方に完全なソリューションを構築しました。
SSEは、テキストベースおよび単方向のトランスポート用に設計されました。 これがブラウザでのEventSourceの現在のサポートです。
EventSourceで利用可能なすべての機能を探索して学習を続けてください。 retry
.