Skip to content

Commit 69f6699

Browse files
committed
[java] A draft of a Netty-based HTTP client
The default is still OkHttp. Pass -Dwebdriver.http.factory=netty to use the new one. Its stability is below my expectations yet. Everything works well if I run tests from IDEA in a single process, but there are random TimeoutException-s during a Bazel run.
1 parent b2e1adc commit 69f6699

23 files changed

+451
-0
lines changed

.idea/libraries/async_http_client.xml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/libraries/netty_reactive_streams.xml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/libraries/reactive_streams.xml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

java/client/client.iml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
<orderEntry type="library" scope="TEST" name="littleproxy" level="project" />
3434
<orderEntry type="library" name="mockito-core" level="project" />
3535
<orderEntry type="library" name="netty" level="project" />
36+
<orderEntry type="library" name="async-http-client" level="project" />
37+
<orderEntry type="library" name="netty-reactive-streams" level="project" />
38+
<orderEntry type="library" name="reactive-streams" level="project" />
3639
<orderEntry type="library" name="objenesis" level="project" />
3740
<orderEntry type="library" name="okhttp" level="project" />
3841
<orderEntry type="library" name="opentracing" level="project" />

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ java_export(
8080
"//java/client/src/org/openqa/selenium/io",
8181
"//java/client/src/org/openqa/selenium/os",
8282
"//java/client/src/org/openqa/selenium/remote/http/okhttp",
83+
"//java/client/src/org/openqa/selenium/remote/http/netty",
8384
"//third_party/java/bytebuddy:byte-buddy",
8485
"//third_party/java/guava",
8586
],

java/client/src/org/openqa/selenium/remote/http/HttpClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ interface Factory {
3838
static Factory createDefault() {
3939
String defaultFactory = System.getProperty("webdriver.http.factory", "okhttp");
4040
switch (defaultFactory) {
41+
case "netty":
42+
try {
43+
Class<? extends Factory> clazz =
44+
Class.forName("org.openqa.selenium.remote.http.netty.NettyClient$Factory")
45+
.asSubclass(Factory.class);
46+
return clazz.getConstructor().newInstance();
47+
} catch (ReflectiveOperationException e) {
48+
throw new UnsupportedOperationException("Unable to create HTTP client factory", e);
49+
}
50+
4151
case "okhttp":
4252
default:
4353
try {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
java_library(
2+
name = "netty",
3+
srcs = glob(["*.java"]),
4+
visibility = [
5+
"//java/client/src/org/openqa/selenium/remote:__pkg__",
6+
"//java/client/test/org/openqa/selenium/remote/http/netty:__pkg__",
7+
],
8+
deps = [
9+
"//java/client/src/org/openqa/selenium/remote/http",
10+
"//third_party/java/guava",
11+
"//third_party/java/netty:netty-all",
12+
"//third_party/java/async-http-client",
13+
"//third_party/java/reactive-streams",
14+
],
15+
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.remote.http.netty;
19+
20+
import static org.asynchttpclient.Dsl.asyncHttpClient;
21+
22+
import org.asynchttpclient.AsyncHttpClient;
23+
import org.openqa.selenium.remote.http.ClientConfig;
24+
25+
import java.util.Objects;
26+
import java.util.function.Function;
27+
28+
class CreateNettyClient implements Function<ClientConfig, AsyncHttpClient> {
29+
30+
@Override
31+
public AsyncHttpClient apply(ClientConfig config) {
32+
Objects.requireNonNull(config, "Client config to use must be set.");
33+
34+
return asyncHttpClient();
35+
}
36+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.remote.http.netty;
19+
20+
import org.openqa.selenium.remote.http.ClientConfig;
21+
import org.openqa.selenium.remote.http.Filter;
22+
import org.openqa.selenium.remote.http.HttpClient;
23+
import org.openqa.selenium.remote.http.HttpHandler;
24+
import org.openqa.selenium.remote.http.HttpRequest;
25+
import org.openqa.selenium.remote.http.HttpResponse;
26+
import org.openqa.selenium.remote.http.WebSocket;
27+
28+
import java.io.UncheckedIOException;
29+
import java.util.Objects;
30+
import java.util.function.BiFunction;
31+
32+
public class NettyClient implements HttpClient {
33+
34+
private final HttpHandler handler;
35+
private BiFunction<HttpRequest, WebSocket.Listener, WebSocket> toWebSocket;
36+
37+
private NettyClient(HttpHandler handler, BiFunction<HttpRequest, WebSocket.Listener, WebSocket> toWebSocket) {
38+
this.handler = Objects.requireNonNull(handler);
39+
this.toWebSocket = Objects.requireNonNull(toWebSocket);
40+
}
41+
42+
@Override
43+
public HttpResponse execute(HttpRequest request) throws UncheckedIOException {
44+
return handler.execute(request);
45+
}
46+
47+
@Override
48+
public WebSocket openSocket(HttpRequest request, WebSocket.Listener listener) {
49+
Objects.requireNonNull(request, "Request to send must be set.");
50+
Objects.requireNonNull(listener, "WebSocket listener must be set.");
51+
52+
return toWebSocket.apply(request, listener);
53+
}
54+
55+
@Override
56+
public HttpClient with(Filter filter) {
57+
Objects.requireNonNull(filter, "Filter to use must be set.");
58+
59+
// TODO: We should probably ensure that websocket requests are run through the filter.
60+
return new NettyClient(handler.with(filter), toWebSocket);
61+
}
62+
63+
public static class Factory implements HttpClient.Factory {
64+
65+
@Override
66+
public HttpClient createClient(ClientConfig config) {
67+
Objects.requireNonNull(config, "Client config to use must be set.");
68+
69+
return new NettyClient(new NettyHttpHandler(config).with(config.filter()), NettyWebSocket.create(config));
70+
}
71+
}
72+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.remote.http.netty;
19+
20+
import org.asynchttpclient.AsyncHttpClient;
21+
import org.asynchttpclient.Response;
22+
import org.openqa.selenium.remote.http.ClientConfig;
23+
import org.openqa.selenium.remote.http.HttpHandler;
24+
import org.openqa.selenium.remote.http.HttpRequest;
25+
import org.openqa.selenium.remote.http.HttpResponse;
26+
import org.openqa.selenium.remote.http.RemoteCall;
27+
28+
import java.util.Objects;
29+
import java.util.concurrent.ExecutionException;
30+
import java.util.concurrent.Future;
31+
32+
public class NettyHttpHandler extends RemoteCall {
33+
34+
private final AsyncHttpClient client;
35+
private final HttpHandler handler;
36+
37+
public NettyHttpHandler(ClientConfig config) {
38+
super(config);
39+
this.client = new CreateNettyClient().apply(config);
40+
this.handler = config.filter().andFinally(this::makeCall);
41+
}
42+
43+
@Override
44+
public HttpResponse execute(HttpRequest request) {
45+
return handler.execute(request);
46+
}
47+
48+
private HttpResponse makeCall(HttpRequest request) {
49+
Objects.requireNonNull(request, "Request must be set.");
50+
51+
Future<Response> whenResponse = client.executeRequest(
52+
NettyMessages.toNettyRequest(getConfig().baseUri(), request));
53+
54+
try {
55+
Response response = whenResponse.get();
56+
return NettyMessages.toSeleniumResponse(response);
57+
} catch (InterruptedException | ExecutionException e) {
58+
throw new RuntimeException(e);
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)