In diesem Lernprogramm werden wir uns die vom Server gesendeten Ereignisse ansehen: eine integrierte EventSource-Klasse, mit der Sie eine Verbindung zum Server aufrechterhalten und Ereignisse von diesem empfangen können.
Sie können lesen , was über SSE ist und wofür es verwendet wird , hier .
Was genau machen wir jetzt?
Wir werden einen einfachen Server schreiben, der ihm auf Anfrage des Kunden Daten von 10 zufälligen Benutzern sendet, und der Kunde wird diese Daten verwenden, um Benutzerkarten zu generieren.
Der Server wird in Node.js implementiert , der Client in JavaScript. Bootstrap wird für das Styling verwendet und Random User Generator wird als API verwendet .
Der Projektcode ist hier...
Wenn Sie interessiert sind, folgen Sie mir bitte.
Ausbildung
Erstellen Sie ein Verzeichnis
sse-tut:
mkdir sse-tut
Wir gehen darauf ein und initialisieren das Projekt:
cd sse-tut
yarn init -y
//
npm init -y
Installieren
axios:
yarn add axios
//
npm i axios
Axios werden verwendet, um Benutzerdaten abzurufen.
Bearbeitung
package.json:
"main": "server.js",
"scripts": {
"start": "node server.js"
},
Projektstruktur:
sse-tut
--node_modules
--client.js
--index.html
--package.json
--server.js
--yarn.lock
Inhalt
index.html:
<head>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<style>
.card {
margin: 0 auto;
max-width: 512px;
}
img {
margin: 1rem;
max-width: 320px;
}
p {
margin: 1rem;
}
</style>
</head>
<body>
<main class="container text-center">
<h1>Server-Sent Events Tutorial</h1>
<button class="btn btn-primary" data-type="start-btn">Start</button>
<button class="btn btn-danger" data-type="stop-btn" disabled>Stop</button>
<p data-type="event-log"></p>
</main>
<script src="client.js"></script>
</body>
Server
Beginnen wir mit der Implementierung des Servers.
Wir öffnen
server.js.
Wir verbinden http und axios, definieren den Port:
const http = require('http')
const axios = require('axios')
const PORT = process.env.PORT || 3000
Wir erstellen eine Funktion zum Empfangen von Benutzerdaten:
const getUserData = async () => {
const response = await axios.get('https://randomuser.me/api')
//
console.log(response)
return response.data.results[0]
}
Erstellen Sie einen Zähler für die Anzahl der gesendeten Benutzer:
let i = 1
Wir schreiben die Funktion des Sendens von Daten an den Kunden:
const sendUserData = (req, res) => {
// - 200
//
// -
//
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
})
// 2
const timer = setInterval(async () => {
// 10
if (i > 10) {
//
clearInterval(timer)
// , 10
console.log('10 users has been sent.')
// -1
// ,
res.write('id: -1\ndata:\n\n')
//
res.end()
return
}
//
const data = await getUserData()
//
// event -
// id - ;
// retry -
// data -
res.write(`event: randomUser\nid: ${i}\nretry: 5000\ndata: ${JSON.stringify(data)}\n\n`)
// ,
console.log('User data has been sent.')
//
i++
}, 2000)
//
req.on('close', () => {
clearInterval(timer)
res.end()
console.log('Client closed the connection.')
})
}
Wir erstellen und starten den Server:
http.createServer((req, res) => {
// CORS
res.setHeader('Access-Control-Allow-Origin', '*')
// - getUser
if (req.url === '/getUsers') {
//
sendUserData(req, res)
} else {
// , , ,
//
res.writeHead(404)
res.end()
}
}).listen(PORT, () => console.log('Server ready.'))
Vollständiger Servercode:
const http = require('http')
const axios = require('axios')
const PORT = process.env.PORT || 3000
const getUserData = async () => {
const response = await axios.get('https://randomuser.me/api')
return response.data.results[0]
}
let i = 1
const sendUserData = (req, res) => {
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
})
const timer = setInterval(async () => {
if (i > 10) {
clearInterval(timer)
console.log('10 users has been sent.')
res.write('id: -1\ndata:\n\n')
res.end()
return
}
const data = await getUserData()
res.write(`event: randomUser\nid: ${i}\nretry: 5000\ndata: ${JSON.stringify(data)}\n\n`)
console.log('User data has been sent.')
i++
}, 2000)
req.on('close', () => {
clearInterval(timer)
res.end()
console.log('Client closed the connection.')
})
}
http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
if (req.url === '/getUsers') {
sendUserData(req, res)
} else {
res.writeHead(404)
res.end()
}
}).listen(PORT, () => console.log('Server ready.'))
Wir führen den Befehl
yarn startoder aus npm start. Das Terminal zeigt die Meldung "Server bereit" an. Öffnen http://localhost:3000: Wir sind
mit dem Server fertig, gehen Sie zur Client-Seite der Anwendung.
Klient
Öffnen Sie die Datei
client.js.
Erstellen Sie eine Funktion zum Generieren einer benutzerdefinierten Kartenvorlage:
const getTemplate = user => `
<div class="card">
<div class="row">
<div class="col-md-4">
<img src="${user.img}" class="card-img" alt="user-photo">
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">${user.id !== null ? `Id: ${user.id}` : `User hasn't id`}</h5>
<p class="card-text">Name: ${user.name}</p>
<p class="card-text">Username: ${user.username}</p>
<p class="card-text">Email: ${user.email}</p>
<p class="card-text">Age: ${user.age}</p>
</div>
</div>
</div>
</div>
`
Die Vorlage wird unter Verwendung der folgenden Daten generiert: Benutzer-ID (falls vorhanden), Name, Login, E-Mail-Adresse und Alter des Benutzers.
Wir beginnen mit der Implementierung der Hauptfunktionalität:
class App {
constructor(selector) {
// -
this.$ = document.querySelector(selector)
//
this.#init()
}
#init() {
this.startBtn = this.$.querySelector('[data-type="start-btn"]')
this.stopBtn = this.$.querySelector('[data-type="stop-btn"]')
//
this.eventLog = this.$.querySelector('[data-type="event-log"]')
//
this.clickHandler = this.clickHandler.bind(this)
//
this.$.addEventListener('click', this.clickHandler)
}
clickHandler(e) {
//
if (e.target.tagName === 'BUTTON') {
//
// ,
//
const {
type
} = e.target.dataset
if (type === 'start-btn') {
this.startEvents()
} else if (type === 'stop-btn') {
this.stopEvents()
}
//
this.changeDisabled()
}
}
changeDisabled() {
if (this.stopBtn.disabled) {
this.stopBtn.disabled = false
this.startBtn.disabled = true
} else {
this.stopBtn.disabled = true
this.startBtn.disabled = false
}
}
//...
Zuerst implementieren wir das Schließen der Verbindung:
stopEvents() {
this.eventSource.close()
// ,
this.eventLog.textContent = 'Event stream closed by client.'
}
Fahren wir mit dem Öffnen des Ereignisstroms fort:
startEvents() {
//
this.eventSource = new EventSource('http://localhost:3000/getUsers')
// ,
this.eventLog.textContent = 'Accepting data from the server.'
// -1
this.eventSource.addEventListener('message', e => {
if (e.lastEventId === '-1') {
//
this.eventSource.close()
//
this.eventLog.textContent = 'End of stream from the server.'
this.changeDisabled()
}
//
}, {once: true})
}
Wir behandeln das benutzerdefinierte Ereignis "randomUser":
this.eventSource.addEventListener('randomUser', e => {
//
const userData = JSON.parse(e.data)
//
console.log(userData)
//
const {
id,
name,
login,
email,
dob,
picture
} = userData
// ,
const i = id.value
const fullName = `${name.first} ${name.last}`
const username = login.username
const age = dob.age
const img = picture.large
const user = {
id: i,
name: fullName,
username,
email,
age,
img
}
//
const template = getTemplate(user)
//
this.$.insertAdjacentHTML('beforeend', template)
})
Vergessen Sie nicht, die Fehlerbehandlung zu implementieren:
this.eventSource.addEventListener('error', e => {
this.eventSource.close()
this.eventLog.textContent = `Got an error: ${e}`
this.changeDisabled()
}, {once: true})
Schließlich initialisieren wir die Anwendung:
const app = new App('main')
Vollständiger Client-Code:
const getTemplate = user => `
<div class="card">
<div class="row">
<div class="col-md-4">
<img src="${user.img}" class="card-img" alt="user-photo">
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">${user.id !== null ? `Id: ${user.id}` : `User hasn't id`}</h5>
<p class="card-text">Name: ${user.name}</p>
<p class="card-text">Username: ${user.username}</p>
<p class="card-text">Email: ${user.email}</p>
<p class="card-text">Age: ${user.age}</p>
</div>
</div>
</div>
</div>
`
class App {
constructor(selector) {
this.$ = document.querySelector(selector)
this.#init()
}
#init() {
this.startBtn = this.$.querySelector('[data-type="start-btn"]')
this.stopBtn = this.$.querySelector('[data-type="stop-btn"]')
this.eventLog = this.$.querySelector('[data-type="event-log"]')
this.clickHandler = this.clickHandler.bind(this)
this.$.addEventListener('click', this.clickHandler)
}
clickHandler(e) {
if (e.target.tagName === 'BUTTON') {
const {
type
} = e.target.dataset
if (type === 'start-btn') {
this.startEvents()
} else if (type === 'stop-btn') {
this.stopEvents()
}
this.changeDisabled()
}
}
changeDisabled() {
if (this.stopBtn.disabled) {
this.stopBtn.disabled = false
this.startBtn.disabled = true
} else {
this.stopBtn.disabled = true
this.startBtn.disabled = false
}
}
startEvents() {
this.eventSource = new EventSource('http://localhost:3000/getUsers')
this.eventLog.textContent = 'Accepting data from the server.'
this.eventSource.addEventListener('message', e => {
if (e.lastEventId === '-1') {
this.eventSource.close()
this.eventLog.textContent = 'End of stream from the server.'
this.changeDisabled()
}
}, {once: true})
this.eventSource.addEventListener('randomUser', e => {
const userData = JSON.parse(e.data)
console.log(userData)
const {
id,
name,
login,
email,
dob,
picture
} = userData
const i = id.value
const fullName = `${name.first} ${name.last}`
const username = login.username
const age = dob.age
const img = picture.large
const user = {
id: i,
name: fullName,
username,
email,
age,
img
}
const template = getTemplate(user)
this.$.insertAdjacentHTML('beforeend', template)
})
this.eventSource.addEventListener('error', e => {
this.eventSource.close()
this.eventLog.textContent = `Got an error: ${e}`
this.changeDisabled()
}, {once: true})
}
stopEvents() {
this.eventSource.close()
this.eventLog.textContent = 'Event stream closed by client.'
}
}
const app = new App('main')
Starten Sie den Server für alle Fälle neu. Wir öffnen
http://localhost:3000. Klicken Sie auf die Schaltfläche "Start":
Wir empfangen Daten vom Server und rendern Benutzerkarten.
Wenn Sie auf die Schaltfläche "Stopp" klicken, wird das Senden von Daten angehalten:
Drücken Sie erneut auf "Start", um das Senden der Daten fortzusetzen.
Wenn das Limit (10 Benutzer) erreicht ist, sendet der Server eine Kennung mit dem Wert -1 und schließt die Verbindung. Der Client schließt wiederum den Ereignisstrom:
Wie Sie sehen können, ist SSE Websockets sehr ähnlich. Der Nachteil ist, dass Nachrichten unidirektional sind: Nachrichten können nur vom Server gesendet werden. Der Vorteil ist die automatische Wiederverbindung und die einfache Implementierung.
Die Unterstützung für diese Technologie beträgt heute 95%:
Ich hoffe dir hat der Artikel gefallen. Danke für die Aufmerksamkeit.