在Thymeleaf和HTMX中使用伺服器傳送的事件 - Wim
可以使用 Websockets 或 Server-Sent Events 將資訊從 Spring Boot 後端推送到 UI。
這篇博文將展示如何將 Thymeleaf 與 HTMX 結合使用,通過 Server-Sent Events 將資訊從伺服器推送到 UI。
前往start.spring.io使用 Spring Web、Spring Security 和 Thymeleaf 啟動器建立一個新的 Java 17 專案。
還要手動將以下依賴項新增到pom.xml:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.0.1-jre</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> <version>0.42</version> </dependency> <dependency> <groupId>org.webjars.npm</groupId> <artifactId>htmx.org</artifactId> <version>1.6.0</version> </dependency> |
要使用伺服器傳送事件,我們需要在 Spring MVC 控制器中有一個 GET 方法,它返回一個SseEmitter例項。
客戶端有責任首先呼叫這個 GET 方法來“開啟通道”,以便伺服器可以通過它推送事件。事件是文字,因此它們可以是 JSON 或 HTML。在這裡,我們將使用 HTML,以便 htmx 可以使用更新的片段更新 DOM。作為伺服器,您需要跟蹤返回的SseEmitter例項,以便知道將資訊傳送到何處。
這是控制器中的 GET 對映:
@Controller public class PdfGenerationController { private final Multimap<String, SseEmitter> sseEmitters = MultimapBuilder.hashKeys().arrayListValues().build(); @GetMapping("/progress-events") public SseEmitter progressEvents(@AuthenticationPrincipal UserDetails userDetails) { SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE); sseEmitters.put(userDetails.getUsername(), sseEmitter); System.out.println("Adding SseEmitter for user: " + userDetails.getUsername()); sseEmitter.onCompletion(() -> LOGGER.info("SseEmitter is completed")); sseEmitter.onTimeout(() -> LOGGER.info("SseEmitter is timed out")); sseEmitter.onError((ex) -> LOGGER.info("SseEmitter got error:", ex)); return sseEmitter; } } |
在這個例子中,我們使用登入使用者的使用者名稱作為Multimap的鍵來跟蹤SseEmitter使用者登入的所有例項(例如不同的瀏覽器)。
在控制器中,我們還將有一個 POST 方法來模擬長時間執行的操作,例如生成 PDF 文件。這是程式碼:
@PostMapping public String generatePdf(@AuthenticationPrincipal UserDetails userDetails) { Collection<SseEmitter> sseEmitter = sseEmitters.get(userDetails.getUsername()); pdfGenerator.generatePdf(new SseEmitterProgressListener(sseEmitter)); return "index"; } |
PdfGenerator只是每100毫秒做一些隨機的進度:
import java.util.random.RandomGenerator; import java.util.random.RandomGeneratorFactory; @Component public class PdfGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(PdfGenerator.class); private final RandomGenerator randomGenerator = RandomGeneratorFactory.getDefault().create(); public void generatePdf(ProgressListener listener) { LOGGER.info("Generating PDF..."); int progress = 0; listener.onProgress(progress); do { sleep(); progress += randomGenerator.nextInt(10); LOGGER.info("Progress: {} ", progress); listener.onProgress(progress); } while (progress < 100); LOGGER.info("Done!"); listener.onCompletion(); } private void sleep() { try { Thread.sleep(100); } catch (InterruptedException e) { } } } |
每次進度發生變化時,ProgressListener都會呼叫回撥。
我們在控制器中使用它通過 Server-Sent 事件將更新傳送到客戶端:
private static class SseEmitterProgressListener implements ProgressListener { private final Collection<SseEmitter> sseEmitters; public SseEmitterProgressListener(Collection<SseEmitter> sseEmitter) { this.sseEmitters = sseEmitter; } @Override public void onProgress(int value) { String html = """ <div id="progress-container" class="progress-container"> \ <div class="progress-bar" style="width:%s%%"></div> \ </div> """.formatted(value); sendToAllClients(html); } @Override public void onCompletion() { String html = "<div><a href=\"#\">Download PDF</div>"; sendToAllClients(html); } private void sendToAllClients(String html) { for (SseEmitter sseEmitter : sseEmitters) { try { sseEmitter.send(html); } catch (IOException e) { LOGGER.error(e.getMessage()); } } } } |
當有進度時,動態傳送到瀏覽器的 DOM 中的 HTML 片段。
PDF 生成完成後,傳送允許使用者下載 PDF 的 HTML。
我們需要為發生的每個傳送捕獲異常,因為客戶端可能突然不再存在並且這不會影響其他客戶端。
客戶端實現
我們需要顯示一個按鈕來開始模擬 PDF 生成並顯示進度的 HTML 是這樣的:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/css/application.css"> </head> <body> <h1>Server Sent Events Demo</h1> <div>Current user: <span sec:authentication="name"></span></div> <div hx-sse="connect:/progress-events"> <button hx-post="/" hx-swap="none">Generate PDF</button> <div style="margin-bottom: 2rem;"></div> <div id="progress-wrapper" hx-sse="swap:message"> </div> </div> <script type="text/javascript" th:src="@{/webjars/htmx.org/dist/htmx.min.js}"></script> </body> </html> |
<div hx-sse="connect:/progress-events"> 中使用hx-sse屬性通過/progress-eventsURL連線到 SSE 通道。
<div id="progress-wrapper" hx-sse="swap:message"> 每次收到訊息時,將 this 的 innerHTML與通過 SSE 通道收到的 HTML交換。
執行
有關此示例的完整原始碼,請參閱GitHub 上的htmx-sse。
啟動 Spring Boot 應用程式並在http://localhost:8080 上開啟瀏覽器。您將被要求登入,您可以使用user1/p1或user2/進行登入p2。
還嘗試使用同一使用者開啟幾個瀏覽器。您應該會看到所有進度條都會更新,即使您只按下其中一個按鈕來開始 PDF 生成。
相關文章
- 伺服器傳送事件(SSE) vs. WebSockets伺服器事件Web
- WebSockets與伺服器傳送事件SSE比較Web伺服器事件
- HTML5伺服器傳送事件(server-sentevent)HTML伺服器事件Server
- 在Java中,使用HttpUtils實現傳送HTTP請求JavaHTTP
- Elasticsearch使用syslog傳送Watcher告警事件Elasticsearch事件
- 在 django 中使用 firebase 傳送通知Django
- 什麼是WebSockets、伺服器傳送事件、長輪詢、WebRTC、WebTransport?Web伺服器事件
- 在CentOS7中使用Sendmail通過PHP傳送郵件CentOSAIPHP
- 在Python如何使用SMTP傳送郵件Python
- 使用python傳送和接收郵件Python
- 分析Android 搜狗輸入法在微信和QQ中傳送圖片和表情Android
- 在ASP.NET中使用SMTPMail傳送郵件的方法ASP.NETAI
- 在python中使用itchat傳送微信訊息Python
- 在恢復計劃中使用日誌傳送
- 在Perl中使用sendmail傳送MIME郵件 (轉)AI
- 無涯教程: Node.js - 事件傳送Node.js事件
- Go的Channel傳送和接收Go
- 我用這種方法在 Spring 中實現訊息的傳送和消費Spring
- 使用C#在應用程式間傳送訊息C#
- RocketMQ中Producer訊息的傳送MQ
- 在J1939中多幀資料如何傳送,它是通過TP.CM_BAM和TP_DT報文傳送
- ssdbgrid中在AfterColUpdate事件中求某列的和事件
- 在ASP.NET中傳送電子郵件的例項教程ASP.NET
- Linux 上使用 Gmail SMTP 伺服器傳送郵件通知LinuxAI伺服器
- mailx 或telnet 使用指定SMTP伺服器傳送郵件AI伺服器
- 使用jQuery在javascript中自定義事件jQueryJavaScript事件
- 用AnySQL在沒有oracle客戶端的伺服器上傳送郵件SQLOracle客戶端伺服器
- 使用 Vim 傳送郵件和檢查日曆
- 使用 request 和 cheerio 庫來傳送 HTTP 請求HTTP
- 使用python傳送郵件和接收郵件Python
- 在.NET框架應用程式中傳送電子郵件框架
- 傳送JSON資料到伺服器JSON伺服器
- RxJava 觀察繫結和事件傳送流程及其中的執行緒切換分析RxJava事件執行緒
- 使用phpmailer傳送郵件PHPAI
- 使用JavaMail傳送郵件JavaAI
- 使用nodemailer傳送郵件AI
- 在 CentOS 7 中使用 Sendmail 通過 PHP 傳送郵件CentOSAIPHP
- JavaMail郵件傳送在linux環境下不能傳送的問題解決JavaAILinux