halo
halo copied to clipboard
期待插件支持 websocket 接口
你当前使用的版本
2.11.0
描述一下此特性
我想在插件中实现服务端向前端推送消息,目前想到比较好的方案为 websocket,但插件好像不支持开发 websocket 接口。 场景:在插件中想要执行一些 shell 脚本,并且将 shell 脚本的输出实时的推送给前端。 shell 脚本会在 halo 容器中下载一些东西,前端可以同步下载进度和 shell 脚本的输出。
感觉轮询比较耗费性能,websocket 可能是个更好的选择,希望 halo 支持插件 websocket。 或者你们是否有更好的方案?
附加信息
No response
Hi @Rainsheep , thank you for reaching out here!
/kind feature /area core /area plugin
我们将研究一下插件中实现 WebSocket。
允许让插件添加 SimpleUrlHandlerMapping 即可实现 websocket 功能
我也需要websocket进行前后端通信
I'm willing to contribute the feature according to https://github.com/halo-dev/halo/issues/5285#issuecomment-1918299978.
/assign
经过一段时间研究,想要在插件中实现 WebSocket 功能似乎不是定义一个 SimpleUrlHandlerMapping 那么容易。尤其是需要动态注册 WebSocketHandler,还需要对 WebSocket 接口增加权限校验,需要完整考虑后才能够正式实现。
目前,我有一个方案暂时可以替代 WebSocket 方案,那就是 Server-Sent Event,具体样例可参考:
- https://www.baeldung.com/spring-server-sent-events
- https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
经过一段时间研究,想要在插件中实现 WebSocket 功能似乎不是定义一个 SimpleUrlHandlerMapping 那么容易。尤其是需要动态注册 WebSocketHandler,还需要对 WebSocket 接口增加权限校验,需要完整考虑后才能够正式实现。
请参考以下的示例: 在 halo 中简单写一个类用来代理插件中的 SimpleHandlerMapping 然后在插件启动时将其注册到代理的 HandlerMapping 中以便被 DispatcherHandler 使用到
@Component
public class PluginHandlerMappingIntegrator {
@Autowired
private PluginSimpleUrlHandlerMapping pluginSimpleUrlHandlerMapping;
// simple example
@EventListener
public void onPluginStartUp(HaloPluginStartedEvent event) {
var pluginWrapper = event.getPlugin();
var p = pluginWrapper.getPlugin();
if (!(p instanceof SpringPlugin springPlugin)) {
return;
}
var simpleUrlHandlerMappings = springPlugin.getApplicationContext()
.getBeansOfType(SimpleUrlHandlerMapping.class);
simpleUrlHandlerMappings.values().forEach(handlerMapping -> {
pluginSimpleUrlHandlerMapping.registerHandlerMapping(handlerMapping);
});
}
@Component
public static class PluginSimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
public PluginSimpleUrlHandlerMapping() {
// must be lower than SimpleUrlHandlerMapping
setOrder(Ordered.LOWEST_PRECEDENCE - 100);
}
@Override
public void initApplicationContext() throws BeansException {
// do nothing
}
public void registerHandlerMapping(SimpleUrlHandlerMapping handlerMapping) {
// simple example
handlerMapping.getHandlerMap()
.forEach((pattern, o) -> {
registerHandler(pattern.getPatternString(), o);
});
}
}
}
然后在随便在哪个插件中写上
public class MyWebSocketHandler implements WebSocketHandler {
@Override
@NonNull
public Mono<Void> handle(WebSocketSession session) {
// 这里编写处理WebSocket会话的逻辑
System.out.println("\nWebSocket session established---------->\n\n");
return session.send(session.receive()
.map(msg -> session.textMessage("Echo: " + msg.getPayloadAsText())));
}
}
@Configuration(proxyBeanMethods = false)
public class WebSocketConfig {
@Bean
public SimpleUrlHandlerMapping handlerMapping() {
return new SimpleUrlHandlerMapping() {{
setUrlMap(Collections.singletonMap("/ws", new MyWebSocketHandler()));
setOrder(10);
}};
}
}
然后测试 websocket 的链接并发送一个 ping 字符串作为示例的结果如下:
如此, API是插件定义的,halo支持 watch的verb,可以在角色模板中声明
如果只是 SSE 现在 halo 不需要做任何事情插件本身就是可以用的, AI 插件就用了SSE 分段发送文本
时间原因,不再追踪此问题,close
Hi @Rainsheep ,期望能够保留当前 Issue,直到 Halo 在插件中彻底支持 WebSocket。
open,后续我可能不再继续追踪此 issue,希望 @liuchangfitcloud 或其它开发者可以关注下。 也希望官方尽快彻底支持
感谢 @guqing 在 https://github.com/halo-dev/halo/issues/5285#issuecomment-2005782748 提供的思路,我将尝试验证并实现。
为了规范 API 设计,我计划在插件中进行如下实现即可拥有 WebSocket 的处理能力:
@Component
public class MyWebSocketConfigurer implements WebSocketConfigurer {
@Override
public WebSocketBuilder builder() {
var handler = session -> {
var messages = session.receive()
.map(message -> {
var payload = message.getPayloadAsText();
return session.textMessage(payload.toUpperCase());
});
return session.send(messages);
};
var firstHandler = ...;
var secondHandler = ...;
return new WebSocketBuilder()
.groupVersion(GroupVersion.parseApiVersion("my-plugin.halowrite.com/v1alpha1"))
.endpoint("/first-resources", firstHandler)
.endpoint("/second-resources", secondHandler);
}
}
插件启动时,Halo core 中将匹配 /apis/my-plugin.halowrite.com/v1alpha1/first-resources
路由,并交给 firstHandler 进行处理。
权限配置样例如下:
- apiGroups: ["my-plugin.halowrite.com"]
resources: ["first-resources"]
verbs: ["watch"]
如果有任何意见或者建议,非常欢迎提出来讨论。
我觉得可以
为了和 CustomEndpoint 风格保持一致,这里更正一下在插件中的实现示例:
@Component
public class MyWebSocketEndpoint implements WebSocketEndpoint {
@Override
public GroupVersion groupVersion() {
return GroupVersion.parseApiVersion("my-plugin.halowrite.com/v1alpha1");
}
@Override
public String urlPath() {
return "/resources";
}
@Override
public WebSocketHandler handler() {
return session -> {
var messages = session.receive()
.map(message -> {
var payload = message.getPayloadAsText();
return session.textMessage(payload.toUpperCase());
});
return session.send(messages);
};
}
}