记录一次spring boot 2.x 集成 websocket
记录一次spring boot 2.x 集成 websocket 以及 SSE的两种实现方式
第一次使用底层 webSocket 失败了 目前还没找到原因
先贴代码:
主要需求:
-
.webSocket
-
通过path传参
-
第一步创建webSocketConfig
@Configuration //这个的有
@EnableWebSocket //这个的有
public class WebSocketConfig implements WebSocketConfigurer {
private static final Logger LOG = LoggerFactory.getLogger(WebSocketConfig.class);
@Autowired
public EventBusToWebSocketTongwebHandler eventBusToWebSocketTongwebHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
LOG.info("我执行了"+ eventBusToWebSocketTongwebHandler.toString());
System.out.println("eventBusToWebSocketTongwebHandler.toString() = " + eventBusToWebSocketTongwebHandler.toString());
registry.addHandler(eventBusToWebSocketTongwebHandler, "/api/v1/event/streams.ws/","/api/v1/event/streams.ws/**","/api/v1/event/streams.ws/b136e2f7-d9a3-4179-a5ec-d4caabf761ad")
.addInterceptors(httpSessionHandshakeInterceptor()).setAllowedOrigins("*");
}
- 创建HandshakeInterceptor,因为我需要从路径中获取参数
package io.syndesis.server.runtime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
public class CustomerHttpSessionHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(CustomerHttpSessionHandshakeInterceptor.class);
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// Get the URI segment corresponding to the auction id during handshake
String path = request.getURI().getPath();
String auctionId = path.substring(path.lastIndexOf('/') + 1);
// This will be added to the websocket session
if(path.startsWith(EventBusToWebSocketTongwebHandler.DEFAULT_PATH)){
attributes.put(EventBusToWebSocketTongwebHandler.SUB_ID, auctionId);
}
LOG.info("path"+ path + " auctionId:"+ auctionId);
//if(path.startsWith(EventBusToWebSocketTongwebHandler.DEFAULT_PATH)){
//attributes.put("auctionId", auctionId);
return super.beforeHandshake(request, response, wsHandler, attributes);
}
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
System.out.println("After Handshake");
LOG.info("After Handshake");
super.afterHandshake(request, response, wsHandler, exception);
}
}
- 创建处理类
import io.syndesis.common.model.EventMessage;
import io.syndesis.common.util.EventBus;
import io.syndesis.server.endpoint.v1.handler.events.EventReservationsHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Connects the the EventBus to an Undertow WebSocket Event handler
* at the "/api/v1/events.ws/{:subscription}" path.
*/
@Component
public class EventBusToWebSocketTongwebHandler extends TextWebSocketHandler {
private static final Logger LOG = LoggerFactory.getLogger(EventBusToWebSocketTongwebHandler.class);
public static final String DEFAULT_PATH = "/api/v1/event/streams.ws";
public static final String SUB_ID = "subscriptionId";
protected final SyndesisCorsConfiguration cors;
protected final EventBus bus;
protected final EventReservationsHandler eventReservationsHandler;
protected String path = DEFAULT_PATH;
public static final HashMap<String, WebSocketSession> userSessionMap;
static {
userSessionMap = new HashMap<>();
}
/**
* 连接成功时候,会触发UI上onopen方法
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("connect to the websocket success......");
LOG.info("connect to the websocket success......");
Map<String, Object> attributes = session.getAttributes();
String subscriptionId = (String) attributes.get(SUB_ID);
if(subscriptionId!= null) {
userSessionMap.put(subscriptionId, session);
}
}
//@Autowired
public EventBusToWebSocketTongwebHandler(SyndesisCorsConfiguration cors, EventBus bus, EventReservationsHandler eventReservationsHandler) {
this.cors = cors;
this.bus = bus;
this.eventReservationsHandler = eventReservationsHandler;
path = DEFAULT_PATH;
LOG.info("EventBusToWebSocketTongwebHandler初始化成功");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String subscriptionId = (String) session.getAttributes().get(SUB_ID);
EventReservationsHandler.Reservation reservation = eventReservationsHandler.claimReservation(subscriptionId);
if (reservation == null) {
LOG.info("Principal is: {}", reservation.getPrincipal());
System.out.println("我是你爸爸1");
send(session,"error", "Invalid subscription: not reserved");
//safeClose(session);
//return;
}else {
LOG.info("Principal is: {}", reservation.getPrincipal());
System.out.println("我是你爸爸2");
send(session, "message", "connected");
}
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
Map<String, Object> attributes = session.getAttributes();
String subscriptionId = (String) attributes.get(SUB_ID);
if(subscriptionId != null) {
userSessionMap.remove(subscriptionId);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
Map<String, Object> attributes = session.getAttributes();
String subscriptionId = (String) attributes.get(SUB_ID);
if(subscriptionId != null) {
userSessionMap.remove(subscriptionId);
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
private void send(WebSocketSession session, String type, String data) throws IOException {
session.sendMessage(new TextMessage(EventMessage.of(type, data).toJson()));
}
private void safeClose(WebSocketSession session) throws IOException {
Map<String, Object> attributes = session.getAttributes();
String subscriptionId = (String) attributes.get(SUB_ID);
session.close();
if(subscriptionId != null) {
userSessionMap.remove(subscriptionId);
}
}
}
这次失败了就先不赘述了
第二次使用@ServerEndpoint 成功了
注意事项
- @ServerEndpoint 注解的类无法使用传统的单例@Resource等,无论是在setter方法或者属性上亦或者在构造方法上
- 需要使用ApplicationContextAware 但是实例化顺序一定要在websocket类之后。我一开始就是websocket类在ApplicationContextAware 之前实例化所有失败了了 spring 实例化应该是从上而下的class开始
贴代码
工具类
package io.syndesis.server.runtime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtils implements ApplicationContextAware {
private static final Logger LOG = LoggerFactory.getLogger(EventBusToWebSocketTongwebController.class);
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
LOG.info("初始化SpringUtils"+ applicationContext);
if (SpringUtils.applicationContext == null) {
SpringUtils.applicationContext = applicationContext;
}
}
public static ApplicationContext getContext() {
return applicationContext != null ? applicationContext : null;
}
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
public static <T> T getBean(Class<T> clazz){
return (T)applicationContext.getBean(clazz);
}
}
WebSocketConfig
package io.syndesis.server.runtime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
@ServerEndpoint
/*
* Copyright (C) 2016 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.syndesis.server.runtime;
import io.syndesis.common.model.EventMessage;
import io.syndesis.common.util.EventBus;
import io.syndesis.server.endpoint.v1.handler.events.EventReservationsHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Connects the the EventBus to an Undertow WebSocket Event handler
* at the "/api/v1/events.ws/{:subscription}" path.
*/
@Controller
@ServerEndpoint("/api/v1/event/streams.ws/{subscriptionId}")
@DependsOn("springUtils") //为了处理无法实例化SpringUtils中的applictioncontext
public class EventBusToWebSocketTongwebController{
private static final Logger LOG = LoggerFactory.getLogger(EventBusToWebSocketTongwebController.class);
public static final String DEFAULT_PATH = "/api/v1/event/streams.ws";
public static final String SUB_ID = "subscriptionId";
protected SyndesisCorsConfiguration cors = SpringUtils.getBean(SyndesisCorsConfiguration.class);
protected EventBus bus = SpringUtils.getBean(EventBus.class);
protected EventReservationsHandler eventReservationsHandler = SpringUtils.getBean(EventReservationsHandler.class);
protected String path = DEFAULT_PATH;
private Session session;
public static CopyOnWriteArraySet<EventBusToWebSocketTongwebController> webSockets = new CopyOnWriteArraySet<>();
private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();
/* @Autowired
public EventBusToWebSocketTongwebController(SyndesisCorsConfiguration cors, EventBus bus, EventReservationsHandler eventReservationsHandler) {
this.cors = cors;
this.bus = bus;
this.eventReservationsHandler = eventReservationsHandler;
path = DEFAULT_PATH;
LOG.info("EventBusToWebSocketTongwebController初始化成功");
}*/
public EventBusToWebSocketTongwebController() {
path = DEFAULT_PATH;
LOG.info("EventBusToWebSocketTongwebController初始化成功");
}
@OnOpen
public void onOpen(Session session, @PathParam(value = "subscriptionId") String subscriptionId) {
LOG.info("获取到subscriptionId:"+subscriptionId);
this.session = session;
webSockets.add(this);
sessionPool.put(subscriptionId, session);
EventReservationsHandler.Reservation reservation = null;
if(null != eventReservationsHandler){
reservation = eventReservationsHandler.claimReservation(subscriptionId);
}
if (reservation == null) {
LOG.info("Principal is: {}", reservation.getPrincipal());
LOG.info("我是你爸爸1");
sendOneMessage(subscriptionId,"error", "Invalid subscription: not reserved");
safeOnClose(subscriptionId);
return;
}else {
LOG.info("Principal is: {}", reservation.getPrincipal());
System.out.println("我是你爸爸2");
sendOneMessage(subscriptionId, "message", "connected");
}
//sessionPool.forEach((key, value) -> {});
LOG.info("【websocket消息】有新的连接,总数为:" + webSockets.size());
}
/**
* 断开连接
*/
@OnClose
public void onClose() {
webSockets.remove(this);
LOG.info("【websocket消息】连接断开,总数为:" + webSockets.size());
}
public void safeOnClose(String subscriptionId) {
webSockets.remove(this);
sessionPool.remove(subscriptionId);
}
/**
* 收到客户端消息
*
* @param message
*/
@OnMessage
public void onMessage(String message) {
LOG.info("【websocket消息】收到客户端消息:" + message);
if(null == message || "".equals(message)){
message = "我是你爸爸";
}
sendAllMessage(message);
}
/**
* 广播消息
*/
public void sendAllMessage(String message) {
for (EventBusToWebSocketTongwebController webSocket : webSockets) {
try {
webSocket.session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 给指定人发送单点消息
*
* @param code
* @param message
*/
public void sendOneMessage(String code, String type, String data) {
Session session = sessionPool.get(code);
//在发送数据之前先确认 session是否已经打开 使用session.isOpen() 为true 则发送消息
if (session != null && session.isOpen()) {
try {
session.getAsyncRemote().sendText(EventMessage.of(type, data).toJson());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public SyndesisCorsConfiguration getCors() {
return cors;
}
public void setCors(SyndesisCorsConfiguration cors) {
this.cors = cors;
}
public EventBus getBus() {
return bus;
}
public void setBus(EventBus bus) {
this.bus = bus;
}
public EventReservationsHandler getEventReservationsHandler() {
return eventReservationsHandler;
}
public void setEventReservationsHandler(EventReservationsHandler eventReservationsHandler) {
this.eventReservationsHandler = eventReservationsHandler;
}
}
原生servlet实现sse
sse教程 参考资料如有侵权请联系删除:Server-Sent Events 教程
nginx对sse支持的配置
这三个配置
proxy_set_header Connection ‘’;
proxy_http_version 1.1;
chunked_transfer_encoding off;
location ~ ^/stream1 {
proxy_pass https://message_upstream;
proxy_buffering off;
proxy_cache off;
proxy_set_header Host $host;
proxy_set_header Connection ‘’;
proxy_http_version 1.1;
chunked_transfer_encoding off;
}
demo
location /api/v1/event/streams {
if ($request_method = ‘OPTIONS’) {
return 204;
}
proxy_pass https://backends;
proxy_cookie_flags ~ secure samesite=none;
proxy_set_header Host fxconnect-connector3.apps.all-in-one.ceake.fanxing;
proxy_ssl_server_name on;
proxy_ssl_session_reuse off;
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
}
原生的写法demo
/*
* Copyright (C) 2016 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import io.syndesis.common.model.EventMessage;
import io.syndesis.common.util.EventBus;
import io.syndesis.common.util.json.JsonUtils;
import io.syndesis.server.endpoint.v1.handler.events.EventReservationsHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/*
* Connects the the EventBus to an Undertow Sever Side Event handler
* at the "/api/v1/events/{:subscription}" path.
*/
//@Component
//@Controller
//@RequestMapping("/api/v1/event/streams")
@WebServlet(name = "myServlet",urlPatterns = "/api/v1/event/streams/*",asyncSupported = true)
public class EventBusToServerSentEventsSseTongweb extends HttpServlet {
/**
* messageId的 SseEmitter对象映射集
*/
private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
private static Map<String, AsyncContext> AsyncContextMap = new ConcurrentHashMap<>();
private static final Logger LOG = LoggerFactory.getLogger(EventBusToServerSentEventsSseTongweb.class);
public final static String SSE_MESSAGE_EVENT = "event: message\n";
public final static String SSE_CHANGE_EVENT = "event: change-event\n";
public final static String EVENT = "event: ";
public static final String DEFAULT_PATH = "/api/v1/event/streams";
protected final SyndesisCorsConfiguration cors;
protected final EventBus bus;
protected final EventReservationsHandler eventReservationsHandler;
protected String path = DEFAULT_PATH;
@Autowired
public EventBusToServerSentEventsSseTongweb(SyndesisCorsConfiguration cors, EventBus bus, EventReservationsHandler eventReservationsHandler) {
this.cors = cors;
this.bus = bus;
this.eventReservationsHandler = eventReservationsHandler;
LOG.info("EventBusToServerSentEventsSseTongweb初始化成功!");
LOG.info(cors+"bus:"+bus+":"+eventReservationsHandler);
}
//private static ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10));
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/event-stream");
response.setCharacterEncoding("utf-8");
final AsyncContext asyncContext =
request.isAsyncStarted()
? request.getAsyncContext()
: request.startAsync(request, response);
String uri = request.getRequestURI();
final String subscriptionId = uri.substring(path.length() + 1);
LOG.info("subscriptionId:"+subscriptionId);
PrintWriter pw = response.getWriter();
EventReservationsHandler.Reservation reservation = eventReservationsHandler.claimReservation(subscriptionId);
if (reservation == null) {
//connection.send("Invalid subscription: not reserved", "error", null, null);
pw.write( SSE_MESSAGE_EVENT + "data: "+ "Invalid subscription: not reserved" +"\n\n");
pw.flush();
pw.close();
//asyncContext.complete();
return;
}
LOG.info("Principal is: {}", reservation.getPrincipal());
String message = "data:"+ "connected" +"\n\n";
LOG.info("发送的消息值:{}",message);
pw.write(message);
pw.flush();
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
LOG.info("asyncContext-onComplete");
AsyncContextMap.remove(subscriptionId);
LOG.info("onCompleteremove:{}",subscriptionId);
bus.unsubscribe(subscriptionId);
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
LOG.info("asyncContext-超时了");
AsyncContextMap.remove(subscriptionId);
//asyncEvent.getAsyncContext().complete();
LOG.info("onTimeoutremove:{}",subscriptionId);
bus.unsubscribe(subscriptionId);
//asyncContext.getResponse().getWriter().print(name + ":timeout");
//asyncContext.complete();
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
LOG.info("asyncContext-onError");
AsyncContextMap.remove(subscriptionId);
LOG.info("onErrorremove:{}",subscriptionId);
bus.unsubscribe(subscriptionId);
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
LOG.info("asyncContext-onStartAsync");
//AsyncContextMap.remove(subscriptionId);
}
});
asyncContext.setTimeout(0L);//设置AsyncContext的超时时间,默认30秒,0或者负值表示不超时
AsyncContextMap.put(subscriptionId,asyncContext);
bus.subscribe(subscriptionId, (type, data) -> {
LOG.info(subscriptionId);
AsyncContext asyncContextla = AsyncContextMap.get(subscriptionId);
if(null == asyncContextla ){
LOG.info("asyncContextla为空");
}else {
try {
if(null != asyncContextla.getRequest()){
PrintWriter pwlamada = asyncContextla.getResponse().getWriter();
if (null == pwlamada) {
LOG.info("pwlamada为空");
} else {
if (!pwlamada.checkError()) {
LOG.info("Principal is: {}", JsonUtils.toPrettyString(data));
String messageInlamada =SSE_CHANGE_EVENT + "data: " + data + "\n\n";
pwlamada.write(messageInlamada);
pwlamada.flush();
//pwlamada.close();
LOG.info("发送的消息值:{}", messageInlamada);
} else {
bus.unsubscribe(subscriptionId);
//asyncContext.complete();
//AsyncContextMap.remove(subscriptionId);
String str = "客户端断开连接";
LOG.info("结果:{}", str);
}
}
}else{
LOG.info("asyncContextla中的request为空了");
}
} catch (IOException e) {
LOG.info("出现异常: {}",e.getMessage());
}
}
});
//asyncContext.setTimeout(25 * 1000);
//executor.execute(()->{doSomething(asyncContext);});
LOG.info("连上了: {}",subscriptionId);
}
/*private void doSomething(AsyncContext asyncContext) {
ServletResponse response = asyncContext.getResponse();
HttpServletRequest request = (HttpServletRequest)asyncContext.getRequest();
String requestUri = request.getRequestURI();
System.out.println("requestUri = " + requestUri);
try {
PrintWriter pw=response.getWriter();
while (true){
Thread.sleep(1000);
pw.write("data:"+ requestUri + "This is a test message\n\n");
pw.flush();
//asyncContext.complete();
if (pw.checkError()) {
asyncContext.complete();
String str = "客户端断开连接";
LOG.info("结果:{}", str);
return;
}
}
} catch (Exception e) {
e.printStackTrace();
return;
}
}*/
//@CrossOrigin
//@RequestMapping(value = "/{subscriptionId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE, method = RequestMethod.GET)
/*public SseEmitter sse(@PathVariable(name = "subscriptionId") String subscriptionId){
LOG.info("EventBusToServerSentEventsSseTongweb接收到请求!"+subscriptionId);
SseEmitter sseEmitter = SseServer.createConnect(subscriptionId);
if(null == sseEmitter){
LOG.info("sseEmitter为空,key:"+subscriptionId);
return sseEmitter;
}
EventReservationsHandler.Reservation reservation = eventReservationsHandler.claimReservation(subscriptionId);
if (reservation == null) {
LOG.info("reservation为null");
SseServer.sendMessage(subscriptionId,EventMessage.of("error","Invalid subscription: not reserved" ).toJson());
//connection.send("Invalid subscription: not reserved", "error", null, null);
SseServer.removeMessageId(subscriptionId);
//connection.shutdown();
return sseEmitter;
}
LOG.info("Principal is: {}", reservation.getPrincipal());
//connection.send("connected", "message", null, null);
SseServer.sendMessage(subscriptionId,EventMessage.of( "message","connected").toJson());
boolean complete = (boolean)getFieldValueByObject(sseEmitter,"complete","ResponseBodyEmitter");
//s.setKeepAliveTime(25 * 1000);
bus.subscribe(subscriptionId, (type, data) -> {
if (!complete) {
LOG.info("Principal is: {}", JsonUtils.toPrettyString(data));
SseServer.sendMessage(subscriptionId,EventMessage.of(type,data).toJson());
} else {
bus.unsubscribe(subscriptionId);
}
});
return sseEmitter;
}
public static Object getFieldValueByObject(Object object, String targetFieldName,String superclassSimpleName) {
// 获取该对象的Class
Class objClass = object.getClass();
// 初始化返回值
Object result = null;
for( ;objClass != Object.class; objClass = objClass.getSuperclass()){
if(null!=superclassSimpleName && superclassSimpleName.equals(objClass.getSimpleName())){
Field[] fields = objClass.getDeclaredFields();
for (Field field : fields) {
// 属性名称
String currentFieldName = "";
// 获取属性上面的注解 import com.fasterxml.jackson.annotation.JsonProperty;
*//**
* 举例: @JsonProperty("roleIds")
* private String roleIds;
*//*
try {
*//* boolean has_JsonProperty = field.isAnnotationPresent(JsonProperty.class);
if (has_JsonProperty) {
currentFieldName = field.getAnnotation(JsonProperty.class).value();
} else {
currentFieldName = field.getName();
}*//*
if (currentFieldName.equals(targetFieldName)) {
field.setAccessible(true);
result = field.get(object);
return result; // 通过反射拿到该属性在此对象中的值(也可能是个对象)
}
} catch (SecurityException e) {
// 安全性异常
e.printStackTrace();
} catch (IllegalArgumentException e) {
// 非法参数
e.printStackTrace();
} catch (IllegalAccessException e) {
// 无访问权限
e.printStackTrace();
}
}
}else {
continue;
}
}
// 获取所有的属性数组
return result;
}*/
}
resteasy的写法
package io.syndesis.server.endpoint.v1.handler.stream;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.syndesis.common.model.ChangeEvent;
import io.syndesis.common.model.EventMessage;
import io.syndesis.common.util.EventBus;
import io.syndesis.common.util.json.JsonUtils;
import io.syndesis.server.endpoint.v1.handler.events.EventReservationsHandler;
import io.syndesis.server.runtime.EventBusToServerSentEventsSseTongweb;
import io.syndesis.server.runtime.SyndesisCorsConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotNull;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.sse.OutboundSseEvent;
import javax.ws.rs.sse.Sse;
import javax.ws.rs.sse.SseBroadcaster;
import javax.ws.rs.sse.SseEventSink;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Path("/event/streams")
@Tag(name = "stream")
@Component
public class StreamHandler {
private Sse sse;
private OutboundSseEvent.Builder eventBuilder;
private SseBroadcaster sseBroadcaster;
public static Map<String,String> ms = new ConcurrentHashMap<>();
private static final Logger LOG = LoggerFactory.getLogger(EventBusToServerSentEventsSseTongweb.class);
public static final String DEFAULT_PATH = "/api/v1/event/streams";
protected final SyndesisCorsConfiguration cors;
protected final EventBus bus;
protected final EventReservationsHandler eventReservationsHandler;
protected String path = DEFAULT_PATH;
@GET
@Path(value = "/{subscriptionId}")
@Produces("text/event-stream")
public void getStockPrices(@NotNull @PathParam("subscriptionId") @Parameter(required = true) final String subscriptionId, @Context SseEventSink sseEventSink, @Context Sse sse) {
LOG.info("subscriptionId:{}",subscriptionId);
EventReservationsHandler.Reservation reservation = eventReservationsHandler.claimReservation(subscriptionId);
LOG.info("reservation: {}",reservation.toString());
if (reservation == null) {
OutboundSseEvent sseEvent = this.eventBuilder
.name("message")
.id(String.valueOf(subscriptionId))
.mediaType(MediaType.APPLICATION_JSON_TYPE)
.data(String.class, "Invalid subscription: not reserved")
.reconnectDelay(25*1000)
.comment("event")
.build();
sseEventSink.send(sseEvent);
sseEventSink.close();
return;
}
LOG.info("Principal is: {}", reservation.getPrincipal());
//connection.send("connected", "message", null, null);
OutboundSseEvent sseEvent = this.eventBuilder
.name("message")
.id(String.valueOf(subscriptionId))
.mediaType(MediaType.APPLICATION_JSON_TYPE)
.data(String.class, "connected")
.reconnectDelay(25*1000)
.comment("event")
.build();
sseEventSink.send(sseEvent);
StreamHandler.ms.put(sseEventSink.toString(),subscriptionId);
this.sseBroadcaster.register(sseEventSink);
//connection.setKeepAliveTime(25 * 1000);
bus.subscribe(subscriptionId, (type, data) -> {
if (!sseEventSink.isClosed()) {
LOG.info("Principal is: {}", JsonUtils.toPrettyString(data));
OutboundSseEvent sseEvent1 = this.eventBuilder
.name(type)
.id(String.valueOf(subscriptionId))
.mediaType(MediaType.APPLICATION_JSON_TYPE)
.data(String.class, data)
.reconnectDelay(25*1000)
.comment("event")
.build();
sseEventSink.send(sseEvent1);
} else {
bus.unsubscribe(subscriptionId);
ms.remove(subscriptionId);
sseEventSink.close();
}
});
LOG.info("运行结束了");
}
@Autowired
public StreamHandler(SyndesisCorsConfiguration cors, EventBus bus, EventReservationsHandler eventReservationsHandler) {
this.cors = cors;
this.bus = bus;
this.eventReservationsHandler = eventReservationsHandler;
LOG.info("StreamHandler初始化成功!");
LOG.info(cors+"bus:"+bus+":"+eventReservationsHandler);
}
@Context
public void setSse(Sse sse) {
this.sse = sse;
this.eventBuilder = sse.newEventBuilder();
this.sseBroadcaster = sse.newBroadcaster();
this.sseBroadcaster.onClose(sseEventSink -> {
LOG.info("我关闭了:{}",sseEventSink.toString());
String id = StreamHandler.ms.get(sseEventSink.toString());
bus.unsubscribe(id);
/* OutboundSseEvent sseEvent1 = this.eventBuilder
.name("message")
.id(String.valueOf(11))
.mediaType(MediaType.APPLICATION_JSON_TYPE)
.data(String.class, EventMessage.of("error","Invalid subscription: not reserved").toJson())
.reconnectDelay(4000)
.comment("close的")
.build();
sseEventSink.send(sseEvent1);*/
});
this.sseBroadcaster.onError((sseEventSink, throwable) -> {
LOG.info("出错了:{}",throwable.getMessage());
String id = StreamHandler.ms.get(sseEventSink.toString());
bus.unsubscribe(id);
});
}
}
springboot 写法(未使用,未测试)
sse 前端写法
let eventSource2 = new EventSource(’
https://10.253.192.62/api/v1/event/streams/bf65f47f-0cde-49e8-9a9f-236fb8ed3ac8’, { withCredentials: false });
eventSource2.onopen = function(event) {
console.log(‘Connection opened’)
}
eventSource2.onmessage = function(event) {
console.log('Received message: ’ + event.data);
}
eventSource2.onerror = function(event) {
console.log('Error occurred: ’ + event.event);
}