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

Commit cc4005f

Browse files
committed
Async Request Feature Request fixes jooby-project#173
1 parent 6bd924d commit cc4005f

File tree

31 files changed

+1409
-363
lines changed

31 files changed

+1409
-363
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.jooby;
2+
3+
import java.util.concurrent.ExecutorService;
4+
import java.util.concurrent.Executors;
5+
6+
import org.jooby.test.ServerFeature;
7+
import org.junit.Test;
8+
9+
public class HelloAsyncFeature extends ServerFeature {
10+
11+
{
12+
ExecutorService executor = Executors.newSingleThreadExecutor();
13+
14+
Object ierr = new Object();
15+
16+
renderer((value, ctx) -> {
17+
if (value == ierr) {
18+
throw new IllegalStateException("/intentional err");
19+
}
20+
});
21+
22+
get("/hi", promise(deferred -> {
23+
executor.execute(() -> {
24+
deferred.resolve("hi");
25+
});
26+
}));
27+
28+
get("/err/init", promise(deferred -> {
29+
throw new Err(Status.SERVER_ERROR);
30+
}));
31+
32+
get("/err/async", promise(deferred -> {
33+
executor.execute(deferred.run(() -> {
34+
throw new Err(Status.NOT_FOUND);
35+
}));
36+
}));
37+
38+
get("/err/send", promise(deferred -> {
39+
executor.execute(deferred.run(() -> ierr));
40+
}));
41+
42+
get("/:name", promise((req, deferred) -> {
43+
executor.execute(deferred.run(() -> {
44+
return req.param("name").value();
45+
}));
46+
}));
47+
}
48+
49+
@Test
50+
public void async() throws Exception {
51+
request()
52+
.get("/hi")
53+
.expect("hi");
54+
55+
request()
56+
.get("/bye")
57+
.expect("bye");
58+
59+
request()
60+
.get("/err/init")
61+
.expect(500);
62+
63+
request()
64+
.get("/err/async")
65+
.expect(404);
66+
67+
request()
68+
.get("/err/send")
69+
.expect(500);
70+
}
71+
72+
}

jooby-jetty/src/main/java/org/jooby/internal/jetty/JettyHandler.java

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public JettyHandler(final HttpHandler dispatcher,
6161
@Override
6262
public void handle(final String target, final Request baseRequest,
6363
final HttpServletRequest request, final HttpServletResponse response) throws IOException,
64-
ServletException {
64+
ServletException {
6565
try {
6666

6767
baseRequest.setHandled(true);
@@ -73,30 +73,28 @@ public void handle(final String target, final Request baseRequest,
7373
multipart = true;
7474
}
7575

76-
dispatcher.handle(
77-
new ServletServletRequest(request, tmpdir, multipart)
78-
.with(new ServletUpgrade() {
79-
80-
@SuppressWarnings("unchecked")
81-
@Override
82-
public <T> T upgrade(final Class<T> type) throws Exception {
83-
if (type == NativeWebSocket.class) {
84-
if (webSocketServerFactory.isUpgradeRequest(request, response)) {
85-
if (webSocketServerFactory.acceptWebSocket(request, response)) {
86-
String key = JettyWebSocket.class.getName();
87-
NativeWebSocket ws = (NativeWebSocket) request.getAttribute(key);
88-
if (ws != null) {
89-
request.removeAttribute(key);
90-
return (T) ws;
91-
}
92-
}
76+
ServletServletRequest nreq = new ServletServletRequest(request, tmpdir, multipart)
77+
.with(new ServletUpgrade() {
78+
79+
@SuppressWarnings("unchecked")
80+
@Override
81+
public <T> T upgrade(final Class<T> type) throws Exception {
82+
if (type == NativeWebSocket.class) {
83+
if (webSocketServerFactory.isUpgradeRequest(request, response)) {
84+
if (webSocketServerFactory.acceptWebSocket(request, response)) {
85+
String key = JettyWebSocket.class.getName();
86+
NativeWebSocket ws = (NativeWebSocket) request.getAttribute(key);
87+
if (ws != null) {
88+
request.removeAttribute(key);
89+
return (T) ws;
9390
}
9491
}
95-
throw new UnsupportedOperationException("Not Supported: " + type);
9692
}
97-
}),
98-
new JettyResponse(baseRequest, response)
99-
);
93+
}
94+
throw new UnsupportedOperationException("Not Supported: " + type);
95+
}
96+
});
97+
dispatcher.handle(nreq, new JettyResponse(nreq, response));
10098
} catch (IOException | ServletException | RuntimeException ex) {
10199
baseRequest.setHandled(false);
102100
log.error("execution of: " + target + " resulted in error", ex);

jooby-jetty/src/main/java/org/jooby/internal/jetty/JettyResponse.java

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@
2323
import java.nio.channels.Channels;
2424
import java.nio.channels.FileChannel;
2525

26-
import javax.servlet.AsyncContext;
2726
import javax.servlet.http.HttpServletResponse;
2827

2928
import org.eclipse.jetty.server.HttpOutput;
30-
import org.eclipse.jetty.server.Request;
3129
import org.eclipse.jetty.server.Response;
3230
import org.eclipse.jetty.util.Callback;
31+
import org.jooby.servlet.ServletServletRequest;
3332
import org.jooby.servlet.ServletServletResponse;
3433
import org.slf4j.Logger;
3534
import org.slf4j.LoggerFactory;
@@ -39,13 +38,11 @@ public class JettyResponse extends ServletServletResponse implements Callback {
3938
/** The logging system. */
4039
private final Logger log = LoggerFactory.getLogger(org.jooby.Response.class);
4140

42-
private Request req;
41+
private ServletServletRequest nreq;
4342

44-
private AsyncContext async;
45-
46-
public JettyResponse(final Request req, final HttpServletResponse rsp) {
47-
super(rsp);
48-
this.req = req;
43+
public JettyResponse(final ServletServletRequest nreq, final HttpServletResponse rsp) {
44+
super(nreq.servletRequest(), rsp);
45+
this.nreq = nreq;
4946
}
5047

5148
@Override
@@ -60,7 +57,7 @@ public void send(final ByteBuffer buffer) throws Exception {
6057

6158
@Override
6259
public void send(final InputStream stream) throws Exception {
63-
this.async = req.startAsync();
60+
nreq.startAsync();
6461
sender().sendContent(Channels.newChannel(stream), this);
6562
}
6663

@@ -71,7 +68,7 @@ public void send(final FileChannel channel) throws Exception {
7168
// sync version, file size is smaller than bufferSize
7269
sender().sendContent(channel);
7370
} else {
74-
this.async = req.startAsync();
71+
nreq.startAsync();
7572
sender().sendContent(channel, this);
7673
}
7774
}
@@ -82,37 +79,23 @@ private HttpOutput sender() {
8279

8380
@Override
8481
public void succeeded() {
85-
complete();
82+
end();
8683
}
8784

8885
@Override
8986
public void failed(final Throwable cause) {
90-
complete();
91-
// TODO: will be nice to log the path of the current request
92-
log.error(rsp.toString(), cause);
87+
log.error("execution of " + nreq.path() + " resulted in exception", cause);
88+
end();
9389
}
9490

9591
@Override
9692
public void end() {
97-
if (async == null) {
98-
close();
99-
}
10093
super.end();
94+
nreq = null;
10195
}
10296

103-
private void complete() {
104-
if (async != null) {
105-
async.complete();
106-
async = null;
107-
} else {
108-
close();
109-
}
110-
}
111-
112-
private void close() {
113-
HttpOutput output = sender();
114-
if (!output.isClosed()) {
115-
output.close();
116-
}
97+
@Override
98+
protected void close() {
99+
sender().close();
117100
}
118101
}

jooby-jetty/src/main/java/org/jooby/internal/jetty/JettyServer.java

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.slf4j.Logger;
4141
import org.slf4j.LoggerFactory;
4242

43+
import com.google.common.base.Throwables;
4344
import com.google.common.primitives.Primitives;
4445
import com.typesafe.config.Config;
4546
import com.typesafe.config.ConfigException;
@@ -63,21 +64,20 @@ private Server server(final HttpHandler handler, final Config config) {
6364
System.setProperty("org.eclipse.jetty.server.Request.maxFormContentSize",
6465
config.getBytes("server.http.MaxRequestSize").toString());
6566

66-
QueuedThreadPool pool = configure(new QueuedThreadPool(), config.getConfig("jetty.threads"),
67+
QueuedThreadPool pool = conf(new QueuedThreadPool(), config.getConfig("jetty.threads"),
6768
"jetty.threads");
68-
pool.setName("jetty");
6969

7070
Server server = new Server(pool);
7171
server.setStopAtShutdown(false);
7272

7373
// HTTP connector
74-
ServerConnector http = http(server, config.getConfig("jetty.http"), "jetty.http");
74+
ServerConnector http = connector(server, config.getConfig("jetty.http"), "jetty.http");
7575
http.setPort(config.getInt("application.port"));
7676
http.setHost(config.getString("application.host"));
7777

7878
server.addConnector(http);
7979

80-
WebSocketPolicy wsConfig = configure(new WebSocketPolicy(WebSocketBehavior.SERVER),
80+
WebSocketPolicy wsConfig = conf(new WebSocketPolicy(WebSocketBehavior.SERVER),
8181
config.getConfig("jetty.ws"), "jetty.ws");
8282
WebSocketServerFactory webSocketServerFactory = new WebSocketServerFactory(wsConfig);
8383
webSocketServerFactory.setCreator((req, rsp) -> {
@@ -92,11 +92,15 @@ private Server server(final HttpHandler handler, final Config config) {
9292
return server;
9393
}
9494

95-
private ServerConnector http(final Server server, final Config config, final String path) {
96-
HttpConfiguration httpConfig = configure(new HttpConfiguration(),
97-
config.withoutPath("connector"), path);
98-
return configure(new ServerConnector(server, new HttpConnectionFactory(httpConfig)),
99-
config.getConfig("connector"), path + ".connector");
95+
private ServerConnector connector(final Server server, final Config conf, final String path) {
96+
HttpConfiguration httpConfig = conf(new HttpConfiguration(), conf.withoutPath("connector"),
97+
path);
98+
99+
HttpConnectionFactory httpFactory = new HttpConnectionFactory(httpConfig);
100+
101+
ServerConnector connector = new ServerConnector(server, httpFactory);
102+
103+
return conf(connector, conf.getConfig("connector"), path + ".connector");
100104
}
101105

102106
@Override
@@ -115,32 +119,37 @@ public void stop() throws Exception {
115119
}
116120

117121
private void tryOption(final Object source, final Config config, final Method option) {
118-
String optionName = option.getName().replace("set", "");
119-
Object optionValue = config.getAnyRef(optionName);
120-
Class<?> optionType = Primitives.wrap(option.getParameterTypes()[0]);
121-
if (Number.class.isAssignableFrom(optionType)) {
122-
if (optionValue instanceof String) {
123-
// either a byte or time unit
124-
try {
125-
optionValue = config.getBytes(optionName);
126-
} catch (ConfigException.BadValue ex) {
127-
optionValue = config.getDuration(optionName, TimeUnit.MILLISECONDS);
128-
}
129-
if (optionType == Integer.class) {
130-
// to int
131-
optionValue = ((Number) optionValue).intValue();
122+
try {
123+
String optionName = option.getName().replace("set", "");
124+
Object optionValue = config.getAnyRef(optionName);
125+
Class<?> optionType = Primitives.wrap(option.getParameterTypes()[0]);
126+
if (Number.class.isAssignableFrom(optionType)) {
127+
if (optionValue instanceof String) {
128+
// either a byte or time unit
129+
try {
130+
optionValue = config.getBytes(optionName);
131+
} catch (ConfigException.BadValue ex) {
132+
optionValue = config.getDuration(optionName, TimeUnit.MILLISECONDS);
133+
}
134+
if (optionType == Integer.class) {
135+
// to int
136+
optionValue = ((Number) optionValue).intValue();
137+
}
132138
}
133139
}
134-
}
135-
try {
136140
log.debug("{}.{}({})", source.getClass().getSimpleName(), option.getName(), optionValue);
137141
option.invoke(source, optionValue);
138-
} catch (IllegalAccessException | InvocationTargetException ex) {
139-
throw new IllegalStateException("Unknown/bad option: " + optionName, ex);
142+
} catch (Exception ex) {
143+
Throwable cause = ex;
144+
if (ex instanceof InvocationTargetException) {
145+
cause = ((InvocationTargetException) ex).getTargetException();
146+
}
147+
log.error("invocation of " + option + " resulted in exception", cause);
148+
throw Throwables.propagate(cause);
140149
}
141150
}
142151

143-
private <T> T configure(final T source, final Config config, final String path) {
152+
private <T> T conf(final T source, final Config config, final String path) {
144153
Map<String, Method> methods = Arrays.stream(source.getClass().getMethods())
145154
.filter(m -> m.getName().startsWith("set") && m.getParameterCount() == 1)
146155
.collect(Collectors.toMap(Method::getName, Function.<Method> identity()));

jooby-jetty/src/main/resources/org/jooby/spi/server.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jetty {
1010

1111
IdleTimeout = ${server.threads.IdleTimeout}
1212

13-
Name = jetty
13+
Name = jetty task
1414
}
1515

1616
http {

0 commit comments

Comments
 (0)