服务器端推送数据到浏览器实现方法之一Server-Sent Event

SSE 的使用方法可以参考:https://blog.csdn.net/xiewz1112/article/details/80591898

EventSource 对象的 API 文档地址:https://developer.mozilla.org/en-US/docs/Web/API/EventSource

SSE 的使用示例(主要代码来自《Java EE 开发的颠覆者 Spring Boot 实战》一书的 4.5.3 部分)

1. 页面sse.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>SSE Demo</title>
</head>
<body>
	<div id="msgFromPush"></div>
	<script type="text/javascript" src="<%=request.getContextPath()%>/assets/js/jquery-2.2.4.min.js"></script>
	<script type="text/javascript">
		if (window.EventSource) {
			console.log("该浏览器支持SSE");
			var source = new EventSource("ssePush");
			var s = "";
			
			source.addEventListener("open", function(e) {
				console.log("连接打开");
			}, false);
			
			source.addEventListener("message", function(e) {
				console.log("message: data=" + e.data + ", lastEventId=" + e.lastEventId);
				s += e.data + "<br>";
				$("#msgFromPush").html(s);
				if (e.data == "-1") {
					source.close();
				}
			});
			
			source.addEventListener("error", function(e) {
				if (e.readyState == EventSource.CLOSED) {
					console.log("连接关闭");
				} else {
					console.log(e.readyState);
				}
			}, false);
		} else {
			console.log("该浏览器不支持SSE");
		}
	</script>
</body>
</html>

2. SseData.java

package com.wisely.highlight_springmvc4.entity;

import java.util.Arrays;
import java.util.List;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;

public class SseData {
	private static final String CR = "\n";
	
	private String data = "";// 表示数据内容。属性来自于MessageEvent
	private String event = "message";// 自定义的事件类型,默认是message事件。
	private String id;// 数据标识符用id字段表示,相当于每一条数据的编号。属性来自于MessageEvent
	private long retry;// 指定浏览器重新发起连接的时间间隔。单位:ms
	private List<String> comments = Lists.newArrayList();
	
	public static class SseDataBuilder {
		private SseData sseData;
		private SseDataBuilder() {
			this.sseData = new SseData();
		}
		
		public static SseDataBuilder builder() {
			return new SseDataBuilder();
		}
		
		public SseData build() {
			return this.sseData;
		}
		
		public SseDataBuilder setData(String data) {
			Preconditions.checkNotNull(data);
			this.sseData.setData(data);
			return this;
		}
		public SseDataBuilder setEvent(String event) {
			Preconditions.checkNotNull(event);
			this.sseData.setEvent(event);
			return this;
		}
		public SseDataBuilder setId(String id) {
			Preconditions.checkNotNull(id);
			this.sseData.setId(id);
			return this;
		}
		public SseDataBuilder setRetry(long retry) {
			Preconditions.checkArgument(retry > 0L, "retry must be greater than 0");
			this.sseData.setRetry(retry);
			return this;
		}
		public SseDataBuilder setComments(String... comments) {
			Preconditions.checkArgument(comments != null && comments.length > 0, "comments must not be empty");
			this.sseData.setComments(Arrays.asList(comments));
			return this;
		}
	}
	

	public String getData() {
		return data;
	}
	public void setData(String data) {
		this.data = data;
	}
	public String getEvent() {
		return event;
	}
	public void setEvent(String event) {
		this.event = event;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public long getRetry() {
		return retry;
	}
	public void setRetry(long retry) {
		this.retry = retry;
	}
	public List<String> getComments() {
		return comments;
	}
	public void setComments(List<String> comments) {
		this.comments = comments;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		
		if (CollectionUtils.isNotEmpty(comments)) {
			for (String comment : comments) {
				if (comment != null) {
					builder.append(':').append(comment).append(CR);
				}
			}
		}
		builder.append("event:").append(StringUtils.defaultString(event, "message")).append(CR);
		if (id != null) {
			builder.append("id:").append(id).append(CR);
		}
		if (retry > 0L) {
			builder.append("retry:").append(retry).append(CR);
		}
		builder.append("data:").append(Strings.nullToEmpty(data)).append(CR).append(CR);
		
		return builder.toString();
	}
}

3. SseController.java

package com.wisely.highlight_springmvc4.ch4_5;

import java.util.Date;

import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.wisely.highlight_springmvc4.entity.SseData.SseDataBuilder;

@Controller
public class SseController {
	
	@RequestMapping(value="/ssePush", produces="text/event-stream")
	@ResponseBody
	public String push() {
		try {
			// 休眠2秒模拟处理过程
			Thread.sleep(2000L);
		} catch (Exception e) {
			// not handler
		}
		
		int num = RandomUtils.nextInt(1, 11);
		if (num % 10 == 0) {// 结束
			return SseDataBuilder.builder()
					.setComments("over")
					.setData("-1")
					.build()
					.toString();
		} else {
			return SseDataBuilder.builder()
					.setComments("comment " + num)
					.setId(String.valueOf(num))
					.setRetry(2000L)
					.setData("ssePush value[" + num + "] to browser at " + FastDateFormat.getInstance("HH:mm:ss").format(new Date()))
					.build().toString();
		}
	}
}

4. 浏览器 Console 控制台效果

5. 项目日志

猜你喜欢

转载自blog.csdn.net/frankingly/article/details/82854102