CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-seleniumhq-selenium--selenium-http

HTTP client and server abstractions for Selenium WebDriver communication

Pending
Overview
Eval results
Files

routing.mddocs/

Routing System

Server-side HTTP request routing with URL template matching, parameter extraction, and nested route support for building HTTP servers and request dispatchers.

Capabilities

Routable Interface

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());
}

Route Abstract Class

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);
}

Route Factory Methods

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());

Route Configuration Classes

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

UrlTemplate Class

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}");

UrlPath Utility

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);
        })
    )
);

Complete Routing Example

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

docs

client-config.md

content-handling.md

filtering.md

http-client.md

index.md

request-response.md

routing.md

websocket.md

tile.json