Quarkus WebSockets 下一步
一、簡介
在本文中,我們將了解 Quarkus 框架的quarkus-websockets-next擴充。此擴展是一個新的實驗性擴展,用於在我們的應用程式中支援 WebSocket。
Quarkus WebSockets Next 是一個新擴展,旨在取代舊的 Quarkus WebSockets 擴展。這將比舊的擴展更容易使用且更有效率。
然而,對於 Quarkus 來說不同尋常的是,它不支援Jakarta WebSockets API ,而是提供了一個簡化且更現代的 API 來使用 WebSockets。它使用自己的註釋的類別和方法,提供更大的功能靈活性,同時還提供 JSON 支援等內建功能。
同時,Quarkus WebSockets Next 仍然建構在標準 Quarkus 核心之上。這意味著我們獲得了我們所期望的所有預期效能和可擴展性,同時也受益於 Quarkus 為我們提供的開發經驗。
2. 依賴關係
如果我們要開始一個全新的項目,我們可以使用 Maven 建立我們的結構,並已安裝websockets-next
擴充:
$ mvn io.quarkus.platform:quarkus-maven-plugin:3.16.4:create \
-DprojectGroupId=com.baeldung.quarkus \
-DprojectArtifactId=quarkus-websockets-next \
-Dextensions='websockets-next'
請注意,我們需要為此使用io.quarkus.platform:quarkus-maven-plugin
因為該擴充仍處於實驗階段。
或者,如果我們正在使用現有項目,我們可以簡單地將適當的依賴項新增至pom.xml
檔案:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
3. 伺服器端點
一旦我們準備好應用程式並安裝了websockets-next
擴展,我們就可以開始使用 WebSockets 了。
伺服器端點是在 Quarkus 中透過建立一個用@WebSocket
註解的新類別來建立的:
@WebSocket(path = "/echo")
public class EchoWebsocket {
// WebSocket code here.
}
這將建立一個監聽所提供路徑的端點。與 Quarkus 一樣,如果需要,我們可以在其中使用路徑參數以及固定路徑。
3.1.訊息回調
為了讓我們的 WebSocket 端點有用,我們需要能夠處理訊息。
WebSocket 連線支援兩種類型的消息 - 文字和二進位。我們可以使用@OnTextMessage
或@OnBinaryMessage
註解的方法在伺服器端點中處理這些:
@OnTextMessage
public String onMessage(String message) {
return message;
}
在本例中,我們接收訊息有效負載作為方法參數,並將方法的回傳值傳送到客戶端。因此,這個例子就是我們對回顯伺服器所需的一切——我們按原樣發回每條收到的訊息。
如果有必要,我們還可以接收二進位有效負載而不是文字有效負載。這是透過使用@OnBinaryMessage
註解來完成的:
@OnBinaryMessage
public Buffer onMessage(Buffer message) {
return message;
}
在這種情況下,我們接受並傳回一個io.vertx.core.buffer.Buffer
實例。這將包含收到的訊息的原始位元組。
3.2.方法參數和返回
除了我們的處理程序接收傳入訊息的原始有效負載之外,Quarkus 還可以將許多其他內容傳遞到我們的回呼訊息中。
所有訊息處理程序都必須具有一個代表傳入訊息有效負載的參數。該參數的確切類型決定了我們如何存取它。
正如我們之前所看到的,如果我們使用Buffer
或byte[]
,我們將獲得傳入訊息的確切位元組。如果我們使用String
,這些位元組將首先被解碼為字串。
不過,我們也可以使用更豐富的物件。如果我們使用JsonObject
或JsonArray
,傳入訊息將被視為 JSON 並根據需要進行解碼。或者,如果我們使用 Quarkus 不支援的任何其他類型,Quarkus 將嘗試將傳入訊息作為 JSON 反序列化為該類型:
@OnTextMessage
public Message onTextMessage(Message message) {
return message;
}
record Message(String message) {}
我們也可以使用所有這些相同的類型作為傳回值,在這種情況下,Quarkus 在將訊息發送回客戶端時將完全按照預期序列化訊息。此外,訊息處理程序可以有void
返回,以指示不會返回任何內容作為回應。
除了這些之外,還有一些我們可以接受的其他方法參數。
正如我們所看到的,訊息有效負載將提供一個未註解的String
參數。不過,我們也可以使用@PathParam
註解的String
參數來接收傳入 URL 中該參數的值:
@WebSocket(path = "/chat/:user")
public class ChatWebsocket {
@OnTextMessage(broadcast = true)
public String onTextMessage(String message, @PathParam("user") String user) {
return user + ": " + message;
}
}
我們也可以接受WebSocketConnection
類型的參數,它將代表客戶端和伺服器之間的確切連線:
@OnTextMessage
public Map<String, String> onTextMessage(String message, WebSocketConnection connection) {
return Map.of(
"message", message,
"connection", connection.toString()
);
}
使用它,我們可以存取客戶端和伺服器之間的網路連線的詳細資訊 - 包括連線的唯一 ID、連線建立的時間以及用於連線的 URL 的路徑參數。
我們還可以使用它更直接地與連接交互,透過向其發送訊息甚至強制關閉它:
@OnTextMessage
public void onTextMessage(String message, WebSocketConnection connection) {
if ("close".equals(message)) {
connection.sendTextAndAwait("Goodbye");
connection.closeAndAwait();
}
}
3.3. OnOpen
和OnClose
回調
除了用於接收訊息的處理程序之外,我們還可以使用@OnOpen
註冊首次開啟新連線時的處理程序,以及使用@OnClose
關閉連線時的處理程序:
@OnOpen
public void onOpen() {
LOG.info("Connection opened");
}
@OnClose
public void onClose() {
LOG.info("Connection closed");
}
這些回呼處理程序無法接收任何訊息有效負載,但可以接收前面所述的任何其他方法參數。
此外, @OnOpen
處理程序可以有一個回傳值,該值將被序列化並傳送到客戶端。這對於在連接時立即發送訊息而無需等待客戶端先發送內容非常有用。執行此操作遵循與訊息處理程序的傳回值相同的規則:
@OnOpen
public String onOpen(WebSocketConnection connection) {
return "Hello, " + connection.id();
}
3.4.存取連線
我們已經看到,我們可以將目前連線注入到回呼處理程序中。但是,這並不是我們存取連線詳細資訊的唯一方法。
Quarkus 允許我們使用@Inject
將WebSocketConnection
物件作為 CDI 會話範圍的 bean 注入。這可以注入到我們系統中的任何其他 bean 中,我們可以從那裡存取當前連接:
@ApplicationScoped
public class CdiConnectionService {
@Inject
WebSocketConnection connection;
}
但是,這僅在從 WebSocket 處理程序的上下文中呼叫時才有效。如果我們嘗試從任何其他上下文(包括常規 HTTP 呼叫)存取此內容,那麼我們將拋出jakarta.enterprise.context.ContextNotActiveException
。
我們也可以透過注入OpenConnections
類型的物件來存取所有目前開啟的 WebSocket 連線:
@Inject
OpenConnections connections;
然後,我們不僅可以使用它來查詢所有當前開啟的連接,還可以向它們發送訊息:
public void sendToAll(String message) {
connections.forEach(connection -> connection.sendTextAndAwait(message));
}
與注入單一WebSocketConnection
不同,這可以在任何上下文中正常運作。這允許我們在需要時從任何其他上下文存取 WebSocket 連線。
3.5.錯誤處理
在某些情況下,當我們處理 WebSocket 回呼時,可能會出現問題。 Quarkus 允許我們透過編寫異常處理程序方法來處理從這些方法拋出的任何異常。這些是用@OnError
註釋的任何方法:
@OnError
public String onError(RuntimeException e) {
return e.toString();
}
關於它們可以接收的參數及其傳回值,它們遵循與其他回呼處理程序相同的規則。此外,它們必須有一個代表要處理的異常的參數。
我們可以根據需要編寫任意數量的錯誤處理程序,只要它們都針對不同的異常類別。如果我們有任何重疊 - 換句話說,如果一個是另一個的子類別 - 那麼最具體的將被稱為:
@OnError
public String onIoException(IOException e) {
// Handles IOException and all subclasses.
}
@OnError
public String onException(Exception e) {
// Handles Exception and all subclasses except for IOException.
}
4. 客戶端API
除了允許我們編寫伺服器端點之外,Quarkus 還允許我們編寫可以與其他伺服器通訊的 WebSocket 用戶端。
4.1.基本連接器
編寫 WebSocket 客戶端最基本的方法是使用BasicWebSocketConnector.
這讓我們可以打開連接並發送和接收原始訊息。
首先,我們需要將BasicWebSocketConnector
注入到我們的程式碼中:
@Inject
BasicWebSocketConnector connector;
然後我們可以使用它連接到遠端服務:
WebSocketClientConnection connection = connector
.baseUri(serverUrl)
.executionModel(BasicWebSocketConnector.ExecutionModel.NON_BLOCKING)
.onTextMessage((c, m) -> {
// Handle incoming messages.
})
.connectAndAwait();
作為此連接的一部分,我們註冊一個 lambda 來處理從伺服器接收到的任何傳入訊息。由於 WebSocket 的非同步、全雙工特性,這是必要的。我們不能僅僅將其視為具有請求和回應對的標準 HTTP 連線。
一旦我們打開連接,我們也可以使用它向伺服器發送訊息:
connection.sendTextAndAwait("Hello, World!");
我們可以從回調處理程序內部和外部執行此操作。但是,請記住該連接不是線程安全的,因此我們需要確保永遠不會同時由多個線程寫入它。
除了onTextMessage
回檔之外,我們還可以註冊其他生命週期事件的回呼,包括onOpen()
和onClose()
。
4.2.富客戶端 Bean
基本連接器對於簡單的連接來說已經足夠好了,但有時,我們需要比這更靈活的東西。 Quarkus 還允許我們編寫更豐富的客戶端,類似於我們編寫伺服器端點的方式。
為此,我們需要寫一個用@WebSocketClient
註解的新類別:
@WebSocketClient(path = "/json")
class RichWebsocketClient {
// Client code here.
}
然後,在此類中,我們使用@OnTextMessage
、 @OnOpen
等註釋,以與伺服器端點相同的方式編寫註釋方法:
@OnTextMessage
void onMessage(String message, WebSocketClientConnection connection) {
// Process message here
}
這遵循與伺服器端點有關方法參數和返回值的所有相同規則,除瞭如果我們想要訪問連接詳細信息,我們使用WebSocketClientConnection
而不是WebSocketConnection
。
一旦我們編寫了客戶端類,我們就可以透過注入的WebSocketConnector<T>
實例來使用它:
@Inject
WebSocketConnector<RichWebsocketClient> connector;
使用它,我們與之前類似地創建連接,只是我們不需要提供任何回調,因為我們的客戶端實例將為我們處理所有這些:
WebSocketClientConnection connection = connector
.baseUri(serverUrl)
.connectAndAwait();
此時,我們有了一個WebSocketClientConnection
,我們可以像以前一樣使用它。
如果我們需要存取客戶端實例,我們可以透過為適當的類別注入Instance<T>
來實現:
@Inject
Instance<RichWebsocketClient> clients;
然而,我們需要記住,這個客戶端實例只能從正確的上下文中獲得,並且因為它都是非同步的,所以我們需要確保某些事件已經發生。
5. 結論
在本文中,我們使用websockets-next
擴充功能簡要介紹了 Quarkus 中的 WebSocket。我們已經了解如何使用它來編寫伺服器和客戶端元件。在這裡,我們只討論了這個庫的基礎知識,但它能夠處理許多更高級的場景。
與往常一樣,本文中的所有範例都可以 在 GitHub 上找到。