البرنامج التعليمي: إنشاء تطبيق دردشة باستخدام خدمة Azure Web PubSub

في البرنامج التعليمي نشر الرسائل والاشتراك فيها، ستتعلم أساسيات نشر الرسائل والاشتراك فيها باستخدام Azure Web PubSub. في هذا البرنامج التعليمي، ستتعلم نظام الأحداث في Azure Web PubSub وتستخدمه لإنشاء تطبيق ويب كامل مع وظائف الاتصال في الوقت الحقيقي.

في هذا البرنامج التعليمي، تتعلم كيفية:

  • إنشاء مثيل خدمة Web PubSub
  • قم بتكوين إعدادات معالج الأحداث لـ Azure Web PubSub
  • أحداث Hanlde في خادم التطبيق وإنشاء تطبيق دردشة في الوقت الحقيقي

إذا لم يكن لديك اشتراك في Azure، فأنشئ حساب Azure مجاني قبل أن تبدأ.

المتطلبات الأساسية

  • يتطلب هذا الإعداد الإصدار 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 في الأمثلة التالية.

az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1

يظهر إخراج هذا الأمر خصائص المورد المنشأ حديثًا. دوّن اثنتين من الخصائص المذكورة أدناه:

  • اسم المورد: الاسم الذي قدمته للمعلمة --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 الذي أحضرته.

export WebPubSubConnectionString="<connection-string>"
SET WebPubSubConnectionString=<connection-string>

إعداد المشروع

المتطلبات الأساسية

قم بإنشاء التطبيق

في Azure Web PubSub، هناك دوران، الخادم والعميل. يشبه هذا المفهوم أدوار الخادم والعميل في تطبيق الويب. الخادم مسؤول عن إدارة العملاء والاستماع والرد على رسائل العميل. العميل مسؤول عن إرسال رسائل المستخدم وتلقيها من الخادم وتصورها للمستخدم النهائي.

في هذا البرنامج التعليمي، نقوم بإنشاء تطبيق ويب للدردشة في الوقت الحقيقي. في تطبيق ويب حقيقي، تتضمن مسؤولية الخادم أيضاً مصادقة العملاء وخدمة صفحات الويب الثابتة لواجهة مستخدم التطبيق.

نستخدم ASP.NET Core 8 لاستضافة صفحات الويب ومعالجة الطلبات الواردة.

أولا دعونا ننشئ تطبيق ويب ASP.NET Core في chatapp مجلد.

  1. إنشاء تطبيق ويب جديد.

    mkdir chatapp
    cd chatapp
    dotnet new web
    
  2. أضف app.UseStaticFiles() Program.cs لدعم استضافة صفحات ويب ثابتة.

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.UseStaticFiles();
    
    app.Run();
    
  3. أنشئ ملف HTML واحفظه باسم wwwroot/index.html، ونستخدمه لواجهة مستخدم تطبيق الدردشة لاحقا.

    <html>
      <body>
        <h1>Azure Web PubSub Chat</h1>
      </body>
    </html>
    

يمكنك اختبار الخادم عن طريق تشغيل dotnet run --urls http://localhost:8080 والوصول http://localhost:8080/index.html في المستعرض.

إضافة نقطة نهاية التفاوض

في البرنامج التعليمي نشر ورسالة الاشتراك، يستهلك المشترك سلسلة الاتصال مباشرة. في تطبيق العالم الحقيقي، ليس من الآمن مشاركة سلسلة الاتصال مع أي عميل، لأن سلسلة الاتصال لديه امتياز كبير للقيام بأي عملية للخدمة. الآن، لنجعل الخادم الخاص بك يستهلك سلسلة الاتصال، ويعرض negotiate نقطة نهاية للعميل للحصول على عنوان URL الكامل مع رمز الوصول المميز. وبهذه الطريقة، يمكن للخادم إضافة برنامج وسيط المصادقة قبل negotiate نقطة النهاية لمنع الوصول غير المصرح به.

قم أولا بتثبيت التبعيات.

dotnet add package Microsoft.Azure.WebPubSub.AspNetCore

الآن دعونا نضيف /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.

يمكنك اختبار واجهة برمجة التطبيقات هذه عن طريق الوصول 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.

app.MapWebPubSubHub<Sample_ChatApp>("/eventhandler/{*path}");
app.Run();

الآن، داخل الفئة 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.

تحديث صفحة الويب

الآن دعنا نحدث 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 الذي تم إنشاؤه من الخطوات السابقة.

az webpubsub hub create -n "<your-unique-resource-name>" -g "myResourceGroup" --hub-name "Sample_ChatApp" --event-handler url-template="tunnel:///eventhandler" user-event-pattern="*" system-event="connected"

تشغيل awps-tunnel محليا

تنزيل وتثبيت awps-tunnel

تعمل الأداة على Node.js الإصدار 16 أو أعلى.

npm install -g @azure/web-pubsub-tunnel-tool

استخدام سلسلة الاتصال الخدمة وتشغيلها

export WebPubSubConnectionString="<your connection string>"
awps-tunnel run --hub Sample_ChatApp --upstream http://localhost:8080

تشغيل خادم الويب

الآن تم تعيين كل شيء. دعونا نشغل خادم الويب ونلعب مع تطبيق الدردشة أثناء العمل.

الآن قم بتشغيل الخادم باستخدام dotnet run --urls http://localhost:8080.

يمكنك الاطلاع على نموذج التعليمة البرمجية لهذا البرنامج التعليمي كاملاً من هنا.

افتح 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 الذي تم إنشاؤه من الخطوات السابقة.

az webpubsub hub update -n "<your-unique-resource-name>" -g "myResourceGroup" --hub-name "Sample_ChatApp" --allow-anonymous true --event-handler url-template="tunnel:///eventhandler" user-event-pattern="*" system-event="connected" system-event="connect"

تحديث منطق المصدر لمعالجة حدث الاتصال

الآن دعونا نحدث منطق المصدر لمعالجة حدث الاتصال. يمكننا أيضا إزالة نقطة نهاية التفاوض الآن.

كما هو الحال مع ما نقوم به في التفاوض على نقطة النهاية كغرض تجريبي، نقرأ أيضا المعرف من معلمات الاستعلام. في حدث الاتصال، يتم الاحتفاظ باستعلام العميل الأصلي في نص إعادة طلب حدث الاتصال.

داخل الفئة Sample_ChatApp، تجاوز OnConnectAsync() لمعالجة connect الحدث:

sealed class Sample_ChatApp : WebPubSubHub
{
    private readonly WebPubSubServiceClient<Sample_ChatApp> _serviceClient;

    public Sample_ChatApp(WebPubSubServiceClient<Sample_ChatApp> serviceClient)
    {
        _serviceClient = serviceClient;
    }

    public override ValueTask<ConnectEventResponse> OnConnectAsync(ConnectEventRequest request, CancellationToken cancellationToken)
    {
        if (request.Query.TryGetValue("id", out var id))
        {
            return new ValueTask<ConnectEventResponse>(request.CreateResponse(userId: id.FirstOrDefault(), null, null, null));
        }

        // The SDK catches this exception and returns 401 to the caller
        throw new UnauthorizedAccessException("Request missing id");
    }

    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();
    }
}

تحديث 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.

اطلع على البرامج التعليمة الأخرى للحصول على نظرة شاملة حول كيفية استخدام الخدمة.