SSE.jpeg
>>>我的博客<<<
1.應用場景
當我們做異步請求處理的時候,客戶端向服務端的請求不是立即返回的,而是在服務端有數據的時候才發送給客戶端的。本例演示是服務端數據是另外的事件觸發并推送給客戶端的。
當使用微信或者支付寶掃碼支付的時候,在客戶支付成功之后,后臺得到微信(支付寶)回調需要通知我們的客戶端支付已經完成,客戶端得到通知后可以做一些后續的操作。
這涉及到后臺需要將支付成功的消息主動推送給客戶端,讓客戶端記性后續的操作。
- 客戶端向后臺(
SseController->push
)發送異步請求,客戶端處于監聽等待狀態; - 微信(支付寶)支付成功后回調后臺(
SseController->payCallback
模擬); -
payCallback
方法通過applicationContext.publishEvent
向系統內部發送支付完成事件; - push方法通過
payCompletedListener
監聽事件并通過SseEmitter
發送給客戶端。
名詞解釋:SSE–server send event
是一種服務端推送的技術,本例使用SseEmitter
來實現。
2. 上代碼
web api 接口類SseController
@RestController
public class SseController {
@Autowired
ApplicationContext applicationContext;
@Autowired
PayCompletedListener payCompletedListener;
@GetMapping("/push")
public SseEmitter push(@RequestParam Long payRecordId){
final SseEmitter emitter = new SseEmitter();
try {
payCompletedListener.addSseEmitters(payRecordId,emitter);
}catch (Exception e){
emitter.completeWithError(e);
}
return emitter;
}
@GetMapping("/pay-callback")
public String payCallback(@RequestParam Long payRecordId){
applicationContext.publishEvent(new PayCompletedEvent(this,payRecordId));
return "請到監聽處查看消息";
}
}
支付完成事件 PayCompletedEvent
@Getter
@Setter
public class PayCompletedEvent extends ApplicationEvent {
private Long payRecordId;
public PayCompletedEvent(Object source, Long payRecordId) {
super(source);
this.payRecordId = payRecordId;
}
}
支付完成事件監聽器PayCompletedListener
@Component
public class PayCompletedListener {
private static Map<Long,SseEmitter> sseEmitters = new Hashtable<>();
public void addSseEmitters(Long payRecordId, SseEmitter sseEmitter) {
sseEmitters.put(payRecordId, sseEmitter);
}
@EventListener
public void deployEventHandler(PayCompletedEvent payCompletedEvent) throws IOException {
Long payRecordId = payCompletedEvent.getPayRecordId();
SseEmitter sseEmitter = sseEmitters.get(payRecordId);
sseEmitter.send("支付成功");
sseEmitter.complete();
}
}
3. 效果演示
-
先訪問
http://localhost:8080/push?payRecordId=123
,此時客戶端處于等待狀態.
sse1.png -
再訪問
http://localhost:8080/pay-callback?payRecordId=123
,模擬回調通知客戶端支付成功,這是會通知上一步并推送到客戶端.
sse2.png
4. 另外一個極簡的例子
- 服務端模擬servlet的一個get請求
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class SSE
*/
@WebServlet("/SSE")
public class SSE extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public SSE() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/event-stream");
response.setCharacterEncoding("utf-8");
for (int i = 0; i < 5; i++) {
// 指定事件標識
response.getWriter().write("event:me\n");
// 格式: data: + 數據 + 2個回車
response.getWriter().write("data:" + i + "\n\n");
response.getWriter().flush();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
- 模擬客戶端的代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<script type="text/javascript">
// 初始化, 參數為url
// 依賴H5
var sse = new EventSource("SSE");
sse.onmessage = function(e) {
console.log("message", e.data, e);
}
// 監聽指定事件, (就不會進入onmessage了)
sse.addEventListener("me", function(e) {
console.log("me event", e.data);
// 如果不關閉,會自動重連
if (e.data == 3) {
sse.close();
}
});
</script>
</body>
</html>
-
效果
沒有加特殊事件監聽之前
只監聽me事件