في هذا البرنامج التعليمي ، سوف نلقي نظرة على الأحداث المرسلة من الخادم: فئة EventSource مضمنة تسمح لك بالحفاظ على اتصال بالخادم واستقبال الأحداث منه.
يمكنك أن تقرأ عن ماهية SSE وما يتم استخدامه هنا .
ماذا سنفعل بالضبط؟
سنكتب خادمًا بسيطًا ، بناءً على طلب العميل ، سيرسل إليه بيانات 10 مستخدمين عشوائيين ، وسيستخدم العميل هذه البيانات لإنشاء بطاقات مستخدم.
سيتم تنفيذ الخادم في Node.js ، العميل في JavaScript. سيتم استخدام Bootstrap للتصميم ، وسيتم استخدام Random User Generator كواجهة برمجة تطبيقات .
كود المشروع هنا...
إذا كنت مهتمًا ، من فضلك اتبعني.
تدريب
قم بإنشاء دليل
sse-tut:
mkdir sse-tut
ندخل فيه ونبدأ المشروع:
cd sse-tut
yarn init -y
//
npm init -y
التثبيت
axios:
yarn add axios
//
npm i axios
سيتم استخدام المحاور للحصول على بيانات المستخدم.
التحرير
package.json:
"main": "server.js",
"scripts": {
"start": "node server.js"
},
هيكل المشروع:
sse-tut
--node_modules
--client.js
--index.html
--package.json
--server.js
--yarn.lock
المحتويات
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.js.
نقوم بتوصيل http و axios ، وتحديد المنفذ:
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')
//
console.log(response)
return response.data.results[0]
}
إنشاء عداد لعدد المستخدمين المرسلين:
let i = 1
نكتب وظيفة إرسال البيانات إلى العميل:
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.')
})
}
نقوم بإنشاء وبدء الخادم:
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.'))
كود الخادم الكامل:
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.'))
نقوم بتنفيذ الأمر
yarn startأو npm start. تعرض المحطة الرسالة "الخادم جاهز". الافتتاح http://localhost:3000: لقد
انتهينا من الخادم ، انتقل إلى جانب العميل في التطبيق.
زبون
افتح الملف
client.js.
قم بإنشاء وظيفة لإنشاء قالب بطاقة مخصص:
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
}
}
//...
أولاً ، نقوم بتنفيذ إغلاق الاتصال:
stopEvents() {
this.eventSource.close()
// ,
this.eventLog.textContent = 'Event stream closed by client.'
}
دعنا ننتقل إلى فتح تدفق الأحداث:
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})
}
نتعامل مع الحدث المخصص "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)
})
لا تنس تنفيذ معالجة الأخطاء:
this.eventSource.addEventListener('error', e => {
this.eventSource.close()
this.eventLog.textContent = `Got an error: ${e}`
this.changeDisabled()
}, {once: true})
أخيرًا ، نقوم بتهيئة التطبيق:
const app = new App('main')
كود العميل الكامل:
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')
أعد تشغيل الخادم في حالة حدوث ذلك. نفتح
http://localhost:3000. انقر فوق الزر "ابدأ":
نبدأ في تلقي البيانات من الخادم وتقديم بطاقات المستخدم.
إذا قمت بالنقر فوق الزر "إيقاف" ، فسيتم تعليق إرسال البيانات:
اضغط على "ابدأ" مرة أخرى ، وسيستمر إرسال البيانات.
عند الوصول إلى الحد (10 مستخدمين) ، يرسل الخادم معرفًا بالقيمة -1 ويغلق الاتصال. يقوم العميل بدوره أيضًا بإغلاق تدفق الأحداث:
كما ترون ، فإن SSE يشبه إلى حد بعيد مآخذ الويب. العيب هو أن الرسائل أحادية الاتجاه: لا يمكن إرسال الرسائل إلا بواسطة الخادم. الميزة هي إعادة الاتصال التلقائي وسهولة التنفيذ.
تبلغ نسبة دعم هذه التقنية اليوم 95٪:
أرجو أن تتمتع هذه المادة. شكرآ لك على أهتمامك.