Skip to content

Commit bc1d147

Browse files
committed
* sse: send ErrorResponse to client via "event: error" on exception
Signed-off-by: neo <1100909+neowu@users.noreply.github.com>
1 parent 50eb785 commit bc1d147

File tree

18 files changed

+97
-40
lines changed

18 files changed

+97
-40
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Change log
22

3+
### 9.1.7 (2/26/2025 - )
4+
5+
* sse: send ErrorResponse to client via "event: error" on exception
6+
37
### 9.1.6 (2/10/2025 - 2/25/2025)
48

59
* http_client: tweak sse checking

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ apply(plugin = "project")
77

88
subprojects {
99
group = "core.framework"
10-
version = "9.1.6"
10+
version = "9.1.7-b0"
1111
}
1212

1313
val elasticVersion = "8.15.0"

core-ng/src/main/java/core/framework/http/EventSource.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public final class EventSource implements AutoCloseable, Iterable<EventSource.Ev
2525
private int responseBodyLength;
2626
private long elapsed;
2727

28-
private String lastId;
28+
private String lastType; // for "event" field
29+
private String lastId; // for "id" field
2930
private Event nextEvent;
3031

3132
public EventSource(int statusCode, Map<String, String> headers, ResponseBody body, int requestBodyLength, long elapsed) {
@@ -69,11 +70,16 @@ private Event parseResponse(BufferedSource source) {
6970
case "id":
7071
lastId = line.substring(index + 2);
7172
break;
73+
case "event":
74+
lastType = line.substring(index + 2);
75+
break;
7276
case "data":
7377
String id = lastId;
7478
lastId = null;
75-
return new Event(id, line.substring(index + 2));
76-
default: // ignore "event", "retry" and other fields
79+
String type = lastType;
80+
lastType = null;
81+
return new Event(id, type, line.substring(index + 2));
82+
default: // ignore "retry" and other fields
7783
}
7884
}
7985
} catch (IOException e) {
@@ -83,7 +89,7 @@ private Event parseResponse(BufferedSource source) {
8389
}
8490
}
8591

86-
public record Event(String id, String data) {
92+
public record Event(String id, String type, String data) {
8793
}
8894

8995
private final class EventIterator implements Iterator<Event> {

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,7 @@ Object errorResponse(Throwable e, String userAgent, String actionId) {
8989
}
9090
return response;
9191
} else {
92-
var response = new ErrorResponse();
93-
response.id = actionId;
94-
response.message = e.getMessage();
95-
if (e instanceof ErrorCode errorCode) {
96-
response.errorCode = errorCode.errorCode();
97-
} else {
98-
response.errorCode = "INTERNAL_ERROR";
99-
}
100-
return response;
92+
return ErrorResponse.errorResponse(e, actionId);
10193
}
10294
}
10395

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import core.framework.internal.log.ActionLog;
55
import core.framework.internal.log.LogManager;
66
import core.framework.internal.log.Trace;
7-
import core.framework.internal.web.bean.ResponseBeanWriter;
87
import core.framework.internal.web.controller.ControllerHolder;
98
import core.framework.internal.web.controller.InvocationImpl;
109
import core.framework.internal.web.controller.WebContextImpl;
@@ -42,8 +41,6 @@ public class HTTPHandler implements HttpHandler {
4241
public final WebContextImpl webContext = new WebContextImpl();
4342
public final HTTPErrorHandler errorHandler;
4443

45-
public final ResponseBeanWriter responseBeanWriter = new ResponseBeanWriter();
46-
4744
private final Logger logger = LoggerFactory.getLogger(HTTPHandler.class);
4845
private final LogManager logManager;
4946
private final SessionManager sessionManager;
@@ -58,7 +55,7 @@ public class HTTPHandler implements HttpHandler {
5855
this.logManager = logManager;
5956
this.sessionManager = sessionManager;
6057
this.handlerContext = handlerContext;
61-
responseHandler = new ResponseHandler(responseBeanWriter, templateManager, sessionManager);
58+
responseHandler = new ResponseHandler(handlerContext.responseBeanWriter, templateManager, sessionManager);
6259
errorHandler = new HTTPErrorHandler(responseHandler);
6360
}
6461

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

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

33
import core.framework.internal.web.bean.RequestBeanReader;
4+
import core.framework.internal.web.bean.ResponseBeanWriter;
45
import core.framework.internal.web.http.IPv4AccessControl;
56
import core.framework.internal.web.http.RateControl;
67
import core.framework.internal.web.request.RequestParser;
@@ -10,6 +11,7 @@
1011
public class HTTPHandlerContext {
1112
public final RequestParser requestParser = new RequestParser();
1213
public final RequestBeanReader requestBeanReader = new RequestBeanReader();
14+
public final ResponseBeanWriter responseBeanWriter = new ResponseBeanWriter();
1315
public final RateControl rateControl = new RateControl();
1416
@Nullable
1517
public IPv4AccessControl accessControl;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
/**
77
* @author neo
88
*/
9-
final class ResponseHandlerContext {
9+
public final class ResponseHandlerContext {
1010
final ResponseBeanWriter writer;
1111
final TemplateManager templateManager;
1212

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
package core.framework.internal.web.service;
22

33
import core.framework.api.json.Property;
4+
import core.framework.log.ErrorCode;
45

56
/**
67
* @author neo
78
*/
8-
public class ErrorResponse {
9+
public final class ErrorResponse {
10+
public static ErrorResponse errorResponse(Throwable e, String actionId) {
11+
var response = new ErrorResponse();
12+
response.id = actionId;
13+
response.message = e.getMessage();
14+
if (e instanceof ErrorCode errorCode) {
15+
response.errorCode = errorCode.errorCode();
16+
} else {
17+
response.errorCode = "INTERNAL_ERROR";
18+
}
19+
return response;
20+
}
21+
922
@Property(name = "id")
1023
public String id;
11-
1224
@Property(name = "errorCode")
1325
public String errorCode;
14-
1526
@Property(name = "message")
1627
public String message;
1728
}

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

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

3+
import core.framework.internal.log.filter.BytesLogParam;
34
import core.framework.log.ActionLogContext;
45
import core.framework.util.Sets;
56
import core.framework.util.StopWatch;
@@ -53,22 +54,22 @@ class ChannelImpl<T> implements java.nio.channels.Channel, Channel<T> {
5354
@Override
5455
public boolean send(String id, T event) {
5556
String data = builder.build(id, event);
56-
return send(data);
57+
return sendBytes(Strings.bytes(data));
5758
}
5859

59-
boolean send(String data) {
60+
boolean sendBytes(byte[] data) {
6061
if (closed) return false;
6162

6263
var watch = new StopWatch();
6364
try {
64-
queue.add(Strings.bytes(data));
65+
queue.add(data);
6566
lastSentTime = System.nanoTime();
6667
sink.getIoThread().execute(() -> writeListener.handleEvent(sink));
6768
return true;
6869
} finally {
6970
long elapsed = watch.elapsed();
70-
ActionLogContext.track("sse", elapsed, 0, data.length());
71-
LOGGER.debug("send sse data, channel={}, data={}, elapsed={}", id, data, elapsed); // message is not in json format, not masked, assume sse won't send any sensitive data
71+
ActionLogContext.track("sse", elapsed, 0, data.length);
72+
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
7273
}
7374
}
7475

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

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

3+
import core.framework.util.Strings;
34
import core.framework.web.sse.Channel;
45
import core.framework.web.sse.ServerSentEventContext;
56
import org.slf4j.Logger;
@@ -67,7 +68,7 @@ public void keepAlive() {
6768
for (Channel<T> channel : channels.values()) {
6869
ChannelImpl<?> impl = (ChannelImpl<?>) channel;
6970
if (now - impl.lastSentTime >= 15_000_000_000L) {
70-
impl.send(":\n");
71+
impl.sendBytes(Strings.bytes(":\n"));
7172
}
7273
}
7374
}

0 commit comments

Comments
 (0)