HTTP client and server abstractions for Selenium WebDriver communication
—
HTTP request/response filtering system using decorator pattern with built-in filters for user agents, logging, and automatic retries, enabling request/response interception and modification.
Functional interface for HTTP request/response filtering using decorator pattern, allowing filters to be chained together for complex processing pipelines.
/**
* Functional interface for HTTP request/response filtering
* Uses decorator pattern to wrap HttpHandler instances
* Extends Function<HttpHandler, HttpHandler> for functional composition
*/
@FunctionalInterface
public interface Filter extends Function<HttpHandler, HttpHandler> {
/**
* Chains this filter with another filter
* Creates composite filter where this filter runs first, then next filter
* @param next Filter to chain after this filter
* @return Combined filter that applies both filters in sequence
*/
Filter andThen(Filter next);
/**
* Terminates filter chain with HttpHandler
* Creates final HttpHandler with all filters applied
* @param end Final HttpHandler to handle filtered requests
* @return HttpHandler with filter chain applied
*/
HttpHandler andFinally(HttpHandler end);
/**
* Terminates filter chain with Routable
* Creates final Routable with all filters applied
* @param end Final Routable to handle filtered requests
* @return Routable with filter chain applied
*/
Routable andFinally(Routable end);
}Usage Examples:
import org.openqa.selenium.remote.http.*;
// Create individual filters
Filter userAgentFilter = new AddSeleniumUserAgent();
Filter loggingFilter = new DumpHttpExchangeFilter();
Filter retryFilter = new RetryRequest();
// Chain filters together
Filter combinedFilter = userAgentFilter
.andThen(loggingFilter)
.andThen(retryFilter);
// Apply filter chain to handler
HttpHandler baseHandler = request -> {
// Base request handling logic
return new HttpResponse().setStatus(200);
};
HttpHandler filteredHandler = combinedFilter.andFinally(baseHandler);
// Use filtered handler
HttpRequest request = new HttpRequest(HttpMethod.GET, "/api/test");
HttpResponse response = filteredHandler.execute(request);
// Apply filters to routable
Route route = Route.get("/users/{id}").to(() -> new UserHandler());
Routable filteredRoute = combinedFilter.andFinally(route);
// Custom filter implementation
Filter customFilter = next -> request -> {
// Pre-processing
System.out.println("Processing request: " + request.getUri());
request.addHeader("X-Request-ID", UUID.randomUUID().toString());
// Execute next handler in chain
HttpResponse response = next.execute(request);
// Post-processing
response.addHeader("X-Processed-By", "CustomFilter");
System.out.println("Response status: " + response.getStatus());
return response;
};
// Use custom filter
HttpHandler customHandler = customFilter.andFinally(baseHandler);Built-in filter that adds Selenium user agent header to HTTP requests, automatically identifying requests as coming from Selenium WebDriver.
/**
* Filter that adds Selenium user agent header to requests
* Automatically applied in default ClientConfig
*/
public class AddSeleniumUserAgent implements Filter {
/**
* Static user agent string containing Selenium version and platform info
* Format: "selenium/{version} ({platform})"
* Example: "selenium/4.33.0 (java mac)"
*/
public static final String USER_AGENT;
/**
* Applies user agent filter to handler
* Adds User-Agent header if not already present
* @param next Next handler in filter chain
* @return HttpHandler that adds user agent header
*/
public HttpHandler apply(HttpHandler next);
}Usage Examples:
import org.openqa.selenium.remote.http.*;
// Filter is automatically included in default client config
ClientConfig defaultConfig = ClientConfig.defaultConfig();
// User-Agent header will be automatically added
// Manual usage of the filter
Filter userAgentFilter = new AddSeleniumUserAgent();
HttpHandler handler = userAgentFilter.andFinally(request -> {
// Check if User-Agent was added
String userAgent = request.getHeader("User-Agent");
System.out.println("User-Agent: " + userAgent);
return new HttpResponse().setStatus(200);
});
// Check the user agent string
System.out.println("Selenium User-Agent: " + AddSeleniumUserAgent.USER_AGENT);
// Combine with other filters
Filter combinedFilter = new AddSeleniumUserAgent()
.andThen(new DumpHttpExchangeFilter());
// User agent is not added if already present
HttpRequest request = new HttpRequest(HttpMethod.GET, "/api/test");
request.addHeader("User-Agent", "Custom User Agent");
HttpHandler filteredHandler = userAgentFilter.andFinally(req -> {
// Will still have "Custom User Agent", not overwritten
return new HttpResponse().setStatus(200);
});
HttpResponse response = filteredHandler.execute(request);Filter implementing automatic retry logic for failed requests with exponential backoff strategy for handling transient network failures and server errors.
/**
* Filter that implements automatic retry logic for failed requests
* Retries connection failures and server errors (5xx status codes)
*/
public class RetryRequest implements Filter {
/**
* Applies retry logic to handler
* Automatically retries on ConnectionFailedException and 5xx responses
* Uses exponential backoff strategy with maximum retry attempts
* @param next Next handler in filter chain
* @return HttpHandler with retry logic applied
*/
public HttpHandler apply(HttpHandler next);
}Usage Examples:
import org.openqa.selenium.remote.http.*;
// Enable retries in client configuration
ClientConfig configWithRetries = ClientConfig.defaultConfig()
.baseUrl(new URL("https://unreliable-api.example.com"))
.withRetries();
HttpClient client = HttpClient.Factory.createDefault().createClient(configWithRetries);
// Manual usage of retry filter
Filter retryFilter = new RetryRequest();
HttpHandler unreliableHandler = request -> {
// Simulate unreliable service
if (Math.random() < 0.7) {
throw new ConnectionFailedException("Connection timeout");
}
if (Math.random() < 0.5) {
return new HttpResponse().setStatus(503); // Service unavailable
}
return new HttpResponse().setStatus(200);
};
HttpHandler retryHandler = retryFilter.andFinally(unreliableHandler);
// Execute request with retries
HttpRequest request = new HttpRequest(HttpMethod.GET, "/api/data");
try {
HttpResponse response = retryHandler.execute(request);
System.out.println("Success after retries: " + response.getStatus());
} catch (ConnectionFailedException e) {
System.out.println("Failed after all retry attempts");
}
// Combine with other filters
Filter robustFilter = new AddSeleniumUserAgent()
.andThen(new DumpHttpExchangeFilter())
.andThen(new RetryRequest());
// Retry behavior:
// - Retries ConnectionFailedException
// - Retries 5xx HTTP status codes
// - Uses exponential backoff
// - Has maximum retry limit
// - Does not retry 4xx client errorsFilter for logging HTTP request/response exchanges with configurable log levels for debugging and monitoring HTTP traffic.
/**
* Filter for logging HTTP request/response exchanges
* Useful for debugging and monitoring HTTP traffic
*/
public class DumpHttpExchangeFilter implements Filter {
/**
* Public logger instance for HTTP exchange logging
*/
public static final Logger LOG;
/**
* Creates logging filter with FINER log level
*/
public DumpHttpExchangeFilter();
/**
* Creates logging filter with specified log level
* @param logLevel Logging level for HTTP exchanges
*/
public DumpHttpExchangeFilter(Level logLevel);
/**
* Applies logging filter to handler
* Logs request details before execution and response details after
* @param next Next handler in filter chain
* @return HttpHandler with logging applied
*/
public HttpHandler apply(HttpHandler next);
}Usage Examples:
import org.openqa.selenium.remote.http.*;
import java.util.logging.Level;
import java.util.logging.Logger;
// Basic logging filter
Filter loggingFilter = new DumpHttpExchangeFilter();
// Logging filter with custom level
Filter infoLoggingFilter = new DumpHttpExchangeFilter(Level.INFO);
Filter warningLoggingFilter = new DumpHttpExchangeFilter(Level.WARNING);
// Configure logger level to see output
Logger httpLogger = DumpHttpExchangeFilter.LOG;
httpLogger.setLevel(Level.FINER);
// Add console handler to see logs
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.FINER);
httpLogger.addHandler(consoleHandler);
// Apply logging filter
HttpHandler handler = loggingFilter.andFinally(request -> {
return new HttpResponse()
.setStatus(200)
.setContent(Contents.utf8String("Response data"));
});
// Execute request - will log request and response details
HttpRequest request = new HttpRequest(HttpMethod.POST, "/api/users");
request.addHeader("Content-Type", "application/json");
request.setContent(Contents.asJson(Map.of("name", "John Doe")));
HttpResponse response = handler.execute(request);
// Combine with other filters for comprehensive logging
Filter debugFilter = new AddSeleniumUserAgent()
.andThen(new DumpHttpExchangeFilter(Level.INFO))
.andThen(new RetryRequest());
// Use in client configuration
ClientConfig debugConfig = ClientConfig.defaultConfig()
.baseUrl(new URL("https://api.example.com"))
.withFilter(new DumpHttpExchangeFilter(Level.INFO));
HttpClient debugClient = HttpClient.Factory.createDefault().createClient(debugConfig);
// All requests will be logged with full details:
// - Request method, URI, headers, and content
// - Response status, headers, and content
// - Timing informationimport org.openqa.selenium.remote.http.*;
import java.time.Instant;
import java.util.UUID;
// Authentication filter
public class AuthenticationFilter implements Filter {
private final String authToken;
public AuthenticationFilter(String authToken) {
this.authToken = authToken;
}
@Override
public HttpHandler apply(HttpHandler next) {
return request -> {
// Add authentication header
request.addHeader("Authorization", "Bearer " + authToken);
return next.execute(request);
};
}
}
// Request timing filter
public class TimingFilter implements Filter {
@Override
public HttpHandler apply(HttpHandler next) {
return request -> {
long startTime = System.currentTimeMillis();
try {
HttpResponse response = next.execute(request);
long duration = System.currentTimeMillis() - startTime;
response.setAttribute("request.duration", duration);
System.out.println("Request to " + request.getUri() +
" took " + duration + "ms");
return response;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
System.err.println("Request to " + request.getUri() +
" failed after " + duration + "ms: " + e.getMessage());
throw e;
}
};
}
}
// Request ID filter
public class RequestIdFilter implements Filter {
@Override
public HttpHandler apply(HttpHandler next) {
return request -> {
String requestId = UUID.randomUUID().toString();
request.addHeader("X-Request-ID", requestId);
request.setAttribute("request.id", requestId);
HttpResponse response = next.execute(request);
response.addHeader("X-Request-ID", requestId);
return response;
};
}
}
// Rate limiting filter
public class RateLimitFilter implements Filter {
private final long minIntervalMs;
private volatile long lastRequestTime = 0;
public RateLimitFilter(long minIntervalMs) {
this.minIntervalMs = minIntervalMs;
}
@Override
public HttpHandler apply(HttpHandler next) {
return request -> {
synchronized (this) {
long now = System.currentTimeMillis();
long timeSinceLastRequest = now - lastRequestTime;
if (timeSinceLastRequest < minIntervalMs) {
long sleepTime = minIntervalMs - timeSinceLastRequest;
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during rate limiting", e);
}
}
lastRequestTime = System.currentTimeMillis();
}
return next.execute(request);
};
}
}import org.openqa.selenium.remote.http.*;
import java.net.URL;
// Combine custom filters
Filter authFilter = new AuthenticationFilter("secret-token-123");
Filter timingFilter = new TimingFilter();
Filter rateLimitFilter = new RateLimitFilter(1000); // 1 second between requests
Filter requestIdFilter = new RequestIdFilter();
// Create comprehensive filter chain
Filter comprehensiveFilter = authFilter
.andThen(requestIdFilter)
.andThen(timingFilter)
.andThen(rateLimitFilter)
.andThen(new DumpHttpExchangeFilter(Level.INFO))
.andThen(new RetryRequest());
// Use in client configuration
ClientConfig advancedConfig = ClientConfig.defaultConfig()
.baseUrl(new URL("https://api.example.com"))
.withFilter(comprehensiveFilter);
HttpClient client = HttpClient.Factory.createDefault().createClient(advancedConfig);
// All requests will have:
// - Authentication header
// - Unique request ID
// - Timing measurement
// - Rate limiting
// - Request/response logging
// - Automatic retries
HttpRequest request = new HttpRequest(HttpMethod.GET, "/users");
HttpResponse response = client.execute(request);
System.out.println("Request ID: " + response.getHeader("X-Request-ID"));
System.out.println("Duration: " + response.getAttribute("request.duration") + "ms");The order of filters in the chain matters. Filters are applied in the order they are chained:
// Filter execution order
Filter filterChain = filterA // Runs first (outermost)
.andThen(filterB) // Runs second
.andThen(filterC); // Runs third (innermost)
HttpHandler handler = filterChain.andFinally(baseHandler);
/*
* Request flow:
* Request -> FilterA -> FilterB -> FilterC -> BaseHandler
*
* Response flow:
* BaseHandler -> FilterC -> FilterB -> FilterA -> Response
*/
// Example with specific filters
Filter orderedChain = new RequestIdFilter() // 1. Add request ID
.andThen(new AuthenticationFilter("token")) // 2. Add auth header
.andThen(new TimingFilter()) // 3. Start timing
.andThen(new DumpHttpExchangeFilter()) // 4. Log request
.andThen(new RateLimitFilter(1000)) // 5. Rate limit
.andThen(new RetryRequest()); // 6. Handle retries
// This ensures proper layering:
// - Request ID is added before logging
// - Authentication is added before rate limiting
// - Timing includes all processing time
// - Retries happen at the innermost levelInstall with Tessl CLI
npx tessl i tessl/maven-org-seleniumhq-selenium--selenium-http