Skip to content

Commit

Permalink
Make OkHttp calls appear to be Just Another HttpHandler
Browse files Browse the repository at this point in the history
The next step is to make the local ends talk to HttpHandlers and
then it'll be easy to switch between local and remote execution.
  • Loading branch information
shs96c committed Jul 4, 2019
1 parent 7df7c1b commit ba67fbd
Show file tree
Hide file tree
Showing 8 changed files with 509 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.remote.http;

import org.openqa.selenium.BuildInfo;
import org.openqa.selenium.Platform;

import java.util.Locale;

public class AddSeleniumUserAgent implements Filter {

private static final String USER_AGENT = String.format(
"selenium/%s (java %s)",
new BuildInfo().getReleaseLabel(),
(Platform.getCurrent().family() == null ?
Platform.getCurrent().toString().toLowerCase(Locale.US) :
Platform.getCurrent().family().toString().toLowerCase(Locale.US)));

@Override
public HttpHandler apply(HttpHandler next) {

return req -> {
if (req.getHeader("User-Agent") == null) {
req.addHeader("User-Agent", USER_AGENT);
}

return next.execute(req);
};
}
}
117 changes: 117 additions & 0 deletions java/client/src/org/openqa/selenium/remote/http/ClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.remote.http;

import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Duration;
import java.util.Objects;

public class ClientConfig {

public static final AddSeleniumUserAgent DEFAULT_FILTER = new AddSeleniumUserAgent();
private final URI baseUri;
private final Duration connectionTimeout;
private final Duration readTimeout;
private final Filter filters;
private final Proxy proxy;

private ClientConfig(
URI baseUri,
Duration connectionTimeout,
Duration readTimeout,
Filter filters,
Proxy proxy) {
this.baseUri = Objects.requireNonNull(baseUri, "Base URI must be set.");
this.connectionTimeout = Objects.requireNonNull(
connectionTimeout,
"Connection timeout must be set.");
this.readTimeout = Objects.requireNonNull(readTimeout, "Connection timeout must be set.");
this.filters = Objects.requireNonNull(filters, "Filters must be set.");
this.proxy = proxy;
}

public static ClientConfig defaultConfig(URI baseUri) {
Objects.requireNonNull(baseUri, "Base URI to use must be set.");
return new ClientConfig(
baseUri,
Duration.ofMinutes(2),
Duration.ofHours(3),
new AddSeleniumUserAgent(),
null);
}

public ClientConfig baseUri(URI baseUri) {
Objects.requireNonNull(baseUri, "Base URI to use must be set.");
return new ClientConfig(baseUri, connectionTimeout, readTimeout, filters, proxy);
}

public ClientConfig baseUrl(URL baseUrl) {
Objects.requireNonNull(baseUrl, "Base URL to use must be set.");
try {
return baseUri(baseUrl.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

public URI baseUri() {
return baseUri;
}

public ClientConfig connectionTimeout(Duration timeout) {
Objects.requireNonNull(timeout, "Connection timeout must be set.");
return new ClientConfig(baseUri, timeout, readTimeout, filters, proxy);
}

public Duration connectionTimeout() {
return connectionTimeout;
}

public ClientConfig readTimeout(Duration timeout) {
Objects.requireNonNull(timeout, "Read timeout must be set.");
return new ClientConfig(baseUri, connectionTimeout, timeout, filters, proxy);
}

public Duration readTimeout() {
return readTimeout;
}

public ClientConfig withFilter(Filter filter) {
return new ClientConfig(
baseUri,
connectionTimeout,
readTimeout,
filter == null ? DEFAULT_FILTER : filter.andThen(DEFAULT_FILTER),
proxy);
}

public Filter filter() {
return filters;
}

public ClientConfig proxy(Proxy proxy) {
return new ClientConfig(baseUri, connectionTimeout, readTimeout, filters, proxy);
}

public Proxy proxy() {
return proxy;
}
}
33 changes: 33 additions & 0 deletions java/client/src/org/openqa/selenium/remote/http/RemoteCall.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.remote.http;

import java.util.Objects;

public abstract class RemoteCall implements HttpHandler {

private final ClientConfig config;

protected RemoteCall(ClientConfig config) {
this.config = Objects.requireNonNull(config, "HTTP configuration is required");
}

public ClientConfig getConfig() {
return config;
}
}
12 changes: 12 additions & 0 deletions java/client/src/org/openqa/selenium/remote/http/okhttp/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
java_library(
name = "okhttp",
srcs = glob(["*.java"]),
deps = [
"//java/client/src/org/openqa/selenium/remote/http",
"//third_party/java/guava",
"//third_party/java/okhttp3:okhttp",
],
visibility = [
"//java/client/test/org/openqa/selenium/remote/http/okhttp:__pkg__",
],
)
104 changes: 104 additions & 0 deletions java/client/src/org/openqa/selenium/remote/http/okhttp/OkHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.remote.http.okhttp;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

import com.google.common.base.Strings;

import org.openqa.selenium.remote.http.ClientConfig;
import org.openqa.selenium.remote.http.HttpHandler;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import org.openqa.selenium.remote.http.RemoteCall;

import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects;

public class OkHandler extends RemoteCall {

private final OkHttpClient client;
private final HttpHandler handler;

public OkHandler(ClientConfig config) {
super(config);
this.client = newClient(config);
this.handler = config.filter().andFinally(this::makeCall);
}

@Override
public HttpResponse execute(HttpRequest request) {
return handler.execute(request);
}

private HttpResponse makeCall(HttpRequest request) {
Objects.requireNonNull(request, "Request must be set.");

try {
Request okReq = OkMessages.toOkHttpRequest(getConfig().baseUri(), request);
Response response = client.newCall(okReq).execute();
return OkMessages.toSeleniumResponse(response);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private static OkHttpClient newClient(ClientConfig config) {
okhttp3.OkHttpClient.Builder client = new okhttp3.OkHttpClient.Builder()
.followRedirects(true)
.followSslRedirects(true)
.proxy(config.proxy())
.readTimeout(config.readTimeout().toMillis(), MILLISECONDS)
.connectTimeout(config.connectionTimeout().toMillis(), MILLISECONDS);

String info = config.baseUri().getUserInfo();
if (!Strings.isNullOrEmpty(info)) {
String[] parts = info.split(":", 2);
String user = parts[0];
String pass = parts.length > 1 ? parts[1] : null;

String credentials = Credentials.basic(user, pass);

client.authenticator((route, response) -> {
if (response.request().header("Authorization") != null) {
return null; // Give up, we've already attempted to authenticate.
}

return response.request().newBuilder()
.header("Authorization", credentials)
.build();
});
}

client.addNetworkInterceptor(chain -> {
Request request = chain.request();
Response response = chain.proceed(request);
return response.code() == 408
? response.newBuilder().code(500).message("Server-Side Timeout").build()
: response;
});

return client.build();
}
}
Loading

0 comments on commit ba67fbd

Please sign in to comment.