main now uses signal.NotifyContext to cancel a root context on
SIGINT or SIGTERM. Both the public and debug listeners share that
context; on cancellation each calls srv.Shutdown with a 30s
timeout to drain in-flight requests before exiting.
If either listener fails the other is signalled to shut down too,
and the process exits 1. Bad CLI args now exit 2 (was 0).
Adds TestGracefulShutdown which fires a slow request, cancels the
context mid-flight, and asserts the request completes successfully
and the server returns nil.
Go 1.22+ ServeMux supports method matching, path wildcards, and
subtree patterns natively, making the bespoke router redundant.
- Delete http/router.go (-73 lines).
- Use 'METHOD /path' patterns and {port} wildcard for /port/.
- Content negotiation for / moved into rootHandler() dispatched
via 'GET /{$}' (exact-match) — Accept header / cliMatcher /
template availability decide which handler runs.
- muxNotFoundWrapper preserves content-negotiated 404 behaviour
by detecting empty pattern from mux.Handler(r) and routing to
NotFoundHandler.
- PortHandler now reads r.PathValue("port").
Net -41 lines, no external behaviour change. All tests pass.
Makefile:
- Add .PHONY declaration for all 16 phony targets.
- Fix parse-time $(error) at line 39: replace with runtime shell
check so the guard actually fires when the recipe runs.
- Simplify check-fmt to portable 'gofmt -l -s .' form.
- Add -trimpath -ldflags='-s -w' to install for smaller,
reproducible binaries.
Dockerfile:
- Switch runtime to distroless/static-debian13:nonroot + USER nonroot.
- Split COPY so go.mod/go.sum cache layer is independent of source.
- Drop ENV GO111MODULE=on (no-op since Go 1.16).
Country/City/ASN had ~11 'if x != "" { y = x }' wrappers around
fields whose targets were already zero-valued. Replaced with
unconditional assignment and cmp.Or for genuine fallback chains
(Country -> RegisteredCountry). Kept guards that prevent real
panics (nil pointer, NaN, slice index).
CLICountryHandler, CLICountryISOHandler, CLICityHandler,
CLICoordinatesHandler, CLIASNHandler, and CLIASNOrgHandler were
near-identical: call newResponse, return badRequest on error,
fmt.Fprintln a single field. Replaced with cliField(extract func)
and inline closures at route registration. -41 lines, no behavior
change.
Add /x and Go build artifacts to .gitignore. Replace the minimal
.dockerignore with an explicit deny list excluding .git, *.mmdb,
data/, vendor/, docs, and build artifacts to shrink build context.
Note: GeoLite2-*.mmdb files still exist in older history; run
git filter-repo separately to purge them per MaxMind EULA.
IsEmpty() ignored the ASN reader, so launching with only an ASN
DB silently disabled all geo routes including /asn. Reader gains
HasCountry/HasCity/HasASN; http.Handler mounts each set
independently.
Get() never promoted accessed entries, so eviction was insertion
order (FIFO), not LRU. Get() now MoveToBacks the element; cache
mu becomes sync.Mutex.
Resize() updated capacity but never evicted to fit the new size,
and incorrectly reset the evictions counter. It now evicts from
the front until len(entries) <= capacity and preserves the counter.
Also cleaned up the Set() eviction loop and added tests for LRU
promotion, immediate-shrink resize, and concurrent access.
The -P flag previously mounted /debug/pprof and /debug/cache on
the public :8080 listener. pprof.Profile pegs a CPU for 30s
(unauthenticated DoS) and /debug/cache/resize was world-writable.
- -P changes from bool to string addr (e.g. 127.0.0.1:6060).
When set, a second http.Server serves DebugHandler() on that addr.
- Public ListenAndServe now uses an explicit *http.Server with
ReadHeaderTimeout=5s, ReadTimeout=10s, WriteTimeout=15s,
IdleTimeout=60s to mitigate slowloris.
- http.New() no longer takes the profile bool.
BREAKING: -P now requires an address argument.
The /port/ endpoint dialed whatever IP the request resolved to,
which combined with trusted X-Forwarded-For made the service an
SSRF / port-scan primitive against loopback, RFC1918, link-local,
and cloud metadata addresses.
LookupPort now refuses invalid, loopback, private, link-local
unicast/multicast, unspecified, and multicast addresses.