Di festival XOXO tahun ini, salah satu pesta penutupan rahasia adalah live live khusus untuk mendengarkan album terbaru Neil Cicerega. Jika Anda tidak terbiasa dengan karya Neil, albumnya yang sebelumnya Mouth Moods mungkin memberi Anda gambaran tentang apa yang dimainkan: album konsep yang aneh dan mengejutkan yang jumlahnya setara dengan menjejalkan terlalu banyak Pure Internet ™ masuk ke telinga Anda melalui mashup, referensi, dan pencampuran yang sangat pintar.
Salah satu penyelenggara XOXO mendekat Reed Kavner dan saya akan membuat semacam instalasi interaktif untuk menemani pesta pendengaran: semacam dinding gif di mana pendengar dapat memposting GIF dan ephemera Internet aneh lainnya sebagai cara untuk tidak mencatat bagian tersebut.
Saya baru memulai pekerjaan baru saya di Microsoft Pendukung Azure tim, jadi saya menganggap ini sebagai kesempatan untuk mencoba sejumlah besar teknologi Azure untuk pertama kalinya!
Dinding Internet Murni

Tujuannya adalah untuk membuat dinding GIF dan teks yang benar-benar luar biasa. Kami ingin orang-orang dapat live-annotate musik dengan menarik musik itu sendiri referensi, sementara itu sendiri bermain ke semacam estetika visual internet-y uap.
Kami memutuskan untuk mengandalkan Slack daripada membangun UI kami sendiri. XOXO memiliki komunitas Slack yang aktif sepanjang tahun, dan sebagian besar peserta telah masuk ke festival Slack di ponsel mereka. Ini menangani sejumlah besar masalah sulit bagi kami: otentikasi, memetakan pos ke nama asli (penting untuk menangani pelanggaran Kode Etik) dan sepenuhnya menangani pencarian GIF (termasuk filter konten eksplisit).
Tingkat kepercayaan yang kami berikan di komunitas kami (bersama dengan kebijakan nama asli kami) berarti kami juga dapat memungkinkan orang untuk mengirim pesan teks biasa, bukan hanya GIF. Bersamaan dengan itu, penting bagi kami bahwa kami mendukung semua emoji kebiasaan yang didukung oleh Slack kami, karena komunitas telah membangun banyak koleksi yang bermakna.
Satu keputusan disain sadar lainnya adalah membatasi kecepatan seberapa sering orang memposting. Saat Anda memposting GIF atau beberapa teks, teks itu muncul di layar dan perlahan-lahan tumbuh seiring waktu, tetapi GIF yang lebih baru yang datang setelah Anda akan menutupi milik Anda. Kami cukup mengatur ukuran mulai dari posting berdasarkan seberapa baru penulis terakhir diposting. Jika seseorang ingin duduk di sana dan mengirim spam ke GIF secepat mungkin, kami ingin membiarkan mereka melakukan itu, tetapi membuat konten mereka mulai lebih kecil berarti mereka tidak akan mengorbankan orang lain yang mengganggu.
Tanpa server? Dengan klien yang sudah berjalan lama ?!
Sementara Reed membangun front-end JS (tersedia di GitHub), saya bertanggung jawab atas infrastruktur server untuk mengirim pesan ke browser web.
Saya tertarik menggunakan Fungsi Azure Cloud untuk menghindari keharusan memutar server saya sendiri pada sesuatu seperti EC2 atau Heroku. Dengan alat "serverless" seperti Azure Cloud Functions, Anda hanya mengunggah satu fungsi mengambang bebas (JS dalam kasus saya), dan alih-alih Anda mempertahankan runtime server, Azure bertanggung jawab untuk memutar instance dan menjalankan fungsi Anda kapan saja seseorang mencapai titik akhir HTTP yang ditentukan. Dalam kasus kami, titik akhir itu adalah webhook yang dipicu oleh aplikasi Slack API.
Di sisi browser, kami menganggap Anda akan menggunakan koneksi WebSocket untuk mengirim pesan ke klien. Namun, WebSockets membutuhkan koneksi yang tahan lama. Dengan fungsi serverless, kami hanya memiliki lingkungan eksekusi saat fungsi kami dipanggil, yang membuatnya agak sulit bagi aplikasi browser untuk memiliki koneksi WS yang persisten!
Masukkan SignalR!
SignalR adalah teknologi yang dirancang untuk memudahkan server untuk menyiarkan pesan real-time ke berbagai klien. Berbeda dengan WebSockets karena searah – hanya dapat digunakan untuk mengirim pesan dari server ke klien, bukan sebaliknya.
Ini sebagian besar dimaksudkan untuk penggunaan yang lebih besar, lebih fokus pada perusahaan: ia dengan anggun menangani hal-hal yang tidak disukai WebSockets seperti autentikasi tangan dan koneksi yang lebih kompleks. Ini beroperasi pada tingkat abstraksi yang lebih tinggi daripada WebSockets: secara default, ia bahkan menggunakan WebSockets di browser sebagai mekanisme transpornya, tetapi dapat kembali ke metode alternatif secara otomatis (mis. Polling) tanpa Anda perlu khawatir tentang hal itu sebagai pengembang.
Kami tidak peduli dengan janji-janji keamanan atau keandalan SignalR, tapi kami peduli bahwa Azure menawarkan layanan SignalR yang di-host yang dapat beroperasi dengan Fungsi Azure Cloud. Ini memungkinkan kita mengatasi masalah memerlukan koneksi yang berjalan lama ke server yang berumur pendek!

Klien browser tersambung ke layanan Azure SignalR, yang menyatakan bahwa koneksi selama browser terbuka. Sementara itu, setiap kali instance Azure Function berputar dan dijalankan, ia dapat secara independen terhubung ke layanan SignalR dan mendorong pesan ke antrian. Kami mendapatkan fleksibilitas menggunakan fungsi tanpa server untuk membangun aplikasi simpul kami, tetapi masih dapat mempertahankan koneksi WebSocket yang sudah berjalan lama ke aplikasi klien. Rapi!
Menggunakan SignalR dengan Fungsi Cloud: Mendeklarasikan Input dan Output
Saya tidak akan menjelaskan di sini cara menyiapkan dengan Fungsi Azure – lihat tutorial ini untuk memulai menggunakan ekstensi Kode VS resmi, yang sejauh ini merupakan cara termudah untuk mengelola bit fiddly – tapi saya lakukan ingin berbicara sedikit tentang bagaimana saya mengintegrasikan SignalR dengan Fungsi cloud saya.
Fungsi Azure memiliki cara yang sangat elegan untuk menangani dependensi eksternal ke dalam kode Anda. Fungsi Azure hanya satu file dengan fungsi kode tunggal, tetapi menyertainya adalah a function.json
file config yang menentukan semua input dan output untuk fungsi diterima. Tambahkan banyak dependensi ke function.json
file, dan secara otomatis akan disuntikkan ke fungsi Anda sebagai argumen!
Menyiapkan SignalR membutuhkan dua fungsi yang berbeda. Pertama, diperlukan jabat tangan pengaturan singkat: browser yang ingin terhubung ke instance SignalR kami harus mencapai titik akhir HTTP yang mengembalikan string koneksi ajaib yang diperlukan untuk menyelesaikan koneksi
{
"dengan disabilitas": Salah,
"binding": [[[[
{
"authLevel": "anonim",
"mengetik": "httpTrigger",
"arah": "di",
"nama": "req"
},
{
"mengetik": "http",
"arah": "di luar",
"nama": "res"
},
{
"mengetik": "signalRConnectionInfo",
"nama": "info koneksi",
"hubName": "obrolan",
"arah": "di"
}
]
}
modul.ekspor = async fungsi (konteks, req, koneksiInfo) {
konteks.res.json(koneksiInfo);
};
Anda dapat melihat di sini kami menyiapkan fungsi yang memiliki permintaan / input / output respon ExpressJS standar, serta tambahan koneksiInfo
argumen yang kami tentukan di function.json
file harus berisi info koneksi SignalR ke antrian pesan yang disebut "obrolan".
Fungsi "memposting pesan" kami sebenarnya Slack webhook memiliki sedikit perbedaan function.json
file, karena menggunakan koneksi SignalR sebagai output (pada dasarnya antrian pesan yang mendorongnya pesan) daripada input:
{
"dengan disabilitas": Salah,
"binding": [{[{[{[{
"authLevel": "anonim",
"mengetik": "httptrigger",
"arah": "di",
"nama": "req",
"metode": [[[[
"pos"
]
},
{
"mengetik": "http",
"arah": "di luar",
"nama": "res"
},
{
"mengetik": "signalR",
"nama": "$ pengembalian",
"hubName": "obrolan",
"arah": "di luar"
}
}
Itu "name": "$ return"
properti berarti bahwa apa pun fungsi kita kembali berakhir didorong ke "obrolan"
Antrian SignalR sebagai pesan, yang pada gilirannya akan didorong ke semua klien SignalR yang terhubung.
Dengan adanya dua fungsi ini, kode klien yang sebenarnya untuk terhubung ke antrian SignalR cukup sederhana:
const koneksi = baru signalR.HubConnectionBuilder()
.denganUrl(`https: // xoxo-closing-party.azurewebsites.net / api`)
.configureLogging(signalR.LogLevel.Informasi)
.membangun();
koneksi.di(& # 39;pesan baru& # 39;, fungsi(m) {
addPost(m); // m adalah gumpalan JSON yang berisi apa pun fungsi kami kirim
});
koneksi.dekat(() => menghibur.log(& # 39;terputus& # 39;));
koneksi.mulai()
.kemudian(() => menghibur.log("Terhubung!"))
.menangkap(menghibur.kesalahan);
Anda akan melihat perpustakaan SignalR sendiri yang bertanggung jawab untuk memukul titik akhir handshake dan kemudian berlangganan pesan baru.
Emoji itu Sulit!
Dengan kode ini sejauh ini, backend saya mengirim pesan ke JS webapp Reed yang berisi pesan teks dan, jika berlaku, data GIF. Tapi semua emoji datang sebagai nama pendek teks gaya Slack. misalnya alih-alih emoji "🎉", pesan berisi string : tada:
.
Memperbaiki ini sebenarnya berarti menangani dua hal yang benar-benar terpisah: emoji Unicode yang tepat, dan set emoji khusus instance Slack kami.
Untuk emoji "resmi", saya dapat menemukan orang lain yang sudah menulis skrip cepat untuk mengambil pemetaan Slack. CLI one-liner yang saya modifikasi dari web memberi saya pemetaan objek JSON dari nama pendek ke titik kode Unicode.
ikal -s https://raw.githubusercontent.com/iamcal/emoji-data/master/emoji.json |
npx ramda-cli
& # 39; tolak (.unified.includes ("-")) & # 39;
& # 39; chain (emoji) -> emoji.short_names.map -> {... emoji, short_name: it} & # 39;
& # 39; urutkan berdasarkan (.short_name) & # 39;
& # 39; index-by (.short_name) & # 39; & # 39; peta -> "0x # {it.unified}" & # 39; > emoji.json
{
...,
"sempoa": "0x1F9EE",
"a B C": "0x1F524",
"a B C D": "0x1F521",
"menerima": "0x1F251",
"dewasa": "0x1F9D1",
"aerial_tramway": "0x1F6A1",
"airplane_arriving": "0x1F6EC",
"airplane_departure": "0x1F6EB",
"alarm_clock": "0x23F0",
"alien": "0x1F47D",
"ambulans": "0x1F691",
"bejana Yunani": "0x1F3FA",
"jangkar": "0x2693",
"malaikat": "0x1F47C",
"marah": "0x1F4A2",
"marah": "0x1F620",
"sedih": "0x1F627",
"semut": "0x1F41C",
"apel": "0x1F34E",
"Aquarius": "0x2652",
...
}
Dari sana, saya dapat menggunakan fungsi penggantian string JS bawaan untuk mengganti semua emoji Unicode yang valid dengan poin kode Unicode yang tepat:
const ganti Emmo = pesan => {
const standardEmojiMap = memerlukan("./emoticon");
kembali pesan.menggantikan(/ :(. *?) :/ g, (asli, nama) => {
jika (standardEmojiMap[[[[nama]) {
kembali Tali.dariCodePoint(standardEmojiMap[[[[nama]);
} lain {
// Ini bukan daftar emoji Unicode kami - baik itu emoji khusus atau omong kosong
kembali asli;
}
});
};
Emoji khusus agak rumit. Slack menawarkan API titik akhir untuk mengambil emoji khusus untuk setiap instance Slack yang diberikan.
Yang terpenting, meskipun ia mengembalikan peta yang kuncinya adalah nama emoji, nilainya dapat berupa satu dari dua hal: URL ke gambar yang di-host CDN untuk emoji itu, atau nama nama emoji lain yang dibuatnya. alias untuk. Jadi ketika melakukan pencarian / penggantian saya sendiri, saya perlu memeriksa apakah itu alias, dan jika demikian pastikan untuk menyelesaikannya. Ketika saya akhirnya mendarat di URL yang sebenarnya, saya mengganti : emoticon:
dengan HTML ![]()
tag diarahkan ke URL CDN.
Ini membuat segalanya sedikit lebih sulit untuk Reed: namun dia menampilkan teks ini di layar, dia sekarang perlu memastikan bahwa ![]()
tag diberikan dengan benar sebagai HTML, tetapi juga melakukannya dengan cara di mana
tags wouldn't be executed as arbitrary JavaScript. It added some complexity, but we concluded that was easier than alternative methods of specifying "this image should be injected at this point within the text".
I cached this custom emoji data from Slack in an Azure CosmosDB database. While it's not like our custom emoji updated all that frequently, I needed to build out that caching infrastructure to handle fetching names as well.
Messages from Slack only contained unique user IDs, not human-readable names, so just like emoji I ended up needing to make some API calls to Slack's user list API endpoint so I could do my own lookup.
I'm not going to go into that process of using CosmosDB right now — our name cache (but not our emoji cache!) ended up falling over in production, and it was suggested to me after-the-fact that Azure Table Storage would have been a better fit for our needs.
The End-Result
...and that's (more or less) all there was to it! I glossed over a whole lot here, but you can check out the GitHub repo to see the code itself. I was impressed how well Azure Functions and SignalR worked — messages came through within a second or two of people sending them, it scaled effortlessly even when we were getting hundreds of messages per minute, and everybody loved the installation!
I'd love to see someone else take our code (or just inspiration from us) and make something similar! Shout at me on Twitter if you do anything cool like this.