Skip to content

Commit 2917a66

Browse files
authored
[grid] Accommodate ability to specify sub-paths (#11271)
* [grid] Accommodate ability to specify sub-paths Fixes: #10847 Sub-paths can now be specified for the below Components: 1. Hub 2. Standalone 3. Router It can be specified via “--sub-path <actualPathGoesHere>” Or via the Toml configuration file by specifying [network] sub-path = “<actualPathGoesHere>” * Fixing review comments * Fixing build dependencies * Simplify route building logic * Renaming the flag to “network”
1 parent 58122b2 commit 2917a66

File tree

10 files changed

+385
-27
lines changed

10 files changed

+385
-27
lines changed

java/src/org/openqa/selenium/grid/TemplateGridServerCommand.java

+42
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717

1818
package org.openqa.selenium.grid;
1919

20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.function.Function;
24+
import java.util.function.Supplier;
2025
import org.openqa.selenium.grid.config.CompoundConfig;
2126
import org.openqa.selenium.grid.config.Config;
2227
import org.openqa.selenium.grid.config.MemoizedConfig;
@@ -30,6 +35,8 @@
3035
import java.util.Optional;
3136
import java.util.function.BiFunction;
3237
import java.util.function.Consumer;
38+
import org.openqa.selenium.remote.http.Routable;
39+
import org.openqa.selenium.remote.http.Route;
3340

3441
public abstract class TemplateGridServerCommand extends TemplateGridCommand {
3542

@@ -46,6 +53,41 @@ public Server<?> asServer(Config initialConfig) {
4653
handler.websocketHandler);
4754
}
4855

56+
private static final String GRAPHQL = "/graphql";
57+
58+
protected static Routable graphqlRoute(String prefix, Supplier<HttpHandler> handler) {
59+
Routable optionsRoute = buildRoute(GRAPHQL,
60+
prefix,
61+
path -> Route.options(path).to(handler)
62+
);
63+
Routable postRoute = buildRoute(GRAPHQL,
64+
prefix,
65+
path -> Route.post(path).to(handler)
66+
);
67+
return Route.combine(optionsRoute, postRoute);
68+
}
69+
70+
protected static Routable hubRoute(String prefix, Route route) {
71+
return buildRoute("/wd/hub",
72+
prefix,
73+
path -> Route.prefix(path).to(route)
74+
);
75+
}
76+
77+
private static Routable buildRoute(String url, String prefix, Function<String, Route> mapper) {
78+
List<String> subPaths = prefix.isEmpty()
79+
? Collections.singletonList(url)
80+
: Arrays.asList(prefix + url, url);
81+
return subPaths.stream()
82+
.map(mapper)
83+
.reduce(Route::combine)
84+
.get();
85+
}
86+
87+
protected static Routable baseRoute(String prefix, Route route) {
88+
return Route.prefix(prefix).to(route);
89+
}
90+
4991
protected abstract Handlers createHandlers(Config config);
5092

5193
public static class Handlers {

java/src/org/openqa/selenium/grid/commands/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ java_library(
3131
"//java/src/org/openqa/selenium/grid/node/config",
3232
"//java/src/org/openqa/selenium/grid/node/local",
3333
"//java/src/org/openqa/selenium/grid/router",
34+
"//java/src/org/openqa/selenium/grid/router/httpd",
3435
"//java/src/org/openqa/selenium/grid/security",
3536
"//java/src/org/openqa/selenium/grid/server",
3637
"//java/src/org/openqa/selenium/grid/sessionmap",

java/src/org/openqa/selenium/grid/commands/Hub.java

+15-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.auto.service.AutoService;
2121
import com.google.common.collect.ImmutableSet;
2222

23+
import java.util.stream.Stream;
2324
import org.openqa.selenium.BuildInfo;
2425
import org.openqa.selenium.UsernameAndPassword;
2526
import org.openqa.selenium.cli.CliCommand;
@@ -40,6 +41,7 @@
4041
import org.openqa.selenium.grid.server.BaseServerOptions;
4142
import org.openqa.selenium.grid.server.EventBusOptions;
4243
import org.openqa.selenium.grid.server.NetworkOptions;
44+
import org.openqa.selenium.grid.router.httpd.RouterOptions;
4345
import org.openqa.selenium.grid.server.Server;
4446
import org.openqa.selenium.grid.sessionmap.SessionMap;
4547
import org.openqa.selenium.grid.sessionmap.local.LocalSessionMap;
@@ -184,15 +186,21 @@ protected Handlers createHandlers(Config config) {
184186
.setContent(Contents.utf8String("Router is " + ready));
185187
};
186188

187-
Routable ui = new GridUiRoute();
188189
Routable routerWithSpecChecks = router.with(networkOptions.getSpecComplianceChecks());
189190

190-
Routable httpHandler = combine(
191-
ui,
192-
routerWithSpecChecks,
193-
Route.prefix("/wd/hub").to(combine(routerWithSpecChecks)),
194-
Route.options("/graphql").to(() -> graphqlHandler),
195-
Route.post("/graphql").to(() -> graphqlHandler));
191+
String subPath = new RouterOptions(config).subPath();
192+
Routable ui = new GridUiRoute(subPath);
193+
194+
Routable appendRoute = Stream.of(
195+
routerWithSpecChecks,
196+
hubRoute(subPath, combine(routerWithSpecChecks)),
197+
graphqlRoute(subPath, () -> graphqlHandler)
198+
).reduce(Route::combine)
199+
.get();
200+
if (!subPath.isEmpty()) {
201+
appendRoute = Route.combine(appendRoute, baseRoute(subPath, combine(routerWithSpecChecks)));
202+
}
203+
Routable httpHandler = combine(ui, appendRoute);
196204

197205
UsernameAndPassword uap = secretOptions.getServerAuthentication();
198206
if (uap != null) {

java/src/org/openqa/selenium/grid/commands/Standalone.java

+16-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.auto.service.AutoService;
2121
import com.google.common.collect.ImmutableSet;
2222

23+
import java.util.stream.Stream;
2324
import org.openqa.selenium.BuildInfo;
2425
import org.openqa.selenium.UsernameAndPassword;
2526
import org.openqa.selenium.cli.CliCommand;
@@ -43,6 +44,7 @@
4344
import org.openqa.selenium.grid.server.BaseServerOptions;
4445
import org.openqa.selenium.grid.server.EventBusOptions;
4546
import org.openqa.selenium.grid.server.NetworkOptions;
47+
import org.openqa.selenium.grid.router.httpd.RouterOptions;
4648
import org.openqa.selenium.grid.server.Server;
4749
import org.openqa.selenium.grid.sessionmap.SessionMap;
4850
import org.openqa.selenium.grid.sessionmap.local.LocalSessionMap;
@@ -185,14 +187,21 @@ protected Handlers createHandlers(Config config) {
185187
serverOptions.getExternalUri(),
186188
getFormattedVersion());
187189

188-
Routable ui = new GridUiRoute();
190+
String subPath = new RouterOptions(config).subPath();
191+
Routable ui = new GridUiRoute(subPath);
189192

190-
Routable httpHandler = combine(
191-
ui,
192-
router,
193-
Route.prefix("/wd/hub").to(combine(router)),
194-
Route.options("/graphql").to(() -> graphqlHandler),
195-
Route.post("/graphql").to(() -> graphqlHandler));
193+
Routable appendRoute = Stream.of(
194+
router,
195+
hubRoute(subPath, combine(router)),
196+
graphqlRoute(subPath, () -> graphqlHandler)
197+
).reduce(Route::combine)
198+
.get();
199+
200+
if (!subPath.isEmpty()) {
201+
appendRoute = Route.combine(appendRoute, baseRoute(subPath, combine(router)));
202+
}
203+
204+
Routable httpHandler = combine(ui, appendRoute);
196205

197206
UsernameAndPassword uap = secretOptions.getServerAuthentication();
198207
if (uap != null) {

java/src/org/openqa/selenium/grid/router/httpd/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ java_library(
66
srcs = glob(["*.java"]),
77
visibility = [
88
"//java/src/org/openqa/selenium/grid:__pkg__",
9+
"//java/src/org/openqa/selenium/grid/commands:__pkg__",
910
"//java/test/org/openqa/selenium/grid/router:__pkg__",
1011
"//java/test/org/openqa/selenium/grid/router/httpd:__pkg__",
1112
],

java/src/org/openqa/selenium/grid/router/httpd/RouterFlags.java

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.openqa.selenium.grid.router.httpd;
1919

2020
import static org.openqa.selenium.grid.config.StandardGridRoles.ROUTER_ROLE;
21+
import static org.openqa.selenium.grid.router.httpd.RouterOptions.NETWORK;
2122

2223
import com.google.auto.service.AutoService;
2324

@@ -56,6 +57,13 @@ public class RouterFlags implements HasRoles {
5657
@ConfigValue(section = "router", name = "password", example = "hunter2")
5758
private String password;
5859

60+
@Parameter(
61+
names = {"--sub-path"},
62+
arity = 1,
63+
description = "A sub-path that should be considered for all user facing routes on the Hub/Router/Standalone")
64+
@ConfigValue(section = NETWORK, name = "sub-path", example = "my_company/selenium_grid")
65+
public String subPath;
66+
5967
@Override
6068
public Set<Role> getRoles() {
6169
return Collections.singleton(ROUTER_ROLE);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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.grid.router.httpd;
19+
20+
import org.openqa.selenium.grid.config.Config;
21+
22+
public class RouterOptions {
23+
24+
static final String NETWORK = "network";
25+
26+
private final Config config;
27+
28+
public RouterOptions(Config config) {
29+
this.config = config;
30+
}
31+
32+
public String subPath() {
33+
return config.get(NETWORK, "sub-path").map(prefix -> {
34+
prefix = prefix.trim();
35+
if (!prefix.startsWith("/")) {
36+
prefix = "/" + prefix; //Prefix with a '/' if absent.
37+
}
38+
if (prefix.endsWith("/")) {
39+
prefix = prefix.substring(0, prefix.length() -1); //Remove the trailing '/' if present.
40+
}
41+
return prefix;
42+
}).orElse("");
43+
}
44+
}

java/src/org/openqa/selenium/grid/router/httpd/RouterServer.java

+14-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.common.collect.ImmutableMap;
2222
import com.google.common.collect.ImmutableSet;
2323

24+
import java.util.stream.Stream;
2425
import org.openqa.selenium.BuildInfo;
2526
import org.openqa.selenium.UsernameAndPassword;
2627
import org.openqa.selenium.cli.CliCommand;
@@ -157,16 +158,22 @@ protected Handlers createHandlers(Config config) {
157158
serverOptions.getExternalUri(),
158159
getServerVersion());
159160

160-
Routable ui = new GridUiRoute();
161+
String subPath = new RouterOptions(config).subPath();
162+
Routable ui = new GridUiRoute(subPath);
161163
Router router = new Router(tracer, clientFactory, sessions, queue, distributor);
162164
Routable routerWithSpecChecks = router.with(networkOptions.getSpecComplianceChecks());
163165

164-
Routable route = Route.combine(
165-
ui,
166-
routerWithSpecChecks,
167-
Route.prefix("/wd/hub").to(combine(routerWithSpecChecks)),
168-
Route.options("/graphql").to(() -> graphqlHandler),
169-
Route.post("/graphql").to(() -> graphqlHandler));
166+
Routable appendRoute = Stream.of(
167+
routerWithSpecChecks,
168+
hubRoute(subPath, combine(routerWithSpecChecks)),
169+
graphqlRoute(subPath, () -> graphqlHandler)
170+
)
171+
.reduce(Route::combine)
172+
.get();
173+
if (!subPath.isEmpty()) {
174+
appendRoute = Route.combine(appendRoute, baseRoute(subPath, combine(routerWithSpecChecks)));
175+
}
176+
Routable route = Route.combine(ui, appendRoute);
170177

171178
UsernameAndPassword uap = secretOptions.getServerAuthentication();
172179
if (uap != null) {

java/src/org/openqa/selenium/grid/web/GridUiRoute.java

+53-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@
1717

1818
package org.openqa.selenium.grid.web;
1919

20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.function.Function;
24+
import java.util.function.Supplier;
25+
import org.openqa.selenium.internal.Require;
2026
import org.openqa.selenium.json.Json;
27+
import org.openqa.selenium.remote.http.HttpHandler;
2128
import org.openqa.selenium.remote.http.HttpRequest;
2229
import org.openqa.selenium.remote.http.HttpResponse;
2330
import org.openqa.selenium.remote.http.Routable;
@@ -38,17 +45,26 @@ public class GridUiRoute implements Routable {
3845

3946
private final Route routes;
4047

41-
public GridUiRoute() {
48+
public GridUiRoute(String prefix) {
49+
Require.nonNull(prefix, "Prefix cannot be null");
4250
URL uiRoot = GridUiRoute.class.getResource(GRID_RESOURCE_WITH_PREFIX);
4351
if (uiRoot != null) {
4452
ResourceHandler uiHandler = new ResourceHandler(new ClassPathResource(uiRoot, GRID_RESOURCE));
4553
HttpResponse uiRedirect = new HttpResponse()
4654
.setStatus(HTTP_MOVED_TEMP)
47-
.addHeader("Location", "/ui");
48-
routes = Route.combine(
49-
get("/").to(() -> req -> uiRedirect),
50-
get("/grid/console").to(() -> req -> uiRedirect),
51-
Route.prefix("/ui").to(Route.matching(req -> true).to(() -> uiHandler)));
55+
.addHeader("Location", prefix.concat("/ui"));
56+
57+
Supplier<HttpHandler> redirectHandler = () -> req -> uiRedirect;
58+
59+
Routable appendRoute = Route.combine(
60+
consoleRoute(prefix, redirectHandler),
61+
uiRoute(prefix, () -> uiHandler)
62+
);
63+
if (!prefix.isEmpty()) {
64+
appendRoute = Route.combine(appendRoute, redirectRoute(prefix, redirectHandler));
65+
}
66+
67+
routes = Route.combine(get("/").to(redirectHandler), appendRoute);
5268
} else {
5369
LOG.warning("It was not possible to load the Grid UI.");
5470
Json json = new Json();
@@ -65,4 +81,35 @@ public boolean matches(HttpRequest req) {
6581
public HttpResponse execute(HttpRequest req) {
6682
return routes.execute(req);
6783
}
84+
85+
private static Routable uiRoute(String prefix, Supplier<HttpHandler> handler) {
86+
return buildRoute(
87+
"/ui",
88+
prefix,
89+
path -> Route.prefix(path).to(Route.matching(req -> true).to(handler))
90+
);
91+
}
92+
93+
private static Routable consoleRoute(String prefix, Supplier<HttpHandler> handler) {
94+
return buildRoute(
95+
"/grid/console",
96+
prefix,
97+
path -> get(path).to(handler)
98+
);
99+
}
100+
101+
private static Routable buildRoute(String url, String prefix, Function<String, Route> mapper) {
102+
List<String> subPaths = prefix.isEmpty()
103+
? Collections.singletonList(url)
104+
: Arrays.asList(prefix + url, url);
105+
return subPaths.stream()
106+
.map(mapper)
107+
.reduce(Route::combine)
108+
.get();
109+
}
110+
111+
private static Routable redirectRoute(String prefix, Supplier<HttpHandler> handler ) {
112+
prefix = prefix.endsWith("/") ? prefix.substring(0, prefix.length() -1) : prefix;
113+
return get(prefix).to(handler);
114+
}
68115
}

0 commit comments

Comments
 (0)