البرنامج التعليمي: إنشاء تطبيق دردشة باستخدام خدمة Azure Web PubSub
مقالة
في البرنامج التعليمي نشر الرسائل والاشتراك فيها، ستتعلم أساسيات نشر الرسائل والاشتراك فيها باستخدام Azure Web PubSub. في هذا البرنامج التعليمي، ستتعلم نظام الأحداث في Azure Web PubSub وتستخدمه لإنشاء تطبيق ويب كامل مع وظائف الاتصال في الوقت الحقيقي.
في هذا البرنامج التعليمي، تتعلم كيفية:
إنشاء مثيل خدمة Web PubSub
قم بتكوين إعدادات معالج الأحداث لـ Azure Web PubSub
أحداث Hanlde في خادم التطبيق وإنشاء تطبيق دردشة في الوقت الحقيقي
إذا كنت تفضل تشغيل أوامر مرجع CLI محلياً قم بتثبيت CLI Azure. إذا كنت تعمل على نظام تشغيل Windows أو macOS، ففكر في تشغيل Azure CLI في حاوية Docker. لمزيد من المعلومات، راجع كيفية تشغيل Azure CLI في حاوية Docker.
إذا كنت تستخدم تثبيت محلي، يُرجى تسجيل الدخول إلى Azure CLI مستخدمًا أمر az login. لإنهاء عملية المصادقة، اتبع الخطوات المعروضة في جهازك. للحصول على خيارات أخرى لتسجيل دخول، راجع تسجيل الدخول باستخدام Azure CLI.
عندما يُطلب منك، قم بتثبيت ملحق Azure CLI عند الاستخدام لأول مرة. لمزيد من المعلومات بشأن الامتدادات، راجع استخدام امتدادات مع Azure CLI.
يُرجى تشغيل إصدار az للوصول إلى الإصدار والمكتبات التابعة التي تم تثبيتها. للتحديث لآخر إصدار، يُرجى تشغيل تحديث az.
يتطلب هذا الإعداد الإصدار 2.22.0 أو أعلى من Azure CLI. إذا كنت تستخدم Azure Cloud Shell، يتم تثبيت أحدث إصدار بالفعل.
إنشاء مثيل Azure Web PubSub
إنشاء مجموعة موارد
وتُعد مجموعة الموارد عبارة عن حاوية منطقية يتم فيها توزيع موارد Azure وإدارتها. استخدم الأمر az group create لإنشاء مجموعة موارد باسم myResourceGroup في eastus الموقع.
az group create --name myResourceGroup --location EastUS
إنشاء مثيل Web PubSub
قم بتشغيل ملحق az add لتثبيت أو ترقية ملحق webpubsub إلى الإصدار الحالي.
az extension add --upgrade --name webpubsub
استخدم الأمر Azure CLI az webpubsub create لإنشاء Web PubSub في مجموعة الموارد التي قمت بإنشائها. ينشئ الأمر التالي مورد Web PubSub مجاني ضمن مجموعة الموارد myResourceGroup في EastUS:
هام
يجب أن يكون لكل مورد Web PubSub ويب اسمًا فريدًا. استبدل <your-unique-resource-name> باسم Web PubSub في الأمثلة التالية.
يظهر إخراج هذا الأمر خصائص المورد المنشأ حديثًا. دوّن اثنتين من الخصائص المذكورة أدناه:
اسم المورد: الاسم الذي قدمته للمعلمة --name أعلاه.
اسم المضيف: في المثال، اسم المضيف هو <your-unique-resource-name>.webpubsub.azure.com/.
في هذه المرحلة، حساب Azure هو الوحيد المصرح به لتنفيذ أي عمليات على هذا المورد الجديد.
احصل على ConnectionString لاستخدامها في المستقبل
هام
سلسلة اتصال تتضمن معلومات التخويل المطلوبة لتطبيقك للوصول إلى خدمة Azure Web PubSub. مفتاح الوصول داخل سلسلة الاتصال يشبه كلمة مرور الجذر للخدمة الخاصة بك. في بيئات الإنتاج، كن حذرا دائما لحماية مفاتيح الوصول الخاصة بك. استخدم Azure Key Vault لإدارة المفاتيح وتدويرها بأمان. تجنب توزيع مفاتيح الوصول إلى مستخدمين آخرين، أو ترميزها ترميزًا ثابتًا، أو حفظها في أي مكان في نص عادي يمكن للآخرين الوصول إليه. قم بتدوير المفاتيح الخاصة بك إذا كنت تعتقد أنها قد تعرضت للخطر.
استخدم الأمر Azure CLI az webpubsub key للحصول على الاتصال ionString للخدمة. <your-unique-resource-name> استبدل العنصر النائب باسم مثيل Azure Web PubSub.
az webpubsub key show --resource-group myResourceGroup --name <your-unique-resource-name> --query primaryConnectionString --output tsv
انسخ سلسلة الاتصال لاستخدامها لاحقا.
انسخ الاتصال ionString التي تم إحضارها وقم بتعيينها إلى متغير WebPubSubConnectionStringالبيئة ، والذي يقرأه البرنامج التعليمي لاحقا. استبدل <connection-string> أدناه ب الاتصال ionString الذي أحضرته.
في Azure Web PubSub، هناك دوران، الخادم والعميل. يشبه هذا المفهوم أدوار الخادم والعميل في تطبيق الويب. الخادم مسؤول عن إدارة العملاء والاستماع والرد على رسائل العميل. العميل مسؤول عن إرسال رسائل المستخدم وتلقيها من الخادم وتصورها للمستخدم النهائي.
في هذا البرنامج التعليمي، نقوم بإنشاء تطبيق ويب للدردشة في الوقت الحقيقي. في تطبيق ويب حقيقي، تتضمن مسؤولية الخادم أيضاً مصادقة العملاء وخدمة صفحات الويب الثابتة لواجهة مستخدم التطبيق.
في البرنامج التعليمي نشر ورسالة الاشتراك، يستهلك المشترك سلسلة الاتصال مباشرة. في تطبيق العالم الحقيقي، ليس من الآمن مشاركة سلسلة الاتصال مع أي عميل، لأن سلسلة الاتصال لديه امتياز كبير للقيام بأي عملية للخدمة. الآن، لنجعل الخادم الخاص بك يستهلك سلسلة الاتصال، ويعرض negotiate نقطة نهاية للعميل للحصول على عنوان URL الكامل مع رمز الوصول المميز. وبهذه الطريقة، يمكن للخادم إضافة برنامج وسيط المصادقة قبل negotiate نقطة النهاية لمنع الوصول غير المصرح به.
الآن دعونا نضيف /negotiate نقطة نهاية للعميل لاستدعاء لإنشاء الرمز المميز.
using Azure.Core;
using Microsoft.Azure.WebPubSub.AspNetCore;
using Microsoft.Azure.WebPubSub.Common;
using Microsoft.Extensions.Primitives;
// Read connection string from environment
var connectionString = Environment.GetEnvironmentVariable("WebPubSubConnectionString");
if (connectionString == null)
{
throw new ArgumentNullException(nameof(connectionString));
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddWebPubSub(o => o.ServiceEndpoint = new WebPubSubServiceEndpoint(connectionString))
.AddWebPubSubServiceClient<Sample_ChatApp>();
var app = builder.Build();
app.UseStaticFiles();
// return the Client Access URL with negotiate endpoint
app.MapGet("/negotiate", (WebPubSubServiceClient<Sample_ChatApp> service, HttpContext context) =>
{
var id = context.Request.Query["id"];
if (StringValues.IsNullOrEmpty(id))
{
context.Response.StatusCode = 400;
return null;
}
return new
{
url = service.GetClientAccessUri(userId: id).AbsoluteUri
};
});
app.Run();
sealed class Sample_ChatApp : WebPubSubHub
{
}
AddWebPubSubServiceClient<THub>() يستخدم لإدخال عميل WebPubSubServiceClient<THub>الخدمة ، والذي يمكننا استخدامه في خطوة التفاوض لإنشاء رمز مميز لاتصال العميل وفي أساليب المركز لاستدعاء واجهات برمجة تطبيقات REST للخدمة عند تشغيل أحداث المركز. يشبه رمز إنشاء الرمز المميز الرمز الذي استخدمناه في البرنامج التعليمي لنشر الرسائل والاشتراك فيها، باستثناء أننا نمرر وسيطة أخرى (userId) عند إنشاء الرمز المميز. يمكن استخدام معرف المستخدم لتحديد هوية العميل ومن ثم عندما تتلقى رسالة تعرف مصدرها.
تقرأ التعليمات البرمجية سلسلة الاتصال من متغير WebPubSubConnectionString البيئة الذي قمنا بتعيينه في الخطوة السابقة.
أعد تشغيل الخادم باستخدام dotnet run --urls http://localhost:8080.
قم أولا بتثبيت Azure Web PubSub SDK:
npm install --save @azure/web-pubsub
الآن دعونا نضيف /negotiate واجهة برمجة تطبيقات لإنشاء الرمز المميز.
const express = require('express');
const { WebPubSubServiceClient } = require('@azure/web-pubsub');
const app = express();
const hubName = 'Sample_ChatApp';
let serviceClient = new WebPubSubServiceClient(process.env.WebPubSubConnectionString, hubName);
app.get('/negotiate', async (req, res) => {
let id = req.query.id;
if (!id) {
res.status(400).send('missing user id');
return;
}
let token = await serviceClient.getClientAccessToken({ userId: id });
res.json({
url: token.url
});
});
app.use(express.static('public'));
app.listen(8080, () => console.log('server started'));
يشبه رمز إنشاء الرمز المميز الرمز الذي استخدمناه في البرنامج التعليمي لنشر الرسائل والاشتراك فيها، باستثناء أننا نمرر وسيطة أخرى (userId) عند إنشاء الرمز المميز. يمكن استخدام معرف المستخدم لتحديد هوية العميل ومن ثم عندما تتلقى رسالة تعرف مصدرها.
تقرأ التعليمات البرمجية سلسلة الاتصال من متغير WebPubSubConnectionString البيئة الذي قمنا بتعيينه في الخطوة السابقة.
أعد تشغيل الخادم عن طريق تشغيل node server.
أضف أولا تبعية Azure Web PubSub SDK وgson في عقدة dependenciespom.xml:
الآن دعونا نضيف /negotiate واجهة برمجة تطبيقات إلى App.java الملف لإنشاء الرمز المميز:
package com.webpubsub.tutorial;
import com.azure.messaging.webpubsub.WebPubSubServiceClient;
import com.azure.messaging.webpubsub.WebPubSubServiceClientBuilder;
import com.azure.messaging.webpubsub.models.GetClientAccessTokenOptions;
import com.azure.messaging.webpubsub.models.WebPubSubClientAccessToken;
import com.azure.messaging.webpubsub.models.WebPubSubContentType;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.javalin.Javalin;
public class App {
public static void main(String[] args) {
String connectionString = System.getenv("WebPubSubConnectionString");
if (connectionString == null) {
System.out.println("Please set the environment variable WebPubSubConnectionString");
return;
}
// create the service client
WebPubSubServiceClient service = new WebPubSubServiceClientBuilder()
.connectionString(connectionString)
.hub("Sample_ChatApp")
.buildClient();
// start a server
Javalin app = Javalin.create(config -> {
config.staticFiles.add("public");
}).start(8080);
// Handle the negotiate request and return the token to the client
app.get("/negotiate", ctx -> {
String id = ctx.queryParam("id");
if (id == null) {
ctx.status(400);
ctx.result("missing user id");
return;
}
GetClientAccessTokenOptions option = new GetClientAccessTokenOptions();
option.setUserId(id);
WebPubSubClientAccessToken token = service.getClientAccessToken(option);
ctx.contentType("application/json");
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("url", token.getUrl());
String response = gson.toJson(jsonObject);
ctx.result(response);
return;
});
}
}
يشبه رمز إنشاء الرمز المميز الرمز الذي استخدمناه في البرنامج التعليمي لنشر الرسائل والاشتراك فيها، باستثناء أننا نستدعي الأسلوب setUserId لإعداد معرف المستخدم عند إنشاء الرمز المميز. يمكن استخدام معرف المستخدم لتحديد هوية العميل ومن ثم عندما تتلقى رسالة تعرف مصدرها.
تقرأ التعليمات البرمجية سلسلة الاتصال من متغير WebPubSubConnectionString البيئة الذي قمنا بتعيينه في الخطوة السابقة.
الآن دعونا نضيف /negotiate واجهة برمجة تطبيقات إلى الخادم لإنشاء الرمز المميز.
import os
from flask import (
Flask,
request,
send_from_directory,
)
from azure.messaging.webpubsubservice import (
WebPubSubServiceClient
)
hub_name = 'Sample_ChatApp'
connection_string = os.environ.get('WebPubSubConnectionString')
app = Flask(__name__)
service = WebPubSubServiceClient.from_connection_string(connection_string, hub=hub_name)
@app.route('/<path:filename>')
def index(filename):
return send_from_directory('public', filename)
@app.route('/negotiate')
def negotiate():
id = request.args.get('id')
if not id:
return 'missing user id', 400
token = service.get_client_access_token(user_id=id)
return {
'url': token['url']
}, 200
if __name__ == '__main__':
app.run(port=8080)
يشبه رمز إنشاء الرمز المميز الرمز الذي استخدمناه في البرنامج التعليمي لنشر الرسائل والاشتراك فيها، باستثناء أننا نمرر وسيطة أخرى (user_id) عند إنشاء الرمز المميز. يمكن استخدام معرف المستخدم لتحديد هوية العميل ومن ثم عندما تتلقى رسالة تعرف مصدرها.
تقرأ التعليمات البرمجية سلسلة الاتصال من متغير WebPubSubConnectionString البيئة الذي قمنا بتعيينه في الخطوة السابقة.
أعد تشغيل الخادم باستخدام python server.py.
يمكنك اختبار واجهة برمجة التطبيقات هذه عن طريق الوصول http://localhost:8080/negotiate?id=user1 وتمنحك عنوان URL الكامل ل Azure Web PubSub مع رمز مميز للوصول.
معالجة الأحداث
في Azure Web PubSub، عندما تحدث أنشطة معينة من جانب العميل (على سبيل المثال، يقوم العميل بالاتصال أو الاتصال أو قطع الاتصال أو إرسال العميل للرسائل)، ترسل الخدمة إعلامات إلى الخادم حتى يتمكن من التفاعل مع هذه الأحداث.
يتم تسليم الأحداث إلى الخادم في شكل Webhook. يتم تقديم خطاف الويب وعرضه بواسطة خادم التطبيق ويتم تسجيله في جانب خدمة Azure Web PubSub. تستدعي الخدمة خطافات الويب كلما وقع حدث.
يتبع Azure Web PubSub CloudEvents لوصف بيانات الحدث.
نعالج connected أدناه أحداث النظام عندما يكون العميل متصلا ويتعامل مع message أحداث المستخدم عندما يرسل العميل رسائل لإنشاء تطبيق الدردشة.
يمكن أن يساعد Web PubSub SDK ل AspNetCore Microsoft.Azure.WebPubSub.AspNetCore الذي قمنا بتثبيته في الخطوة السابقة أيضا في تحليل طلبات CloudEvents ومعالجتها.
أولا، أضف معالجات الأحداث قبل app.Run(). حدد مسار نقطة النهاية للأحداث، وليكن مثلاً /eventhandler.
الآن، داخل الفئة Sample_ChatApp التي أنشأناها في الخطوة السابقة، أضف منشئا للعمل معه WebPubSubServiceClient<Sample_ChatApp> نستخدمه لاستدعاء خدمة Web PubSub. وللرد OnConnectedAsync() عند connected تشغيل الحدث، OnMessageReceivedAsync() لمعالجة الرسائل من العميل.
sealed class Sample_ChatApp : WebPubSubHub
{
private readonly WebPubSubServiceClient<Sample_ChatApp> _serviceClient;
public Sample_ChatApp(WebPubSubServiceClient<Sample_ChatApp> serviceClient)
{
_serviceClient = serviceClient;
}
public override async Task OnConnectedAsync(ConnectedEventRequest request)
{
Console.WriteLine($"[SYSTEM] {request.ConnectionContext.UserId} joined.");
}
public override async ValueTask<UserEventResponse> OnMessageReceivedAsync(UserEventRequest request, CancellationToken cancellationToken)
{
await _serviceClient.SendToAllAsync(RequestContent.Create(
new
{
from = request.ConnectionContext.UserId,
message = request.Data.ToString()
}),
ContentType.ApplicationJson);
return new UserEventResponse();
}
}
في التعليمات البرمجية أعلاه، نستخدم عميل الخدمة لبث رسالة إعلام بتنسيق JSON لجميع من انضموا إلى SendToAllAsync.
يساعد Web PubSub SDK for express @azure/web-pubsub-express في تحليل طلبات CloudEvents ومعالجتها.
npm install --save @azure/web-pubsub-express
تحديث server.js مع التعليمات البرمجية التالية لعرض واجهة برمجة تطبيقات REST في /eventhandler (الذي يتم بواسطة البرنامج الوسيط السريع الذي يوفره Web PubSub SDK) للتعامل مع الحدث المتصل بالعميل:
const express = require("express");
const { WebPubSubServiceClient } = require("@azure/web-pubsub");
const { WebPubSubEventHandler } = require("@azure/web-pubsub-express");
const app = express();
const hubName = "Sample_ChatApp";
let serviceClient = new WebPubSubServiceClient(process.env.WebPubSubConnectionString, hubName);
let handler = new WebPubSubEventHandler(hubName, {
path: "/eventhandler",
onConnected: async (req) => {
console.log(`${req.context.userId} connected`);
},
handleUserEvent: async (req, res) => {
if (req.context.eventName === "message")
await serviceClient.sendToAll({
from: req.context.userId,
message: req.data,
});
res.success();
},
});
app.get("/negotiate", async (req, res) => {
let id = req.query.id;
if (!id) {
res.status(400).send("missing user id");
return;
}
let token = await serviceClient.getClientAccessToken({ userId: id });
res.json({
url: token.url,
});
});
app.use(express.static("public"));
app.use(handler.getMiddleware());
app.listen(8080, () => console.log("server started"));
في التعليمات البرمجية أعلاه، onConnected ما عليك سوى طباعة رسالة إلى وحدة التحكم عند اتصال عميل. يمكنك أن ترى أننا نستخدم req.context.userId حتى نتمكن من رؤية هوية العميل المتصل. ويتم handleUserEvent استدعاؤه عندما يرسل العميل رسالة. يستخدم WebPubSubServiceClient.sendToAll() لبث الرسالة في كائن JSON لجميع العملاء. يمكنك أن ترى أن لدى handleUserEvent أيضاً كائن res حيث يمكنك إرسال رسالة إلى مرسل الحدث. هنا نستدعي res.success() ببساطة لجعل WebHook إرجاع 200 (لاحظ أن هذا الاستدعاء مطلوب حتى أنك لا تريد إعادة أي شيء إلى العميل، وإلا فلن يرجع WebHook أبدا ويغلق اتصال العميل).
في الوقت الحالي، تحتاج إلى تنفيذ معالج الأحداث بنفسك في Java. الخطوات مباشرة إلى الأمام بعد مواصفات البروتوكول وموضحة في القائمة أدناه:
أولاً نود التعامل مع طلبات خيارات حماية إساءة الاستخدام، ونتحقق مما إذا كان العنوان يحتوي على WebHook-Request-Origin، ونعيد العنوان WebHook-Allowed-Origin. للتبسيط لغرض العرض التجريبي، نعيد * للسماح بكل الأصول.
ثم نود أن نتحقق مما إذا كانت الطلبات الواردة هي الأحداث التي نتوقعها. لنفترض أننا نهتم الآن بحدث النظام connected، والذي يجب أن يحتوي على العنوان ce-type باسم azure.webpubsub.sys.connected. نضيف المنطق بعد الحماية من إساءة الاستخدام لبث الحدث المتصل لجميع العملاء حتى يتمكنوا من رؤية من انضم إلى غرفة المحادثة.
في التعليمة البرمجية أعلاه، نقوم ببساطة بطباعة رسالة إلى وحدة التحكم عند اتصال عميل. يمكنك أن ترى أننا نستخدم ctx.header("ce-userId") حتى نتمكن من رؤية هوية العميل المتصل.
ce-typemessage الحدث هو دائما azure.webpubsub.user.message. التفاصيل راجع رسالة الحدث. نقوم بتحديث المنطق لمعالجة الرسائل التي عندما تأتي رسالة نبث الرسالة بتنسيق JSON لجميع العملاء المتصلين.
// handle events: https://learn.microsoft.com/azure/azure-web-pubsub/reference-cloud-events#events
app.post("/eventhandler", ctx -> {
String event = ctx.header("ce-type");
if ("azure.webpubsub.sys.connected".equals(event)) {
String id = ctx.header("ce-userId");
System.out.println(id + " connected.");
} else if ("azure.webpubsub.user.message".equals(event)) {
String id = ctx.header("ce-userId");
String message = ctx.body();
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("from", id);
jsonObject.addProperty("message", message);
String messageToSend = gson.toJson(jsonObject);
service.sendToAll(messageToSend, WebPubSubContentType.APPLICATION_JSON);
}
ctx.status(200);
});
في الوقت الحالي، تحتاج إلى تنفيذ معالج الأحداث بنفسك في Python. الخطوات مباشرة إلى الأمام بعد مواصفات البروتوكول ويتم توضيحها في القائمة أدناه:
أولاً نود التعامل مع طلبات خيارات حماية إساءة الاستخدام، ونتحقق مما إذا كان العنوان يحتوي على WebHook-Request-Origin، ونعيد العنوان WebHook-Allowed-Origin. للتبسيط لغرض العرض التجريبي، نعيد * للسماح بكل الأصول.
# validation: https://learn.microsoft.com/azure/azure-web-pubsub/reference-cloud-events#protection
@app.route('/eventhandler', methods=['OPTIONS'])
def handle_event():
if request.method == 'OPTIONS':
if request.headers.get('WebHook-Request-Origin'):
res = Response()
res.headers['WebHook-Allowed-Origin'] = '*'
res.status_code = 200
return res
ثم نود أن نتحقق مما إذا كانت الطلبات الواردة هي الأحداث التي نتوقعها. لنفترض أننا نهتم الآن بحدث النظام connected، والذي يجب أن يحتوي على العنوان ce-type باسم azure.webpubsub.sys.connected. نضيف المنطق بعد حماية إساءة الاستخدام:
في التعليمة البرمجية أعلاه، نقوم ببساطة بطباعة رسالة إلى وحدة التحكم عند اتصال عميل. يمكنك أن ترى أننا نستخدم request.headers.get('ce-userid') حتى نتمكن من رؤية هوية العميل المتصل.
ce-typemessage الحدث هو دائما azure.webpubsub.user.message. التفاصيل راجع رسالة الحدث. نقوم بتحديث المنطق لمعالجة الرسائل التي عندما تأتي رسالة نبث الرسالة إلى جميع العملاء المتصلين.
@app.route('/eventhandler', methods=['POST', 'OPTIONS'])
def handle_event():
if request.method == 'OPTIONS':
if request.headers.get('WebHook-Request-Origin'):
res = Response()
res.headers['WebHook-Allowed-Origin'] = '*'
res.status_code = 200
return res
elif request.method == 'POST':
user_id = request.headers.get('ce-userid')
type = request.headers.get('ce-type')
if type == 'azure.webpubsub.sys.connected':
print(f"{user_id} connected")
return '', 204
elif type == 'azure.webpubsub.user.message':
# default uses JSON
service.send_to_all(message={
'from': user_id,
'message': request.data.decode('UTF-8')
})
# returned message is also received by the client
return {
'from': "system",
'message': "message handled by server"
}, 200
else:
return 'Bad Request', 400
تحديث صفحة الويب
الآن دعنا نحدث index.html لإضافة المنطق للاتصال وإرسال الرسائل وعرض الرسائل المستلمة في الصفحة.
<html>
<body>
<h1>Azure Web PubSub Chat</h1>
<input id="message" placeholder="Type to chat...">
<div id="messages"></div>
<script>
(async function () {
let id = prompt('Please input your user name');
let res = await fetch(`/negotiate?id=${id}`);
let data = await res.json();
let ws = new WebSocket(data.url);
ws.onopen = () => console.log('connected');
let messages = document.querySelector('#messages');
ws.onmessage = event => {
let m = document.createElement('p');
let data = JSON.parse(event.data);
m.innerText = `[${data.type || ''}${data.from || ''}] ${data.message}`;
messages.appendChild(m);
};
let message = document.querySelector('#message');
message.addEventListener('keypress', e => {
if (e.charCode !== 13) return;
ws.send(message.value);
message.value = '';
});
})();
</script>
</body>
</html>
يمكنك أن ترى في التعليمات البرمجية أعلاه التي نتصل بها استخدام واجهة برمجة تطبيقات WebSocket الأصلية في المستعرض، واستخدام WebSocket.send() لإرسال رسالة والاستماع WebSocket.onmessage إلى الرسائل المستلمة.
يمكنك أيضا استخدام Client SDKs للاتصال بالخدمة، ما يمكنك من إعادة الاتصال التلقائي ومعالجة الأخطاء والمزيد.
هناك الآن خطوة واحدة متبقية حتى تعمل الدردشة. لنقم بتكوين الأحداث التي نهتم بها ومكان إرسال الأحداث إليها في خدمة Web PubSub.
إعداد معالج الأحداث
قمنا بتعيين معالج الأحداث في خدمة Web PubSub لإخبار الخدمة بمكان إرسال الأحداث إليها.
عند تشغيل خادم الويب محليا، كيف تستدعي خدمة Web PubSub المضيف المحلي إذا لم يكن لديه نقطة نهاية يمكن الوصول إليها عبر الإنترنت؟ هناك عادة طريقتان. أحدهما هو كشف المضيف المحلي للجمهور باستخدام أداة نفق عامة، والآخر هو استخدام awps-tunnel لنفق نسبة استخدام الشبكة من خدمة Web PubSub من خلال الأداة إلى الخادم المحلي الخاص بك.
في هذا القسم، نستخدم Azure CLI لتعيين معالجات الأحداث واستخدام awps-tunnel لتوجيه نسبة استخدام الشبكة إلى localhost.
تكوين إعدادات المركز
قمنا بتعيين قالب URL لاستخدام tunnel النظام بحيث يوجه Web PubSub الرسائل من خلال awps-tunnelاتصال النفق. يمكن تعيين معالجات الأحداث إما من المدخل أو CLI كما هو موضح في هذه المقالة، هنا نعينها من خلال CLI. نظرا لأننا نستمع إلى الأحداث في المسار /eventhandler كمجموعات الخطوات السابقة، نقوم بتعيين قالب url إلى tunnel:///eventhandler.
استخدم الأمر Azure CLI az webpubsub hub create لإنشاء إعدادات معالج الأحداث للمركز Sample_ChatApp .
هام
استبدل <your-unique-resource-name> باسم مورد Web PubSub الذي تم إنشاؤه من الخطوات السابقة.
يمكنك الاطلاع على نموذج التعليمة البرمجية لهذا البرنامج التعليمي كاملاً من هنا.
الآن قم بتشغيل الخادم باستخدام python server.py.
يمكنك الاطلاع على نموذج التعليمة البرمجية لهذا البرنامج التعليمي كاملاً من هنا.
افتح http://localhost:8080/index.html. يمكنك إدخال اسم المستخدم وبدء الدردشة.
مصادقة كسولة مع connect معالج الأحداث
في الأقسام السابقة، نوضح كيفية استخدام نقطة نهاية التفاوض لإرجاع عنوان URL لخدمة Web PubSub ورمز الوصول إلى JWT للعملاء للاتصال بخدمة Web PubSub. في بعض الحالات، على سبيل المثال، أجهزة الحافة التي تحتوي على موارد محدودة، قد يفضل العملاء الاتصال المباشر بموارد Web PubSub. في مثل هذه الحالات، يمكنك تكوين connect معالج الأحداث لمصادقة العملاء البطيئة، وتعيين معرف المستخدم للعملاء، وتحديد المجموعات التي ينضم إليها العملاء بمجرد الاتصال، وتكوين الأذونات التي يمتلكها العملاء وWebSocket subprotocol كاستجابة WebSocket للعميل، وما إلى ذلك. يرجى الرجوع إلى التفاصيل لتوصيل مواصفات معالج الأحداث.
الآن دعونا نستخدم connect معالج الأحداث لتحقيق ما يشبه ما يفعله قسم التفاوض .
تحديث إعدادات المركز
أولا دعونا نحدث إعدادات المركز لتضمين connect معالج الأحداث أيضا، نحتاج أيضا إلى السماح بالاتصال المجهول بحيث يمكن للعملاء الذين لا يملكون رمز وصول JWT الاتصال بالخدمة.
استخدم الأمر Azure CLI az webpubsub hub update لإنشاء إعدادات معالج الأحداث للمركز Sample_ChatApp .
هام
استبدل <your-unique-resource-name> باسم مورد Web PubSub الذي تم إنشاؤه من الخطوات السابقة.
الآن دعونا نحدث منطق المصدر لمعالجة حدث الاتصال. يمكننا أيضا إزالة نقطة نهاية التفاوض الآن.
كما هو الحال مع ما نقوم به في التفاوض على نقطة النهاية كغرض تجريبي، نقرأ أيضا المعرف من معلمات الاستعلام. في حدث الاتصال، يتم الاحتفاظ باستعلام العميل الأصلي في نص إعادة طلب حدث الاتصال.
الآن دعونا نضيف المنطق لمعالجة حدث azure.webpubsub.sys.connectالاتصال :
// validation: https://learn.microsoft.com/azure/azure-web-pubsub/reference-cloud-events#protection
app.options("/eventhandler", ctx -> {
ctx.header("WebHook-Allowed-Origin", "*");
});
// handle events: https://learn.microsoft.com/azure/azure-web-pubsub/reference-cloud-events#connect
app.post("/eventhandler", ctx -> {
String event = ctx.header("ce-type");
if ("azure.webpubsub.sys.connect".equals(event)) {
String body = ctx.body();
System.out.println("Reading from request body...");
Gson gson = new Gson();
JsonObject requestBody = gson.fromJson(body, JsonObject.class); // Parse JSON request body
JsonObject query = requestBody.getAsJsonObject("query");
if (query != null) {
System.out.println("Reading from request body query:" + query.toString());
JsonElement idElement = query.get("id");
if (idElement != null) {
JsonArray idInQuery = query.get("id").getAsJsonArray();
if (idInQuery != null && idInQuery.size() > 0) {
String id = idInQuery.get(0).getAsString();
ctx.contentType("application/json");
Gson response = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("userId", id);
ctx.result(response.toJson(jsonObject));
return;
}
}
} else {
System.out.println("No query found from request body.");
}
ctx.status(401).result("missing user id");
} else if ("azure.webpubsub.sys.connected".equals(event)) {
String id = ctx.header("ce-userId");
System.out.println(id + " connected.");
ctx.status(200);
} else if ("azure.webpubsub.user.message".equals(event)) {
String id = ctx.header("ce-userId");
String message = ctx.body();
service.sendToAll(String.format("{\"from\":\"%s\",\"message\":\"%s\"}", id, message), WebPubSubContentType.APPLICATION_JSON);
ctx.status(200);
}
});
الآن دعونا نعالج حدث النظام connect ، والذي يجب أن يحتوي على العنوان ce-type ك azure.webpubsub.sys.connect. نضيف المنطق بعد حماية إساءة الاستخدام:
@app.route('/eventhandler', methods=['POST', 'OPTIONS'])
def handle_event():
if request.method == 'OPTIONS' or request.method == 'GET':
if request.headers.get('WebHook-Request-Origin'):
res = Response()
res.headers['WebHook-Allowed-Origin'] = '*'
res.status_code = 200
return res
elif request.method == 'POST':
user_id = request.headers.get('ce-userid')
type = request.headers.get('ce-type')
print("Received event of type:", type)
# Sample connect logic if connect event handler is configured
if type == 'azure.webpubsub.sys.connect':
body = request.data.decode('utf-8')
print("Reading from connect request body...")
query = json.loads(body)['query']
print("Reading from request body query:", query)
id_element = query.get('id')
user_id = id_element[0] if id_element else None
if user_id:
return {'userId': user_id}, 200
return 'missing user id', 401
elif type == 'azure.webpubsub.sys.connected':
return user_id + ' connected', 200
elif type == 'azure.webpubsub.user.message':
service.send_to_all(content_type="application/json", message={
'from': user_id,
'message': request.data.decode('UTF-8')
})
return Response(status=204, content_type='text/plain')
else:
return 'Bad Request', 400
تحديث index.html للاتصال المباشر
الآن دعونا نحدث صفحة الويب للاتصال المباشر بخدمة Web PubSub. هناك شيء واحد يجب الإشارة إليه هو أنه الآن لغرض العرض التوضيحي، يتم ترميز نقطة نهاية خدمة Web PubSub في التعليمات البرمجية للعميل، يرجى تحديث اسم <the host name of your service> مضيف الخدمة في html أدناه بالقيمة من الخدمة الخاصة بك. قد لا يزال من المفيد إحضار قيمة نقطة نهاية خدمة Web PubSub من الخادم الخاص بك، فهو يمنحك المزيد من المرونة وإمكانية التحكم في المكان الذي يتصل به العميل.
<html>
<body>
<h1>Azure Web PubSub Chat</h1>
<input id="message" placeholder="Type to chat...">
<div id="messages"></div>
<script>
(async function () {
// sample host: mock.webpubsub.azure.com
let hostname = "<the host name of your service>";
let id = prompt('Please input your user name');
let ws = new WebSocket(`wss://${hostname}/client/hubs/Sample_ChatApp?id=${id}`);
ws.onopen = () => console.log('connected');
let messages = document.querySelector('#messages');
ws.onmessage = event => {
let m = document.createElement('p');
let data = JSON.parse(event.data);
m.innerText = `[${data.type || ''}${data.from || ''}] ${data.message}`;
messages.appendChild(m);
};
let message = document.querySelector('#message');
message.addEventListener('keypress', e => {
if (e.charCode !== 13) return;
ws.send(message.value);
message.value = '';
});
})();
</script>
</body>
</html>
إعادة تشغيل الخادم
الآن أعد تشغيل الخادم وقم بزيارة صفحة الويب باتباع الإرشادات السابقة. إذا قمت بإيقاف awps-tunnel، يرجى أيضا إعادة تشغيل أداة النفق.
الخطوات التالية
يوفر لك هذا البرنامج التعليمي فكرة أساسية عن كيفية عمل نظام الأحداث في خدمة Azure Web PubSub.
اطلع على البرامج التعليمة الأخرى للحصول على نظرة شاملة حول كيفية استخدام الخدمة.