or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-config.mdcontent-handling.mdfiltering.mdhttp-client.mdindex.mdrequest-response.mdrouting.mdwebsocket.md

routing.mddocs/

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

```