我目前正在使用 Clojure、Pedestal 和 Jetty 在 Web 应用程序中实现服务器发送事件 (SSE)。
当我在后端打印消息时,通道已打开,当我调用
io.pedestal.http.sse/send-event
时,它返回 true,但是在前端,我在浏览器控制台中的 Javascript 中没有看到 console.log() 打印。就好像没有收到数据一样。我在Postman中测试了SSE连接,它是成功的,但响应是(empty)
。
后端:
(def SSE-REGISTRY (atom {}))
(defn get-user-channel [token]
(get @SSE-REGISTRY token))
(defn test-print [channel]
(println "channel:" channel) ;; channel:#object[clojure.core.async.impl.chan...]
(println "channel opened:" (not (chan/closed? channel))) ;; channel opened: true
(println "sent-event:"
(sse/send-event channel "status"
(json/write-str {:id 1
:workflowId 3
:status :GOOD}) ;; sent-event: true
(defn send-sse-msg [name data id]
(when-let [sse-channels (vals @SSE-REGISTRY)]
(doseq [channel sse-channels]
(when-not (chan/closed? channel)
(test-print channel)
(sse/send-event channel name data id)))))
(def sse-route ["/rest/sse" :get
(sse/start-event-stream send-sse-msg 60 100)
:route-name :sse])
(defn create-sse-channel []
(async/chan (async/sliding-buffer 100)))
前端:
const protocol = window.location.protocol;
const host = window.location.port;
const sseUrl = protocol + '//'+ host + '/rest/sse';
const RUN_BUTTON_STATUS_TIME = 2000;
const sseInitOptionsMap = {
headers: {
'Content-Type': 'text/event-stream; charset=utf-8',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache'
},
withCredentials: true,
https: {rejectUnauthorized: true}
};
export const eventSource = new EventSource(sseUrl, sseInitOptionsMap);
eventSource.addEventListener('integrationStatus', sendStatusHandler);
eventSource.addEventListener('stopAction', sendStopActionHandler);
eventSource.onopen = (e) => {
console.log("SSE connection opened:" + e);
};
eventSource.onerror = (e) => {
console.log("error:" + e);
if (e.readyState == EventSource.CLOSED) {
console.log("connection closed:" + e);
} else {
eventSource.close();
console.log("SSE connection closed:" + e);
}
};
export function sendStatusHandler(event) {
const data = JSON.parse(event.data);
console.log("data:" + data);
let id = data.id;
let workflowId = data.workflowId;
let status = data.status;
cljsDisp("set-global-operation-status", id, workflowId, status);
configurtorActionRunButtonStatus(id, workflowId, status);
setTimeout(cljsDisp("get-last-run-action-data", id, workflowId, status),
300);
}
function configurtorActionRunButtonStatus (id, workflowId, status) {
if (status === "GOOD") {
showStatus(id, workflowId, true);
} else if (status === "BAD") {
showStatus(id, workflowId, true);
cljsDisp("configurator-error-message-show", id, workflowId, true);
} else {
cljsDisp("configurator-action-run-button-status-visible?", id, workflowId, false);
}
}
export function sendStopWorkflowHandler(event) {
const data = JSON.parse(event.data);
console.log("data:" + data); // prints nothing
let workflowId = data.workflowId;
cljsDisp("stop-action-by-sse-msg", workflowId);
}
function showStatus(integrationId, operationId, showStatus) {
setTimeout(cljsDisp("configurator-action-run-button-status-visible?",
integrationId, operationId, showStatus),
RUN_BUTTON_STATUS_TIME);
}
export function closeSse() {
if (eventSource.readyState != eventSource.CLOSED) {
eventSource.close();
}
}
我好像错过了什么。你能帮我吗。问题在于,Clojure 与 SSE 的 Pedestal 实现的例子并不多。大多数都是 JavaScript 和其他语言。 这是基座SSE本身的实现
在基座文档上有一个小示例,您可以将其添加为单独的路线。
您的代码的基本问题是,当您使用以下命令创建拦截器时:
(sse/start-event-stream send-sse-msg 60 100)
send-sse-msg 函数应该是一个带有 2 个参数的函数,即原始请求的通道和上下文。上下文可以像任何其他拦截器一样被解构。
使用上下文的文档示例的稍微修改版本是:
(defn sse-stream-ready
"Starts sending counter events to client."
[event-ch context]
(let [count-num (Integer/parseInt
(get-in context [:request :query-params :counter] "5"))]
(loop [counter count-num]
(async/put!
event-ch {:name "count"
:data (str counter ", T: "
(.getId (Thread/currentThread)))})
(Thread/sleep 2000)
(if (> counter 1)
(recur (dec counter))
(do
(async/put! event-ch {:name "close" :data "I am done!"})
(async/close! event-ch))))))
您的代码看起来更像是您将事件通道保存到注册表中(由某个上下文元素键入?),并让另一个异步函数向所有通道发送消息(我在那里推断类似聊天)。