除了常見的 HTTP 協定之外,面對不同的需求,在後端有時候也會需要處理其他的協定。
今天我們來看怎麼用 Ktor 處理 websocket 的需求
類似於 HTTP 通訊協定,WebSockets 也是一種通訊協定。不過不一樣的地方是,它可以在用戶端和伺服器之間建立一個持久、雙向的連線。所以在後端的邏輯上,不同於 HTTP 接收到請求,處理好回傳之後就沒事了。websocket 通常會需要建立一個長期存在的進程或線程,來持續的維持這個雙向連線。
很幸運的,由於 Ktor 本身非常善用 coroutine 來建立非同步的服務,所以在維持雙向連線上的語法也非常的單純。我們只需要引用 Ktor 的 WebSocket 套件,就可以完成這個任務
跟前面的套件一樣,在建立專案時可以從官網內點選 WebSocket 套件並安裝。
如果要在既有專案上建立,一樣可以在 build.gradle.kts
加上
implementation("io.ktor:ktor-websockets:$ktor_version")
然後重新 build 整個專案。
安裝並重新引入之後,我們就可以開始使用 WebSocket 套件了。
我們來看看官方提供的範例
package com.example
import io.ktor.server.application.*
import io.ktor.server.html.*
import io.ktor.server.http.content.*
import io.ktor.server.plugins.di.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import java.time.Duration
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
import kotlinx.html.*
fun Application.configureSockets() {
install(WebSockets) {
pingPeriod = 15.seconds
timeout = 15.seconds
maxFrameSize = Long.MAX_VALUE
masking = false
}
routing {
webSocket("/echo") {
send("Please enter your name")
for (frame in incoming) {
frame as? Frame.Text ?: continue
val receivedText = frame.readText().trim()
if (receivedText.equals("bye", ignoreCase = true)) {
close(CloseReason(CloseReason.Codes.NORMAL, "Client said BYE"))
} else {
send(Frame.Text("Hi, $receivedText!"))
}
}
}
}
}
這邊我們看到,由於建立的是一個持續的連線,所以我們需要一個迴圈來不斷地從 incoming
這個 ReceiveChannel
物件接收對話。
接收進來的資料以 Frame 作為單位,這邊我們假設收到的都會是純文字,所以試著將 frame 轉換成 Frame.Text
一直到我們對話輸入 bye
之後,伺服器才會中斷連線。
運作起來之後,我們就可以用 websocket 測試工具來測試這個功能。我個人的習慣是使用指令列工具 websocat
測試
在 mac 上的安裝可以用 brew
brew install websocat
安裝好之後, 我們就可以測試我們的 ws://0.0.0.0:8080/echo
路由了
websocat ws://0.0.0.0:8080/echo
Please enter your name
recca
Hi, recca!
alice
Hi, alice!
bye
^C
今天的內容就到這邊,我們明天見!