Skip to content

Commit 480a5ad

Browse files
committed
Make the Netty server implement our Server interface
This also resolves some underlying issues with the server, including not properly handling the buffers used in responses.
1 parent 8249101 commit 480a5ad

File tree

10 files changed

+212
-102
lines changed

10 files changed

+212
-102
lines changed

java/client/src/org/openqa/selenium/remote/http/okhttp/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ java_library(
44
visibility = [
55
"//java/client/src/org/openqa/selenium/remote:__pkg__",
66
"//java/client/test/org/openqa/selenium/remote/http/okhttp:__pkg__",
7+
"//java/server/test/org/openqa/selenium/netty/server:__pkg__",
78
],
89
deps = [
910
"//java/client/src/org/openqa/selenium/remote/http",

java/server/src/org/openqa/selenium/grid/web/BUILD.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ java_library(
33
srcs = glob(["*.java"]),
44
visibility = [
55
"//java/server/src/org/openqa/selenium/grid:__subpackages__",
6+
"//java/server/src/org/openqa/selenium/netty/server:__pkg__",
67
"//java/server/src/org/openqa/selenium/remote/server:__subpackages__",
7-
"//java/server/test/org/openqa/selenium/grid:__subpackages__",
8-
"//java/server/test/org/openqa/selenium/jetty/server:__pkg__",
8+
"//java/server/test/org/openqa/selenium:__subpackages__",
99
],
1010
deps = [
1111
"//java/client/src/org/openqa/selenium:core",

java/server/src/org/openqa/selenium/netty/server/BUILD.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
java_library(
22
name = "server",
33
srcs = glob(["*.java"]),
4+
visibility = [
5+
"//java/server/src/org/openqa/selenium/grid:__subpackages__",
6+
"//java/server/src/org/openqa/selenium/netty/server:__pkg__",
7+
"//java/server/src/org/openqa/selenium/remote/server:__pkg__",
8+
"//java/server/test/org/openqa/selenium:__subpackages__",
9+
],
10+
exports = [
11+
"//java/client/src/org/openqa/selenium/remote/http",
12+
"//java/server/src/org/openqa/selenium/grid/server",
13+
],
414
deps = [
15+
"//java/client/src/org/openqa/selenium/json",
516
"//java/client/src/org/openqa/selenium/remote/http",
17+
"//java/server/src/org/openqa/selenium/grid/server",
18+
"//java/server/src/org/openqa/selenium/grid/web",
619
"//third_party/java/guava",
720
"//third_party/java/netty:netty-all",
821
],

java/server/src/org/openqa/selenium/netty/server/NettyServer.java

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,62 +17,93 @@
1717

1818
package org.openqa.selenium.netty.server;
1919

20-
import static org.openqa.selenium.remote.http.Contents.utf8String;
21-
22-
import org.openqa.selenium.remote.http.Contents;
23-
import org.openqa.selenium.remote.http.HttpHandler;
24-
import org.openqa.selenium.remote.http.HttpResponse;
25-
2620
import io.netty.bootstrap.ServerBootstrap;
2721
import io.netty.channel.Channel;
22+
import io.netty.channel.ChannelOption;
2823
import io.netty.channel.EventLoopGroup;
2924
import io.netty.channel.nio.NioEventLoopGroup;
3025
import io.netty.channel.socket.nio.NioServerSocketChannel;
3126
import io.netty.handler.logging.LogLevel;
3227
import io.netty.handler.logging.LoggingHandler;
28+
import org.openqa.selenium.grid.server.AddWebDriverSpecHeaders;
29+
import org.openqa.selenium.grid.server.BaseServerOptions;
30+
import org.openqa.selenium.grid.server.Server;
31+
import org.openqa.selenium.grid.server.WrapExceptions;
32+
import org.openqa.selenium.grid.server.BaseServerOptions;
33+
import org.openqa.selenium.grid.server.Server;
34+
import org.openqa.selenium.remote.http.HttpHandler;
3335

36+
import java.io.IOException;
37+
import java.io.UncheckedIOException;
38+
import java.net.MalformedURLException;
39+
import java.net.URL;
3440
import java.util.Objects;
3541

36-
public class NettyServer {
42+
public class NettyServer implements Server<NettyServer> {
43+
44+
private final EventLoopGroup bossGroup;
45+
private final EventLoopGroup workerGroup;
46+
private final int port;
47+
private final URL externalUrl;
48+
private final HttpHandler handler;
3749

38-
private HttpHandler handler;
50+
private Channel channel;
3951

40-
public NettyServer(HttpHandler handler) throws InterruptedException {
41-
this.handler = Objects.requireNonNull(handler, "Handler to use must be set.");
52+
public NettyServer(BaseServerOptions options, HttpHandler handler) {
53+
Objects.requireNonNull(options, "Server options must be set.");
54+
Objects.requireNonNull(handler, "Handler to use must be set.");
4255

43-
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
44-
EventLoopGroup workerGroup = new NioEventLoopGroup();
56+
this.handler = handler.with(new WrapExceptions().andThen(new AddWebDriverSpecHeaders()));
57+
58+
bossGroup = new NioEventLoopGroup(1);
59+
workerGroup = new NioEventLoopGroup();
60+
61+
port = options.getPort();
4562
try {
46-
ServerBootstrap b = new ServerBootstrap();
47-
b.group(bossGroup, workerGroup)
48-
.channel(NioServerSocketChannel.class)
49-
.handler(new LoggingHandler(LogLevel.INFO))
50-
.childHandler(new SeleniumHttpInitializer(handler));
63+
externalUrl = options.getExternalUri().toURL();
64+
} catch (MalformedURLException e) {
65+
throw new UncheckedIOException("Server URI is not a valid URL: " + options.getExternalUri(), e);
66+
}
67+
}
5168

52-
Channel ch = b.bind(4444).sync().channel();
69+
@Override
70+
public boolean isStarted() {
71+
return channel != null;
72+
}
5373

54-
System.err.println("Open your web browser and navigate to http://127.0.0.1:4444/");
74+
@Override
75+
public URL getUrl() {
76+
return externalUrl;
77+
}
5578

56-
ch.closeFuture().sync();
79+
@Override
80+
public void stop() {
81+
try {
82+
channel.closeFuture().sync();
83+
} catch (InterruptedException e) {
84+
Thread.currentThread().interrupt();
85+
throw new UncheckedIOException(new IOException("Shutdown interrupted", e));
5786
} finally {
87+
channel = null;
5888
bossGroup.shutdownGracefully();
5989
workerGroup.shutdownGracefully();
6090
}
6191
}
6292

6393
public NettyServer start() {
64-
return this;
65-
}
94+
ServerBootstrap b = new ServerBootstrap();
95+
b.group(bossGroup, workerGroup)
96+
.channel(NioServerSocketChannel.class)
97+
.handler(new LoggingHandler(LogLevel.INFO))
98+
.childHandler(new SeleniumHttpInitializer(handler));
6699

67-
public static void main(String[] args) throws InterruptedException {
68-
NettyServer server = new NettyServer(req -> {
69-
System.out.println(Contents.string(req));
70-
HttpResponse res = new HttpResponse();
71-
res.setContent(utf8String("Hello, World!\n"));
72-
73-
return res;
74-
});
100+
try {
101+
channel = b.bind(port).sync().channel();
102+
} catch (InterruptedException e) {
103+
Thread.currentThread().interrupt();
104+
throw new UncheckedIOException(new IOException("Start up interrupted", e));
105+
}
75106

76-
server.start();
107+
return this;
77108
}
78109
}

java/server/src/org/openqa/selenium/netty/server/RequestConverter.java

Lines changed: 15 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,20 @@
1717

1818
package org.openqa.selenium.netty.server;
1919

20-
import static org.openqa.selenium.remote.http.Contents.bytes;
21-
2220
import com.google.common.io.ByteStreams;
23-
24-
import org.openqa.selenium.remote.http.Contents;
25-
import org.openqa.selenium.remote.http.HttpMethod;
26-
import org.openqa.selenium.remote.http.HttpRequest;
27-
2821
import io.netty.buffer.ByteBuf;
2922
import io.netty.buffer.ByteBufInputStream;
3023
import io.netty.channel.ChannelHandlerContext;
3124
import io.netty.channel.SimpleChannelInboundHandler;
32-
import io.netty.handler.codec.http.FullHttpRequest;
3325
import io.netty.handler.codec.http.HttpContent;
3426
import io.netty.handler.codec.http.HttpObject;
27+
import io.netty.handler.codec.http.HttpUtil;
3528
import io.netty.handler.codec.http.LastHttpContent;
29+
import org.openqa.selenium.remote.http.Contents;
30+
import org.openqa.selenium.remote.http.HttpMethod;
31+
import org.openqa.selenium.remote.http.HttpRequest;
32+
import org.openqa.selenium.remote.http.HttpResponse;
3633

37-
import java.io.ByteArrayOutputStream;
3834
import java.io.IOException;
3935
import java.io.InputStream;
4036
import java.io.PipedInputStream;
@@ -49,52 +45,32 @@ class RequestConverter extends SimpleChannelInboundHandler<HttpObject> {
4945

5046
private static final Logger LOG = Logger.getLogger(RequestConverter.class.getName());
5147
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
52-
private PipedOutputStream out;
48+
private volatile PipedOutputStream out;
5349

5450
@Override
5551
protected void channelRead0(
5652
ChannelHandlerContext ctx,
5753
HttpObject msg) throws Exception {
5854
LOG.fine("Incoming message: " + msg);
5955

60-
if (msg instanceof FullHttpRequest) {
61-
LOG.fine("Is full http request: " + msg);
62-
reset();
63-
FullHttpRequest nettyRequest = (FullHttpRequest) msg;
64-
HttpRequest req = createRequest(nettyRequest);
65-
66-
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
67-
ByteBufInputStream bis = new ByteBufInputStream(nettyRequest.content())) {
68-
ByteStreams.copy(bis, bos);
69-
byte[] bytes = bos.toByteArray();
70-
req.setContent(bytes(bytes));
71-
}
72-
73-
ctx.fireChannelRead(req);
74-
ctx.flush();
75-
return;
76-
}
77-
7856
if (msg instanceof io.netty.handler.codec.http.HttpRequest) {
79-
LOG.fine("Is start of http request: " + msg);
80-
reset();
57+
LOG.fine("Start of http request: " + msg);
58+
8159
io.netty.handler.codec.http.HttpRequest nettyRequest =
82-
(io.netty.handler.codec.http.HttpRequest) msg;
60+
(io.netty.handler.codec.http.HttpRequest) msg;
8361

84-
HttpRequest req = new HttpRequest(
85-
HttpMethod.valueOf(nettyRequest.method().name()),
86-
nettyRequest.uri());
62+
if (HttpUtil.is100ContinueExpected(nettyRequest)) {
63+
ctx.write(new HttpResponse().setStatus(100));
64+
return;
65+
}
8766

88-
nettyRequest.headers().entries().stream()
89-
.filter(entry -> entry.getKey() != null)
90-
.forEach(entry -> req.addHeader(entry.getKey(), entry.getValue()));
67+
HttpRequest req = createRequest(nettyRequest);
9168

9269
out = new PipedOutputStream();
9370
InputStream in = new PipedInputStream(out);
9471

9572
req.setContent(Contents.memoize(() -> in));
9673
ctx.fireChannelRead(req);
97-
ctx.flush();
9874
}
9975

10076
if (msg instanceof HttpContent) {
@@ -111,7 +87,7 @@ protected void channelRead0(
11187
}
11288

11389
if (msg instanceof LastHttpContent) {
114-
LOG.info("Closing input pipe.");
90+
LOG.fine("Closing input pipe.");
11591
EXECUTOR.submit(() -> {
11692
try {
11793
out.close();
@@ -122,20 +98,6 @@ protected void channelRead0(
12298
}
12399
}
124100

125-
@Override
126-
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
127-
ctx.flush();
128-
reset();
129-
super.channelReadComplete(ctx);
130-
}
131-
132-
@Override
133-
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
134-
cause.printStackTrace();
135-
ctx.close();
136-
reset();
137-
}
138-
139101
private HttpRequest createRequest(io.netty.handler.codec.http.HttpRequest nettyRequest) {
140102
HttpRequest req = new HttpRequest(
141103
HttpMethod.valueOf(nettyRequest.method().name()),
@@ -147,9 +109,4 @@ private HttpRequest createRequest(io.netty.handler.codec.http.HttpRequest nettyR
147109

148110
return req;
149111
}
150-
151-
private void reset() throws Exception {
152-
}
153-
154-
155112
}

java/server/src/org/openqa/selenium/netty/server/ResponseConverter.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import static io.netty.handler.codec.http.HttpHeaderValues.CHUNKED;
2323
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
2424

25+
import io.netty.handler.codec.http.HttpHeaderNames;
26+
import io.netty.handler.codec.http.HttpHeaderValues;
2527
import org.openqa.selenium.remote.http.HttpResponse;
2628

2729
import io.netty.buffer.Unpooled;
@@ -55,14 +57,16 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
5557
byte[] ary = new byte[CHUNK_SIZE];
5658
InputStream is = seResponse.getContent().get();
5759
int byteCount = is.read(ary);
60+
// If there are no bytes left to read, then -1 is returned by read, and this is bad.
61+
byteCount = byteCount == -1 ? 0 : byteCount;
5862

5963
DefaultHttpResponse first;
6064
if (byteCount < CHUNK_SIZE) {
6165
is.close();
6266
first = new DefaultFullHttpResponse(
6367
HTTP_1_1,
6468
HttpResponseStatus.valueOf(seResponse.getStatus()),
65-
Unpooled.wrappedBuffer(ary));
69+
Unpooled.wrappedBuffer(ary, 0, byteCount));
6670
first.headers().addInt(CONTENT_LENGTH, byteCount);
6771
copyHeaders(seResponse, first);
6872
ctx.write(first);
@@ -92,6 +96,9 @@ private void copyHeaders(HttpResponse seResponse, DefaultHttpResponse first) {
9296
continue;
9397
}
9498
for (String value : seResponse.getHeaders(name)) {
99+
if (value == null) {
100+
continue;
101+
}
95102
first.headers().add(name, value);
96103
}
97104
}

java/server/src/org/openqa/selenium/netty/server/SeleniumHandler.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
package org.openqa.selenium.netty.server;
1919

20-
import org.openqa.selenium.remote.http.HttpHandler;
21-
import org.openqa.selenium.remote.http.HttpRequest;
22-
2320
import io.netty.channel.ChannelHandlerContext;
2421
import io.netty.channel.SimpleChannelInboundHandler;
22+
import org.openqa.selenium.grid.web.ErrorHandler;
23+
import org.openqa.selenium.json.Json;
24+
import org.openqa.selenium.remote.http.HttpHandler;
25+
import org.openqa.selenium.remote.http.HttpRequest;
26+
import org.openqa.selenium.remote.http.HttpResponse;
2527

2628
import java.util.Objects;
2729
import java.util.concurrent.ExecutorService;
@@ -30,7 +32,8 @@
3032
class SeleniumHandler extends SimpleChannelInboundHandler<HttpRequest> {
3133

3234
private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool();
33-
private HttpHandler seleniumHandler;
35+
private static final Json JSON = new Json();
36+
private final HttpHandler seleniumHandler;
3437

3538
public SeleniumHandler(HttpHandler seleniumHandler) {
3639
super(HttpRequest.class);
@@ -40,11 +43,13 @@ public SeleniumHandler(HttpHandler seleniumHandler) {
4043
@Override
4144
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) {
4245
EXECUTOR.submit(() -> {
46+
HttpResponse res;
4347
try {
44-
ctx.writeAndFlush(seleniumHandler.execute(msg));
45-
} catch (Exception e) {
46-
ctx.fireExceptionCaught(e);
48+
res = seleniumHandler.execute(msg);
49+
} catch (Throwable e) {
50+
res = new ErrorHandler(JSON, e).execute(msg);
4751
}
52+
ctx.writeAndFlush(res);
4853
});
4954
}
5055
}

0 commit comments

Comments
 (0)