在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 生成。
相關文章
- Spring Boot中Thymeleaf和htmx助手工具庫Spring Boot
- Spring Boot 和 Thymeleaf 實現 Java 版 HTMXSpring BootJava
- 伺服器傳送事件(SSE) vs. WebSockets伺服器事件Web
- Elasticsearch使用syslog傳送Watcher告警事件Elasticsearch事件
- WebSockets與伺服器傳送事件SSE比較Web伺服器事件
- 在Java中,使用HttpUtils實現傳送HTTP請求JavaHTTP
- 在 django 中使用 firebase 傳送通知Django
- 在Python如何使用SMTP傳送郵件Python
- 什麼是WebSockets、伺服器傳送事件、長輪詢、WebRTC、WebTransport?Web伺服器事件
- 使用 Go、SSE 和 htmx 實時更新網站Go網站
- 我用這種方法在 Spring 中實現訊息的傳送和消費Spring
- thymeleaf的使用技巧
- 在python中使用itchat傳送微信訊息Python
- 無涯教程: Node.js - 事件傳送Node.js事件
- SOAPMessage的組成和傳送
- Go的Channel傳送和接收Go
- Linux 上使用 Gmail SMTP 伺服器傳送郵件通知LinuxAI伺服器
- 如何使用 request-promise 在傳送請求時使用代理ip?Promise
- 在J1939中多幀資料如何傳送,它是通過TP.CM_BAM和TP_DT報文傳送
- 在html中使用axios傳送請求到servlet時遇到的傳值問題HTMLiOSServlet
- 在 `el-upload` 的事件中傳遞更多引數的方法事件
- HTML5的伺服器(server-sent event)傳送事件有什麼應用場景?HTML伺服器Server事件
- 在silverlight中利用socket傳送圖片或檔案
- 使用 request 和 cheerio 庫來傳送 HTTP 請求HTTP
- 使用 Vim 傳送郵件和檢查日曆
- 使用node的emailjs傳送郵箱AIJS
- 用AnySQL在沒有oracle客戶端的伺服器上傳送郵件SQLOracle客戶端伺服器
- Thymeleaf基本使用
- RocketMQ中Producer訊息的傳送MQ
- 使用phpmailer傳送郵件PHPAI
- 使用 smtplib 傳送郵件
- 使用 Python 傳送簡訊?Python
- Linux伺服器上傳檔案傳送檔案Linux伺服器
- 如何使用 SAP Kyma 控制檯手動傳送 SAP Commerce Cloud Mock 應用暴露的事件CloudMock事件
- RxJava 觀察繫結和事件傳送流程及其中的執行緒切換分析RxJava事件執行緒
- 在python中傳送郵件亂碼了怎麼辦?Python
- [thymeleaf]springboot整合thymeleaf, html使用預置方法Spring BootHTML
- 使用Postman傳送POST請求的指南Postman