|
2 | 2 |
|
3 | 3 | # JavaHttpClient |
4 | 4 |
|
5 | | -Spring based rest service to test http routes in kubernetes. |
6 | | -Showing http status, response, timing and istio envy settings. |
| 5 | +A Spring Boot REST service for testing and diagnosing HTTP routes in Kubernetes clusters with Istio service mesh. It acts as a configurable HTTP proxy that forwards requests to any target URL and returns the response along with timing, status and header information. Additionally it exposes Kubernetes and Istio diagnostic endpoints to inspect the service mesh configuration from within the cluster. |
7 | 6 |
|
8 | | -## Web ui |
| 7 | +## Features |
| 8 | + |
| 9 | +- **HTTP proxy** — forward GET, POST, PUT, DELETE, PATCH, HEAD and OPTIONS requests to any URL, with optional header forwarding and custom headers |
| 10 | +- **Kubernetes context** — read namespace, pod name and Istio sidecar status directly from the Kubernetes API |
| 11 | +- **Istio diagnostics** — list VirtualServices, DestinationRules, Gateways, ServiceEntries, Sidecars, EnvoyFilters, PeerAuthentications, RequestAuthentications and AuthorizationPolicies |
| 12 | +- **Istio full report** — fetch all Envoy configurations and error statistics of the Istio sidecar |
| 13 | +- **URL correlation** — check whether a given URL is covered by a VirtualService and which routes/DestinationRules apply |
| 14 | +- **TLS inspection** — open a direct TLS connection to a target and return protocol, cipher suite, certificate chain and SPIFFE/mTLS information |
| 15 | +- **Web UI** — browser-based frontend to interact with all endpoints |
| 16 | +- **OpenAPI/Swagger** — fully documented REST API with SpringDoc |
| 17 | + |
| 18 | +## Tech Stack |
| 19 | + |
| 20 | +| Component | Version | |
| 21 | +| --- | --- | |
| 22 | +| Java | 25 | |
| 23 | +| Spring Boot | 4.0.6 | |
| 24 | +| Servlet Container | Jetty (Tomcat excluded) | |
| 25 | +| OpenAPI | SpringDoc 3.0.3 | |
| 26 | +| Kubernetes Client | client-java 26.0.0 | |
| 27 | +| Observability | Micrometer Tracing + Actuator | |
| 28 | + |
| 29 | +## Screenshots |
| 30 | + |
| 31 | +### Web UI |
9 | 32 |
|
10 | 33 |  |
11 | 34 |
|
12 | | -## Istio tab |
| 35 | +### Istio Tab |
13 | 36 |
|
14 | 37 |  |
15 | 38 |
|
16 | | -## Swagger |
| 39 | +### Swagger UI |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | +### Helm Chart |
17 | 44 |
|
18 | | - |
| 45 | + |
19 | 46 |
|
20 | | -# Build |
| 47 | +--- |
| 48 | + |
| 49 | +## Build |
21 | 50 |
|
22 | 51 | ```bash |
23 | 52 | mvn package |
24 | 53 | ``` |
25 | 54 |
|
26 | | -# Docker build |
| 55 | +### Generate OpenAPI spec (`openapi.json`) |
| 56 | + |
| 57 | +`mvn verify` starts the application as part of the integration test phase, calls `/api-docs`, writes the result to `target/openapi.json` and stops the application again. The file at the project root is the committed result of the last run. |
27 | 58 |
|
28 | 59 | ```bash |
29 | | -docker build -t wlanboy/javahttpclient:latest . |
| 60 | +mvn verify |
| 61 | +cp target/openapi.json openapi.json |
30 | 62 | ``` |
31 | 63 |
|
32 | | -# Docker build with jlink and without |
| 64 | +--- |
| 65 | + |
| 66 | +## Docker |
| 67 | + |
| 68 | +### Standard image (UBI9 + OpenJDK 25 JRE) |
| 69 | + |
| 70 | +Uses the Red Hat UBI9 base image with the full OpenJDK 25 JRE. Includes Spring Boot AOT processing and CDS (Class Data Sharing) archive for faster startup. |
33 | 71 |
|
34 | 72 | ```bash |
35 | 73 | docker build -f Dockerfile25 -t javahttpclient:jre . |
| 74 | +``` |
| 75 | + |
| 76 | +### Minimal image (jlink custom JRE + distroless) |
| 77 | + |
| 78 | +Uses `jlink` to create a custom JRE containing only the required modules, then packages it into a `gcr.io/distroless/cc` image. Includes AOT processing and a CDS archive built with the custom JRE. |
| 79 | + |
| 80 | +```bash |
36 | 81 | docker build -f Dockerfile25Jlink -t javahttpclient:jlink . |
| 82 | +``` |
37 | 83 |
|
38 | | -docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | grep "javahttpclient" |
| 84 | +### Image size comparison |
| 85 | + |
| 86 | +```text |
39 | 87 | javahttpclient jre 510MB |
40 | 88 | javahttpclient jlink 295MB |
41 | 89 | ``` |
42 | 90 |
|
43 | | -# Run container |
| 91 | +### Run |
44 | 92 |
|
45 | 93 | ```bash |
46 | 94 | docker run --rm --name httpclient --publish 8080:8080 javahttpclient:jre |
47 | 95 |
|
48 | 96 | docker run --rm --name httpclient --publish 8080:8080 javahttpclient:jlink |
49 | 97 | ``` |
50 | 98 |
|
51 | | -# Docker hub |
| 99 | +### Multi-arch build |
| 100 | + |
| 101 | +```bash |
| 102 | +bash multiarch-build.sh |
| 103 | +``` |
| 104 | + |
| 105 | +### Docker Hub |
| 106 | + |
| 107 | +- <https://hub.docker.com/r/wlanboy/javahttpclient> |
| 108 | + |
| 109 | +```bash |
| 110 | +docker build -t wlanboy/javahttpclient:latest . |
| 111 | +``` |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +## API Reference |
| 116 | + |
| 117 | +The full OpenAPI 3.1 specification is available in [openapi.json](./openapi.json). |
| 118 | + |
| 119 | +Interactive Swagger UI: <http://localhost:8080/swagger-ui/index.html> |
| 120 | + |
| 121 | +### `POST /client` — Forward an HTTP request |
| 122 | + |
| 123 | +Forwards an HTTP request with the specified method, URL and optional body to the target system and returns the response. |
| 124 | + |
| 125 | +**Request body** (`application/json`): |
| 126 | + |
| 127 | +| Field | Type | Required | Description | |
| 128 | +| --- | --- | --- | --- | |
| 129 | +| `url` | string | yes | Target URL — must start with `http://` or `https://` | |
| 130 | +| `method` | string | yes | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS` | |
| 131 | +| `body` | string | no | Optional request body | |
| 132 | +| `contentType` | string | no | Content-Type of the body, e.g. `application/json` | |
| 133 | +| `copyHeaders` | boolean | no | Copy incoming request headers to the forwarded request | |
| 134 | +| `customHeaders` | object | no | Additional headers to add, as `{"Header-Name": "value"}` | |
| 135 | + |
| 136 | +**Responses:** `200` response from target, `400` validation error, `502` connection error. |
| 137 | + |
| 138 | +#### Examples |
| 139 | + |
| 140 | +Simple GET request: |
| 141 | + |
| 142 | +```bash |
| 143 | +curl -X POST http://localhost:8080/client \ |
| 144 | + -H 'Content-Type: application/json' \ |
| 145 | + -d '{"url": "https://github.com", "method": "GET", "copyHeaders": false}' |
| 146 | +``` |
| 147 | + |
| 148 | +POST with body and custom content type: |
| 149 | + |
| 150 | +```bash |
| 151 | +curl -X POST http://localhost:8080/client \ |
| 152 | + -H 'Content-Type: application/json' \ |
| 153 | + -d '{ |
| 154 | + "url": "https://httpbin.org/post", |
| 155 | + "method": "POST", |
| 156 | + "body": "{\"hello\": \"world\"}", |
| 157 | + "contentType": "application/json", |
| 158 | + "copyHeaders": false |
| 159 | + }' |
| 160 | +``` |
| 161 | + |
| 162 | +Forward incoming headers and add a custom header: |
| 163 | + |
| 164 | +```bash |
| 165 | +curl -X POST http://localhost:8080/client \ |
| 166 | + -H 'Content-Type: application/json' \ |
| 167 | + -H 'X-Correlation-Id: abc-123' \ |
| 168 | + -d '{ |
| 169 | + "url": "http://my-service.default.svc.cluster.local/api/v1/data", |
| 170 | + "method": "GET", |
| 171 | + "copyHeaders": true, |
| 172 | + "customHeaders": {"Authorization": "Bearer mytoken"} |
| 173 | + }' |
| 174 | +``` |
| 175 | + |
| 176 | +DNS resolution via a MirrorService: |
| 177 | + |
| 178 | +```bash |
| 179 | +curl -X POST http://localhost:8080/client \ |
| 180 | + -H 'Content-Type: application/json' \ |
| 181 | + -d '{ |
| 182 | + "url": "http://gmk:8003/resolve/google.com", |
| 183 | + "method": "GET", |
| 184 | + "copyHeaders": false |
| 185 | + }' |
| 186 | +``` |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +### `GET /api/k8s/context` — Kubernetes context |
| 191 | + |
| 192 | +Returns the current namespace, pod name and Istio sidecar status. Used by the web UI navbar on page load. |
| 193 | + |
| 194 | +```bash |
| 195 | +curl http://localhost:8080/api/k8s/context |
| 196 | +``` |
| 197 | + |
| 198 | +--- |
| 199 | + |
| 200 | +### `GET /api/k8s/status` — Kubernetes client status |
| 201 | + |
| 202 | +Shows whether the Kubernetes API client was initialized successfully and lists supported Istio resource types and API versions. |
| 203 | + |
| 204 | +```bash |
| 205 | +curl http://localhost:8080/api/k8s/status |
| 206 | +``` |
| 207 | + |
| 208 | +--- |
| 209 | + |
| 210 | +### `GET /api/k8s/istio/{type}` — List Istio resources |
| 211 | + |
| 212 | +Lists specific Istio resources in a namespace. The `namespace` query parameter defaults to `default`. |
| 213 | + |
| 214 | +Supported types: `virtualservices`, `destinationrules`, `gateways`, `serviceentries`, `sidecars`, `envoyfilters`, `peerauthentications`, `requestauthentications`, `authorizationpolicies` |
| 215 | + |
| 216 | +```bash |
| 217 | +# List all VirtualServices in the default namespace |
| 218 | +curl "http://localhost:8080/api/k8s/istio/virtualservices" |
| 219 | + |
| 220 | +# List DestinationRules in a specific namespace |
| 221 | +curl "http://localhost:8080/api/k8s/istio/destinationrules?namespace=production" |
| 222 | + |
| 223 | +# List PeerAuthentications (mTLS policies) |
| 224 | +curl "http://localhost:8080/api/k8s/istio/peerauthentications?namespace=istio-system" |
| 225 | +``` |
| 226 | + |
| 227 | +--- |
| 228 | + |
| 229 | +### `GET /api/k8s/istio/full-report` — Istio full report |
| 230 | + |
| 231 | +Fetches all Envoy configurations and error statistics of the Istio sidecar. Returns `200` even on error — the body contains the error description in that case. |
| 232 | + |
| 233 | +```bash |
| 234 | +curl http://localhost:8080/api/k8s/istio/full-report |
| 235 | +``` |
| 236 | + |
| 237 | +--- |
52 | 238 |
|
53 | | -* https://hub.docker.com/r/wlanboy/javahttpclient |
| 239 | +### `GET /api/k8s/correlate` — Correlate URL with Istio routing |
54 | 240 |
|
55 | | -# Test java http client |
| 241 | +Checks whether the given URL is covered by a VirtualService and which routes and DestinationRules apply. |
56 | 242 |
|
57 | 243 | ```bash |
58 | | -curl -L -X POST 'http://127.0.0.1:8080/client' -H 'Content-Type: application/json' \ |
59 | | --d '{"url" : "https://github.com", "copyHeaders": "false"}' |
| 244 | +curl "http://localhost:8080/api/k8s/correlate?url=http://my-service.default.svc.cluster.local/api/v1&namespace=default" |
60 | 245 | ``` |
61 | 246 |
|
62 | | -# local dev |
| 247 | +--- |
| 248 | + |
| 249 | +### `GET /api/k8s/tls` — Inspect TLS certificate |
| 250 | + |
| 251 | +Opens a direct TLS connection to the target URL and returns protocol, cipher suite, the full certificate chain and SPIFFE/mTLS information. The URL must use `https://`. |
63 | 252 |
|
64 | 253 | ```bash |
| 254 | +curl "http://localhost:8080/api/k8s/tls?url=https://example.com" |
| 255 | + |
| 256 | +# Inspect mTLS within the mesh |
| 257 | +curl "http://localhost:8080/api/k8s/tls?url=https://my-service.default.svc.cluster.local" |
| 258 | +``` |
| 259 | + |
| 260 | +--- |
| 261 | + |
| 262 | +## Local development with mirrord |
| 263 | + |
| 264 | +[mirrord](https://mirrord.dev) lets you run the application locally while it intercepts traffic from a pod running in the cluster — useful for debugging without a full cluster deployment. |
| 265 | + |
| 266 | +```bash |
| 267 | +# Install mirrord |
65 | 268 | curl -fsSL https://raw.githubusercontent.com/metalbear-co/mirrord/main/scripts/install.sh | bash |
66 | 269 |
|
| 270 | +# Look up the target pod |
67 | 271 | POD=$(kubectl get pod -n javahttpclient -l app=javahttpclient -o jsonpath='{.items[0].metadata.name}') |
68 | 272 |
|
69 | | -mirrord exec -n javahttpclient --target deployment/javahttpclient -- mvn spring-boot:run |
| 273 | +# Run with Maven (hot reload) |
| 274 | +mirrord exec -n javahttpclient --target deployment/javahttpclient -- mvn spring-boot:run |
70 | 275 |
|
| 276 | +# Run the packaged JAR against a specific pod |
71 | 277 | mirrord exec -n javahttpclient --target pod/$POD -- java -jar target/javahttpclient-0.0.1-SNAPSHOT.jar |
72 | 278 |
|
| 279 | +# Run the packaged JAR against the deployment |
73 | 280 | mirrord exec -n javahttpclient --target deployment/javahttpclient -- java -jar target/javahttpclient-0.0.1-SNAPSHOT.jar |
74 | 281 | ``` |
75 | 282 |
|
76 | | -# swagger uri |
| 283 | +--- |
77 | 284 |
|
78 | | -* http://localhost:8080/swagger-ui/index.html#/http-client-controller/postMapping |
| 285 | +## Kubernetes / Helm deployment |
79 | 286 |
|
80 | | -# curl calls for mirrorservice |
81 | | - |
82 | | -* see: https://github.com/wlanboy/MirrorService |
| 287 | +The chart is located in [javahttpclient-chart/](./javahttpclient-chart/). It includes Deployment, Service, ServiceAccount, RBAC, ConfigMap, Certificate, Gateway and VirtualService templates. |
83 | 288 |
|
84 | 289 | ```bash |
85 | | -curl -X 'POST' \ |
86 | | - 'http://localhost:8080/client' \ |
87 | | - -H 'accept: */*' \ |
88 | | - -H 'Content-Type: application/json' \ |
89 | | - -d '{ |
90 | | - "url": "http://gmk:8003/resolve/google.com", |
91 | | - "method": "GET", |
92 | | - "body": "", |
93 | | - "copyHeaders": false |
94 | | -}' |
95 | | - |
96 | | -curl -X 'POST' \ |
97 | | - 'http://localhost:8080/client' \ |
98 | | - -H 'accept: */*' \ |
99 | | - -H 'Content-Type: application/json' \ |
100 | | - -d '{ |
101 | | - "url": "http://gmk:8003/mirror?request=HalloWelt&statuscode=200&wait=4", |
102 | | - "method": "GET", |
103 | | - "body": "", |
104 | | - "copyHeaders": true |
105 | | -}' |
| 290 | +# Install |
| 291 | +helm install httpclient ./javahttpclient-chart -n clients --create-namespace |
| 292 | + |
| 293 | +# Verify |
| 294 | +kubectl get gateway,virtualservice -n clients |
| 295 | +kubectl get secret httpclient-tls -n istio-ingress |
| 296 | + |
| 297 | +# Upgrade |
| 298 | +helm upgrade httpclient ./javahttpclient-chart -n clients |
| 299 | + |
| 300 | +# Uninstall |
| 301 | +helm uninstall httpclient -n clients |
106 | 302 | ``` |
| 303 | + |
| 304 | +--- |
| 305 | + |
| 306 | +## Related projects |
| 307 | + |
| 308 | +- [MirrorService](https://github.com/wlanboy/MirrorService) — echo/mirror service for testing HTTP routing and response simulation |
0 commit comments