Skip to content

Commit 35bde2e

Browse files
committed
* ws: remove ws support
Signed-off-by: neo <[email protected]>
1 parent ae15be9 commit 35bde2e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+97
-1206
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* sse: support @LimitRate on sse connect or class
77
> use @LimitRate and http().limitRate().add() to configure rate limit
88
* sse: log event count and size on sse:close action
9+
* ws: remove ws support
10+
> websocket is not used anymore, use sse/ajax instead
911
1012
### 9.1.7 (2/26/2025 - 3/6/2025)
1113

core-ng/src/main/java/core/framework/internal/module/ModuleContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void initialize() {
7878

7979
private HTTPServer createHTTPServer() {
8080
var httpServer = new HTTPServer(logManager);
81-
beanFactory.bind(WebContext.class, null, httpServer.handler.webContext);
81+
beanFactory.bind(WebContext.class, null, httpServer.httpHandler.webContext);
8282
beanFactory.bind(SessionContext.class, null, httpServer.siteManager.sessionManager);
8383
beanFactory.bind(WebDirectory.class, null, httpServer.siteManager.webDirectory);
8484

@@ -107,7 +107,7 @@ public final void route(HTTPMethod method, String path, Controller controller, b
107107
var inspector = new ControllerInspector(controller);
108108
new ControllerClassValidator(inspector.targetClass, inspector.targetMethod).validate();
109109
String action = "http:" + ASCII.toLowerCase(method.name()) + ":" + path;
110-
httpServer.handler.route.add(method, path, new ControllerHolder(controller, inspector.targetMethod, inspector.controllerInfo, action, skipInterceptor));
110+
httpServer.httpHandler.route.add(method, path, new ControllerHolder(controller, inspector.targetMethod, inspector.controllerInfo, action, skipInterceptor));
111111
}
112112

113113
public <T extends Config> T config(Class<T> configClass, @Nullable String name) {

core-ng/src/main/java/core/framework/internal/web/HTTPHandlerContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ public class HTTPHandlerContext {
1212
public final RequestParser requestParser = new RequestParser();
1313
public final RequestBeanReader requestBeanReader = new RequestBeanReader();
1414
public final ResponseBeanWriter responseBeanWriter = new ResponseBeanWriter();
15-
public final RateControl rateControl = new RateControl();
16-
public boolean limitRate; // TODO: simplify this design?
15+
@Nullable
16+
public RateControl rateControl;
1717
@Nullable
1818
public IPv4AccessControl accessControl;
1919
}

core-ng/src/main/java/core/framework/internal/web/HTTPIOHandler.java

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import core.framework.internal.web.request.RequestBodyReader;
44
import core.framework.internal.web.sse.ServerSentEventHandler;
5-
import core.framework.internal.web.websocket.WebSocketHandler;
65
import io.undertow.server.HttpHandler;
76
import io.undertow.server.HttpServerExchange;
87
import io.undertow.server.handlers.form.FormDataParser;
@@ -24,20 +23,20 @@
2423
public class HTTPIOHandler implements HttpHandler {
2524
public static final String HEALTH_CHECK_PATH = "/health-check";
2625
private final Logger logger = LoggerFactory.getLogger(HTTPIOHandler.class);
26+
27+
private final HTTPHandler httpHandler;
28+
private final ServerSentEventHandler sseHandler;
29+
private final ShutdownHandler shutdownHandler;
30+
2731
private final FormParserFactory formParserFactory;
2832
private final long maxEntitySize;
29-
private final HTTPHandler handler;
30-
private final ShutdownHandler shutdownHandler;
31-
private final ServerSentEventHandler sseHandler;
32-
private final WebSocketHandler webSocketHandler;
3333

34-
HTTPIOHandler(HTTPHandler handler, ShutdownHandler shutdownHandler, long maxEntitySize, ServerSentEventHandler sseHandler, WebSocketHandler webSocketHandler) {
35-
this.handler = handler;
34+
HTTPIOHandler(HTTPHandler httpHandler, ShutdownHandler shutdownHandler, long maxEntitySize, ServerSentEventHandler sseHandler) {
35+
this.httpHandler = httpHandler;
3636
this.shutdownHandler = shutdownHandler;
3737
formParserFactory = createFormParserFactory();
3838
this.maxEntitySize = maxEntitySize;
3939
this.sseHandler = sseHandler;
40-
this.webSocketHandler = webSocketHandler;
4140
}
4241

4342
private FormParserFactory createFormParserFactory() {
@@ -59,31 +58,26 @@ private FormParserFactory createFormParserFactory() {
5958
public void handleRequest(HttpServerExchange exchange) throws Exception {
6059
String path = exchange.getRequestPath();
6160
if (HEALTH_CHECK_PATH.equals(path)) { // not treat health-check as action
62-
handler.addKeepAliveHeader(exchange);
61+
httpHandler.addKeepAliveHeader(exchange);
6362
exchange.endExchange(); // end exchange will send 200 / content-length=0
6463
return;
6564
}
6665

6766
long contentLength = exchange.getRequestContentLength();
6867
if (!checkContentLength(contentLength, exchange)) return;
6968

70-
HttpString method = exchange.getRequestMethod();
71-
HeaderMap headers = exchange.getRequestHeaders();
72-
73-
var requestHandler = new Handler(exchange);
74-
boolean ws = webSocketHandler != null && webSocketHandler.check(method, headers); // TODO: retire ws and simplify
75-
boolean active = !requestHandler.sse && !ws;
76-
boolean shutdown = shutdownHandler.handle(exchange, active);
69+
var handler = new Handler(exchange);
70+
boolean shutdown = shutdownHandler.handle(exchange, handler.sse);
7771
if (shutdown) return;
7872

79-
if (hasBody(contentLength, method)) { // parse body early, not process until body is read (e.g. for chunked), to save one blocking thread during read
73+
if (hasBody(contentLength, exchange.getRequestMethod())) { // parse body early, not process until body is read (e.g. for chunked), to save one blocking thread during read
8074
FormDataParser parser = formParserFactory.createParser(exchange); // no need to close, refer to io.undertow.server.handlers.form.MultiPartParserDefinition.create, it closes on ExchangeCompletionListener
8175
if (parser != null) {
82-
parser.parse(handler);
76+
parser.parse(httpHandler);
8377
return;
8478
}
8579

86-
var reader = new RequestBodyReader(exchange, requestHandler);
80+
var reader = new RequestBodyReader(exchange, handler);
8781
StreamSourceChannel channel = exchange.getRequestChannel();
8882
reader.read(channel); // channel will be null if getRequestChannel() is already called, but here should not be that case
8983
if (!reader.complete()) {
@@ -93,11 +87,7 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
9387
}
9488
}
9589

96-
if (ws) {
97-
exchange.dispatch(webSocketHandler);
98-
} else {
99-
requestHandler.handle();
100-
}
90+
handler.handle();
10191
}
10292

10393
// undertow is not handling max entity size checking correctly, it terminates request directly and bypass exchange.endExchange() in certain cases, and log errors in debug level
@@ -141,7 +131,7 @@ public void handle() {
141131
if (sse) {
142132
sseHandler.handleRequest(exchange); // not dispatch, continue in io thread
143133
} else {
144-
exchange.dispatch(handler);
134+
exchange.dispatch(httpHandler);
145135
}
146136
}
147137
}

core-ng/src/main/java/core/framework/internal/web/HTTPServer.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import core.framework.internal.log.LogManager;
55
import core.framework.internal.web.site.SiteManager;
66
import core.framework.internal.web.sse.ServerSentEventHandler;
7-
import core.framework.internal.web.websocket.WebSocketHandler;
87
import core.framework.util.StopWatch;
98
import core.framework.web.Interceptor;
109
import io.undertow.Undertow;
@@ -36,22 +35,21 @@ public class HTTPServer {
3635

3736
public final SiteManager siteManager = new SiteManager();
3837
public final HTTPHandlerContext handlerContext = new HTTPHandlerContext();
39-
public final HTTPHandler handler;
38+
public final HTTPHandler httpHandler;
4039
final ShutdownHandler shutdownHandler = new ShutdownHandler();
4140
private final Logger logger = LoggerFactory.getLogger(HTTPServer.class);
4241
private final ExecutorService worker = ThreadPools.virtualThreadExecutor("http-handler-");
4342

44-
public WebSocketHandler webSocketHandler;
4543
public ServerSentEventHandler sseHandler;
4644
private Undertow server;
4745

4846
public HTTPServer(LogManager logManager) {
49-
handler = new HTTPHandler(logManager, siteManager.sessionManager, siteManager.templateManager, handlerContext);
47+
httpHandler = new HTTPHandler(logManager, siteManager.sessionManager, siteManager.templateManager, handlerContext);
5048
}
5149

5250
public void start(HTTPServerConfig config) {
5351
var watch = new StopWatch();
54-
handler.interceptors = config.interceptors.toArray(new Interceptor[0]);
52+
httpHandler.interceptors = config.interceptors.toArray(new Interceptor[0]);
5553
HTTPHost httpHost = config.httpHost;
5654
HTTPHost httpsHost = config.httpsHost();
5755
try {
@@ -92,7 +90,7 @@ public void start(HTTPServerConfig config) {
9290
}
9391

9492
private HttpHandler handler(HTTPServerConfig config) {
95-
HttpHandler handler = new HTTPIOHandler(this.handler, shutdownHandler, config.maxEntitySize, sseHandler, webSocketHandler);
93+
HttpHandler handler = new HTTPIOHandler(httpHandler, shutdownHandler, config.maxEntitySize, sseHandler);
9694
if (config.gzip) {
9795
// only support gzip, deflate is less popular
9896
handler = new EncodingHandler(handler, new ContentEncodingRepository()
@@ -105,7 +103,6 @@ public void shutdown() {
105103
if (server != null) {
106104
logger.info("shutting down http server");
107105
shutdownHandler.shutdown();
108-
if (webSocketHandler != null) webSocketHandler.shutdown();
109106
if (sseHandler != null) sseHandler.shutdown();
110107
}
111108
}

core-ng/src/main/java/core/framework/internal/web/ShutdownHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ public class ShutdownHandler implements ExchangeCompletionListener {
2424

2525
private volatile boolean shutdown;
2626

27-
boolean handle(HttpServerExchange exchange, boolean active) {
28-
if (active) { // do not count sse/ws requests
27+
boolean handle(HttpServerExchange exchange, boolean sse) {
28+
if (!sse) { // do not count sse requests
2929
activeRequests.increase();
3030
exchange.addExchangeCompleteListener(this);
3131
}

core-ng/src/main/java/core/framework/internal/web/bean/ResponseBeanWriter.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import core.framework.internal.web.service.ErrorResponse;
88
import core.framework.internal.web.service.InternalErrorResponse;
99
import core.framework.util.Maps;
10-
import core.framework.util.Strings;
1110

1211
import java.lang.reflect.Type;
1312
import java.util.Map;
@@ -37,17 +36,17 @@ public boolean contains(Class<?> beanClass) {
3736
return context.containsKey(beanClass);
3837
}
3938

40-
public byte[] toJSON(Object bean) {
39+
public String toJSON(Object bean) {
4140
if (bean instanceof Optional<?> optional) { // only support Optional<T> as response bean type
42-
if (optional.isEmpty()) return Strings.bytes("null");
41+
if (optional.isEmpty()) return "null";
4342
Object value = optional.get();
4443
Context<Object> context = context(this.context, value.getClass());
4544
context.validator.validate(value, false);
46-
return context.writer.toJSON(value);
45+
return context.writer.toJSONString(value);
4746
} else {
4847
Context<Object> context = context(this.context, bean.getClass());
4948
context.validator.validate(bean, false);
50-
return context.writer.toJSON(bean);
49+
return context.writer.toJSONString(bean);
5150
}
5251
}
5352

core-ng/src/main/java/core/framework/internal/web/response/BeanBody.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package core.framework.internal.web.response;
22

3-
import core.framework.internal.log.filter.BytesLogParam;
3+
import core.framework.util.Strings;
44
import io.undertow.io.Sender;
55
import org.slf4j.Logger;
66
import org.slf4j.LoggerFactory;
@@ -20,9 +20,10 @@ public BeanBody(Object bean) {
2020

2121
@Override
2222
public long send(Sender sender, ResponseHandlerContext context) {
23-
byte[] body = context.writer.toJSON(bean);
24-
LOGGER.debug("[response] body={}", new BytesLogParam(body));
25-
sender.send(ByteBuffer.wrap(body));
26-
return body.length;
23+
String body = context.writer.toJSON(bean);
24+
LOGGER.debug("[response] body={}", body);
25+
byte[] bytes = Strings.bytes(body);
26+
sender.send(ByteBuffer.wrap(bytes));
27+
return bytes.length;
2728
}
2829
}

core-ng/src/main/java/core/framework/internal/web/sse/ChannelImpl.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ChannelImpl<T> implements java.nio.channels.Channel, Channel<T>, Channel.C
3737
final Deque<byte[]> queue = new ConcurrentLinkedDeque<>();
3838

3939
private final ServerSentEventContextImpl<T> serverSentEventContext;
40-
private final ServerSentEventBuilder<T> builder;
40+
private final ServerSentEventWriter<T> builder;
4141

4242
private final ReentrantLock lock = new ReentrantLock();
4343
private final HttpServerExchange exchange;
@@ -53,7 +53,7 @@ class ChannelImpl<T> implements java.nio.channels.Channel, Channel<T>, Channel.C
5353
String clientIP;
5454
String traceId;
5555

56-
ChannelImpl(HttpServerExchange exchange, StreamSinkChannel sink, ServerSentEventContextImpl<T> serverSentEventContext, ServerSentEventBuilder<T> builder, String refId) {
56+
ChannelImpl(HttpServerExchange exchange, StreamSinkChannel sink, ServerSentEventContextImpl<T> serverSentEventContext, ServerSentEventWriter<T> builder, String refId) {
5757
this.exchange = exchange;
5858
this.sink = sink;
5959
this.serverSentEventContext = serverSentEventContext;
@@ -63,30 +63,32 @@ class ChannelImpl<T> implements java.nio.channels.Channel, Channel<T>, Channel.C
6363

6464
@Override
6565
public boolean send(String id, T event) {
66-
String data = builder.build(id, event);
67-
return sendBytes(Strings.bytes(data));
66+
String message = builder.toMessage(id, event);
67+
return sendBytes(Strings.bytes(message));
6868
}
6969

7070
@Override
7171
public Context context() {
7272
return this;
7373
}
7474

75-
boolean sendBytes(byte[] data) {
75+
boolean sendBytes(byte[] event) {
7676
if (closed) return false;
7777

7878
var watch = new StopWatch();
7979
try {
80-
queue.add(data);
81-
lastSentTime = System.nanoTime();
80+
queue.add(event);
8281
sink.getIoThread().execute(() -> writeListener.handleEvent(sink));
82+
83+
lastSentTime = System.nanoTime();
8384
eventCount++;
84-
eventSize += data.length;
85+
eventSize += event.length;
86+
8587
return true;
8688
} finally {
8789
long elapsed = watch.elapsed();
88-
ActionLogContext.track("sse", elapsed, 0, data.length);
89-
LOGGER.debug("send sse data, channel={}, data={}, elapsed={}", id, new BytesLogParam(data), elapsed); // message is not in json format, not masked, assume sse won't send any sensitive data
90+
ActionLogContext.track("sse", elapsed, 0, event.length);
91+
LOGGER.debug("send sse message, channel={}, message={}, elapsed={}", id, new BytesLogParam(event), elapsed); // message is not in json format, not masked, assume sse won't send any sensitive event
9092
}
9193
}
9294

core-ng/src/main/java/core/framework/internal/web/sse/ChannelSupport.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
class ChannelSupport<T> {
1111
final ChannelListener<T> listener;
1212
final ServerSentEventContextImpl<T> context;
13-
final ServerSentEventBuilder<T> builder;
13+
final ServerSentEventWriter<T> builder;
1414
final LimitRate limitRate;
1515

1616
ChannelSupport(ChannelListener<T> listener, Class<T> eventClass, ServerSentEventContextImpl<T> context) {
1717
this.listener = listener;
1818
this.context = context;
19-
builder = new ServerSentEventBuilder<>(eventClass);
19+
builder = new ServerSentEventWriter<>(eventClass);
2020
limitRate = limitRate(listener);
2121
}
2222

core-ng/src/main/java/core/framework/internal/web/sse/ServerSentEventHandler.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.xnio.channels.StreamSinkChannel;
2525

2626
import java.io.IOException;
27-
import java.nio.ByteBuffer;
2827
import java.time.Duration;
2928
import java.util.HashMap;
3029
import java.util.List;
@@ -96,7 +95,7 @@ void handle(HttpServerExchange exchange, StreamSinkChannel sink) {
9695
ChannelSupport<Object> support = (ChannelSupport<Object>) supports.get(key(request.method().name(), path)); // ServerSentEventHandler.check() ensures path exists
9796
actionLog.action("sse:" + path + ":connect");
9897

99-
if (handlerContext.limitRate) {
98+
if (handlerContext.rateControl != null) {
10099
limitRate(handlerContext.rateControl, support, request.clientIP());
101100
}
102101

@@ -125,8 +124,8 @@ void handle(HttpServerExchange exchange, StreamSinkChannel sink) {
125124
logManager.logError(e);
126125

127126
if (channel != null) {
128-
byte[] error = errorResponse(handlerContext.responseBeanWriter.toJSON(ErrorResponse.errorResponse(e, actionLog.id)));
129-
channel.sendBytes(error);
127+
String message = errorMessage(handlerContext.responseBeanWriter.toJSON(ErrorResponse.errorResponse(e, actionLog.id)));
128+
channel.sendBytes(Strings.bytes(message));
130129
channel.close(); // gracefully shutdown connection to make sure retry/error can be sent
131130
}
132131
} finally {
@@ -142,12 +141,10 @@ void limitRate(RateControl rateControl, ChannelSupport<Object> support, String c
142141
}
143142
}
144143

145-
byte[] errorResponse(byte[] errorResponse) {
146-
ByteBuffer buffer = ByteBuffer.wrap(new byte[errorResponse.length + 38]);
147-
buffer.put(Strings.bytes("retry: 86400000\n\nevent: error\ndata: ")); // tell browser retry in 24 hours
148-
buffer.put(errorResponse);
149-
buffer.put(Strings.bytes("\n\n"));
150-
return buffer.array();
144+
String errorMessage(String errorResponse) {
145+
return "retry: 86400000\n\n" +
146+
"event: error\n" +
147+
"data: " + errorResponse + "\n\n";
151148
}
152149

153150
public <T> void add(HTTPMethod method, String path, Class<T> eventClass, ChannelListener<T> listener, ServerSentEventContextImpl<T> context) {

core-ng/src/main/java/core/framework/internal/web/sse/ServerSentEventBuilder.java renamed to core-ng/src/main/java/core/framework/internal/web/sse/ServerSentEventWriter.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@
33
import core.framework.internal.json.JSONWriter;
44
import core.framework.internal.validate.Validator;
55

6-
class ServerSentEventBuilder<T> {
6+
class ServerSentEventWriter<T> {
77
private final JSONWriter<T> writer;
88
private final Validator<T> validator;
99

10-
ServerSentEventBuilder(Class<T> eventClass) {
10+
ServerSentEventWriter(Class<T> eventClass) {
1111
writer = new JSONWriter<>(eventClass);
1212
validator = Validator.of(eventClass);
1313
}
1414

15-
String build(String id, T event) {
15+
String toMessage(String id, T event) {
1616
validator.validate(event, false);
1717
String data = writer.toJSONString(event);
1818

19-
return build(id, data);
19+
return message(id, data);
2020
}
2121

22-
String build(String id, String data) {
22+
String message(String id, String data) {
2323
var builder = new StringBuilder(data.length() + 7 + (id == null ? 0 : id.length() + 4));
2424
if (id != null) builder.append("id: ").append(id).append('\n');
2525
builder.append("data: ").append(data).append("\n\n");

0 commit comments

Comments
 (0)