一种zure SignalR is a fully managed service that makes it easy to add highly-scalable real-time messaging to any application using WebSockets and other protocols. SignalR Service has integrations for ASP.NET and Azure Functions. Other backend apps can use the service's RESTful HTTP API.
在本文中,我们将研究使用Azure SignalR Service进行实时通信的好处,以及如何使用服务的HTTP API将其与Java Spring Boot聊天应用程序集成。
Azure SignalR Service overview
尽管许多库或框架都支持WebSocket,但适当地扩展实时应用程序并非易事。 它通常需要设置Redis或其他基础结构来充当背板。 Azure SignalR服务完成了管理客户端连接和向外扩展的所有工作。 我们可以使用简化的API与之集成。
SignalR服务使用由ASP.NET普及的SignalR实时协议。 它提供了一个编程模型,该模型抽象出了底层的通信渠道。 代替我们自己管理单个WebSocket连接,我们可以通过单个API调用向所有人,单个用户或任意组连接发送消息。
SignalR还为每个连接协商最佳协议。 它更喜欢使用WebSockets,但是如果它不能用于给定的连接,它将自动回退到服务器发送的事件或长时间轮询。
There are many SignalR client SDKs for connecting to Azure SignalR Service. They're available in .NEŤ, JavaScript/ŤypeScript, and Java. There are also third-party open source clients for languages like Swift and Python.
Azure SignalR Service RESTful HTTP API
服务器应用程序(例如Java Spring应用程序)可以使用HTTP API将信号从SignalR Service发送到其连接的客户端。 还有一些用于管理组成员身份的API。 我们可以将用户分为任意组,并将消息发送到一组连接。
The API documentation can be found on GitHub. We'll be using these APIs in the rest of this article.
Integrating SignalR Service with Java
将SignalR Service与应用程序集成的四个主要步骤。
- 创建一个Azure SignalR服务实例添加API端点(/谈判)在SignalR客户端的Java应用中检索令牌以连接到SignalR Service使用SignalR客户端SDK创建连接(我们将在浏览器中使用JavaScript应用)通过Java应用发送消息
How it works
- SignalR客户端SDK使用以下命令请求SignalR服务URL和访问令牌:/谈判终点客户端SDK自动使用该信息建立与SignalR Service的连接Java应用程序使用SignalR Service的RESTful API将消息发送到已连接的客户端
Create a SignalR Service instance
我们可以使用Azure CLI或Azure门户创建一个SignalR Service的免费实例。 要使用REST API,请将其配置为使用无服务器模式。
For more information on how to create an SignalR Service instance, check out the docs.
Add a "negotiate" endpoint
SignalR Service由密钥保护。 我们从不希望将此密钥公开给我们的客户。 相反,在后端应用程序中,我们为要连接的每个客户端生成一个JSON Web令牌(JWT),并用此密钥签名。 SignalR客户端向我们在应用程序中定义的HTTP端点发送请求,以检索此JWT。
这是我们的Spring Boot应用程序中的协商端点。 它生成令牌并将其返回给调用方。 我们可以(可选)将用户ID嵌入令牌中,以便我们可以发送针对该用户的消息。
@PostMapping("/signalr/negotiate")
public SignalRConnectionInfo negotiate() {
String hubUrl = signalRServiceBaseEndpoint + "/client/?hub=" + hubName;
String userId = "12345"; // optional
String accessKey = generateJwt(hubUrl, userId);
return new SignalRConnectionInfo(hubUrl, accessKey);
}
请注意,路线以/谈判。 这是必需的,因为它是SignalR客户端使用的约定。
生成JWT的方法使用Java JWT(jjwt)库,并用SignalR Service密钥对其进行签名。 注意,我们将受众设置为中心URL。
集线器是我们的消息的虚拟名称空间。 单个SignalR服务中可以有多个集线器。 例如,我们可以将中心用于聊天消息,将另一个用于通知。
private String generateJwt(String audience, String userId) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
long expMillis = nowMillis + (30 * 60 * 1000);
Date exp = new Date(expMillis);
byte[] apiKeySecretBytes = signalRServiceKey.getBytes(StandardCharsets.UTF_8);
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
JwtBuilder builder = Jwts.builder()
.setAudience(audience)
.setIssuedAt(now)
.setExpiration(exp)
.signWith(signingKey);
if (userId != null) {
builder.claim("nameid", userId);
}
return builder.compact();
}
Create a client connection
在我们的网页上,我们引入SignalR JavaScript SDK并创建一个连接。 我们添加了一个或多个事件侦听器,当从服务器接收到消息时将调用这些侦听器。 最后,我们开始连接。
<script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@3.0.0/dist/browser/signalr.min.js"></script>
const connection = new signalR.HubConnectionBuilder()
.withUrl(`/signalr`)
.withAutomaticReconnect()
.build()
connection.on('newMessage', function(message) {
// do something with the message
})
connection.start()
.then(() => data.ready = true)
.catch(console.error)
请注意,我们使用的是协商网址,/谈判分割。 SignalR客户端SDK自动尝试协商附加/谈判URL。
启动应用程序并打开网页时,我们应该在浏览器控制台中看到成功的连接。
Send messages from the Java app
现在我们的客户已连接到SignalR服务,我们可以向他们发送消息了。
我们的示例是一个聊天应用程序,因此我们的前端应用程序将调用该端点来发送消息。 我们使用与/谈判端点以生成JWT。 这次,JWT在我们向服务发送消息的HTTP请求中用作承载令牌。
@PostMapping("/api/messages")
public void sendMessage(@RequestBody ChatMessage message) {
String hubUrl = signalRServiceBaseEndpoint + "/api/v1/hubs/" + hubName;
String accessKey = generateJwt(hubUrl, null);
Unirest.post(hubUrl)
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + accessKey)
.body(new SignalRMessage("newMessage", new Object[] { message }))
.asEmpty();
}
现在我们的应用程序应该可以正常工作了! 要支持成千上万的连接,我们只需要进入Azure门户并使用滑块增加连接单元的数量。