こちらのプログラムを簡単にしたサンプルを作成しました。
simonprickett/server-sent-events-demo
サーバー
フォルダー構成
$ tree
.
├── package.json
└── server.js
server.js
// ---------------------------------------------------------------
// server/server.js
//
// May/20/2021
// ---------------------------------------------------------------
const http = require('http')
const PORT = process.env.PORT || 5000
const memes = [
'https://ekzemplaro.org/tmp/images/a001.jpg',
'https://ekzemplaro.org/tmp/images/a002.jpg',
'https://ekzemplaro.org/tmp/images/a003.jpg',
'https://ekzemplaro.org/tmp/images/a004.jpg',
'https://ekzemplaro.org/tmp/images/a005.jpg',
'https://ekzemplaro.org/tmp/images/a006.jpg',
'https://ekzemplaro.org/tmp/images/a007.jpg'
]
// ---------------------------------------------------------------
function getRandomIndex(low, high) {
const min = Math.ceil(low)
const max = Math.floor(high)
return Math.floor(Math.random() * (max - min + 1)) + min
}
// ---------------------------------------------------------------
function createRandomMemeMessage() {
console.log('Sending meme message.')
return (`event: meme\ndata:${memes[getRandomIndex(0, memes.length - 1)]}\n\n\n`)
}
// ---------------------------------------------------------------
function createRandomNamedEvents(request, response) {
response.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
})
let eventsSent = 1
const interval = setInterval(() => {
if (eventsSent === 31) {
clearInterval(interval)
console.log('Sent 30 events, stopping.')
response.write('id: -1\ndata:\n\n\n')
response.end()
return
}
response.write(`id: ${eventsSent < 10 ? '0' : ''}${eventsSent}\n`)
response.write(createRandomMemeMessage())
eventsSent += 1
}, 3000)
request.on('close', () => {
clearInterval(interval)
response.end()
console.log('Stopped sending events as client closed the connection.')
})
}
http.createServer((request, response) => {
response.setHeader('Access-Control-Allow-Origin', '*')
response.setHeader('Access-Control-Request-Method', '*')
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET')
response.setHeader('Access-Control-Allow-Headers', '*')
if (request.method === 'OPTIONS') {
response.writeHead(200)
response.end()
return
}
switch (request.url) {
case '/randomNamedEvents':
createRandomNamedEvents(request, response)
break
default:
// Unknown URL
response.writeHead(404)
response.end()
}
}).listen(PORT)
console.log(`server-sent-events-demo server running on port ${PORT}`)
// ---------------------------------------------------------------
package.json
{
"name": "sse-server-demo",
"version": "0.0.1",
"description": "Server Sent Events Demonstration Server",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/simonprickett/server-sent-events-demo.git"
},
"keywords": [
"server-sent-events",
"node"
],
"author": "Simon Prickett",
"license": "MIT",
"bugs": {
"url": "https://github.com/simonprickett/server-sent-events-demo/issues"
},
"homepage": "https://github.com/simonprickett/server-sent-events-demo#readme"
}
サーバーの起動
npm install
npm start
クライアント
以下のファイルを、Nginx または Apache2 の配下に置きます。
次のようにサーバーを動かす手もあります。
python -m http.server 8000
>フォルダー構成
>```text
$ tree
.
├── css
│ └── app.css
├── index.html
└── js
└── app.js
index.html
<! DOCTYPE html>
<html>
<head>
<meta charset="utf-8"
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Server Sent Events Demo</title>
<link rel="stylesheet" href="css/app.css">
<script src="js/app.js"></script>
</head>
<body>
<div id="browserSupport" class="browserSupport">
</div>
<div id="meme" class="box memes">
Meme Image Events
</div>
<div id="eventLog" class="rectangle eventLog">
<!-- Event Log will go here -->
</div>
<hr />
May/20/2021 AM 06:57<br />
</body>
</html>
js/app.js
// ---------------------------------------------------------------
// client/js/app.js
//
// May/20/2021
// ---------------------------------------------------------------
const app = {
updateEventsReceived: (event) => {
document.getElementById('eventLog').insertAdjacentHTML('afterbegin', `<br>${event.lastEventId}: (${event.type}) ${event.data}`);
},
resetStartButton: () => {
document.getElementById('startEvents').value = 'Start Events';
},
startEvents: () => {
app.eventSource = new EventSource('http://localhost:5000/randomNamedEvents');
app.eventSource.onmessage = (e) => {
console.log(e);
if (e.lastEventId === '-1') {
app.eventSource.close();
document.getElementById('eventLog').insertAdjacentHTML('afterbegin', '<br>End of event stream from server.');
app.resetStartButton();
}
};
app.eventSource.addEventListener('meme', (e) => {
console.log(e);
document.getElementById('meme').innerHTML = `<img class="memeImage" src="${e.data}">`;
app.updateEventsReceived(e);
});
app.eventSource.addEventListener('error', (e) => {
console.log('got an error');
console.log(e);
app.resetStartButton();
});
}
};
document.addEventListener('DOMContentLoaded', () => {
const browserSupportElem = document.getElementById('browserSupport');
if (!! window.EventSource) {
browserSupportElem.innerHTML = '<input type="submit" class="startEvents" id="startEvents" value="Start Events">';
// browserSupportElem.innerHTML = 'This browser supports <tt>EventSource</tt>! <input type="submit" class="startEvents" id="startEvents" value="Start Events">';
document.getElementById('startEvents').addEventListener('click', function(event) {
if (this.value === 'Start Events') {
this.value = 'Stop Events';
app.startEvents();
} else {
app.eventSource.close();
document.getElementById('eventLog').insertAdjacentHTML('afterbegin', '<br>Event stream closed by browser.');
app.resetStartButton();
}
});
} else {
browserSupportElem.innerHTML = 'This browser does not support <tt>EventSource</tt> :(';
browserSupportElem.classList.add('unsupportedBrowser');
}
});
// ---------------------------------------------------------------
css/app.css
/* client/css/app.css */
html {
background-color: #000000;
color: #FFFFFF;
}
a {
color: #EEEE00;
text-decoration: none;
}
.header {
text-align: center;
font-size: 3.0em;
margin-top: 15px;
}
.container {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
margin: auto;
max-width: 1200px;
width: 90%;
}
.browserSupport {
width: 80%;
height: 60px;
max-width: 960px;
text-align: center;
border-radius: 5px;
border-color: #FFFFFF;
border-width: 1px;
background-color: #00BB00;
color: #FFFFFF;
font-size: 2em;
line-height: 60px;
margin-bottom: 30px;
margin-top: 30px;
margin-left: auto;
margin-right: auto;
}
.unsupportedBrowser {
background-color: #EE0000;
}
.box {
width: 180px;
height: 180px;
border-radius: 5px;
border-style: solid;
border-color: #FFFFFF;
border-width: 1px;
margin: .5em;
padding: 1em;
text-align: center;
font-size: 2em;
color: #FFFFFF;
}
.startEvents {
border-color: #FFFFFF;
border-radius: 5px;
border-width: 1px;
border-style: solid;
padding: 5px;
font-size: 0.75em;
float: right;
margin-top: 10px;
margin-right: 20px;
}
.startEvents:active {
background-color: #888888;
}
.memes {
background-color: #FFEE00;
}
.memeImage {
width: 100%;
height: 100%;
}
.eventLog {
font-family: monospace;
font-size: 1.25em;
height: 150px;
width: 600px;
border-radius: 5px;
border-style: solid;
border-color: #FFFFFF;
border-width: 1px;
margin: 20px;
overflow-y: scroll;
overflow-x: hidden;
padding: 5px;
}