Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit 04bebf0

Browse files
committed
SSE in jooby-netty not working with CORS fix jooby-project#1067
1 parent 045ce48 commit 04bebf0

File tree

10 files changed

+258
-234
lines changed

10 files changed

+258
-234
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ pom.xml.versionsBackup
2222
jacoco.exec
2323
.*.md.html
2424
node
25+
dump

modules/jooby-netty/src/main/java/org/jooby/internal/netty/NettyHandler.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@
203203
*/
204204
package org.jooby.internal.netty;
205205

206+
import io.netty.handler.codec.http.DefaultHttpHeaders;
207+
import io.netty.handler.codec.http.HttpHeaders;
206208
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
207209

208210
import org.jooby.internal.ConnectionResetByPeer;
@@ -268,9 +270,10 @@ public void channelRead0(final ChannelHandlerContext ctx, final Object msg) {
268270
try {
269271
String streamId = req.headers().get(STREAM_ID);
270272

273+
HttpHeaders headers = new DefaultHttpHeaders();
271274
handler.handle(
272-
new NettyRequest(ctx, req, tmpdir, wsMaxMessageSize),
273-
new NettyResponse(ctx, bufferSize, keepAlive, streamId));
275+
new NettyRequest(ctx, req, headers, tmpdir, wsMaxMessageSize),
276+
new NettyResponse(ctx, headers, bufferSize, keepAlive, streamId));
274277

275278
} catch (Throwable ex) {
276279
exceptionCaught(ctx, ex);

modules/jooby-netty/src/main/java/org/jooby/internal/netty/NettyRequest.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@
215215
import io.netty.handler.codec.http.FullHttpRequest;
216216
import io.netty.handler.codec.http.HttpContent;
217217
import io.netty.handler.codec.http.HttpHeaderNames;
218+
import io.netty.handler.codec.http.HttpHeaders;
218219
import io.netty.handler.codec.http.HttpMethod;
219220
import io.netty.handler.codec.http.HttpRequest;
220221
import io.netty.handler.codec.http.QueryStringDecoder;
@@ -261,10 +262,11 @@ public class NettyRequest implements NativeRequest {
261262

262263
public static final AttributeKey<Boolean> SECURE = AttributeKey
263264
.newInstance(NettyRequest.class.getName() + ".secure");
264-
;
265265

266266
private HttpRequest req;
267267

268+
private final HttpHeaders responseHeaders;
269+
268270
private QueryStringDecoder query;
269271

270272
private List<org.jooby.Cookie> cookies;
@@ -281,11 +283,11 @@ public class NettyRequest implements NativeRequest {
281283

282284
private int wsMaxMessageSize;
283285

284-
public NettyRequest(final ChannelHandlerContext ctx,
285-
final HttpRequest req, final String tmpdir,
286-
final int wsMaxMessageSize) throws IOException {
286+
public NettyRequest(final ChannelHandlerContext ctx, final HttpRequest req,
287+
final HttpHeaders responseHeaders, final String tmpdir, final int wsMaxMessageSize) {
287288
this.ctx = ctx;
288289
this.req = req;
290+
this.responseHeaders = responseHeaders;
289291
this.tmpdir = tmpdir;
290292
this.query = new QueryStringDecoder(req.uri());
291293
this.path = Router.decode(query.path());
@@ -369,7 +371,7 @@ public List<NativeUpload> files(final String name) throws IOException {
369371
}
370372

371373
@Override
372-
public InputStream in() throws IOException {
374+
public InputStream in() {
373375
ByteBuf content = ((HttpContent) req).content();
374376
return new ByteBufInputStream(content);
375377
}
@@ -394,7 +396,7 @@ public boolean secure() {
394396

395397
@SuppressWarnings("unchecked")
396398
@Override
397-
public <T> T upgrade(final Class<T> type) throws Exception {
399+
public <T> T upgrade(final Class<T> type) {
398400
if (type == NativeWebSocket.class) {
399401
String protocol = ifSecure("wss", "ws");
400402
String webSocketURL = protocol + "://" + req.headers().get(HttpHeaderNames.HOST) + path;
@@ -411,7 +413,7 @@ public <T> T upgrade(final Class<T> type) throws Exception {
411413
ctx.channel().attr(NettyWebSocket.KEY).set(result);
412414
return (T) result;
413415
} else if (type == Sse.class) {
414-
NettySse sse = new NettySse(ctx);
416+
NettySse sse = new NettySse(ctx, responseHeaders);
415417
return (T) sse;
416418
} else if (type == NativePushPromise.class) {
417419
return (T) new NettyPush(ctx,

modules/jooby-netty/src/main/java/org/jooby/internal/netty/NettyResponse.java

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -203,29 +203,17 @@
203203
*/
204204
package org.jooby.internal.netty;
205205

206-
import static io.netty.channel.ChannelFutureListener.CLOSE;
207-
208-
import java.io.InputStream;
209-
import java.nio.ByteBuffer;
210-
import java.nio.channels.FileChannel;
211-
import java.util.Collections;
212-
import java.util.List;
213-
import java.util.Optional;
214-
215-
import org.jooby.spi.NativeResponse;
216-
217206
import com.google.common.collect.ImmutableList;
218207
import com.google.common.io.ByteStreams;
219-
220208
import io.netty.buffer.ByteBuf;
221209
import io.netty.buffer.Unpooled;
222210
import io.netty.channel.ChannelFuture;
211+
import static io.netty.channel.ChannelFutureListener.CLOSE;
223212
import io.netty.channel.ChannelHandlerContext;
224213
import io.netty.channel.ChannelPipeline;
225214
import io.netty.channel.ChannelPromise;
226215
import io.netty.channel.DefaultFileRegion;
227216
import io.netty.handler.codec.http.DefaultFullHttpResponse;
228-
import io.netty.handler.codec.http.DefaultHttpHeaders;
229217
import io.netty.handler.codec.http.DefaultHttpResponse;
230218
import io.netty.handler.codec.http.HttpChunkedInput;
231219
import io.netty.handler.codec.http.HttpHeaderNames;
@@ -240,6 +228,14 @@
240228
import io.netty.handler.stream.ChunkedStream;
241229
import io.netty.handler.stream.ChunkedWriteHandler;
242230
import io.netty.util.Attribute;
231+
import org.jooby.spi.NativeResponse;
232+
233+
import java.io.InputStream;
234+
import java.nio.ByteBuffer;
235+
import java.nio.channels.FileChannel;
236+
import java.util.Collections;
237+
import java.util.List;
238+
import java.util.Optional;
243239

244240
public class NettyResponse implements NativeResponse {
245241

@@ -255,17 +251,17 @@ public class NettyResponse implements NativeResponse {
255251

256252
private int bufferSize;
257253

258-
public NettyResponse(final ChannelHandlerContext ctx, final int bufferSize,
259-
final boolean keepAlive) {
260-
this(ctx, bufferSize, keepAlive, null);
254+
public NettyResponse(final ChannelHandlerContext ctx, final HttpHeaders headers,
255+
final int bufferSize, final boolean keepAlive) {
256+
this(ctx, headers, bufferSize, keepAlive, null);
261257
}
262258

263-
public NettyResponse(final ChannelHandlerContext ctx, final int bufferSize,
264-
final boolean keepAlive, final String streamId) {
259+
public NettyResponse(final ChannelHandlerContext ctx, final HttpHeaders headers,
260+
final int bufferSize, final boolean keepAlive, final String streamId) {
265261
this.ctx = ctx;
266262
this.bufferSize = bufferSize;
267263
this.keepAlive = keepAlive;
268-
this.headers = new DefaultHttpHeaders();
264+
this.headers = headers;
269265
if (streamId != null) {
270266
headers.set(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId);
271267
}

modules/jooby-netty/src/main/java/org/jooby/internal/netty/NettySse.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,9 @@
207207
import io.netty.channel.ChannelFuture;
208208
import io.netty.channel.ChannelFutureListener;
209209
import io.netty.channel.ChannelHandlerContext;
210-
import io.netty.handler.codec.http.DefaultHttpHeaders;
211210
import io.netty.handler.codec.http.DefaultHttpResponse;
212211
import io.netty.handler.codec.http.HttpHeaderNames;
212+
import io.netty.handler.codec.http.HttpHeaders;
213213
import io.netty.handler.codec.http.HttpResponseStatus;
214214
import io.netty.handler.codec.http.HttpVersion;
215215
import org.jooby.Sse;
@@ -229,8 +229,8 @@ private static class DoneCallback implements ChannelFutureListener {
229229
private Optional<Object> id;
230230

231231
public DoneCallback(final CompletableFuture<Optional<Object>> promise,
232-
final Optional<Object> id,
233-
final Consumer<Throwable> ifClose) {
232+
final Optional<Object> id,
233+
final Consumer<Throwable> ifClose) {
234234
this.id = id;
235235
this.promise = promise;
236236
this.ifClose = ifClose;
@@ -248,10 +248,12 @@ public void operationComplete(final ChannelFuture future) throws Exception {
248248
}
249249
}
250250

251-
private ChannelHandlerContext ctx;
251+
private final ChannelHandlerContext ctx;
252+
private final HttpHeaders headers;
252253

253-
public NettySse(final ChannelHandlerContext ctx) {
254+
public NettySse(final ChannelHandlerContext ctx, final HttpHeaders headers) {
254255
this.ctx = ctx;
256+
this.headers = headers;
255257
}
256258

257259
@Override
@@ -261,11 +263,10 @@ protected void closeInternal() {
261263

262264
@Override
263265
protected void handshake(final Runnable handler) throws Exception {
264-
DefaultHttpHeaders headers = new DefaultHttpHeaders();
265266
headers.set(HttpHeaderNames.CONNECTION, "Close");
266267
headers.set(HttpHeaderNames.CONTENT_TYPE, "text/event-stream; charset=utf-8");
267268
ctx.writeAndFlush(
268-
new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, headers));
269+
new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, headers));
269270
ctx.executor().execute(handler);
270271
}
271272

@@ -274,7 +275,7 @@ protected CompletableFuture<Optional<Object>> send(final Optional<Object> id, fi
274275
synchronized (this) {
275276
CompletableFuture<Optional<Object>> promise = new CompletableFuture<>();
276277
ctx.writeAndFlush(Unpooled.wrappedBuffer(data))
277-
.addListener(new DoneCallback(promise, id, this::ifClose));
278+
.addListener(new DoneCallback(promise, id, this::ifClose));
278279
return promise;
279280
}
280281
}

modules/jooby-netty/src/test/java/org/jooby/internal/netty/NettyHandlerTest.java

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
11
package org.jooby.internal.netty;
22

3-
import static org.easymock.EasyMock.expect;
4-
import static org.easymock.EasyMock.expectLastCall;
5-
6-
import java.io.IOException;
7-
8-
import org.jooby.spi.HttpHandler;
9-
import org.jooby.test.MockUnit;
10-
import org.jooby.test.MockUnit.Block;
11-
import org.junit.Test;
12-
import org.junit.runner.RunWith;
13-
import org.powermock.core.classloader.annotations.PrepareForTest;
14-
import org.powermock.modules.junit4.PowerMockRunner;
15-
163
import com.typesafe.config.Config;
17-
184
import io.netty.channel.Channel;
195
import io.netty.channel.ChannelFuture;
206
import io.netty.channel.ChannelHandlerContext;
217
import io.netty.handler.codec.http.DefaultFullHttpResponse;
8+
import io.netty.handler.codec.http.DefaultHttpHeaders;
229
import io.netty.handler.codec.http.FullHttpRequest;
2310
import io.netty.handler.codec.http.HttpHeaders;
2411
import io.netty.handler.codec.http.HttpMethod;
@@ -30,6 +17,17 @@
3017
import io.netty.handler.codec.http2.HttpConversionUtil;
3118
import io.netty.handler.timeout.IdleStateEvent;
3219
import io.netty.util.Attribute;
20+
import static org.easymock.EasyMock.expect;
21+
import static org.easymock.EasyMock.expectLastCall;
22+
import org.jooby.spi.HttpHandler;
23+
import org.jooby.test.MockUnit;
24+
import org.jooby.test.MockUnit.Block;
25+
import org.junit.Test;
26+
import org.junit.runner.RunWith;
27+
import org.powermock.core.classloader.annotations.PrepareForTest;
28+
import org.powermock.modules.junit4.PowerMockRunner;
29+
30+
import java.io.IOException;
3331

3432
@RunWith(PowerMockRunner.class)
3533
@PrepareForTest({NettyHandler.class, NettyRequest.class, NettyResponse.class,
@@ -51,7 +49,8 @@ public void channelReadCompleteRead0With100ContinueExpected() throws Exception {
5149
FullHttpRequest.class)
5250
.expect(channel)
5351
.expect(unit -> {
54-
HttpHeaders headers = unit.mock(HttpHeaders.class);
52+
HttpHeaders headers = unit.constructor(DefaultHttpHeaders.class)
53+
.build();
5554
expect(headers.get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()))
5655
.andReturn(null);
5756

@@ -83,12 +82,12 @@ public void channelReadCompleteRead0With100ContinueExpected() throws Exception {
8382
expect(channel.attr(NettyHandler.PATH)).andReturn(attr);
8483

8584
NettyRequest nreq = unit.constructor(NettyRequest.class)
86-
.args(ChannelHandlerContext.class, HttpRequest.class, String.class, int.class)
87-
.build(unit.get(ChannelHandlerContext.class), req, "target", 3000);
85+
.args(ChannelHandlerContext.class, HttpRequest.class, HttpHeaders.class, String.class, int.class)
86+
.build(unit.get(ChannelHandlerContext.class), req, headers, "target", 3000);
8887

8988
NettyResponse nrsp = unit.constructor(NettyResponse.class)
90-
.args(ChannelHandlerContext.class, int.class, boolean.class, String.class)
91-
.build(unit.get(ChannelHandlerContext.class), 8192, true, null);
89+
.args(ChannelHandlerContext.class, HttpHeaders.class, int.class, boolean.class, String.class)
90+
.build(unit.get(ChannelHandlerContext.class), headers, 8192, true, null);
9291

9392
unit.get(HttpHandler.class).handle(nreq, nrsp);
9493
})
@@ -117,8 +116,8 @@ public void channelReadCompleteRead0WithKeepAlive() throws Exception {
117116
FullHttpRequest.class)
118117
.expect(channel)
119118
.expect(unit -> {
120-
121-
HttpHeaders headers = unit.mock(HttpHeaders.class);
119+
HttpHeaders headers = unit.constructor(DefaultHttpHeaders.class)
120+
.build();
122121
expect(headers.get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()))
123122
.andReturn(null);
124123

@@ -144,12 +143,12 @@ public void channelReadCompleteRead0WithKeepAlive() throws Exception {
144143
expect(channel.attr(NettyHandler.PATH)).andReturn(attr);
145144

146145
NettyRequest nreq = unit.constructor(NettyRequest.class)
147-
.args(ChannelHandlerContext.class, HttpRequest.class, String.class, int.class)
148-
.build(unit.get(ChannelHandlerContext.class), req, "target", 3000);
146+
.args(ChannelHandlerContext.class, HttpRequest.class, HttpHeaders.class, String.class, int.class)
147+
.build(unit.get(ChannelHandlerContext.class), req, headers, "target", 3000);
149148

150149
NettyResponse nrsp = unit.constructor(NettyResponse.class)
151-
.args(ChannelHandlerContext.class, int.class, boolean.class, String.class)
152-
.build(unit.get(ChannelHandlerContext.class), 8192, true, null);
150+
.args(ChannelHandlerContext.class, HttpHeaders.class, int.class, boolean.class, String.class)
151+
.build(unit.get(ChannelHandlerContext.class), headers, 8192, true, null);
153152

154153
unit.get(HttpHandler.class).handle(nreq, nrsp);
155154
})
@@ -271,20 +270,21 @@ public void channelReadCompleteRead0WithException() throws Exception {
271270
.expect(unit -> {
272271
ChannelHandlerContext ctx = unit.get(ChannelHandlerContext.class);
273272

274-
HttpHeaders headers = unit.mock(HttpHeaders.class);
273+
HttpHeaders headers = unit.constructor(DefaultHttpHeaders.class)
274+
.build();
275275
expect(headers.get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()))
276276
.andReturn(null);
277277

278278
FullHttpRequest request = unit.get(FullHttpRequest.class);
279279
expect(request.headers()).andReturn(headers);
280280

281281
NettyRequest req = unit.constructor(NettyRequest.class)
282-
.args(ChannelHandlerContext.class, HttpRequest.class, String.class, int.class)
283-
.build(ctx, request, "target", 3000);
282+
.args(ChannelHandlerContext.class, HttpRequest.class, HttpHeaders.class, String.class, int.class)
283+
.build(ctx, request, headers, "target", 3000);
284284

285285
NettyResponse rsp = unit.constructor(NettyResponse.class)
286-
.args(ChannelHandlerContext.class, int.class, boolean.class, String.class)
287-
.build(ctx, 8192, true, null);
286+
.args(ChannelHandlerContext.class, HttpHeaders.class, int.class, boolean.class, String.class)
287+
.build(ctx, headers, 8192, true, null);
288288

289289
HttpHandler dispatcher = unit.get(HttpHandler.class);
290290
dispatcher.handle(req, rsp);

modules/jooby-netty/src/test/java/org/jooby/internal/netty/NettyRequestTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jooby.internal.netty;
22

3+
import io.netty.handler.codec.http.HttpHeaders;
34
import static org.easymock.EasyMock.expect;
45

56
import org.jooby.test.MockUnit;
@@ -36,24 +37,24 @@ public class NettyRequestTest {
3637

3738
@Test
3839
public void newObject() throws Exception {
39-
new MockUnit(ChannelHandlerContext.class, HttpRequest.class)
40+
new MockUnit(ChannelHandlerContext.class, HttpRequest.class, HttpHeaders.class)
4041
.expect(channel)
4142
.expect(newObject)
4243
.run(unit -> {
43-
new NettyRequest(unit.get(ChannelHandlerContext.class), unit.get(HttpRequest.class),
44-
"target", 60);
44+
new NettyRequest(unit.get(ChannelHandlerContext.class),
45+
unit.get(HttpRequest.class), unit.get(HttpHeaders.class), "target", 60);
4546
});
4647
}
4748

4849
@Test(expected = UnsupportedOperationException.class)
4950
public void unknownUpgrade() throws Exception {
50-
new MockUnit(ChannelHandlerContext.class, HttpRequest.class)
51+
new MockUnit(ChannelHandlerContext.class, HttpRequest.class, HttpHeaders.class)
5152
.expect(channel)
5253
.expect(newObject)
5354
.run(unit -> {
54-
new NettyRequest(unit.get(ChannelHandlerContext.class), unit.get(HttpRequest.class),
55-
"target", 60)
56-
.upgrade(Object.class);
55+
new NettyRequest(unit.get(ChannelHandlerContext.class),
56+
unit.get(HttpRequest.class), unit.get(HttpHeaders.class), "target", 60)
57+
.upgrade(Object.class);
5758
});
5859
}
5960

0 commit comments

Comments
 (0)