Artikel ini merupakan terjemahan seri tutorial Real time Chat application built with Vue, Django, RabbitMQ and uWSGI WebSockets bagian keempat yang ditulis oleh Osaetin Daniel.
Daftar isi:
Di bagian 3, kita melihat bagaimana cara membuat API dengan django rest api. Dibagian ini kita akan membangun UI aplikasi Chat dan menghubungkannya dengan
API
yang baru kita bangun. Diakhir
bagian ini kita akan memiliki aplikasi chat yang lengkap sehingga bisa
kita bagikan ke teman-teman dan ber-chat-ing ria.UI/UX Layar Chat
Sebelum melanjutkan, mari kita bahas sebentar UI/UX layar aplikasi Chat yang akan dibuat.Pertama, user harus menekan tombol Start Chatting, lalu di backend, kita membuat sesi chat baru dengan user tersebut sebagai owner (pemilik), selanjutnya, kita membawa user (mengganti URL dan antarmuka chat) ke antarmuka baru dimana user bisa saling berbagi pesan dan mengundang user lain.
Garis biru yang ada di layar Start Chat dan Join Chat menunjukkan bahwa mereka ditangani oleh satu component Vue. Bagian Join Chat akan membuka URL sesi chat yang valid lalu menampilkan jendela chat dengan perintah-perintah yang sebelumnya sudah ada.
Impelementasi
Penulis sudah membuat antarmuka chat menggunakan componentChat.vue
menggunakan bootstrap.<template>
<div class="container">
<div class="row">
<div class="col-sm-6 offset-3">
<div v-if="sessionStarted" id="chat-container" class="card">
<div class="card-header text-white text-center font-weight-bold subtle-blue-gradient">
Share the page URL to invite new friends
</div>
<div class="card-body">
<div class="container chat-body">
<div class="row chat-section">
<div class="col-sm-2">
<img class="rounded-circle" src="http://placehold.it/40/f16000/fff&text=D" />
</div>
<div class="col-sm-7">
<span class="card-text speech-bubble speech-bubble-peer">Hello!</span>
</div>
</div>
<div class="row chat-section">
<div class="col-sm-7 offset-3">
<span class="card-text speech-bubble speech-bubble-user float-right text-white subtle-blue-gradient">
Whatsup, another chat app?
</span>
</div>
<div class="col-sm-2">
<img class="rounded-circle" src="http://placehold.it/40/333333/fff&text=A" />
</div>
</div>
<div class="row chat-section">
<div class="col-sm-2">
<img class="rounded-circle" src="http://placehold.it/40/f16000/fff&text=D" />
</div>
<div class="col-sm-7">
<p class="card-text speech-bubble speech-bubble-peer">
Yes this is Chatire, it's pretty cool and it's Open source
and it was built with Django and Vue JS so we can tweak it to our satisfaction.
</p>
</div>
</div>
<div class="row chat-section">
<div class="col-sm-7 offset-3">
<p class="card-text speech-bubble speech-bubble-user float-right text-white subtle-blue-gradient">
Okay i'm already hacking around let me see what i can do to this thing.
</p>
</div>
<div class="col-sm-2">
<img class="rounded-circle" src="http://placehold.it/40/333333/fff&text=A" />
</div>
</div>
<div class="row chat-section">
<div class="col-sm-7 offset-3">
<p class="card-text speech-bubble speech-bubble-user float-right text-white subtle-blue-gradient">
We should invite james to see this.
</p>
</div>
<div class="col-sm-2">
<img class="rounded-circle" src="http://placehold.it/40/333333/fff&text=A" />
</div>
</div>
</div>
</div>
<div class="card-footer text-muted">
<form>
<div class="row">
<div class="col-sm-10">
<input type="text" placeholder="Type a message" />
</div>
<div class="col-sm-2">
<button class="btn btn-primary">Send</button>
</div>
</div>
</form>
</div>
</div>
<div v-else>
<h3 class="text-center">Welcome !</h3>
<br />
<p class="text-center">
To start chatting with friends click on the button below, it'll start a new chat session
and then you can invite your friends over to chat!
</p>
<br />
<button @click="startChatSession" class="btn btn-primary btn-lg btn-block">Start Chatting</button>
</div>
</div>
</div>
</div>
</template>
<script>
const $ = window.jQuery
export default {
data () {
return {
sessionStarted: false
}
},
created () {
this.username = sessionStorage.getItem('username')
},
methods: {
startChatSession () {
this.sessionStarted = true
this.$router.push('/chats/chat_url/')
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
.btn {
border-radius: 0 !important;
}
.card-footer input[type="text"] {
background-color: #ffffff;
color: #444444;
padding: 7px;
font-size: 13px;
border: 2px solid #cccccc;
width: 100%;
height: 38px;
}
.card-header a {
text-decoration: underline;
}
.card-body {
background-color: #ddd;
}
.chat-body {
margin-top: -15px;
margin-bottom: -5px;
height: 380px;
overflow-y: auto;
}
.speech-bubble {
display: inline-block;
position: relative;
border-radius: 0.4em;
padding: 10px;
background-color: #fff;
font-size: 14px;
}
.subtle-blue-gradient {
background: linear-gradient(45deg,#004bff, #007bff);
}
.speech-bubble-user:after {
content: "";
position: absolute;
right: 4px;
top: 10px;
width: 0;
height: 0;
border: 20px solid transparent;
border-left-color: #007bff;
border-right: 0;
border-top: 0;
margin-top: -10px;
margin-right: -20px;
}
.speech-bubble-peer:after {
content: "";
position: absolute;
left: 3px;
top: 10px;
width: 0;
height: 0;
border: 20px solid transparent;
border-right-color: #ffffff;
border-top: 0;
border-left: 0;
margin-top: -10px;
margin-left: -20px;
}
.chat-section:first-child {
margin-top: 10px;
}
.chat-section {
margin-top: 15px;
}
.send-section {
margin-bottom: -20px;
padding-bottom: 10px;
}
</style>
Ingat bahwa @click merupakan alternatif lain untuk v-on:clickKarena tutorial ini tidak membahas desain, maka template di atas masih ala kadarnya.
Meski begitu, kita tetap harus memberi perhatian pada chat dummy tersebut karena kita perlu membedakan pesan user dengan pesan yang lain.
Disini kita membuat sebuah properti bernama
sessionStarted
yang menentukan apakah sesi chat kita aktif atau tidak. Jika ada sebuah
sesi chat yang aktif, kita akan merender jendela chat, jika tidak maka
kita menampilkan layar Start Chatting.Di dalam method
created
kita mengambil username dari sessionStorage
lalu menyailnnya sebagai properti component Vue.Pembaca mungkin bertanya mengapa tidak menggunakan
data
untuk component ini. Kita tidak memakainya karena properti username
tidak reaktif. Kita tidak memerlukan UI untuk memberikan respon yang
reaktif untuk mengganti nilainya.Sampai saat ini, username tidak akan berubah selama user login (akan aneh kita username kita berubah-ubah).
Kita hanya perlu menyimpan properti yang bersifat reaktif di dalam
data
. Vue tidak akan mengamati secara reaktif atribut yang berada diluar data
.Berikut ini tampilan componen start chatting-nya:
Jika tombol "Start Chatting" dipilih kita akan diberi halaman kosong dan berubah URL. Halaman kosong ini muncul karena tidak ada route yang memenuhi url
/chats/chat_url
. Beruntung router Vue memperbolehkan kita untuk secara mencocokkan parameter URL secara dinamis.Kembali ke router
index.js
lalu ubah route Chat
menjadi:{
path: '/chats/:uri?',
name: 'Chat',
component: Chat
},
Tanda tandanya diakhir memberitahu router vue bahwa parameter uri
bersifat opsional sehingga ia bisa mendeteksi /chats
, /chats/chat_url
bahkan /chats/abazaba
. Apapun yang muncul setelah /matchs
akan cocok.Kita juga bisa mengambil mengambil isi
uri
dengan:this.$route.params
yang akan mengembalikan: Object { uri: "chat_url" }
. Kita akan membutuhkannya nanti.Reload halaman tadi maka kita akan melihat layar Chat
Memulai Sesi Baru
Untuk memulai sesi baru, kita cukup mengirim rikues POST ke endpoint API yang sudah kita buat di bagian 3.created () {
this.username = sessionStorage.getItem('username')
// Setup headers for all requests
$.ajaxSetup({
headers: {
'Authorization': `Token ${sessionStorage.getItem('authToken')}`
}
})
},
methods: {
startChatSession () {
$.post('http://localhost:8000/api/chats/', (data) => {
alert("A new session has been created you'll be redirected automatically")
this.sessionStarted = true
this.$router.push(`/chats/${data.uri}/`)
})
.fail((response) => {
alert(response.responseText)
})
}
}
Di dalam created
kita mengatur header
otorisasi rikues Ajax. Tanpa otorisasi tersebut, rikues akan gagal
karena kita mencoba mengirim post sebagai user yang tidak terdaftar.Mengirim Pesan
Lalu bagaimana caranya mengirim pesan?Caranya dengan mengirimkannya ke endpoint messages. Kita hapus dulu pesan dummy dan menyimpan pesan asli ke dalam
data
sebagai sebuah Array.Berikut ini adalah component
Chat
(tanpa CSS):<template>
<div class="container">
<div class="row">
<div class="col-sm-6 offset-3">
<div v-if="sessionStarted" id="chat-container" class="card">
<div class="card-header text-white text-center font-weight-bold subtle-blue-gradient">
Share the page URL to invite new friends
</div>
<div class="card-body">
<div class="container chat-body">
<div v-for="message in messages" :key="message.id" class="row chat-section">
<template v-if="username === message.user.username">
<div class="col-sm-7 offset-3">
<span class="card-text speech-bubble speech-bubble-user float-right text-white subtle-blue-gradient">
{{ message.message }}
</span>
</div>
<div class="col-sm-2">
<img class="rounded-circle" :src="`http://placehold.it/40/007bff/fff&text=${message.user.username[0].toUpperCase()}`" />
</div>
</template>
<template v-else>
<div class="col-sm-2">
<img class="rounded-circle" :src="`http://placehold.it/40/333333/fff&text=${message.user.username[0].toUpperCase()}`" />
</div>
<div class="col-sm-7">
<span class="card-text speech-bubble speech-bubble-peer">
{{ message.message }}
</span>
</div>
</template>
</div>
</div>
</div>
<div class="card-footer text-muted">
<form>
<div class="row">
<div class="col-sm-10">
<input type="text" placeholder="Type a message" />
</div>
<div class="col-sm-2">
<button class="btn btn-primary">Send</button>
</div>
</div>
</form>
</div>
</div>
<div v-else>
<h3 class="text-center">Welcome !</h3>
<br />
<p class="text-center">
To start chatting with friends click on the button below, it'll start a new chat session
and then you can invite your friends over to chat!
</p>
<br />
<button @click="startChatSession" class="btn btn-primary btn-lg btn-block">Start Chatting</button>
</div>
</div>
</div>
</div>
</template>
<script>
const $ = window.jQuery
export default {
data () {
return {
sessionStarted: false,
messages: [
{"status":"SUCCESS","uri":"040213b14a02451","message":"Hello!","user":{"id":1,"username":"danidee","email":"osaetindaniel@gmail.com","first_name":"","last_name":""}},
{"status":"SUCCESS","uri":"040213b14a02451","message":"Hey whatsup! i dey","user":{"id":2,"username":"daniel","email":"","first_name":"","last_name":""}}
]
}
},
created () {
this.username = sessionStorage.getItem('username')
// Setup headers for all requests
$.ajaxSetup({
headers: {
'Authorization': `Token ${sessionStorage.getItem('authToken')}`
}
})
},
methods: {
startChatSession () {
$.post('http://localhost:8000/api/chats/', (data) => {
alert("A new session has been created you'll be redirected automatically")
this.sessionStarted = true
this.$router.push(`/chats/${data.uri}/`)
})
.fail((response) => {
alert(response.responseText)
})
}
}
}
</script>
Layar Chat akan terlihat seperti ini:Layar Chat menampilkan pesan-pesan dari array.
Kita menggunakan
v-if
untuk membandingkan pengirim pesan apakah user yang sedang login.
Berdasarkan hasilnya, kita bisa menentukan seperti apa pesan itu harus
ditampilkan.Pesan-pesan yang dikirim oleh user ditampilkan ke sebelah kanan dengan warna latar biru sementara yang dikirim oleh user lain ditampilkan di sebelah kiri dengan warna latar biru.
Dengan ini, kita cukup mengirim pesan baru ke dalam list dan Vue akan mengurus bagaimana menampilkan pesan-pesan tersebut.
<script>
const $ = window.jQuery
export default {
data () {
return {
sessionStarted: false, messages: [], message: ''
}
},
created () {
...
},
methods: {
...
postMessage (event) {
const data = {message: this.message}
$.post(`http://localhost:8000/api/chats/${this.$route.params.uri}/messages/`, data, (data) => {
this.messages.push(data)
this.message = '' // clear the message after sending
})
.fail((response) => {
alert(response.responseText)
})
}
}
}
</script>
Kita menambah properti message
ke objek data kita. Properti ini akan mengikuti perubahan teks yang ditulis didalam field input.Mari beritahu Vue tentang template ini:
<form @submit.prevent="postMessage">
<div class="row">
<div class="col-sm-10">
<input v-model="message" type="text" placeholder="Type a message" />
</div>
<div class="col-sm-2">
<button class="btn btn-primary">Send</button>
</div>
</div>
</form>
@submit.prevent
adalah versi pendek dari v-on:submit.prevent
bagian .prevent
mencegah form untuk dikirim ke server. Inilah salah satu alasan yang
membuat penulis menyukai Vue.js. Ia memiliki method pembantu yang
memudahkan.Kita juga bisa melakukannya dengan
event.preventDefault
dibagian postMessage
tapi cara itu bukan gaya Vue.Jika semua berjalan dengan benar, kita seharusnya sudah bisa mengirim pesan dan menampilkannya di UI aplikasi.
Bergabung dengan Sesi Baru
Kita akhirnya bisa mengirim pesan tapi aplikasi chat ini masih hampa karena belum ada lawan bicara.. Bagaimana cara mengundang teman untuk ikut bercakap-cakap disana?Kita juga memiliki masalah lain. Apabila tombol refresh diklik, kita akan dibawa ke layar Start Chatting. Baik pemilik sesi chat maupun teman-temannya yang bergabung tidak bisa melanjutkan sesi tersebut.
Untuk menyelesaikan masalah ini, kita perlu mengirim sebuah rikues
PATCH
ke /api/chats/
dan jika kita bisa mendapatkan user dari hasil yang dikembalikan dari
server artinya mereka sudah berhasil ditambahkan ke sesi chat (atau
sudah bergabung sebelumnya). Lalu, kita bisa mengambil riwayat pesan dan
menampilkannya ke para member.<script>
const $ = window.jQuery
export default {
data () {
return {
sessionStarted: false, messages: [], message: ''
}
},
created () {
this.username = sessionStorage.getItem('username')
// Setup headers for all requests
$.ajaxSetup({
headers: {
'Authorization': `Token ${sessionStorage.getItem('authToken')}`
}
})
if (this.$route.params.uri) {
this.joinChatSession()
}
},
methods: {
startChatSession () {
...
},
postMessage (event) {
...
},
joinChatSession () {
const uri = this.$route.params.uri
$.ajax({
url: `http://localhost:8000/api/chats/${uri}/`,
data: {username: this.username},
type: 'PATCH',
success: (data) => {
const user = data.members.find((member) => member.username === this.username)
if (user) {
// The user belongs/has joined the session
this.sessionStarted = true
this.fetchChatSessionHistory()
}
}
})
},
fetchChatSessionHistory () {
$.get(`http://127.0.0.1:8000/api/chats/${this.$route.params.uri}/messages/`, (data) => {
this.messages = data.messages
})
}
}
}
</script>
Sekarang refresh browser dan pembaca seharusnya sudah bisa untuk melanjutkan chat dan melihat riwayat sebelumnya.Lalu, buka tab baru, login dan masuk ke URL chat tadi. Jika kode yang dimasukkan tadi sudah benar, maka kita akan mendapatkan riwayat pesan di tab baru ini yang artinya user lain bisa bergabung ke sesi chat baru.
REALTIME MESSAGING
Saat ini kita harus melakukan refresh untuk mengambil pesan baru. Idealnya kita ingin proses ini terjadi secara otomatis.Solusinya sudah ada didepan mata.
- Kita sudah memiliki method untuk mengambil semua pesan dari server
- Kita sudah memiliki fungsi
setInterval
- Kita memiliki JavaScript
created () {
this.username = sessionStorage.getItem('username')
// Setup headers for all requests
$.ajaxSetup({
headers: {
'Authorization': `Token ${sessionStorage.getItem('authToken')}`
}
})
if (this.$route.params.uri) {
this.joinChatSession()
}
setInterval(this.fetchChatSessionHistory, 3000)
},
Yang kita lakukan sangat biasa, kita hanya perlu menambah satu baris ini ke dalam
created
.setInterval(this.fetchChatSessionHistory, 3000)
yang akan mengambil riwayat pesan setiap 3 detik sehingga memberi ilusi Realtime messaging pada user.
Yang baru kita lakukan tadi dinamakan
polling
. Untuk aplikasi, teknik ini tidak masalah, tapi jika aplikasi kita besar, polling
bisa menjadi tidak efisien.Ayo kita hitung-hitungan:
Untuk dua user di sebuah sesi (anggap mereka login dalam waktu yang bersamaan). Dalam 3 detik, mereka akan membuat 2 rikues. Dalam satu menit mereka akan membuat 40 rikues. Dalam satu jam itu artinya 2400 rikues. Hanya untuk 2 user saja!. Untuk 100 user yang aktif selama satu jam kita akan mendapat 240.000 rikues!
Server yang bagus tentu sanggup menangani 240k rikues per jam dengan mudah. Tapi, masalah utama disini adalah
polling
yang tidak perlu sehingga menambah beban kerja server yang seharusnya
tidak perlu dilakukan secara terus menerus. (Ingat bahwa setiap rikues
akan memanggil perintah SELECT
di database).Dalam jangka panjang, teknik ini tentu akan menyusahkan server dan yang paling parah, saat user tidak melakukan apa-apa browser tetap terus mengirim rikues bak ada pesan baru maupun tidak. Kita bisa mencari tahu kapan user idle dengan memeriksa waktu terakhir mereka mengetik sesuai dan memanggil
clearInterval
untuk menghentikan polling ke url,
akan tetapi kita masih mengirimkan rikues-rikues yang tidak perlu karena
kita tidak bisa memprediksi waktu yang tepat kapan user idle. Mereka
berhenti mengetik bisa saja karena menunggu user lain membalas bukan
berarti mereka tidak ingin menerima pesan baru.Selain itu, setiap rikues juga menghabiskan bandwidth karena didalamnya ada
headers
, cookies
juga informasi otentikasi yang kita tidak perlukan, kita hanya ingin sebuah pesan.Seharusnya ada cara yang lebih efisien dibandingkan solusi yang sudah diterapkan.
Inilah masalah yang bisa ditangani oleh WebSockets dengan membuka koneksi dua arah antara server dengan klien yang artinya klien tidak perlu meminta server untuk informasi baru. Saat informasi baru tersedia, server akan langsung mengirimkannya ke server.
Lalu, jika klien perlu mengirim informasi ke server, ia bisa mengirimnya di koneksi yang sama.
That’s the exact problem WebSockets solve by opening a persistent bi-directional connection between the server and the client which means the client never needs to ask the server for new information. When it’s available, the server simply pushes it to the client.
WebSocket lebih efisien dibanding polling dan dibagian berikutnya kita akan belajar bagaimana menerapkannya dengan aplikasi kita menggunakan
uWSGI
tanpa mengubah banyak kode yang sudah kita tulis.Sumber : Membuat Aplikasi Web Realtime dengan Django, RabbitMQ dan Vue.js Bagian 4: Menghubungkan Vue dengan Backend
No comments:
Post a Comment