Tutorial - Publikasikan dan berlangganan pesan antara klien WebSocket menggunakan subprotokol
Artikel
Dalam Tutorial membangun aplikasi obrolan, Anda mempelajari cara menggunakan API WebSocket untuk mengirim dan menerima data dengan Azure Web PubSub. Anda dapat melihat bahwa tidak ada protokol yang diperlukan saat klien berkomunikasi dengan layanan. Misalnya, Anda dapat mengirim semua jenis data menggunakan WebSocket.send(), dan server menerimanya apa adanya. Proses API WebSocket mudah digunakan, tetapi fungsionalitasnya terbatas. Misalnya, Anda tidak dapat menentukan nama peristiwa saat mengirim peristiwa ke server Anda, atau menerbitkan pesan ke klien lain alih-alih mengirimkannya ke server Anda. Dalam tutorial ini, Anda mempelajari cara menggunakan subprotokola untuk memperluas fungsionalitas klien.
Dalam tutorial ini, Anda akan mempelajari cara:
Membuat instans layanan Web PubSub
Menghasilkan URL lengkap untuk membuat koneksi WebSocket
Memublikasikan pesan antar klien WebSocket menggunakan subprotokol
Jika Anda lebih suka menjalankan perintah referensi CLI secara lokal, instal Azure CLI. Jika Anda menjalankan Windows atau macOS, pertimbangkan untuk menjalankan Azure CLI dalam kontainer Docker. Untuk informasi lebih lanjut, lihat Cara menjalankan Azure CLI di kontainer Docker.
Jika Anda menggunakan instalasi lokal, masuk ke Azure CLI dengan menggunakan perintah login az. Untuk menyelesaikan proses autentikasi, ikuti langkah-langkah yang ditampilkan di terminal Anda. Untuk opsi masuk lainnya, lihat Masuk dengan Azure CLI.
Saat Anda diminta, instal ekstensi Azure CLI pada penggunaan pertama. Untuk informasi selengkapnya tentang ekstensi, lihat Menggunakan ekstensi dengan Azure CLI.
Jalankan versi az untuk menemukan versi dan pustaka dependen yang diinstal. Untuk meningkatkan ke versi terbaru, jalankan peningkatan az.
Penyiapan ini memerlukan CLI Azure versi 2.22.0 atau lebih tinggi. Jika menggunakan Azure Cloud Shell, versi terbaru sudah terinstal.
Membuat instans Azure Web PubSub
Buat grup sumber daya
Grup sumber daya adalah kontainer logis yang disebarkan dan dikelola oleh sumber daya Azure. Gunakan perintah az group create untuk membuat grup sumber daya bernama myResourceGroup di eastus lokasi.
az group create --name myResourceGroup --location EastUS
Membuat instans Web PubSub
Jalankan az extension add untuk menginstal atau meningkatkan ekstensi webpubsub ke versi saat ini.
az extension add --upgrade --name webpubsub
Gunakan perintah az webpubsub create Azure CLI untuk membuat Web PubSub di grup sumber daya yang telah Anda buat. Perintah berikut membuat sumber daya Web PubSub Gratis di bawah grup sumber daya myResourceGroup di EastUS:
Penting
Setiap sumber daya Web PubSub harus memiliki nama yang unik. Ganti <your-unique-resource-name> dengan nama Web PubSub Anda dalam contoh berikut.
Keluaran dari perintah ini menunjukkan properti sumber daya yang baru dibuat. Perhatikan dua properti yang tercantum di bawah:
Nama Sumber Daya: Nama yang Anda berikan untuk parameter --name di atas.
hostName: Dalam contoh, nama host adalah <your-unique-resource-name>.webpubsub.azure.com/.
Pada titik ini, akun Azure Anda adalah satu-satunya yang berwenang untuk melakukan operasi apa pun di sumber daya baru ini.
Mendapatkan ConnectionString untuk penggunaan di masa mendatang
Penting
String koneksi menyertakan informasi otorisasi yang diperlukan agar aplikasi Anda mengakses layanan Azure Web PubSub. Kunci akses di dalam string koneksi mirip dengan kata sandi root untuk layanan Anda. Di lingkungan produksi, selalu berhati-hatilah untuk melindungi kunci akses Anda. Gunakan Azure Key Vault untuk mengelola dan memutar kunci Anda dengan aman. Hindari mendistribusikan kunci akses ke pengguna lain, melakukan hard-coding, atau menyimpannya di mana saja dalam teks biasa yang dapat diakses orang lain. Putar kunci Anda jika Anda yakin bahwa kunci tersebut mungkin telah disusupi.
Gunakan perintah Azure CLI az webpubsub key untuk mendapatkan ConnectionString layanan. <your-unique-resource-name> Ganti tempat penampung dengan nama instans Azure Web PubSub Anda.
az webpubsub key show --resource-group myResourceGroup --name <your-unique-resource-name> --query primaryConnectionString --output tsv
Salin string koneksi untuk digunakan nanti.
Salin Koneksi ionString yang diambil dan gunakan nanti dalam tutorial ini sebagai nilai <connection_string>.
Klien dapat memulai koneksi WebSocket menggunakan subprotokol tertentu. Layanan Azure Web PubSub mendukung subprotokola yang dipanggil json.webpubsub.azure.v1 untuk memberdayakan klien untuk melakukan penerbitan/berlangganan langsung melalui layanan Web PubSub alih-alih pulang pergi ke server upstream. Periksa subprotokol WebSocket JSON yang didukung Azure Web PubSub untuk detail tentang subprotokol.
Jika Anda menggunakan nama protokol lain, nama tersebut akan diabaikan oleh layanan dan passthrough ke server di sambungkan penanganan aktivitas, sehingga Anda dapat membangun protokol Anda sendiri.
Kini mari membuat aplikasi web menggunakan subprotokol json.webpubsub.azure.v1.
Buat server.py untuk menghosting API dan halaman web /negotiate.
import json
import sys
from http.server import HTTPServer, SimpleHTTPRequestHandler
from azure.messaging.webpubsubservice import WebPubSubServiceClient
service = WebPubSubServiceClient.from_connection_string(sys.argv[1], hub='stream')
class Resquest(SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/':
self.path = 'public/index.html'
return SimpleHTTPRequestHandler.do_GET(self)
elif self.path == '/negotiate':
roles = ['webpubsub.sendToGroup.stream',
'webpubsub.joinLeaveGroup.stream']
token = service.get_client_access_token(roles=roles)
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({
'url': token['url']
}).encode())
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Usage: python server.py <connection-string>')
exit(1)
server = HTTPServer(('localhost', 8080), Resquest)
print('server started')
server.serve_forever()
Mari kita navigasikan ke direktori /src/main/java/com/webpubsub/tutorial, buka file App.java di penyunting Anda, gunakan Javalin.create untuk melayani file statis:
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 io.javalin.Javalin;
public class App {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Expecting 1 arguments: <connection-string>");
return;
}
// create the service client
WebPubSubServiceClient service = new WebPubSubServiceClientBuilder()
.connectionString(args[0])
.hub("chat")
.buildClient();
// start a server
Javalin app = Javalin.create(config -> {
config.addStaticFiles("public");
}).start(8080);
// Handle the negotiate request and return the token to the client
app.get("/negotiate", ctx -> {
GetClientAccessTokenOptions option = new GetClientAccessTokenOptions();
option.addRole("webpubsub.sendToGroup.stream");
option.addRole("webpubsub.joinLeaveGroup.stream");
WebPubSubClientAccessToken token = service.getClientAccessToken(option);
// return JSON string
ctx.result("{\"url\":\"" + token.getUrl() + "\"}");
return;
});
}
}
Bergantung pada penyiapan, Anda mungkin perlu secara eksplisit mengatur tingkat bahasa ke Java 8 di pom.xml. Tambahkan cuplikan berikut:
Buat halaman HTML dengan konten di bawah ini dan simpan sebagai wwwroot/index.html:
Buat halaman HTML dengan konten di bawah ini dan simpan sebagai public/index.html:
Buat halaman HTML dengan konten di bawah ini dan simpan sebagai public/index.html:
Buat halaman HTML dengan konten di bawah ini dan simpan ke /src/main/resources/public/index.html:
<html>
<body>
<div id="output"></div>
<script>
(async function () {
let res = await fetch('/negotiate')
let data = await res.json();
let ws = new WebSocket(data.url, 'json.webpubsub.azure.v1');
ws.onopen = () => {
console.log('connected');
};
let output = document.querySelector('#output');
ws.onmessage = event => {
let d = document.createElement('p');
d.innerText = event.data;
output.appendChild(d);
};
})();
</script>
</body>
</html>
Kode di atas terhubung ke layanan dan mencetak pesan apa pun yang diterima ke halaman. Perubahan utama adalah bahwa kita menentukan subprotokol saat membuat koneksi WebSocket.
Kami menggunakan alat Manajer Rahasia untuk .NET Core guna mengatur string koneksi. Jalankan perintah di bawah ini, ganti <connection_string> dengan yang diambil di langkah sebelumnya dan buka http://localhost:5000/index.html di browser:
dotnet user-secrets init
dotnet user-secrets set Azure:WebPubSub:ConnectionString "<connection-string>"
dotnet run
Sekarang jalankan perintah di bawah ini, ganti <connection-string> dengan ConnectionString yang diambil di langkah sebelumnya, dan buka http://localhost:8080 di browser:
export WebPubSubConnectionString="<connection-string>"
node server
Sekarang jalankan perintah di bawah ini, ganti <connection-string> dengan ConnectionString yang diambil di langkah sebelumnya, dan buka http://localhost:8080 di browser:
python server.py "<connection-string>"
Sekarang jalankan perintah di bawah ini, ganti <connection-string> dengan ConnectionString yang diambil di langkah sebelumnya, dan buka http://localhost:8080 di browser:
Jika Anda menggunakan Chrome, Anda dapat menekan F12 atau klik kanan ->Inspect ->Developer Tools, dan pilih tab Jaringan . Muat halaman web, dan Anda dapat melihat koneksi WebSocket dibuat. Pilih untuk memeriksa koneksi WebSocket, Anda dapat melihat pesan peristiwa di bawah connected ini diterima di klien. Anda dapat melihat bahwa Anda mendapatkan connectionId dibuat untuk klien ini.
Anda dapat melihat bahwa dengan bantuan subprotokol, Anda bisa mendapatkan beberapa metadata koneksi saat koneksi adalah connected.
Klien sekarang menerima pesan JSON alih-alih teks biasa. Pesan JSON berisi informasi lebih lanjut seperti jenis dan sumber pesan. Jadi Anda dapat menggunakan informasi ini untuk melakukan lebih banyak pemrosesan ke pesan (misalnya menampilkan pesan dengan gaya yang berbeda jika dari sumber yang berbeda), yang dapat Anda temukan di bagian selanjutnya.
Memublikasikan pesan dari klien
Dalam tutorial Membangun aplikasi obrolan, ketika klien mengirim pesan melalui koneksi WebSocket ke layanan Web PubSub, layanan memicu peristiwa pengguna di sisi server Anda. Dengan subprotokola, klien memiliki lebih banyak fungsi dengan mengirim pesan JSON. Misalnya, Anda dapat menerbitkan pesan langsung dari klien melalui layanan Web PubSub ke klien lain.
Ini berguna jika Anda ingin mengalirkan sejumlah besar data ke klien lain secara real time. Mari kita gunakan fitur ini untuk membangun aplikasi streaming log, yang dapat melakukan streaming log konsol ke browser secara waktu nyata.
using System;
using System.Net.Http;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace stream
{
class Program
{
private static readonly HttpClient http = new HttpClient();
static async Task Main(string[] args)
{
// Get client url from remote
var stream = await http.GetStreamAsync("http://localhost:5000/negotiate");
var url = (await JsonSerializer.DeserializeAsync<ClientToken>(stream)).url;
var client = new ClientWebSocket();
client.Options.AddSubProtocol("json.webpubsub.azure.v1");
await client.ConnectAsync(new Uri(url), default);
Console.WriteLine("Connected.");
var streaming = Console.ReadLine();
while (streaming != null)
{
if (!string.IsNullOrEmpty(streaming))
{
var message = JsonSerializer.Serialize(new
{
type = "sendToGroup",
group = "stream",
data = streaming + Environment.NewLine,
});
Console.WriteLine("Sending " + message);
await client.SendAsync(Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text, true, default);
}
streaming = Console.ReadLine();
}
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, null, default);
}
private sealed class ClientToken
{
public string url { get; set; }
}
}
}
Buat stream.js dengan konten berikut.
const WebSocket = require('ws');
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
async function main() {
let res = await fetch(`http://localhost:8080/negotiate`);
let data = await res.json();
let ws = new WebSocket(data.url, 'json.webpubsub.azure.v1');
let ackId = 0;
ws.on('open', () => {
process.stdin.on('data', data => {
ws.send(JSON.stringify({
type: 'sendToGroup',
group: 'stream',
ackId: ++ackId,
dataType: 'text',
data: data.toString()
}));
});
});
ws.on('message', data => console.log("Received: %s", data));
process.stdin.on('close', () => ws.close());
}
main();
Kode di atas membuat koneksi WebSocket ke layanan dan kemudian setiap kali menerima beberapa data, kode menggunakan ws.send() untuk memublikasikan data. Untuk memublikasikan ke yang lainnya, Anda hanya perlu mengatur type ke sendToGroup dan menentukan nama grup di pesan.
Buka jendela bash untuk program stream, dan pasang dependensi websockets:
mkdir stream
cd stream
# Create venv
python -m venv env
# Active venv
source ./env/bin/activate
pip install websockets
Buat stream.py dengan konten berikut.
import asyncio
import sys
import threading
import time
import websockets
import requests
import json
async def connect(url):
async with websockets.connect(url, subprotocols=['json.webpubsub.azure.v1']) as ws:
print('connected')
id = 1
while True:
data = input()
payload = {
'type': 'sendToGroup',
'group': 'stream',
'dataType': 'text',
'data': str(data + '\n'),
'ackId': id
}
id = id + 1
await ws.send(json.dumps(payload))
await ws.recv()
if __name__ == '__main__':
res = requests.get('http://localhost:8080/negotiate').json()
try:
asyncio.get_event_loop().run_until_complete(connect(res['url']))
except KeyboardInterrupt:
pass
Kode di atas membuat koneksi WebSocket ke layanan dan kemudian setiap kali menerima beberapa data, kode menggunakan ws.send() untuk memublikasikan data. Untuk memublikasikan ke yang lainnya, Anda hanya perlu mengatur type ke sendToGroup dan menentukan nama grup di pesan.
Mari gunakan terminal lain dan kembali ke folder akar untuk membuat aplikasi konsol streaming logstream-streaming dan beralih ke folder logstream-streaming:
Sekarang mari kita gunakan WebSocket untuk menghubungkan ke layanan. Mari buka direktori /src/main/java/com/webpubsub/quickstart, buka file App.java di penyunting Anda, dan ganti kode dengan kode di bawah ini:
package com.webpubsub.quickstart;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.WebSocket;
import java.util.concurrent.CompletionStage;
import com.google.gson.Gson;
public class App
{
public static void main( String[] args ) throws IOException, InterruptedException
{
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/negotiate"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
Gson gson = new Gson();
String url = gson.fromJson(response.body(), Entity.class).url;
WebSocket ws = HttpClient.newHttpClient().newWebSocketBuilder().subprotocols("json.webpubsub.azure.v1")
.buildAsync(URI.create(url), new WebSocketClient()).join();
int id = 0;
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String streaming = reader.readLine();
App app = new App();
while (streaming != null && !streaming.isEmpty()){
String frame = gson.toJson(app.new GroupMessage(streaming + "\n", ++id));
System.out.println("Sending: " + frame);
ws.sendText(frame, true);
streaming = reader.readLine();
}
}
private class GroupMessage{
public String data;
public int ackId;
public final String type = "sendToGroup";
public final String group = "stream";
GroupMessage(String data, int ackId){
this.data = data;
this.ackId = ackId;
}
}
private static final class WebSocketClient implements WebSocket.Listener {
private WebSocketClient() {
}
@Override
public void onOpen(WebSocket webSocket) {
System.out.println("onOpen using subprotocol " + webSocket.getSubprotocol());
WebSocket.Listener.super.onOpen(webSocket);
}
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
System.out.println("onText received " + data);
return WebSocket.Listener.super.onText(webSocket, data, last);
}
@Override
public void onError(WebSocket webSocket, Throwable error) {
System.out.println("Bad day! " + webSocket.toString());
WebSocket.Listener.super.onError(webSocket, error);
}
}
private static final class Entity {
public String url;
}
}
Buka direktori yang berisi file pom.xml dan jalankan proyek menggunakan perintah di bawah ini
Anda dapat melihat ada konsep baru "grup" di sini. Grup adalah konsep logis di hub tempat Anda dapat memublikasikan pesan ke sekelompok koneksi. Di hub, Anda dapat memiliki beberapa grup dan satu klien dapat berlangganan beberapa grup secara bersamaan. Saat menggunakan subprotokol, Anda hanya dapat memublikasikan ke grup alih-alih menyiarkan ke seluruh hub. Untuk detail tentang istilah, periksa konsep dasar.
Karena kita menggunakan grup di sini, kita juga perlu memperbarui halaman web index.html untuk bergabung dengan grup saat koneksi WebSocket didirikan di panggilan balik ws.onopen.
Anda dapat melihat klien bergabung ke grup dengan mengirimkan pesan dalam jenis joinGroup.
Perbarui juga logika panggilan balik ws.onmessage sedikit untuk mengurai respon JSON dan mencetak pesan hanya dari grup stream sehingga bertindak sebagai printer streaming langsung.
ws.onmessage = event => {
let message = JSON.parse(event.data);
if (message.type === 'message' && message.group === 'stream') {
let d = document.createElement('span');
d.innerText = message.data;
output.appendChild(d);
window.scrollTo(0, document.body.scrollHeight);
}
};
Untuk pertimbangan keamanan, secara default klien tidak dapat memublikasikan atau berlangganan grup dengan sendirinya. Jadi Anda melihat bahwa kami mengatur roles ke klien saat menghasilkan token:
Sampel kode lengkap dari tutorial ini dapat ditemukan di sini.
Langkah berikutnya
Tutorial ini memberi Anda gambaran dasar tentang cara menyambungkan ke layanan Web PubSub dan cara menerbitkan pesan ke klien yang terhubung menggunakan subprotokola.
Lihat tutorial lain untuk mempelajari lebih lanjut cara menggunakan layanan ini.