Skip to content

Commit 1777898

Browse files
committed
docs: Add bind address design spec
1 parent 438b247 commit 1777898

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Configurable bind address
2+
3+
Date: 2026-05-20
4+
Branch: `fix/support-loopback`
5+
6+
## Problem
7+
8+
`OpenApiServer` binds via `new InetSocketAddress(port)` (`OpenApiServer.java:87`), which always listens on the wildcard address (all local interfaces). Callers that want to restrict the server to the loopback interface — common for local development, sidecar/companion processes, or tests — have no way to do so.
9+
10+
## Goal
11+
12+
Let callers choose the bind interface. Default behavior stays unchanged (wildcard).
13+
14+
Non-goals: dual-stack tuning, SO_REUSEADDR exposure, multiple bind addresses, hostname strings.
15+
16+
## API
17+
18+
Add one optional builder method:
19+
20+
```java
21+
public Builder bindAddress(InetAddress bindAddress) {
22+
this.bindAddress = bindAddress; // null allowed -> wildcard
23+
return this;
24+
}
25+
```
26+
27+
Typed `InetAddress` (not `String`) — no parsing, no ambiguity, and the standard library already provides the relevant factories:
28+
29+
```java
30+
OpenApiServer.builder()
31+
.spec(spec)
32+
.handlers(handlers)
33+
.port(8080)
34+
.bindAddress(InetAddress.getLoopbackAddress())
35+
.build();
36+
```
37+
38+
Unset (or explicitly `null`) preserves the current wildcard behavior — no source or behavioural change for existing callers.
39+
40+
## Implementation
41+
42+
`OpenApiServer.Builder` gains a private `InetAddress bindAddress` field, threaded through `build()` into the package-private constructor as a new parameter alongside `port` and `shutdownTimeoutSeconds`.
43+
44+
In the constructor, replace line 87:
45+
46+
```java
47+
InetSocketAddress socketAddress = (bindAddress == null)
48+
? new InetSocketAddress(port)
49+
: new InetSocketAddress(bindAddress, port);
50+
this.httpServer = HttpServer.create(socketAddress, 0);
51+
```
52+
53+
`bindAddress` is a network-binding concern; it stays out of `HandlerConfig` and sits directly on the constructor signature next to `port`.
54+
55+
### Startup log
56+
57+
Extend the existing line 119 log so the bound host is visible — helpful when verifying that a loopback restriction took effect:
58+
59+
```
60+
Server started ({}:{}) in {}ms
61+
```
62+
63+
Format using `httpServer.getAddress().getHostString()` and `.getPort()`. The existing `(port {})` form becomes `(host:port)` consistently for all callers.
64+
65+
## Testing
66+
67+
Add to the existing `OpenApiServerTest` (unit) suite:
68+
69+
1. **Loopback binding works** — build with `bindAddress(InetAddress.getLoopbackAddress())`, issue a request against `127.0.0.1:<listenPort>`, assert 2xx.
70+
2. **Default is wildcard** — build without calling `bindAddress(...)`; assert `httpServer.getAddress().getAddress().isAnyLocalAddress()` is `true`. (Access via a small package-private accessor or by reading `listenPort()`-style getter on the bound address — pick whichever fits the existing test conventions; do not add public API just for tests.)
71+
3. **Explicit null behaves as unset**`bindAddress(null)` round-trips to wildcard.
72+
73+
No new integration tests; the change is a single line inside `HttpServer.create(...)` and is fully covered by unit tests.
74+
75+
## Documentation
76+
77+
README: add a short bullet under "Getting Started" / configuration showing the loopback example. One snippet, no extended discussion.
78+
79+
## Risk and rollback
80+
81+
Pure additive API. Default path is byte-identical to before (same `new InetSocketAddress(port)` call). Rollback is reverting the commit.

0 commit comments

Comments
 (0)