s3api: report the bound addresses from the listen hook

`WithOnListen` tells an embedder when the S3 server is serving, but not
where: the callback takes no arguments, and the server reports the
addresses it bound nowhere else. An embedder that wants an ephemeral port
therefore cannot ask for port 0; it has to pick a free port itself,
release it, and pass it in, which loses to any other process that binds
the same port in between.

Add `WithOnListenAddrs`, which passes the callback the address of every
listener the server bound, in port-specification order, so an embedder can
serve on `127.0.0.1:0` and learn the port the kernel chose. `WithOnListen`
is unchanged. `MultiListener` gains `Addrs`, the every-listener counterpart
of `Addr`, to supply them.
This commit is contained in:
RaduBerinde
2026-09-08 06:52:37 -07:00
parent 7f0a793150
commit 49e0c36a0f
4 changed files with 119 additions and 3 deletions
+19 -3
View File
@@ -61,6 +61,7 @@ type S3ApiServer struct {
middlewares []middlewareMount
socketPerm os.FileMode
onListen func()
onListenAddrs func(addrs []net.Addr)
}
type routeMount struct {
@@ -355,6 +356,16 @@ func WithOnListen(fn func()) Option {
return func(s *S3ApiServer) { s.onListen = fn }
}
// WithOnListenAddrs is WithOnListen with the addresses the server bound
// passed to the callback: one per listener, in the order of the port
// specifications given to ServeMultiPort, with a specification that resolves
// to several addresses contributing one each. This is how a caller that asked
// for port 0 learns which port the kernel chose; the server does not report
// it anywhere else.
func WithOnListenAddrs(fn func(addrs []net.Addr)) Option {
return func(s *S3ApiServer) { s.onListenAddrs = fn }
}
// ServeMultiPort creates listeners for multiple port specifications and serves
// on all of them simultaneously. This supports listening on multiple ports and/or
// addresses (e.g., [":7070", "localhost:8080", "0.0.0.0:9090"]).
@@ -389,10 +400,15 @@ func (sa *S3ApiServer) ServeMultiPort(ports []string) error {
// Combine all listeners
finalListener := netutil.NewMultiListener(listeners...)
if sa.onListen != nil {
fn := sa.onListen
if sa.onListen != nil || sa.onListenAddrs != nil {
fn, fnAddrs := sa.onListen, sa.onListenAddrs
sa.app.Hooks().OnListen(func(fiber.ListenData) error {
fn()
if fn != nil {
fn()
}
if fnAddrs != nil {
fnAddrs(finalListener.Addrs())
}
return nil
})
}