0
# Routing System
1
2
Server-side HTTP request routing with URL template matching, parameter extraction, and nested route support for building HTTP servers and request dispatchers.
3
4
## Capabilities
5
6
### Routable Interface
7
8
Interface for handlers that can determine if they match a request, extending HttpHandler with matching capability.
9
10
```java { .api }
11
/**
12
* Interface for handlers that can determine request matching
13
* Extends HttpHandler with request matching capability
14
*/
15
public interface Routable extends HttpHandler {
16
/**
17
* Determines if this handler can process the given request
18
* @param req HTTP request to check for matching
19
* @return true if this handler matches the request, false otherwise
20
*/
21
boolean matches(HttpRequest req);
22
23
/**
24
* Applies filter to this routable
25
* Default implementation creates filtered routable
26
* @param filter Filter to apply
27
* @return Routable with filter applied
28
*/
29
default Routable with(Filter filter);
30
}
31
```
32
33
**Usage Examples:**
34
35
```java
36
import org.openqa.selenium.remote.http.*;
37
38
// Custom routable implementation
39
Routable customRoute = new Routable() {
40
@Override
41
public boolean matches(HttpRequest req) {
42
return req.getMethod() == HttpMethod.GET &&
43
req.getUri().startsWith("/api/");
44
}
45
46
@Override
47
public HttpResponse execute(HttpRequest req) {
48
return new HttpResponse()
49
.setStatus(200)
50
.setContent(Contents.utf8String("API response"));
51
}
52
};
53
54
// Test route matching
55
HttpRequest getRequest = new HttpRequest(HttpMethod.GET, "/api/users");
56
HttpRequest postRequest = new HttpRequest(HttpMethod.POST, "/api/users");
57
58
boolean matchesGet = customRoute.matches(getRequest); // true
59
boolean matchesPost = customRoute.matches(postRequest); // false
60
61
// Apply filters to routable
62
Filter loggingFilter = new DumpHttpExchangeFilter();
63
Routable filteredRoute = customRoute.with(loggingFilter);
64
65
// Use in routing logic
66
if (filteredRoute.matches(getRequest)) {
67
HttpResponse response = filteredRoute.execute(getRequest);
68
System.out.println("Route handled request: " + response.getStatus());
69
}
70
```
71
72
### Route Abstract Class
73
74
Base class for HTTP routing with URL template matching and parameter extraction, providing the foundation for building complex routing systems.
75
76
```java { .api }
77
/**
78
* Abstract base class for HTTP routing with URL template matching
79
* Implements both HttpHandler and Routable interfaces
80
*/
81
public abstract class Route implements HttpHandler, Routable {
82
/**
83
* Sets fallback handler for unmatched requests
84
* @param handler Supplier providing fallback HttpHandler
85
* @return HttpHandler that uses this route or fallback
86
*/
87
public HttpHandler fallbackTo(Supplier<HttpHandler> handler);
88
89
/**
90
* Final implementation of request execution with routing logic
91
* Checks if request matches, then calls handle() method
92
* @param req HTTP request to execute
93
* @return HTTP response from matched route
94
*/
95
public final HttpResponse execute(HttpRequest req);
96
97
/**
98
* Abstract method for handling matched requests
99
* Subclasses implement specific request handling logic
100
* @param req HTTP request that matched this route
101
* @return HTTP response for the request
102
*/
103
protected abstract HttpResponse handle(HttpRequest req);
104
}
105
```
106
107
### Route Factory Methods
108
109
Static factory methods for creating different types of routes with URL templates and predicates.
110
111
```java { .api }
112
/**
113
* Static factory methods for creating routes
114
*/
115
public abstract class Route {
116
/**
117
* Creates route based on predicate matching
118
* @param predicate Function to test request matching
119
* @return PredicatedConfig for configuring predicate-based route
120
*/
121
public static PredicatedConfig matching(Predicate<HttpRequest> predicate);
122
123
/**
124
* Creates DELETE route with URL template
125
* @param template URL template pattern (e.g., "/users/{id}")
126
* @return TemplatizedRouteConfig for configuring DELETE route
127
*/
128
public static TemplatizedRouteConfig delete(String template);
129
130
/**
131
* Creates GET route with URL template
132
* @param template URL template pattern (e.g., "/users/{id}")
133
* @return TemplatizedRouteConfig for configuring GET route
134
*/
135
public static TemplatizedRouteConfig get(String template);
136
137
/**
138
* Creates POST route with URL template
139
* @param template URL template pattern (e.g., "/users")
140
* @return TemplatizedRouteConfig for configuring POST route
141
*/
142
public static TemplatizedRouteConfig post(String template);
143
144
/**
145
* Creates OPTIONS route with URL template
146
* @param template URL template pattern (e.g., "/users/{id}")
147
* @return TemplatizedRouteConfig for configuring OPTIONS route
148
*/
149
public static TemplatizedRouteConfig options(String template);
150
151
/**
152
* Creates nested route with path prefix
153
* @param prefix Path prefix for nested routes (e.g., "/api/v1")
154
* @return NestedRouteConfig for configuring nested routing
155
*/
156
public static NestedRouteConfig prefix(String prefix);
157
158
/**
159
* Combines multiple routes into single route
160
* @param first First route to combine
161
* @param others Additional routes to combine
162
* @return Combined route that tries routes in order
163
*/
164
public static Route combine(Routable first, Routable... others);
165
166
/**
167
* Combines routes from iterable into single route
168
* @param routes Iterable of routes to combine
169
* @return Combined route that tries routes in order
170
*/
171
public static Route combine(Iterable<Routable> routes);
172
}
173
```
174
175
**Usage Examples:**
176
177
```java
178
import org.openqa.selenium.remote.http.*;
179
import java.util.Map;
180
181
// Create GET route with URL template
182
Route getUserRoute = Route.get("/users/{id}")
183
.to(() -> new HttpHandler() {
184
@Override
185
public HttpResponse execute(HttpRequest req) {
186
String userId = req.getAttribute("id").toString();
187
return new HttpResponse()
188
.setStatus(200)
189
.setContent(Contents.asJson(Map.of("id", userId, "name", "User " + userId)));
190
}
191
});
192
193
// Create POST route
194
Route createUserRoute = Route.post("/users")
195
.to(() -> req -> {
196
String requestBody = Contents.string(req);
197
return new HttpResponse()
198
.setStatus(201)
199
.setContent(Contents.utf8String("User created"));
200
});
201
202
// Create DELETE route with parameter handling
203
Route deleteUserRoute = Route.delete("/users/{id}")
204
.to(params -> req -> {
205
String userId = params.get("id");
206
System.out.println("Deleting user: " + userId);
207
return new HttpResponse().setStatus(204);
208
});
209
210
// Predicate-based route
211
Route apiRoute = Route.matching(req ->
212
req.getUri().startsWith("/api/") &&
213
req.getHeader("Authorization") != null)
214
.to(() -> req -> new HttpResponse().setStatus(200));
215
216
// Combine routes
217
Route combinedRoutes = Route.combine(
218
getUserRoute,
219
createUserRoute,
220
deleteUserRoute,
221
apiRoute
222
);
223
224
// Use with fallback
225
HttpHandler server = combinedRoutes.fallbackTo(() ->
226
req -> new HttpResponse().setStatus(404).setContent(Contents.utf8String("Not Found")));
227
228
// Test routing
229
HttpRequest getRequest = new HttpRequest(HttpMethod.GET, "/users/123");
230
HttpResponse response = server.execute(getRequest);
231
System.out.println("Response: " + response.getStatus());
232
```
233
234
### Route Configuration Classes
235
236
Configuration classes for building different types of routes with fluent API.
237
238
```java { .api }
239
/**
240
* Configuration class for template-based routes
241
*/
242
public static class TemplatizedRouteConfig {
243
/**
244
* Sets handler supplier for this route
245
* @param handler Supplier providing HttpHandler for matched requests
246
* @return Configured Route instance
247
*/
248
Route to(Supplier<HttpHandler> handler);
249
250
/**
251
* Sets parameterized handler function for this route
252
* Handler receives extracted URL parameters as Map
253
* @param handlerFunc Function that takes parameters and returns HttpHandler
254
* @return Configured Route instance
255
*/
256
Route to(Function<Map<String, String>, HttpHandler> handlerFunc);
257
}
258
259
/**
260
* Configuration class for nested routes
261
*/
262
public static class NestedRouteConfig {
263
/**
264
* Sets nested route for this prefix
265
* @param route Route to nest under the prefix
266
* @return Configured Route instance
267
*/
268
Route to(Route route);
269
}
270
271
/**
272
* Configuration class for predicate-based routes
273
*/
274
public static class PredicatedConfig {
275
/**
276
* Sets handler supplier for predicate-matched requests
277
* @param handler Supplier providing HttpHandler for matched requests
278
* @return Configured Route instance
279
*/
280
Route to(Supplier<HttpHandler> handler);
281
}
282
```
283
284
**Usage Examples:**
285
286
```java
287
import org.openqa.selenium.remote.http.*;
288
import java.util.Map;
289
290
// Template-based route with parameter extraction
291
Route userRoute = Route.get("/users/{id}/posts/{postId}")
292
.to(params -> req -> {
293
String userId = params.get("id");
294
String postId = params.get("postId");
295
296
return new HttpResponse()
297
.setStatus(200)
298
.setContent(Contents.asJson(Map.of(
299
"userId", userId,
300
"postId", postId,
301
"title", "Post " + postId + " by User " + userId
302
)));
303
});
304
305
// Simple handler supplier
306
Route simpleRoute = Route.post("/webhooks")
307
.to(() -> req -> {
308
String payload = Contents.string(req);
309
processWebhook(payload);
310
return new HttpResponse().setStatus(200);
311
});
312
313
// Nested route structure
314
Route apiV1Routes = Route.combine(
315
Route.get("/users").to(() -> new UserListHandler()),
316
Route.get("/users/{id}").to(params -> new UserDetailHandler(params.get("id"))),
317
Route.post("/users").to(() -> new CreateUserHandler())
318
);
319
320
Route nestedApi = Route.prefix("/api/v1").to(apiV1Routes);
321
322
// Predicate-based route with complex matching
323
Route adminRoute = Route.matching(req ->
324
req.getUri().startsWith("/admin/") &&
325
"admin".equals(req.getHeader("X-User-Role")))
326
.to(() -> new AdminHandler());
327
328
// Combine all routes
329
Route mainRouter = Route.combine(
330
nestedApi,
331
adminRoute,
332
Route.get("/health").to(() -> req ->
333
new HttpResponse().setStatus(200).setContent(Contents.utf8String("OK")))
334
);
335
336
private void processWebhook(String payload) { /* process webhook */ }
337
338
private static class UserListHandler implements HttpHandler {
339
public HttpResponse execute(HttpRequest req) {
340
return new HttpResponse().setStatus(200);
341
}
342
}
343
344
private static class UserDetailHandler implements HttpHandler {
345
private final String userId;
346
347
public UserDetailHandler(String userId) {
348
this.userId = userId;
349
}
350
351
public HttpResponse execute(HttpRequest req) {
352
return new HttpResponse().setStatus(200);
353
}
354
}
355
356
private static class CreateUserHandler implements HttpHandler {
357
public HttpResponse execute(HttpRequest req) {
358
return new HttpResponse().setStatus(201);
359
}
360
}
361
362
private static class AdminHandler implements HttpHandler {
363
public HttpResponse execute(HttpRequest req) {
364
return new HttpResponse().setStatus(200);
365
}
366
}
367
```
368
369
## URL Template Matching
370
371
### UrlTemplate Class
372
373
URL template matching with parameter extraction for dynamic route handling.
374
375
```java { .api }
376
/**
377
* URL template matching with parameter extraction
378
* Supports parameterized URLs with {param} syntax
379
*/
380
public class UrlTemplate {
381
/**
382
* Creates URL template from pattern string
383
* @param template Template pattern (e.g., "/users/{id}/posts/{postId}")
384
*/
385
public UrlTemplate(String template);
386
387
/**
388
* Matches URL against template pattern
389
* @param matchAgainst URL string to match
390
* @return Match result with extracted parameters, or null if no match
391
*/
392
public Match match(String matchAgainst);
393
394
/**
395
* Matches URL against template with prefix removal
396
* @param matchAgainst URL string to match
397
* @param prefix Prefix to remove before matching
398
* @return Match result with extracted parameters, or null if no match
399
*/
400
public Match match(String matchAgainst, String prefix);
401
402
/**
403
* Match result containing URL and extracted parameters
404
*/
405
public static class Match {
406
/**
407
* Gets the matched URL
408
* @return Matched URL string
409
*/
410
public String getUrl();
411
412
/**
413
* Gets extracted parameters from URL template
414
* @return Map of parameter names to values
415
*/
416
public Map<String, String> getParameters();
417
}
418
}
419
```
420
421
**Usage Examples:**
422
423
```java
424
import org.openqa.selenium.remote.http.*;
425
import java.util.Map;
426
427
// Create URL templates
428
UrlTemplate userTemplate = new UrlTemplate("/users/{id}");
429
UrlTemplate postTemplate = new UrlTemplate("/users/{userId}/posts/{postId}");
430
UrlTemplate fileTemplate = new UrlTemplate("/files/{path...}"); // Captures remaining path
431
432
// Test template matching
433
UrlTemplate.Match userMatch = userTemplate.match("/users/123");
434
if (userMatch != null) {
435
Map<String, String> params = userMatch.getParameters();
436
String userId = params.get("id"); // "123"
437
System.out.println("User ID: " + userId);
438
}
439
440
// Complex template matching
441
UrlTemplate.Match postMatch = postTemplate.match("/users/456/posts/789");
442
if (postMatch != null) {
443
Map<String, String> params = postMatch.getParameters();
444
String userId = params.get("userId"); // "456"
445
String postId = params.get("postId"); // "789"
446
System.out.println("User: " + userId + ", Post: " + postId);
447
}
448
449
// Template with prefix removal
450
UrlTemplate apiTemplate = new UrlTemplate("/users/{id}");
451
UrlTemplate.Match prefixMatch = apiTemplate.match("/api/v1/users/999", "/api/v1");
452
if (prefixMatch != null) {
453
String userId = prefixMatch.getParameters().get("id"); // "999"
454
String matchedUrl = prefixMatch.getUrl(); // "/users/999"
455
}
456
457
// Use in custom route implementation
458
public class CustomTemplateRoute extends Route {
459
private final UrlTemplate template;
460
private final HttpMethod method;
461
462
public CustomTemplateRoute(HttpMethod method, String template) {
463
this.method = method;
464
this.template = new UrlTemplate(template);
465
}
466
467
@Override
468
public boolean matches(HttpRequest req) {
469
return req.getMethod() == method && template.match(req.getUri()) != null;
470
}
471
472
@Override
473
protected HttpResponse handle(HttpRequest req) {
474
UrlTemplate.Match match = template.match(req.getUri());
475
Map<String, String> params = match.getParameters();
476
477
// Store parameters as request attributes
478
params.forEach(req::setAttribute);
479
480
return processRequest(req, params);
481
}
482
483
private HttpResponse processRequest(HttpRequest req, Map<String, String> params) {
484
// Custom processing logic
485
return new HttpResponse().setStatus(200);
486
}
487
}
488
489
// Use custom template route
490
Route customRoute = new CustomTemplateRoute(HttpMethod.GET, "/products/{category}/{id}");
491
```
492
493
### UrlPath Utility
494
495
Utility class for handling URL paths in routing context with prefix management.
496
497
```java { .api }
498
/**
499
* Utility class for URL path handling in routing context
500
*/
501
public class UrlPath {
502
/**
503
* Constant for route prefix attribute key
504
*/
505
public static final String ROUTE_PREFIX_KEY = "selenium.route";
506
507
/**
508
* Returns location relative to server
509
* @param req HTTP request for context
510
* @param location Location string to make relative
511
* @return Server-relative location
512
*/
513
public static String relativeToServer(HttpRequest req, String location);
514
515
/**
516
* Returns location relative to context with route prefixes
517
* Uses route prefix attributes to build context-relative paths
518
* @param req HTTP request for context
519
* @param location Location string to make relative
520
* @return Context-relative location with prefixes applied
521
*/
522
public static String relativeToContext(HttpRequest req, String location);
523
}
524
```
525
526
**Usage Examples:**
527
528
```java
529
import org.openqa.selenium.remote.http.*;
530
531
// Set route prefix in request
532
HttpRequest request = new HttpRequest(HttpMethod.GET, "/api/v1/users/123");
533
request.setAttribute(UrlPath.ROUTE_PREFIX_KEY, "/api/v1");
534
535
// Get relative paths
536
String serverRelative = UrlPath.relativeToServer(request, "/users/456");
537
// Result: "/users/456"
538
539
String contextRelative = UrlPath.relativeToContext(request, "/users/456");
540
// Result: "/api/v1/users/456" (includes prefix)
541
542
// Use in route handlers for generating links
543
Route userRoute = Route.get("/users/{id}")
544
.to(params -> req -> {
545
String userId = params.get("id");
546
String editLink = UrlPath.relativeToContext(req, "/users/" + userId + "/edit");
547
548
return new HttpResponse()
549
.setStatus(200)
550
.setContent(Contents.asJson(Map.of(
551
"id", userId,
552
"editUrl", editLink
553
)));
554
});
555
556
// Nested routing with prefix handling
557
Route apiRoutes = Route.prefix("/api/v1").to(
558
Route.combine(
559
Route.get("/users/{id}").to(params -> req -> {
560
// req will have ROUTE_PREFIX_KEY set to "/api/v1"
561
String selfLink = UrlPath.relativeToContext(req, "/users/" + params.get("id"));
562
return new HttpResponse().setStatus(200);
563
})
564
)
565
);
566
```
567
568
## Complete Routing Example
569
570
```java
571
import org.openqa.selenium.remote.http.*;
572
import java.util.Map;
573
import java.util.List;
574
import java.util.concurrent.ConcurrentHashMap;
575
576
public class CompleteRoutingExample {
577
578
// Simple in-memory data store
579
private final Map<String, User> users = new ConcurrentHashMap<>();
580
private int nextId = 1;
581
582
public Route createUserAPI() {
583
// Individual route handlers
584
Route listUsers = Route.get("/users")
585
.to(() -> req -> {
586
List<User> userList = List.copyOf(users.values());
587
return new HttpResponse()
588
.setStatus(200)
589
.setContent(Contents.asJson(userList));
590
});
591
592
Route getUser = Route.get("/users/{id}")
593
.to(params -> req -> {
594
String id = params.get("id");
595
User user = users.get(id);
596
597
if (user == null) {
598
return new HttpResponse()
599
.setStatus(404)
600
.setContent(Contents.utf8String("User not found"));
601
}
602
603
return new HttpResponse()
604
.setStatus(200)
605
.setContent(Contents.asJson(user));
606
});
607
608
Route createUser = Route.post("/users")
609
.to(() -> req -> {
610
try {
611
User userData = Contents.fromJson(req, User.class);
612
String id = String.valueOf(nextId++);
613
User newUser = new User(id, userData.getName(), userData.getEmail());
614
users.put(id, newUser);
615
616
return new HttpResponse()
617
.setStatus(201)
618
.setContent(Contents.asJson(newUser));
619
} catch (Exception e) {
620
return new HttpResponse()
621
.setStatus(400)
622
.setContent(Contents.utf8String("Invalid user data"));
623
}
624
});
625
626
Route updateUser = Route.post("/users/{id}") // Using POST for simplicity
627
.to(params -> req -> {
628
String id = params.get("id");
629
if (!users.containsKey(id)) {
630
return new HttpResponse().setStatus(404);
631
}
632
633
try {
634
User userData = Contents.fromJson(req, User.class);
635
User updatedUser = new User(id, userData.getName(), userData.getEmail());
636
users.put(id, updatedUser);
637
638
return new HttpResponse()
639
.setStatus(200)
640
.setContent(Contents.asJson(updatedUser));
641
} catch (Exception e) {
642
return new HttpResponse().setStatus(400);
643
}
644
});
645
646
Route deleteUser = Route.delete("/users/{id}")
647
.to(params -> req -> {
648
String id = params.get("id");
649
User removed = users.remove(id);
650
651
return new HttpResponse()
652
.setStatus(removed != null ? 204 : 404);
653
});
654
655
// Health check route
656
Route healthCheck = Route.get("/health")
657
.to(() -> req -> new HttpResponse()
658
.setStatus(200)
659
.setContent(Contents.utf8String("OK")));
660
661
// Combine all routes
662
Route userRoutes = Route.combine(
663
listUsers,
664
getUser,
665
createUser,
666
updateUser,
667
deleteUser
668
);
669
670
// Create API with prefix
671
Route apiV1 = Route.prefix("/api/v1").to(userRoutes);
672
673
// Combine with health check
674
return Route.combine(apiV1, healthCheck);
675
}
676
677
public HttpHandler createServer() {
678
Route mainRouter = createUserAPI();
679
680
// Add logging and error handling
681
Filter loggingFilter = new DumpHttpExchangeFilter();
682
Route filteredRouter = (Route) mainRouter.with(loggingFilter);
683
684
// Add fallback for unmatched routes
685
return filteredRouter.fallbackTo(() -> req ->
686
new HttpResponse()
687
.setStatus(404)
688
.setContent(Contents.asJson(Map.of(
689
"error", "Not Found",
690
"path", req.getUri()
691
))));
692
}
693
694
public static void main(String[] args) {
695
CompleteRoutingExample example = new CompleteRoutingExample();
696
HttpHandler server = example.createServer();
697
698
// Test the server
699
testServer(server);
700
}
701
702
private static void testServer(HttpHandler server) {
703
// Create user
704
HttpRequest createReq = new HttpRequest(HttpMethod.POST, "/api/v1/users");
705
createReq.setContent(Contents.asJson(new User(null, "John Doe", "john@example.com")));
706
HttpResponse createResp = server.execute(createReq);
707
System.out.println("Create user: " + createResp.getStatus());
708
709
// Get user
710
HttpRequest getReq = new HttpRequest(HttpMethod.GET, "/api/v1/users/1");
711
HttpResponse getResp = server.execute(getReq);
712
System.out.println("Get user: " + getResp.getStatus());
713
System.out.println("User data: " + Contents.string(getResp));
714
715
// List users
716
HttpRequest listReq = new HttpRequest(HttpMethod.GET, "/api/v1/users");
717
HttpResponse listResp = server.execute(listReq);
718
System.out.println("List users: " + listResp.getStatus());
719
720
// Health check
721
HttpRequest healthReq = new HttpRequest(HttpMethod.GET, "/health");
722
HttpResponse healthResp = server.execute(healthReq);
723
System.out.println("Health check: " + healthResp.getStatus());
724
725
// 404 test
726
HttpRequest notFoundReq = new HttpRequest(HttpMethod.GET, "/unknown");
727
HttpResponse notFoundResp = server.execute(notFoundReq);
728
System.out.println("Not found: " + notFoundResp.getStatus());
729
}
730
731
// Simple User class for example
732
public static class User {
733
private String id;
734
private String name;
735
private String email;
736
737
public User() {} // For JSON deserialization
738
739
public User(String id, String name, String email) {
740
this.id = id;
741
this.name = name;
742
this.email = email;
743
}
744
745
// Getters and setters
746
public String getId() { return id; }
747
public void setId(String id) { this.id = id; }
748
public String getName() { return name; }
749
public void setName(String name) { this.name = name; }
750
public String getEmail() { return email; }
751
public void setEmail(String email) { this.email = email; }
752
}
753
}
754
```