用于在服务端接收客户端的请求
//基本配置
SockJSHandlerOptions options = new SockJSHandlerOptions()
.setHeartbeatInterval(2000)
//配置允许的客户端源
.setOrigin("http://localhost:5173/");
//创建SockJSHandler
SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options);
//接收客户端请求
sockJSHandler.socketHandler(sockJSSocket -> {
sockJSSocket.handler(buffer -> {
// 处理消息
log.info("Received message: " + buffer.toString());
// 将消息回写
sockJSSocket.write(buffer);
});
});
//将SockJSHandler注册到子路由当中,必须是子路由
Router router = Router.router(vertx);
router.route("/im/*")
.subRouter(sockJSHandler.socketHandler(sockJSSocket -> {
sockJSSocket.handler(buffer -> {
// 处理消息
log.info("Received message: " + buffer.toString());
// 将消息回写
sockJSSocket.write(buffer);
});
}));
//服务端监听地址
httpServer.requestHandler(router).listen(8088);
在vue中使用SockJS示例:
安装:npm install sockjs-client
代码:
将sockJSSocket写入到event bus中
EventBus eventBus = vertx.eventBus();
Router router = Router.router(vertx);
SockJSHandlerOptions options = new SockJSHandlerOptions()
//开启注册事件处理器
.setRegisterWriteHandler(true)
//如果是集群,需要额外配置
.setLocalWriteHandler(false);
SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options);
router.route("/myapp/*")
.subRouter(sockJSHandler.socketHandler(sockJSSocket -> {
// 获取 writeHandlerID 并将其存放 (例如放在LocalMap中)
String writeHandlerID = sockJSSocket.writeHandlerID();
eventBus.send(writeHandlerID, Buffer.buffer("foo"));
}));
//服务端监听地址
httpServer.requestHandler(router).listen(8088);
作用:使得 event bus 不仅可以在服务器端多个 Vert.x 实例中使用,还可以通过运行在浏览器里的 JavaScript 访问
服务端:
Router router = Router.router(vertx);
SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
// 连接桥配置
SockJSBridgeOptions options = new SockJSBridgeOptions();
// 将连接桥挂载到子路由上
router
.route("/eventbus/*")
.subRouter(sockJSHandler.bridge(options));
//服务端监听地址
httpServer.requestHandler(router).listen(8088);
客户端:
安装:npm install @vertx/eventbus-bridge-client.js
代码:
守护连接桥
按上面的例子此时还无法接收消息,因为为避免event bus将信息发送到任意客户端和服务端,所以SockJS连接桥默认会拒绝所有消息。因此客户端和服务端之间需要建立匹配规则来守护连接桥,即通过SockJSBridgeOptions配置。
每一个匹配规则对应一个 PermittedOptions
对象:
setAddress
:定义消息可以被发送到哪些地址。setAddressRegex
:通过正则表达式来定义消息可以被发送到哪些地址。setMatch
:通过消息的结构来控制消息是否可被发送。该配置中定义的每一个字段必须在消息中存在,且值一致。 目前仅适用于 JSON 格式的消息。SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
// 客户端 -> 服务端 的匹配规则
// 允许客户端向地址 `server1` 发送消息
PermittedOptions inboundPermitted1 = new PermittedOptions()
.setAddress("server1");
// 允许客户端向地址 `server2` 发送,且消息必须包含有值为ailu的name字段
PermittedOptions inboundPermitted2 = new PermittedOptions()
.setAddress("server2")
.setMatch(new JsonObject().put("name", "ailu"));
// 服务端 -> 客户端 的匹配规则
// 允许服务端向客户端地址为 `client1` 的消息
PermittedOptions outboundPermitted1 = new PermittedOptions()
.setAddress("client1");
// 允许服务端向客户端地址为 `client2.` 开头的消息
PermittedOptions outboundPermitted2 = new PermittedOptions()
.setAddressRegex("client2\\..+");
//将匹配规则注册到SockJSBridgeOptions中
SockJSBridgeOptions options = new SockJSBridgeOptions().
addInboundPermitted(inboundPermitted1).
addInboundPermitted(inboundPermitted2).
addOutboundPermitted(outboundPermitted1).
addOutboundPermitted(outboundPermitted2);
// 将连接桥挂载到路由器上
router
.route("/eventbus/*")
.subRouter(sockJSHandler.bridge(options));
处理事件总线桥事件:希望在桥时上发生事件时(BridgeEvent
实例进)收到通知,可以提供一个处理器在调用 bridge
时调用,事件类型有:
可以通过BridgeEvent.getRawMessage
获取事件的原始信息,获取内容如下:
//正常事件存储的数据
{
"type": "send"|"publish"|"receive"|"register"|"unregister",
"address": 发送/发布/注册/注销的信息总线地址
"body": 消息体
}
//SOCKET_ERROR时存储的数据
{
"type": "err",
"failureType": "socketException",
"message": "可选的,来自被引发的异常的消息"
}
事件(BridgeEvent
)是Promise
的一个实例,处理完事件后可以用true来完成promise
以进一步处理。如果不希望处理事件,则可以用false来完成promise,以过滤桥上面的信息,例子:
Router router = Router.router(vertx);
// 让客户端发送到 "server"的任何消息通过
PermittedOptions inboundPermitted = new PermittedOptions()
.setAddress("server");
SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
SockJSBridgeOptions options = new SockJSBridgeOptions()
.addInboundPermitted(inboundPermitted);
// 将桥挂载到子路由器上
router
.route("/eventbus/*")
.subRouter(sockJSHandler
.bridge(options, bridgeEvent -> {
//如果发布类型为 SEND 或 PUBLISH,表示客户端发送消息
if(bridgeEvent.type() == BridgeEventType.SEND
|| bridgeEvent.type() == BridgeEventType.PUBLISH){
log.info("Bridge event: " + bridgeEvent.type());
log.info("收到消息:" + bridgeEvent.getRawMessage());
//获取socket对象
SockJSSocket socket = bridgeEvent.socket();
//消息传递给客户端
ChatMessge msg = ChatMessage.builder().content("AI").build();
socket.write(Json.encode(msg));
}
// 注意不要忘了complete事件!
bridgeEvent.complete(true);
})
);
修改原始消息:改变消息体、添加消息头
sockJSHandler.bridge(options, be -> {
if (
be.type() == BridgeEventType.PUBLISH ||
be.type() == BridgeEventType.SEND) {
// 添加一些头
JsonObject headers = new JsonObject()
.put("header1", "val")
.put("header2", "val2");
JsonObject rawMessage = be.getRawMessage();
rawMessage.put("headers", headers);
be.setRawMessage(rawMessage);
}
be.complete(true);
})
也可以直接通过Event Bus来交互消息
//向clinet1地址发送消息
vertx.eventBus().send("client1",Json.encode(ChatMessage.builder().content("ailu").build()));
消息授权
可以通过setMatch
方法进行权限控制
或者通过 setRequiredAuthority
方法来指定对于登录用户,需要具有哪些权限才允许访问这个消息,此时需要配置一个 Vert.x 认证处理器来处理登录和授权。
例如:
PermittedOptions inboundPermitted = new PermittedOptions()
.setAddress("demo");
// 仅限用户已登录并且拥有权限 `write`
.setRequiredAuthority("write");
//配置匹配规则
SockJSBridgeOptions options = new SockJSBridgeOptions()
.addInboundPermitted(inboundPermitted);
SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
//创建Session存储器、注册Session处理器
router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));
// 设置基础认证处理器
AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
router.route("/eventbus/*").handler(basicAuthHandler);
// 将连接桥挂载到路由器上
router
.route("/eventbus/*")
.subRouter(sockJSHandler.bridge(options));
解决:使用唯一token进行校验
服务端创建
router.route().handler(CSRFHandler.create(vertx, "abracadabra"));
客户端携带"X-XSRF-TOKEN"请求头,值需要与服务端协商
HTTP严格传输安全性(HSTS)是一种Web安全策略机制, 可帮助保护网站免受中间人攻击,例如协议降级攻击和cookie劫持。 它允许Web服务器要求Web浏览器应仅使用提供传输层安全性 (TLS/SSL)的HTTPS连接自动与其进行交互
router.route().handler(HSTSHandler.create());
内容安全策略(CSP)是安全性的附加层,有助于检测和缓解某些类型的攻击, 包括跨站点脚本(XSS)和数据注入攻击。
CSP设计为完全向后兼容, 不支持CSP的浏览器只是忽略它,照常运行,默认为Web内容的标准同源策略。 如果该站点不提供CSP标头, 则浏览器同样会使用标准的同源策略。
router.route().handler(
CSPHandler.create()
.addDirective("default-src", "*.trusted.com"));
X-Frame-Options
HTTP响应标头可用于指示是否应允许浏览器在 frame
,iframe
,embed
或 object
中呈现页面。 网站可以通过确保其内容未嵌入其他网站来避免点击劫持攻击。
仅当访问文档的用户使用支持 X-Frame-Options
的浏览器时, 才提供附加的安全性。
如果指定 DENY
,则从其他站点加载时,不仅在fram中加载页面失败,而且从同一站点加载时,也会失败。 另一方面,如果您指定 SAMEORIGIN
,则只要frame中包含该页面的站点与提供该页面的站点相同, 您仍可以在frame中使用该页面。
router.route().handler(XFrameHandler.create(XFrameHandler.DENY));