or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdparameter-injection.mdreactive-streaming.mdrequest-context.mdrequest-filtering.mdroute-declaration.md

request-filtering.mddocs/

0

# Request Filtering

1

2

Cross-cutting concern implementation using `@RouteFilter` annotation for handling authentication, logging, request modification, and other concerns that apply across multiple endpoints.

3

4

## Capabilities

5

6

### @RouteFilter Annotation

7

8

Defines filters that execute on every HTTP request with configurable priority ordering for comprehensive request/response processing.

9

10

```java { .api }

11

/**

12

* Defines a filter that runs on every HTTP request

13

* Filters are executed in priority order (higher priority values run first)

14

* @param value Filter priority (default: DEFAULT_PRIORITY = 10)

15

*/

16

@RouteFilter(10) // or @RouteFilter(value = 10)

17

/**

18

* Filter methods must:

19

* - Take RoutingExchange as parameter

20

* - Return void or call exchange.context().next() to continue processing

21

* - Can modify request/response or terminate processing

22

*/

23

24

/** Default filter priority constant */

25

public static final int DEFAULT_PRIORITY = 10;

26

```

27

28

**Usage Examples:**

29

30

```java

31

import io.quarkus.vertx.web.RouteFilter;

32

import io.quarkus.vertx.web.RoutingExchange;

33

import jakarta.enterprise.context.ApplicationScoped;

34

35

@ApplicationScoped

36

public class FilterExamples {

37

38

// Basic request logging filter

39

@RouteFilter(10)

40

public void logRequests(RoutingExchange exchange) {

41

String method = exchange.request().method().name();

42

String path = exchange.request().path();

43

String userAgent = exchange.getHeader("User-Agent").orElse("Unknown");

44

45

System.out.printf("[%s] %s %s - %s%n",

46

Instant.now(), method, path, userAgent);

47

48

// Continue to next filter or route handler

49

exchange.context().next();

50

}

51

52

// CORS filter (high priority)

53

@RouteFilter(1)

54

public void corsFilter(RoutingExchange exchange) {

55

exchange.response()

56

.putHeader("Access-Control-Allow-Origin", "*")

57

.putHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")

58

.putHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");

59

60

// Handle preflight OPTIONS requests

61

if ("OPTIONS".equals(exchange.request().method().name())) {

62

exchange.response().setStatusCode(200).end();

63

return; // Don't call next() - terminate here

64

}

65

66

exchange.context().next();

67

}

68

69

// Authentication filter (medium priority)

70

@RouteFilter(5)

71

public void authFilter(RoutingExchange exchange) {

72

String path = exchange.request().path();

73

74

// Skip authentication for public paths

75

if (path.startsWith("/public") || path.equals("/health")) {

76

exchange.context().next();

77

return;

78

}

79

80

String authHeader = exchange.getHeader("Authorization").orElse("");

81

if (!authHeader.startsWith("Bearer ")) {

82

exchange.response()

83

.setStatusCode(401)

84

.putHeader("WWW-Authenticate", "Bearer")

85

.end("Unauthorized");

86

return;

87

}

88

89

String token = authHeader.substring(7);

90

if (!isValidToken(token)) {

91

exchange.response().setStatusCode(403).end("Invalid token");

92

return;

93

}

94

95

// Add user info to context for downstream handlers

96

exchange.context().put("userId", extractUserIdFromToken(token));

97

exchange.context().next();

98

}

99

100

private boolean isValidToken(String token) {

101

// Token validation logic

102

return token.length() > 10 && !token.equals("invalid");

103

}

104

105

private String extractUserIdFromToken(String token) {

106

// Extract user ID from token

107

return "user-" + token.hashCode();

108

}

109

}

110

```

111

112

### Filter Execution Order

113

114

Filters execute in priority order, with lower priority values executing first. Multiple filters with the same priority execute in undefined order.

115

116

```java

117

@ApplicationScoped

118

public class OrderedFilters {

119

120

// First filter - security headers

121

@RouteFilter(1)

122

public void securityHeaders(RoutingExchange exchange) {

123

exchange.response()

124

.putHeader("X-Content-Type-Options", "nosniff")

125

.putHeader("X-Frame-Options", "DENY")

126

.putHeader("X-XSS-Protection", "1; mode=block");

127

exchange.context().next();

128

}

129

130

// Second filter - request timing

131

@RouteFilter(2)

132

public void startTiming(RoutingExchange exchange) {

133

exchange.context().put("startTime", System.currentTimeMillis());

134

exchange.context().next();

135

}

136

137

// Third filter - content negotiation

138

@RouteFilter(3)

139

public void contentNegotiation(RoutingExchange exchange) {

140

String accept = exchange.getHeader("Accept").orElse("*/*");

141

exchange.context().put("acceptHeader", accept);

142

exchange.context().next();

143

}

144

145

// Last filter - request completion timing

146

@RouteFilter(1000)

147

public void endTiming(RoutingExchange exchange) {

148

// This runs after route handler completion

149

Long startTime = exchange.context().get("startTime");

150

if (startTime != null) {

151

long duration = System.currentTimeMillis() - startTime;

152

exchange.response().putHeader("X-Response-Time", duration + "ms");

153

}

154

exchange.context().next();

155

}

156

}

157

```

158

159

### Authentication and Authorization Filters

160

161

Comprehensive authentication and authorization patterns using filters.

162

163

```java

164

@ApplicationScoped

165

public class AuthFilters {

166

167

// API key authentication

168

@RouteFilter(5)

169

public void apiKeyAuth(RoutingExchange exchange) {

170

String path = exchange.request().path();

171

172

// Only apply to API paths

173

if (!path.startsWith("/api/")) {

174

exchange.context().next();

175

return;

176

}

177

178

String apiKey = exchange.getHeader("X-API-Key").orElse("");

179

if (apiKey.isEmpty()) {

180

exchange.response().setStatusCode(401)

181

.end("API key required");

182

return;

183

}

184

185

if (!isValidApiKey(apiKey)) {

186

exchange.response().setStatusCode(403)

187

.end("Invalid API key");

188

return;

189

}

190

191

exchange.context().put("apiKeyValid", true);

192

exchange.context().next();

193

}

194

195

// Role-based authorization

196

@RouteFilter(6)

197

public void roleBasedAuth(RoutingExchange exchange) {

198

String path = exchange.request().path();

199

String method = exchange.request().method().name();

200

201

// Admin-only paths

202

if (path.startsWith("/admin/")) {

203

String userRole = getUserRole(exchange);

204

if (!"admin".equals(userRole)) {

205

exchange.response().setStatusCode(403)

206

.end("Admin access required");

207

return;

208

}

209

}

210

211

// Write operations require elevated permissions

212

if (method.matches("POST|PUT|DELETE") && path.startsWith("/api/")) {

213

String userRole = getUserRole(exchange);

214

if ("readonly".equals(userRole)) {

215

exchange.response().setStatusCode(403)

216

.end("Write access denied");

217

return;

218

}

219

}

220

221

exchange.context().next();

222

}

223

224

// JWT token validation

225

@RouteFilter(4)

226

public void jwtValidation(RoutingExchange exchange) {

227

String authHeader = exchange.getHeader("Authorization").orElse("");

228

229

if (authHeader.startsWith("Bearer ")) {

230

String token = authHeader.substring(7);

231

try {

232

JwtClaims claims = validateJwtToken(token);

233

exchange.context().put("jwtClaims", claims);

234

exchange.context().put("userId", claims.getSubject());

235

} catch (Exception e) {

236

exchange.response().setStatusCode(401)

237

.end("Invalid JWT token");

238

return;

239

}

240

}

241

242

exchange.context().next();

243

}

244

245

private boolean isValidApiKey(String apiKey) {

246

// API key validation logic

247

return apiKey.startsWith("ak_") && apiKey.length() == 32;

248

}

249

250

private String getUserRole(RoutingExchange exchange) {

251

// Extract user role from context or token

252

JwtClaims claims = exchange.context().get("jwtClaims");

253

return claims != null ? claims.getStringClaimValue("role") : "guest";

254

}

255

256

private JwtClaims validateJwtToken(String token) throws Exception {

257

// JWT validation logic (placeholder)

258

if (token.length() < 10) {

259

throw new Exception("Invalid token");

260

}

261

// Return mock claims

262

return new JwtClaims() {

263

public String getSubject() { return "user123"; }

264

public String getStringClaimValue(String claim) {

265

return "role".equals(claim) ? "user" : null;

266

}

267

};

268

}

269

270

// Mock JWT claims interface

271

private interface JwtClaims {

272

String getSubject();

273

String getStringClaimValue(String claim);

274

}

275

}

276

```

277

278

### Request/Response Modification Filters

279

280

Filters for modifying requests and responses, including content transformation and header manipulation.

281

282

```java

283

@ApplicationScoped

284

public class ModificationFilters {

285

286

// Request body logging and modification

287

@RouteFilter(7)

288

public void requestModification(RoutingExchange exchange) {

289

String method = exchange.request().method().name();

290

291

// Only process request bodies for write operations

292

if (!method.matches("POST|PUT|PATCH")) {

293

exchange.context().next();

294

return;

295

}

296

297

// Log request body (in real implementation, be careful with sensitive data)

298

exchange.request().body().onSuccess(buffer -> {

299

if (buffer != null && buffer.length() > 0) {

300

System.out.println("Request body size: " + buffer.length() + " bytes");

301

302

// Example: Add request ID to context

303

String requestId = "req-" + System.currentTimeMillis();

304

exchange.context().put("requestId", requestId);

305

exchange.response().putHeader("X-Request-ID", requestId);

306

}

307

exchange.context().next();

308

}).onFailure(throwable -> {

309

exchange.response().setStatusCode(400).end("Invalid request body");

310

});

311

}

312

313

// Response compression filter

314

@RouteFilter(999)

315

public void responseCompression(RoutingExchange exchange) {

316

String acceptEncoding = exchange.getHeader("Accept-Encoding").orElse("");

317

318

if (acceptEncoding.contains("gzip")) {

319

exchange.response().putHeader("Content-Encoding", "gzip");

320

// Note: Actual compression would be handled by Quarkus/Vert.x configuration

321

}

322

323

exchange.context().next();

324

}

325

326

// Content type enforcement

327

@RouteFilter(8)

328

public void contentTypeEnforcement(RoutingExchange exchange) {

329

String method = exchange.request().method().name();

330

String path = exchange.request().path();

331

332

// Enforce JSON content type for API endpoints

333

if (method.matches("POST|PUT|PATCH") && path.startsWith("/api/")) {

334

String contentType = exchange.getHeader("Content-Type").orElse("");

335

336

if (!contentType.contains("application/json")) {

337

exchange.response().setStatusCode(415)

338

.putHeader("Accept", "application/json")

339

.end("Content-Type must be application/json");

340

return;

341

}

342

}

343

344

exchange.context().next();

345

}

346

347

// Response headers standardization

348

@RouteFilter(900)

349

public void responseHeaders(RoutingExchange exchange) {

350

// Add standard response headers

351

exchange.response()

352

.putHeader("X-Powered-By", "Quarkus Reactive Routes")

353

.putHeader("X-Content-Type-Options", "nosniff");

354

355

// Add cache headers based on path

356

String path = exchange.request().path();

357

if (path.startsWith("/static/")) {

358

exchange.response().putHeader("Cache-Control", "public, max-age=86400");

359

} else if (path.startsWith("/api/")) {

360

exchange.response().putHeader("Cache-Control", "no-cache, no-store, must-revalidate");

361

}

362

363

exchange.context().next();

364

}

365

}

366

```

367

368

### Error Handling and Monitoring Filters

369

370

Filters for comprehensive error handling, monitoring, and observability.

371

372

```java

373

@ApplicationScoped

374

public class MonitoringFilters {

375

376

// Global error handling filter

377

@RouteFilter(value = 1001, type = Route.HandlerType.FAILURE)

378

public void globalErrorHandler(RoutingExchange exchange) {

379

Throwable failure = exchange.context().failure();

380

String requestId = exchange.context().get("requestId");

381

382

// Log error with context

383

System.err.printf("Error [%s]: %s - %s%n",

384

requestId, failure.getClass().getSimpleName(), failure.getMessage());

385

386

// Return appropriate error response

387

if (failure instanceof IllegalArgumentException) {

388

exchange.response().setStatusCode(400)

389

.end("Bad Request: " + failure.getMessage());

390

} else if (failure instanceof SecurityException) {

391

exchange.response().setStatusCode(403)

392

.end("Access Denied");

393

} else {

394

exchange.response().setStatusCode(500)

395

.end("Internal Server Error");

396

}

397

}

398

399

// Request metrics collection

400

@RouteFilter(2)

401

public void metricsCollection(RoutingExchange exchange) {

402

String method = exchange.request().method().name();

403

String path = exchange.request().path();

404

405

// Increment request counter (pseudo-code)

406

// MetricsRegistry.counter("http_requests_total", "method", method, "path", path).increment();

407

408

// Track concurrent requests

409

exchange.context().put("startTime", System.currentTimeMillis());

410

exchange.context().addBodyEndHandler(v -> {

411

Long startTime = exchange.context().get("startTime");

412

if (startTime != null) {

413

long duration = System.currentTimeMillis() - startTime;

414

int statusCode = exchange.response().getStatusCode();

415

416

// Record response time histogram (pseudo-code)

417

// MetricsRegistry.timer("http_request_duration", "method", method, "status", String.valueOf(statusCode))

418

// .record(Duration.ofMillis(duration));

419

420

System.out.printf("Request completed: %s %s - %d (%dms)%n",

421

method, path, statusCode, duration);

422

}

423

});

424

425

exchange.context().next();

426

}

427

428

// Rate limiting filter

429

@RouteFilter(3)

430

public void rateLimiting(RoutingExchange exchange) {

431

String clientIp = getClientIp(exchange);

432

String path = exchange.request().path();

433

434

// Skip rate limiting for health checks

435

if (path.equals("/health") || path.equals("/ready")) {

436

exchange.context().next();

437

return;

438

}

439

440

// Check rate limit (pseudo-implementation)

441

if (isRateLimited(clientIp)) {

442

exchange.response()

443

.setStatusCode(429)

444

.putHeader("Retry-After", "60")

445

.end("Rate limit exceeded");

446

return;

447

}

448

449

exchange.context().next();

450

}

451

452

private String getClientIp(RoutingExchange exchange) {

453

// Check for forwarded IP headers

454

return exchange.getHeader("X-Forwarded-For")

455

.or(() -> exchange.getHeader("X-Real-IP"))

456

.orElse(exchange.request().remoteAddress().host());

457

}

458

459

private boolean isRateLimited(String clientIp) {

460

// Rate limiting logic (would use Redis, in-memory cache, etc.)

461

return false; // Placeholder

462

}

463

}

464

```

465

466

### Filter Configuration and Context Sharing

467

468

Advanced patterns for filter configuration and sharing data between filters and route handlers.

469

470

```java

471

@ApplicationScoped

472

public class AdvancedFilters {

473

474

// Context enrichment filter

475

@RouteFilter(4)

476

public void contextEnrichment(RoutingExchange exchange) {

477

String userAgent = exchange.getHeader("User-Agent").orElse("Unknown");

478

String acceptLanguage = exchange.getHeader("Accept-Language").orElse("en");

479

480

// Parse and add request context

481

RequestContext requestContext = new RequestContext(

482

userAgent,

483

parseLanguage(acceptLanguage),

484

isMobileClient(userAgent),

485

System.currentTimeMillis()

486

);

487

488

exchange.context().put("requestContext", requestContext);

489

exchange.context().next();

490

}

491

492

// Feature flag filter

493

@RouteFilter(6)

494

public void featureFlags(RoutingExchange exchange) {

495

String path = exchange.request().path();

496

RequestContext context = exchange.context().get("requestContext");

497

498

// Check feature flags based on request context

499

if (path.startsWith("/api/v2/") && !isFeatureEnabled("api_v2", context)) {

500

exchange.response().setStatusCode(404)

501

.end("API version not available");

502

return;

503

}

504

505

exchange.context().next();

506

}

507

508

private String parseLanguage(String acceptLanguage) {

509

return acceptLanguage.split(",")[0].trim();

510

}

511

512

private boolean isMobileClient(String userAgent) {

513

return userAgent.toLowerCase().contains("mobile");

514

}

515

516

private boolean isFeatureEnabled(String feature, RequestContext context) {

517

// Feature flag logic

518

return true; // Placeholder

519

}

520

521

// Request context data class

522

public static class RequestContext {

523

private final String userAgent;

524

private final String language;

525

private final boolean mobile;

526

private final long timestamp;

527

528

public RequestContext(String userAgent, String language, boolean mobile, long timestamp) {

529

this.userAgent = userAgent;

530

this.language = language;

531

this.mobile = mobile;

532

this.timestamp = timestamp;

533

}

534

535

// Getters

536

public String getUserAgent() { return userAgent; }

537

public String getLanguage() { return language; }

538

public boolean isMobile() { return mobile; }

539

public long getTimestamp() { return timestamp; }

540

}

541

}

542

```