Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2ce67ec
feat: enhance NodeStatusChanged event to include node ID and update r…
watashi-00 Jul 25, 2026
9c780d9
docs: remove obsolete plans and documentation files
watashi-00 Jul 26, 2026
30dccd8
fix: resolve ServerNode state not updating on status events
watashi-00 Jul 27, 2026
7a77076
refactor: migrate cluster map key from fullHost to nodeId
watashi-00 Jul 27, 2026
434a552
refactor(Cluster.java): remove unused fullHost variable
watashi-00 Jul 27, 2026
ca81e87
feat: add RoutingProtocol to ServerNode — separate routing from ping …
watashi-00 Jul 27, 2026
18d31c7
feat: multi-cluster ServerManager + RoutingProtocol-aware transport r…
watashi-00 Jul 27, 2026
17537a7
feat: ExternalAuthFilter + authService() builder — auth_request deleg…
watashi-00 Jul 27, 2026
8fa8add
refactor(ExternalAuthFilter): remove unused imports
watashi-00 Jul 27, 2026
b178b78
Merge pull request #18 from watashi-00/fix/16-servernode-state-telemetry
watashi-00 Jul 27, 2026
5550d6b
feat: make TUI System.out redirection optional and disabled by default
watashi-00 Jul 28, 2026
8b198d8
fix(tui): add redirectSystemOut getter and update TuiLogRedirectionTe…
watashi-00 Jul 28, 2026
ae611da
feat: inject X-Forwarded traceability headers in L7 proxy requests
watashi-00 Jul 28, 2026
49fc6b3
fix(server): deduplicate L7 traceability header injection and expand …
watashi-00 Jul 28, 2026
7960223
feat: support TCP keep-alive, timeouts and robust dual-socket closing…
watashi-00 Jul 28, 2026
a653d61
perf: bound TCP proxy buffer pool cache size to prevent memory leaks
watashi-00 Jul 28, 2026
e7529c9
fix(l4): eliminate check-then-act race in buffer pool & fix buffer dr…
watashi-00 Jul 28, 2026
d1d0d9f
fix(test): wrap testBoundedBufferPool execution in try-finally to cle…
watashi-00 Jul 28, 2026
b5d5c05
docs: update gateway and terminal-ui documentation for issue #16 and …
watashi-00 Jul 28, 2026
25cb5cf
refactor(log): change startup/shutdown lifecycle logs from debug to i…
watashi-00 Jul 29, 2026
323b4a3
feat(reflection): implement native ClassScanner and unit tests
watashi-00 Jul 29, 2026
feeda43
feat(dx): support scanPackages and lazy route registration during boo…
watashi-00 Jul 29, 2026
776f129
fix(dx): optimize findResourcesDir to walk up parent directories, avo…
watashi-00 Jul 29, 2026
c3b0682
fix(log): eliminate duplicate event dispatches, config folder, and sc…
watashi-00 Jul 29, 2026
6757aed
fix(log): remove standard output prints inside HttpTransport request …
watashi-00 Jul 29, 2026
9b13bf7
fix(dx): support base package resolution from stack trace for fat JAR…
watashi-00 Jul 29, 2026
757a405
feat(http): implement ProxyResponse, JdkHttpProxyClient and PathResol…
watashi-00 Jul 29, 2026
adce5b3
feat(http): implement central HttpErrorHandler and ReverseProxyService
watashi-00 Jul 29, 2026
ad74f50
feat(cors): add CorsFilter and refactor route mappings to lowercase s…
watashi-00 Jul 29, 2026
64a6e8f
feat(http): refactor and unify HttpTransport and UndertowHttpTransport
watashi-00 Jul 29, 2026
f3f5be8
fix(cluster): disable redundant disk state persistence on telemetry u…
watashi-00 Jul 29, 2026
aa01947
fix(model): generate node ID from host:port when custom name is null …
watashi-00 Jul 29, 2026
5b0ddc1
fix(proxy): resolve GET request timeout and bind client connections t…
watashi-00 Jul 29, 2026
1fa041d
perf(proxy): resolve localhost instantly to 127.0.0.1 and strip hop-b…
watashi-00 Jul 29, 2026
084e4ad
perf(undertow): dispatch request processing to Loom virtual thread ex…
watashi-00 Jul 29, 2026
a6302fb
fix(undertow): use heap buffers and disable thread-local cache in Def…
watashi-00 Jul 29, 2026
0d7e143
fix(undertow): restore threadLocalCacheSize to 24 while maintaining H…
watashi-00 Jul 29, 2026
e324624
fix(undertow): tune DefaultByteBufferPool buffer size to 8KB and cach…
watashi-00 Jul 29, 2026
07e824e
perf(undertow): restore non-blocking FastPrintWriter fast-path for lo…
watashi-00 Jul 29, 2026
6626780
perf(transport): restore non-blocking fast-path for local routes in J…
watashi-00 Jul 29, 2026
b09a01d
refactor(transport): remove bypass fast-path to guarantee unified fil…
watashi-00 Jul 29, 2026
87983e1
feat(route): add support for explicit fastPath property in RouteMappi…
watashi-00 Jul 29, 2026
e4a6bc7
perf(writer): remove BufferedWriter wrapper from http responses to av…
watashi-00 Jul 29, 2026
3a9acfa
refactor(HttpTransport): extract route execution logic into separate …
watashi-00 Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ CODE_REVIEW.md

.agent
.state
.superpowers
.superpowers
.tmp
50 changes: 41 additions & 9 deletions docs/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,24 @@ GatewayBuilderPort builder = GatewayFactory.createGateway("gateway-1", "my-clust

This creates a local gateway adapter with a specific gateway name and cluster name, sets the base transport port, and selects transport protocol listeners (including L4 TCP proxy).

## Registering Nodes with NodeBuilder
### Registering Nodes with NodeBuilder

To support advanced telemetry, authorization, and custom health check paths, GateBridge provides a fluent `NodeBuilder` API:

```java
builder.registerNode("node-1", "http://localhost", 3005)
.pingProtocol(PingProtocol.HTTP)
.routingProtocol(RoutingProtocol.HTTP)
.telemetryOnly(false)
.pingPath("/healthz")
.pingHeader("Authorization", "Bearer token123")
.register();
```

* **`registerNode(name, host, port)`** — Specifies the node name, host target, and socket port.
* **`pingProtocol(PingProtocol)`** — Toggles and configures the active health check protocol (`HTTP`, `WEBSOCKET`, `TCP`, `UDP`, `GRPC`, or `NONE` for Push-only).
* **`routingProtocol(RoutingProtocol)`** — Selects the routing engine protocol for this node (`HTTP`, `TCP`, `GRPC`).
* **`telemetryOnly(boolean)`** — Flags the node as a passive telemetry-only node. If `true`, the node is ignored during reverse proxy load-balancing routing, but telemetry collection remains active.
* **`pingPath(path)`** — Changes the URI path for active health check requests.
* **`pingHeader(name, value)`** — Appends custom authentication headers to ping checks.

Expand All @@ -41,6 +45,18 @@ For simpler registrations, you can still register a node quickly:
builder.registerServer(3001, NodeStatus.OFFLINE);
```

## Ingress Route Rules

GateBridge supports Nginx-style virtual host and path-pattern Ingress routing rules to map incoming domain requests to target backend clusters:

```java
// Map requests with Host "app.local" and paths starting with "/api/" to "api-cluster"
RouteRule apiRule = new RouteRule("app.local", "/api/*", "api-cluster");
builder.addRouteRule(apiRule);
```

On incoming requests, GateBridge matches the `Host` header and the request URI path against registered `RouteRule` models. Precedence is given to local server API paths to prevent wildcard rules from shadowing core endpoints.

## State Persistence Layer

The gateway features automatic state persistence:
Expand Down Expand Up @@ -68,6 +84,12 @@ The optional `event` parameter dispatches a `ClusterEvent.NodeEventSubmitted` ev

Refer to [Ping Health-Check Contracts](ping-api-contract.md) for full details.

## API Versioning & Auth Bypass

Internal framework telemetry and management endpoints are exposed under the `/v1/` URI prefix (e.g., `/v1/clusters`).
- **Path Normalization**: Incoming request paths are automatically normalized to strip the `/v1/` prefix before routing.
- **Authentication Bypass**: In `TokenAuthFilter`, normalized public endpoints mapped under `/v1/` (such as ping endpoints or telemetry push hooks) bypass authentication verification if configured as public.

## Cluster Routing Modes

Clusters can be configured in one of three routing modes to explicitly define behavior:
Expand All @@ -85,19 +107,22 @@ cluster.setRoutingMode(Cluster.RoutingMode.HYBRID);

## Layer 7 HTTP Reverse Proxy Load-Balancer

When a cluster is in `LOAD_BALANCER_ONLY` or `HYBRID` mode, any incoming HTTP request targeting the REST server on path `/clusters/{clusterName}/{path}` will be proxied:
1. GateBridge selects an active node using thread-safe, overflow-safe Round-Robin.
When a cluster is in `LOAD_BALANCER_ONLY` or `HYBRID` mode, any incoming HTTP request targeting the REST server on path `/clusters/{clusterName}/{path}` (or matched via `RouteRule`) will be proxied:
1. GateBridge filters out `telemetryOnly` nodes and inactive offline nodes, then selects an active node using thread-safe, overflow-safe Round-Robin.
2. The request method, headers, and body are forwarded to the selected node's backend address.
3. The response body is streamed back using chunked transfer encoding (`Transfer-Encoding: chunked`) to prevent JVM heap OOMs.
4. Connection latency is measured passively, and CPU/RAM parameters are extracted from response headers (`X-Telemetry-CPU`, `X-Telemetry-RAM`) to update node state.
5. If target node connection fails, the client receives a `502 Bad Gateway` response.
3. **Traceability Headers**: Auto-injects `X-Forwarded-For` (appending client IP to any existing list), `X-Forwarded-Proto` (the scheme of the client connection), and `X-Forwarded-Host` (original target Host) headers.
4. The response body is streamed back using chunked transfer encoding (`Transfer-Encoding: chunked`) to prevent JVM heap OOMs.
5. Connection latency is measured passively, and CPU/RAM parameters are extracted from response headers (`X-Telemetry-CPU`, `X-Telemetry-RAM`) to update node state.
6. If target node connection fails, the client receives a `502 Bad Gateway` response.

## Layer 4 TCP Proxy Tunneling Load-Balancer

When enabled via `.enableTcpProxy(true)`, GateBridge starts a raw Layer 4 TCP proxy on `basePort + 3`:
* Spawns virtual threads (`ThreadManager.startVirtual`) to tunnel data bidirectionally between client and backend node.
* Uses Round-Robin node selection to distribute raw TCP streams.
* Handles TCP half-close sequences natively via output shutdown, preserving active tunnels while ensuring clean socket closure upon termination.
* Uses Round-Robin node selection, ignoring `telemetryOnly` nodes, to distribute raw TCP streams.
* **Socket Configurations**: Exposes `tcpSoTimeout(int)` and `tcpKeepAlive(boolean)` configurations on `GatewayBuilderPort` to govern proxy sockets.
* **Dual-Socket Teardown**: Ensures that a close or timeout on either side of the bidirectional tunnel immediately shuts down both sockets, releasing socket file descriptors instantly.
* **Bounded Buffer Pool**: Limits buffer allocation memory footprint using a bounded `BUFFER_POOL` capped at `MAX_POOL_SIZE = 512` cached `8KB` byte arrays, managed via thread-safe atomic size tracking.
* Connection latency is passively tracked and updated in the telemetry dashboard.

## Complete Bootstrap Example
Expand All @@ -110,14 +135,21 @@ GatewayBuilderPort builder = GatewayFactory.createGateway("gateway-1", "producti
.enableTelnet(true)
.enableHttp(true)
.enableWs(true)
.enableTcpProxy(true); // Enables L4 TCP Proxy load-balancing
.enableTcpProxy(true) // Enables L4 TCP Proxy load-balancing
.tcpSoTimeout(30000) // Sets socket timeout to 30s
.tcpKeepAlive(true); // Enables TCP keep-alive pings

// Configure routing mode
builder.getCluster().setRoutingMode(Cluster.RoutingMode.HYBRID);

// Define Ingress mapping rule
builder.addRouteRule(new RouteRule("service.company.internal", "/users/*", "production-cluster"));

// Register node with name, host, and port
builder.registerNode("node-a", "http://localhost", 3001)
.pingProtocol(PingProtocol.HTTP)
.routingProtocol(RoutingProtocol.HTTP)
.telemetryOnly(false)
.pingPath("/health")
.pingHeader("X-Token", "secret")
.register();
Expand Down
35 changes: 0 additions & 35 deletions docs/superpowers/plans/2026-07-21-apply-optimizations.md

This file was deleted.

46 changes: 0 additions & 46 deletions docs/superpowers/plans/2026-07-21-benchmark-infrastructure.md

This file was deleted.

40 changes: 0 additions & 40 deletions docs/superpowers/plans/2026-07-21-undertow-transport.md

This file was deleted.

9 changes: 9 additions & 0 deletions docs/terminal-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,19 @@ TerminalUiFactory.createTui("DevOps Control Plane")
.clusterManagementEnabled(true) // Enable [C] key to create clusters
.nodeManagementEnabled(true) // Enable [A]/[D] to register/deregister nodes
.nodeConfigurationEnabled(true) // Enable [Enter] config of ping routes & headers
.redirectSystemOut(false) // If true, redirects System.out/System.err to TUI log panel
.seedGateway(hexacloud) // Inject already started gateway instance
.startToggleMode(); // Start in non-blocking toggle/detachable mode
```

* **`readOnly(boolean)`** — Restricts write actions inside the console.
* **`gatewayManagementEnabled(boolean)`** — Enables manually starting/stopping gateway listeners.
* **`clusterManagementEnabled(boolean)`** — Enables cluster creation.
* **`nodeManagementEnabled(boolean)`** — Enables registering/deregistering service nodes.
* **`nodeConfigurationEnabled(boolean)`** — Enables updating route/ping header settings.
* **`redirectSystemOut(boolean)`** — Sets whether standard output (`System.out` and `System.err`) is hijacked and redirected to the dashboard's log pane (defaults to `false` for embedded library usage, `true` for standalone executables).
* **`seedGateway(RunningGatewayPort)`** — Seeds the console with a pre-configured gateway.

## Non-Blocking Detachable Mode (`startToggleMode`)

When calling `startToggleMode()`, the application runs the gateways in the background and prints standard framework logs to standard output.
Expand Down
28 changes: 20 additions & 8 deletions java/src-java8/hexacloud/core/cluster/event/ClusterEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,12 @@ public String toString() {
public static class NodeStatusChanged implements ClusterEvent {
private final String host;
private final NodeStatus status;
private final String nodeId;

public NodeStatusChanged(String host, NodeStatus status) {
public NodeStatusChanged(String host, NodeStatus status, String nodeId) {
this.host = host;
this.status = status;
this.nodeId = nodeId;
}

public String host() {
Expand All @@ -118,52 +120,62 @@ public NodeStatus status() {
return status;
}

public String nodeId() {
return nodeId;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeStatusChanged that = (NodeStatusChanged) o;
return Objects.equals(host, that.host) && status == that.status;
return Objects.equals(host, that.host) && status == that.status && Objects.equals(nodeId, that.nodeId);
}

@Override
public int hashCode() {
return Objects.hash(host, status);
return Objects.hash(host, status, nodeId);
}

@Override
public String toString() {
return "NodeStatusChanged[host=" + host + ", status=" + status + "]";
return "NodeStatusChanged[host=" + host + ", status=" + status + ", nodeId=" + nodeId + "]";
}
}

public static class NodeTelemetryUpdated implements ClusterEvent {
private final String host;
private final String nodeId;

public NodeTelemetryUpdated(String host) {
public NodeTelemetryUpdated(String host, String nodeId) {
this.host = host;
this.nodeId = nodeId;
}

public String host() {
return host;
}

public String nodeId() {
return nodeId;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeTelemetryUpdated that = (NodeTelemetryUpdated) o;
return Objects.equals(host, that.host);
return Objects.equals(host, that.host) && Objects.equals(nodeId, that.nodeId);
}

@Override
public int hashCode() {
return Objects.hash(host);
return Objects.hash(host, nodeId);
}

@Override
public String toString() {
return "NodeTelemetryUpdated[host=" + host + "]";
return "NodeTelemetryUpdated[host=" + host + ", nodeId=" + nodeId + "]";
}
}

Expand Down
2 changes: 1 addition & 1 deletion java/src-java8/hexacloud/infra/server/WsTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public void stop() {
}
closeAllClients();
threadPool.shutdownNow();
DebugUtils.log("WebSocket Transport stopped.");
DebugUtils.info("WebSocket Transport stopped.");
}

private void closeAllClients() {
Expand Down
3 changes: 2 additions & 1 deletion java/src/hexacloud/application/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public void start() {
// Launch the DevOps Panel in non-blocking toggle mode (detach/reattach with ENTER)
TerminalUiFactory.createTui("MyCompany - GateBridge DevOps Panel")
.seedGateway(runningGateway)
.redirectSystemOut(true)
.startToggleMode();
}

Expand Down Expand Up @@ -124,7 +125,7 @@ public void onNodeEventSubmitted(NodeEventSubmitted event) {

// Custom developer endpoint controller - automatically discovered by PathUtils scanner
public static class CustomAppController implements RouteController {
@RouteMapping("HELLO")
@RouteMapping("/hello")
public void sayHello(String args, PrintWriter out) {
out.println("HELLO FROM DEVELOPER ROUTE! Args: " + args);
}
Expand Down
Loading
Loading