HTTP client and server abstractions for Selenium WebDriver communication
—
Server-side HTTP request routing with URL template matching, parameter extraction, and nested route support for building HTTP servers and request dispatchers.
Interface for handlers that can determine if they match a request, extending HttpHandler with matching capability.
/**
* Interface for handlers that can determine request matching
* Extends HttpHandler with request matching capability
*/
public interface Routable extends HttpHandler {
/**
* Determines if this handler can process the given request
* @param req HTTP request to check for matching
* @return true if this handler matches the request, false otherwise
*/
boolean matches(HttpRequest req);
/**
* Applies filter to this routable
* Default implementation creates filtered routable
* @param filter Filter to apply
* @return Routable with filter applied
*/
default Routable with(Filter filter);
}Usage Examples:
import org.openqa.selenium.remote.http.*;
// Custom routable implementation
Routable customRoute = new Routable() {
@Override
public boolean matches(HttpRequest req) {
return req.getMethod() == HttpMethod.GET &&
req.getUri().startsWith("/api/");
}
@Override
public HttpResponse execute(HttpRequest req) {
return new HttpResponse()
.setStatus(200)
.setContent(Contents.utf8String("API response"));
}
};
// Test route matching
HttpRequest getRequest = new HttpRequest(HttpMethod.GET, "/api/users");
HttpRequest postRequest = new HttpRequest(HttpMethod.POST, "/api/users");
boolean matchesGet = customRoute.matches(getRequest); // true
boolean matchesPost = customRoute.matches(postRequest); // false
// Apply filters to routable
Filter loggingFilter = new DumpHttpExchangeFilter();
Routable filteredRoute = customRoute.with(loggingFilter);
// Use in routing logic
if (filteredRoute.matches(getRequest)) {
HttpResponse response = filteredRoute.execute(getRequest);
System.out.println("Route handled request: " + response.getStatus());
}Base class for HTTP routing with URL template matching and parameter extraction, providing the foundation for building complex routing systems.
/**
* Abstract base class for HTTP routing with URL template matching
* Implements both HttpHandler and Routable interfaces
*/
public abstract class Route implements HttpHandler, Routable {
/**
* Sets fallback handler for unmatched requests
* @param handler Supplier providing fallback HttpHandler
* @return HttpHandler that uses this route or fallback
*/
public HttpHandler fallbackTo(Supplier<HttpHandler> handler);
/**
* Final implementation of request execution with routing logic
* Checks if request matches, then calls handle() method
* @param req HTTP request to execute
* @return HTTP response from matched route
*/
public final HttpResponse execute(HttpRequest req);
/**
* Abstract method for handling matched requests
* Subclasses implement specific request handling logic
* @param req HTTP request that matched this route
* @return HTTP response for the request
*/
protected abstract HttpResponse handle(HttpRequest req);
}Static factory methods for creating different types of routes with URL templates and predicates.
/**
* Static factory methods for creating routes
*/
public abstract class Route {
/**
* Creates route based on predicate matching
* @param predicate Function to test request matching
* @return PredicatedConfig for configuring predicate-based route
*/
public static PredicatedConfig matching(Predicate<HttpRequest> predicate);
/**
* Creates DELETE route with URL template
* @param template URL template pattern (e.g., "/users/{id}")
* @return TemplatizedRouteConfig for configuring DELETE route
*/
public static TemplatizedRouteConfig delete(String template);
/**
* Creates GET route with URL template
* @param template URL template pattern (e.g., "/users/{id}")
* @return TemplatizedRouteConfig for configuring GET route
*/
public static TemplatizedRouteConfig get(String template);
/**
* Creates POST route with URL template
* @param template URL template pattern (e.g., "/users")
* @return TemplatizedRouteConfig for configuring POST route
*/
public static TemplatizedRouteConfig post(String template);
/**
* Creates OPTIONS route with URL template
* @param template URL template pattern (e.g., "/users/{id}")
* @return TemplatizedRouteConfig for configuring OPTIONS route
*/
public static TemplatizedRouteConfig options(String template);
/**
* Creates nested route with path prefix
* @param prefix Path prefix for nested routes (e.g., "/api/v1")
* @return NestedRouteConfig for configuring nested routing
*/
public static NestedRouteConfig prefix(String prefix);
/**
* Combines multiple routes into single route
* @param first First route to combine
* @param others Additional routes to combine
* @return Combined route that tries routes in order
*/
public static Route combine(Routable first, Routable... others);
/**
* Combines routes from iterable into single route
* @param routes Iterable of routes to combine
* @return Combined route that tries routes in order
*/
public static Route combine(Iterable<Routable> routes);
}Usage Examples:
import org.openqa.selenium.remote.http.*;
import java.util.Map;
// Create GET route with URL template
Route getUserRoute = Route.get("/users/{id}")
.to(() -> new HttpHandler() {
@Override
public HttpResponse execute(HttpRequest req) {
String userId = req.getAttribute("id").toString();
return new HttpResponse()
.setStatus(200)
.setContent(Contents.asJson(Map.of("id", userId, "name", "User " + userId)));
}
});
// Create POST route
Route createUserRoute = Route.post("/users")
.to(() -> req -> {
String requestBody = Contents.string(req);
return new HttpResponse()
.setStatus(201)
.setContent(Contents.utf8String("User created"));
});
// Create DELETE route with parameter handling
Route deleteUserRoute = Route.delete("/users/{id}")
.to(params -> req -> {
String userId = params.get("id");
System.out.println("Deleting user: " + userId);
return new HttpResponse().setStatus(204);
});
// Predicate-based route
Route apiRoute = Route.matching(req ->
req.getUri().startsWith("/api/") &&
req.getHeader("Authorization") != null)
.to(() -> req -> new HttpResponse().setStatus(200));
// Combine routes
Route combinedRoutes = Route.combine(
getUserRoute,
createUserRoute,
deleteUserRoute,
apiRoute
);
// Use with fallback
HttpHandler server = combinedRoutes.fallbackTo(() ->
req -> new HttpResponse().setStatus(404).setContent(Contents.utf8String("Not Found")));
// Test routing
HttpRequest getRequest = new HttpRequest(HttpMethod.GET, "/users/123");
HttpResponse response = server.execute(getRequest);
System.out.println("Response: " + response.getStatus());Configuration classes for building different types of routes with fluent API.
/**
* Configuration class for template-based routes
*/
public static class TemplatizedRouteConfig {
/**
* Sets handler supplier for this route
* @param handler Supplier providing HttpHandler for matched requests
* @return Configured Route instance
*/
Route to(Supplier<HttpHandler> handler);
/**
* Sets parameterized handler function for this route
* Handler receives extracted URL parameters as Map
* @param handlerFunc Function that takes parameters and returns HttpHandler
* @return Configured Route instance
*/
Route to(Function<Map<String, String>, HttpHandler> handlerFunc);
}
/**
* Configuration class for nested routes
*/
public static class NestedRouteConfig {
/**
* Sets nested route for this prefix
* @param route Route to nest under the prefix
* @return Configured Route instance
*/
Route to(Route route);
}
/**
* Configuration class for predicate-based routes
*/
public static class PredicatedConfig {
/**
* Sets handler supplier for predicate-matched requests
* @param handler Supplier providing HttpHandler for matched requests
* @return Configured Route instance
*/
Route to(Supplier<HttpHandler> handler);
}Usage Examples:
import org.openqa.selenium.remote.http.*;
import java.util.Map;
// Template-based route with parameter extraction
Route userRoute = Route.get("/users/{id}/posts/{postId}")
.to(params -> req -> {
String userId = params.get("id");
String postId = params.get("postId");
return new HttpResponse()
.setStatus(200)
.setContent(Contents.asJson(Map.of(
"userId", userId,
"postId", postId,
"title", "Post " + postId + " by User " + userId
)));
});
// Simple handler supplier
Route simpleRoute = Route.post("/webhooks")
.to(() -> req -> {
String payload = Contents.string(req);
processWebhook(payload);
return new HttpResponse().setStatus(200);
});
// Nested route structure
Route apiV1Routes = Route.combine(
Route.get("/users").to(() -> new UserListHandler()),
Route.get("/users/{id}").to(params -> new UserDetailHandler(params.get("id"))),
Route.post("/users").to(() -> new CreateUserHandler())
);
Route nestedApi = Route.prefix("/api/v1").to(apiV1Routes);
// Predicate-based route with complex matching
Route adminRoute = Route.matching(req ->
req.getUri().startsWith("/admin/") &&
"admin".equals(req.getHeader("X-User-Role")))
.to(() -> new AdminHandler());
// Combine all routes
Route mainRouter = Route.combine(
nestedApi,
adminRoute,
Route.get("/health").to(() -> req ->
new HttpResponse().setStatus(200).setContent(Contents.utf8String("OK")))
);
private void processWebhook(String payload) { /* process webhook */ }
private static class UserListHandler implements HttpHandler {
public HttpResponse execute(HttpRequest req) {
return new HttpResponse().setStatus(200);
}
}
private static class UserDetailHandler implements HttpHandler {
private final String userId;
public UserDetailHandler(String userId) {
this.userId = userId;
}
public HttpResponse execute(HttpRequest req) {
return new HttpResponse().setStatus(200);
}
}
private static class CreateUserHandler implements HttpHandler {
public HttpResponse execute(HttpRequest req) {
return new HttpResponse().setStatus(201);
}
}
private static class AdminHandler implements HttpHandler {
public HttpResponse execute(HttpRequest req) {
return new HttpResponse().setStatus(200);
}
}URL template matching with parameter extraction for dynamic route handling.
/**
* URL template matching with parameter extraction
* Supports parameterized URLs with {param} syntax
*/
public class UrlTemplate {
/**
* Creates URL template from pattern string
* @param template Template pattern (e.g., "/users/{id}/posts/{postId}")
*/
public UrlTemplate(String template);
/**
* Matches URL against template pattern
* @param matchAgainst URL string to match
* @return Match result with extracted parameters, or null if no match
*/
public Match match(String matchAgainst);
/**
* Matches URL against template with prefix removal
* @param matchAgainst URL string to match
* @param prefix Prefix to remove before matching
* @return Match result with extracted parameters, or null if no match
*/
public Match match(String matchAgainst, String prefix);
/**
* Match result containing URL and extracted parameters
*/
public static class Match {
/**
* Gets the matched URL
* @return Matched URL string
*/
public String getUrl();
/**
* Gets extracted parameters from URL template
* @return Map of parameter names to values
*/
public Map<String, String> getParameters();
}
}Usage Examples:
import org.openqa.selenium.remote.http.*;
import java.util.Map;
// Create URL templates
UrlTemplate userTemplate = new UrlTemplate("/users/{id}");
UrlTemplate postTemplate = new UrlTemplate("/users/{userId}/posts/{postId}");
UrlTemplate fileTemplate = new UrlTemplate("/files/{path...}"); // Captures remaining path
// Test template matching
UrlTemplate.Match userMatch = userTemplate.match("/users/123");
if (userMatch != null) {
Map<String, String> params = userMatch.getParameters();
String userId = params.get("id"); // "123"
System.out.println("User ID: " + userId);
}
// Complex template matching
UrlTemplate.Match postMatch = postTemplate.match("/users/456/posts/789");
if (postMatch != null) {
Map<String, String> params = postMatch.getParameters();
String userId = params.get("userId"); // "456"
String postId = params.get("postId"); // "789"
System.out.println("User: " + userId + ", Post: " + postId);
}
// Template with prefix removal
UrlTemplate apiTemplate = new UrlTemplate("/users/{id}");
UrlTemplate.Match prefixMatch = apiTemplate.match("/api/v1/users/999", "/api/v1");
if (prefixMatch != null) {
String userId = prefixMatch.getParameters().get("id"); // "999"
String matchedUrl = prefixMatch.getUrl(); // "/users/999"
}
// Use in custom route implementation
public class CustomTemplateRoute extends Route {
private final UrlTemplate template;
private final HttpMethod method;
public CustomTemplateRoute(HttpMethod method, String template) {
this.method = method;
this.template = new UrlTemplate(template);
}
@Override
public boolean matches(HttpRequest req) {
return req.getMethod() == method && template.match(req.getUri()) != null;
}
@Override
protected HttpResponse handle(HttpRequest req) {
UrlTemplate.Match match = template.match(req.getUri());
Map<String, String> params = match.getParameters();
// Store parameters as request attributes
params.forEach(req::setAttribute);
return processRequest(req, params);
}
private HttpResponse processRequest(HttpRequest req, Map<String, String> params) {
// Custom processing logic
return new HttpResponse().setStatus(200);
}
}
// Use custom template route
Route customRoute = new CustomTemplateRoute(HttpMethod.GET, "/products/{category}/{id}");Utility class for handling URL paths in routing context with prefix management.
/**
* Utility class for URL path handling in routing context
*/
public class UrlPath {
/**
* Constant for route prefix attribute key
*/
public static final String ROUTE_PREFIX_KEY = "selenium.route";
/**
* Returns location relative to server
* @param req HTTP request for context
* @param location Location string to make relative
* @return Server-relative location
*/
public static String relativeToServer(HttpRequest req, String location);
/**
* Returns location relative to context with route prefixes
* Uses route prefix attributes to build context-relative paths
* @param req HTTP request for context
* @param location Location string to make relative
* @return Context-relative location with prefixes applied
*/
public static String relativeToContext(HttpRequest req, String location);
}Usage Examples:
import org.openqa.selenium.remote.http.*;
// Set route prefix in request
HttpRequest request = new HttpRequest(HttpMethod.GET, "/api/v1/users/123");
request.setAttribute(UrlPath.ROUTE_PREFIX_KEY, "/api/v1");
// Get relative paths
String serverRelative = UrlPath.relativeToServer(request, "/users/456");
// Result: "/users/456"
String contextRelative = UrlPath.relativeToContext(request, "/users/456");
// Result: "/api/v1/users/456" (includes prefix)
// Use in route handlers for generating links
Route userRoute = Route.get("/users/{id}")
.to(params -> req -> {
String userId = params.get("id");
String editLink = UrlPath.relativeToContext(req, "/users/" + userId + "/edit");
return new HttpResponse()
.setStatus(200)
.setContent(Contents.asJson(Map.of(
"id", userId,
"editUrl", editLink
)));
});
// Nested routing with prefix handling
Route apiRoutes = Route.prefix("/api/v1").to(
Route.combine(
Route.get("/users/{id}").to(params -> req -> {
// req will have ROUTE_PREFIX_KEY set to "/api/v1"
String selfLink = UrlPath.relativeToContext(req, "/users/" + params.get("id"));
return new HttpResponse().setStatus(200);
})
)
);import org.openqa.selenium.remote.http.*;
import java.util.Map;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public class CompleteRoutingExample {
// Simple in-memory data store
private final Map<String, User> users = new ConcurrentHashMap<>();
private int nextId = 1;
public Route createUserAPI() {
// Individual route handlers
Route listUsers = Route.get("/users")
.to(() -> req -> {
List<User> userList = List.copyOf(users.values());
return new HttpResponse()
.setStatus(200)
.setContent(Contents.asJson(userList));
});
Route getUser = Route.get("/users/{id}")
.to(params -> req -> {
String id = params.get("id");
User user = users.get(id);
if (user == null) {
return new HttpResponse()
.setStatus(404)
.setContent(Contents.utf8String("User not found"));
}
return new HttpResponse()
.setStatus(200)
.setContent(Contents.asJson(user));
});
Route createUser = Route.post("/users")
.to(() -> req -> {
try {
User userData = Contents.fromJson(req, User.class);
String id = String.valueOf(nextId++);
User newUser = new User(id, userData.getName(), userData.getEmail());
users.put(id, newUser);
return new HttpResponse()
.setStatus(201)
.setContent(Contents.asJson(newUser));
} catch (Exception e) {
return new HttpResponse()
.setStatus(400)
.setContent(Contents.utf8String("Invalid user data"));
}
});
Route updateUser = Route.post("/users/{id}") // Using POST for simplicity
.to(params -> req -> {
String id = params.get("id");
if (!users.containsKey(id)) {
return new HttpResponse().setStatus(404);
}
try {
User userData = Contents.fromJson(req, User.class);
User updatedUser = new User(id, userData.getName(), userData.getEmail());
users.put(id, updatedUser);
return new HttpResponse()
.setStatus(200)
.setContent(Contents.asJson(updatedUser));
} catch (Exception e) {
return new HttpResponse().setStatus(400);
}
});
Route deleteUser = Route.delete("/users/{id}")
.to(params -> req -> {
String id = params.get("id");
User removed = users.remove(id);
return new HttpResponse()
.setStatus(removed != null ? 204 : 404);
});
// Health check route
Route healthCheck = Route.get("/health")
.to(() -> req -> new HttpResponse()
.setStatus(200)
.setContent(Contents.utf8String("OK")));
// Combine all routes
Route userRoutes = Route.combine(
listUsers,
getUser,
createUser,
updateUser,
deleteUser
);
// Create API with prefix
Route apiV1 = Route.prefix("/api/v1").to(userRoutes);
// Combine with health check
return Route.combine(apiV1, healthCheck);
}
public HttpHandler createServer() {
Route mainRouter = createUserAPI();
// Add logging and error handling
Filter loggingFilter = new DumpHttpExchangeFilter();
Route filteredRouter = (Route) mainRouter.with(loggingFilter);
// Add fallback for unmatched routes
return filteredRouter.fallbackTo(() -> req ->
new HttpResponse()
.setStatus(404)
.setContent(Contents.asJson(Map.of(
"error", "Not Found",
"path", req.getUri()
))));
}
public static void main(String[] args) {
CompleteRoutingExample example = new CompleteRoutingExample();
HttpHandler server = example.createServer();
// Test the server
testServer(server);
}
private static void testServer(HttpHandler server) {
// Create user
HttpRequest createReq = new HttpRequest(HttpMethod.POST, "/api/v1/users");
createReq.setContent(Contents.asJson(new User(null, "John Doe", "john@example.com")));
HttpResponse createResp = server.execute(createReq);
System.out.println("Create user: " + createResp.getStatus());
// Get user
HttpRequest getReq = new HttpRequest(HttpMethod.GET, "/api/v1/users/1");
HttpResponse getResp = server.execute(getReq);
System.out.println("Get user: " + getResp.getStatus());
System.out.println("User data: " + Contents.string(getResp));
// List users
HttpRequest listReq = new HttpRequest(HttpMethod.GET, "/api/v1/users");
HttpResponse listResp = server.execute(listReq);
System.out.println("List users: " + listResp.getStatus());
// Health check
HttpRequest healthReq = new HttpRequest(HttpMethod.GET, "/health");
HttpResponse healthResp = server.execute(healthReq);
System.out.println("Health check: " + healthResp.getStatus());
// 404 test
HttpRequest notFoundReq = new HttpRequest(HttpMethod.GET, "/unknown");
HttpResponse notFoundResp = server.execute(notFoundReq);
System.out.println("Not found: " + notFoundResp.getStatus());
}
// Simple User class for example
public static class User {
private String id;
private String name;
private String email;
public User() {} // For JSON deserialization
public User(String id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-seleniumhq-selenium--selenium-http