-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHttpVerticle.java
More file actions
186 lines (158 loc) · 7.95 KB
/
Copy pathHttpVerticle.java
File metadata and controls
186 lines (158 loc) · 7.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package ziadatari.ReactiveAPI.web;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.openapi.RouterBuilder;
import io.vertx.micrometer.PrometheusScrapingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ziadatari.ReactiveAPI.auth.RateLimitHandler;
import ziadatari.ReactiveAPI.auth.VerificationHandler;
/**
* Verticle responsible for running the HTTP server.
* Uses OpenAPI 3.0 RouterBuilder for contract-driven routing (v4.5 Update).
*/
public class HttpVerticle extends AbstractVerticle {
private static final Logger logger = LoggerFactory.getLogger(HttpVerticle.class);
public HttpVerticle() {
// Default constructor
}
/**
* Starts the HTTP server using OpenAPI RouterBuilder.
*
* @param startPromise a promise to signal success or failure of server startup
*/
@Override
public void start(Promise<Void> startPromise) {
// --- WEB CLIENT ---
WebClientOptions options = new WebClientOptions()
.setMaxPoolSize(100)
.setConnectTimeout(2000)
.setIdleTimeout(10);
WebClient webClient = WebClient.create(vertx, options);
// --- CONTROLLERS ---
EmployeeController controller = new EmployeeController(vertx);
CustomCircuitBreaker loginCB = new CustomCircuitBreaker(vertx, "auth-login", 1000, 2000, 5);
AuthController authController = new AuthController(vertx, loginCB);
// --- CIRCUIT BREAKERS FOR VERIFICATION ---
CustomCircuitBreaker v1VerificationCB = new CustomCircuitBreaker(vertx, "v1-verify", 500, 800, 5);
CustomCircuitBreaker v3VerificationCB = new CustomCircuitBreaker(vertx, "v3-verify", 500, 800, 5);
String verifyHost = config().getString("verification.host", "localhost");
int verifyPort = config().getInteger("verification.port", 8080);
String serverUrl = config().getString("SERVER_URL", "http://localhost:8888");
// --- JWT AUTH HANDLER ---
JwtAuthHandler jwtAuthHandler = new JwtAuthHandler(vertx, config());
// --- OPENAPI ROUTER BUILDER ---
RouterBuilder.create(vertx, "openapi.yaml")
.onSuccess(routerBuilder -> {
logger.info("OpenAPI specification loaded successfully");
// Register security handler for BearerAuth
routerBuilder.securityHandler("BearerAuth", jwtAuthHandler);
// --- OPERATION HANDLERS ---
// Auth (no security required)
routerBuilder.operation("login").handler(authController::login);
// V1 (Legacy - no security required)
routerBuilder.operation("getAllEmployeesV1").handler(controller::getAll);
routerBuilder.operation("createEmployeeV1").handler(controller::create);
routerBuilder.operation("updateEmployeeV1").handler(controller::update);
routerBuilder.operation("deleteEmployeeV1").handler(controller::delete);
// V3 (Authenticated - JWT required)
routerBuilder.operation("getAllEmployeesV3").handler(controller::getAll);
routerBuilder.operation("createEmployeeV3").handler(controller::create);
routerBuilder.operation("updateEmployeeV3").handler(controller::update);
routerBuilder.operation("deleteEmployeeV3").handler(controller::delete);
// Health (defined in spec but simple handler)
routerBuilder.operation("healthLive").handler(ctx -> {
ctx.response()
.putHeader("Content-Type", "application/json")
.end("{\"outcome\": \"UP\"}");
});
// Metrics (operationId from OpenAPI spec)
routerBuilder.operation("getMetrics").handler(PrometheusScrapingHandler.create());
// --- BUILD OPENAPI ROUTER ---
Router apiRouter = routerBuilder.createRouter();
// --- MAIN ROUTER (for global middleware and infrastructure) ---
Router mainRouter = Router.router(vertx);
// 0. CORS Handler: Allow external web applications to access the API
mainRouter.route().handler(CorsHandler.create()
.addOrigin(".*") // Enable for all origins. For production, specify origins.
.allowedMethod(HttpMethod.GET)
.allowedMethod(HttpMethod.POST)
.allowedMethod(HttpMethod.PUT)
.allowedMethod(HttpMethod.DELETE)
.allowedMethod(HttpMethod.OPTIONS)
.allowedHeader("Access-Control-Request-Method")
.allowedHeader("Access-Control-Allow-Credentials")
.allowedHeader("Access-Control-Allow-Origin")
.allowedHeader("Access-Control-Allow-Headers")
.allowedHeader("Content-Type")
.allowedHeader("Authorization"));
// 1. BodyHandler: Essential for reading JSON bodies
mainRouter.route().handler(BodyHandler.create());
// 2. Swagger UI Static Files (v4.6 Update)
// Redirect /swagger to /swagger/index.html
mainRouter.route("/swagger").handler(ctx -> {
ctx.response()
.setStatusCode(302)
.putHeader("Location", "/swagger/index.html")
.end();
});
// Serve static files from classpath
mainRouter.route("/swagger/*").handler(
StaticHandler.create("webroot/swagger")
.setCachingEnabled(false));
// 3. Serve OpenAPI spec for Swagger UI
mainRouter.route("/openapi.yaml").handler(ctx -> {
vertx.fileSystem().readFile("openapi.yaml", ar -> {
if (ar.succeeded()) {
String openApiContent = ar.result().toString();
// Replace hardcoded URL with configured SERVER_URL
// We target the specific line indentation/format to be safe, or just the URL
// value
// In openapi.yaml: " - url: http://localhost:8888"
// We'll do a simple string replacement of the default URL if present,
// or we could trust the user hasn't changed the file structure too much.
// A more robust way is to replace "http://localhost:8888" specifically.
String dynamicContent = openApiContent.replace("http://localhost:8888", serverUrl);
ctx.response()
.putHeader("Content-Type", "application/yaml")
.end(dynamicContent);
} else {
ctx.fail(ar.cause());
}
});
});
// 4. RateLimitHandler: Global rate limiting
mainRouter.route().handler(new RateLimitHandler(vertx, 100, 1000));
// 5. Verification Handlers for V1 and V3 paths
mainRouter.route("/v1/*")
.handler(new VerificationHandler(webClient, v1VerificationCB, "/v1/ip", verifyHost, verifyPort, false));
mainRouter.route("/v3/*")
.handler(new VerificationHandler(webClient, v3VerificationCB, "/v3/ip", verifyHost, verifyPort, true));
// 6. Mount the OpenAPI Router
mainRouter.route("/*").subRouter(apiRouter);
// --- START HTTP SERVER ---
vertx.createHttpServer()
.requestHandler(mainRouter)
.listen(config().getInteger("http.port"), http -> {
if (http.succeeded()) {
logger.info("HTTP server started on port {} (OpenAPI mode)", http.result().actualPort());
startPromise.complete();
} else {
logger.error("CRITICAL: HTTP server failed to start", http.cause());
startPromise.fail(http.cause());
}
});
})
.onFailure(err -> {
logger.error("CRITICAL: Failed to load OpenAPI specification", err);
startPromise.fail(err);
});
}
}