Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
9b6608418b
|
|||
|
1801754a9b
|
|||
|
5ab0120597
|
24
CHANGELOG.md
24
CHANGELOG.md
@@ -5,6 +5,28 @@ All notable changes to this module will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.2] - 2026-05-12
|
||||
|
||||
### Added
|
||||
|
||||
- `Config.ShutdownTimeout time.Duration` (`SERVER_SHUTDOWN_TIMEOUT`, default `10s`) — configures the graceful-shutdown deadline passed to `http.Server.Shutdown`. Previously hardcoded to 10 seconds; callers that serve long-running requests (uploads, streaming) or prefer a shorter fail-fast window can now set this per environment.
|
||||
|
||||
### Changed
|
||||
|
||||
- `launcher` and `logz` dependencies bumped from v0.9.0 to v1.0.1.
|
||||
- `go` directive updated from 1.25 to 1.26.
|
||||
|
||||
## [0.9.2] - 2026-03-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- `OnStart` now binds the TCP listener synchronously via `net.Listen` before
|
||||
launching the serve goroutine. A port-in-use (or any other bind) error is
|
||||
returned immediately from `OnStart`, allowing the launcher to treat it as a
|
||||
fatal startup failure and trigger a clean shutdown. Previously the error only
|
||||
appeared in a log line while the application continued running without an
|
||||
HTTP server.
|
||||
|
||||
## [0.9.1] - 2026-03-21
|
||||
|
||||
### Fixed
|
||||
@@ -33,5 +55,7 @@ and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.
|
||||
- No middleware is installed by default; the full middleware stack is composed explicitly via `WithMiddleware` at construction time, keeping the stack visible and ordering unambiguous in the application source
|
||||
- chi was chosen as the underlying router because it uses stdlib `http.Handler` throughout, making it fully compatible with `httpmw` middleware and `httputil` handler adapters without any wrapper code at the boundary
|
||||
|
||||
[1.0.2]: https://code.nochebuena.dev/go/httpserver/releases/tag/v1.0.2
|
||||
[0.9.2]: https://code.nochebuena.dev/go/httpserver/releases/tag/v0.9.2
|
||||
[0.9.1]: https://code.nochebuena.dev/go/httpserver/releases/tag/v0.9.1
|
||||
[0.9.0]: https://code.nochebuena.dev/go/httpserver/releases/tag/v0.9.0
|
||||
|
||||
6
go.mod
6
go.mod
@@ -1,10 +1,10 @@
|
||||
module code.nochebuena.dev/go/httpserver
|
||||
|
||||
go 1.25
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
code.nochebuena.dev/go/launcher v0.9.0
|
||||
code.nochebuena.dev/go/launcher v1.0.1
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
)
|
||||
|
||||
require code.nochebuena.dev/go/logz v0.9.0 // indirect
|
||||
require code.nochebuena.dev/go/logz v1.0.1 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,6 +1,6 @@
|
||||
code.nochebuena.dev/go/launcher v0.9.0 h1:dJHonA9Xm03AQKK0919FJaQn9ZKHZ+RZfB9yxjnx3TA=
|
||||
code.nochebuena.dev/go/launcher v0.9.0/go.mod h1:IBtntmbnyddukjEhxlc7Ysdzz9nZsnd9+8FzAIHt77g=
|
||||
code.nochebuena.dev/go/logz v0.9.0 h1:wfV7vtI4V/8ED7Hm31Fbql7Y5iOGrlHN4X8Z5ajTZZE=
|
||||
code.nochebuena.dev/go/logz v0.9.0/go.mod h1:qODhSbKb+tWE7rdhHLcKweiP5CgwIaWoZxadCT3bQV8=
|
||||
code.nochebuena.dev/go/launcher v1.0.1 h1:hbPV8jNtyxfchrT7igzz3M2tKGI3bm8uWkHBXRvSPgg=
|
||||
code.nochebuena.dev/go/launcher v1.0.1/go.mod h1:1KwndVuqm31JN9Dpl9YvOmlogPlKKzoDMo9aRFkYwmM=
|
||||
code.nochebuena.dev/go/logz v1.0.1 h1:kK9aZo19L208CwCr2D/dbSOMaOv62cXsigMSsdFu+8Y=
|
||||
code.nochebuena.dev/go/logz v1.0.1/go.mod h1:YNpNm03fURm2v0ySh/477z9AJhtfRcd9rFOW6fFqgNM=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -25,6 +26,7 @@ type Config struct {
|
||||
ReadTimeout time.Duration `env:"SERVER_READ_TIMEOUT" envDefault:"5s"`
|
||||
WriteTimeout time.Duration `env:"SERVER_WRITE_TIMEOUT" envDefault:"10s"`
|
||||
IdleTimeout time.Duration `env:"SERVER_IDLE_TIMEOUT" envDefault:"120s"`
|
||||
ShutdownTimeout time.Duration `env:"SERVER_SHUTDOWN_TIMEOUT" envDefault:"10s"`
|
||||
}
|
||||
|
||||
// serverOpts holds functional options.
|
||||
@@ -80,7 +82,10 @@ func (s *httpServer) OnInit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnStart starts the HTTP server in a background goroutine.
|
||||
// OnStart binds the TCP listener synchronously so that a port conflict returns
|
||||
// an error immediately — allowing the launcher to trigger a clean shutdown
|
||||
// instead of running silently without an HTTP server. The accepted connections
|
||||
// are then served in a background goroutine.
|
||||
func (s *httpServer) OnStart() error {
|
||||
host := s.cfg.Host
|
||||
if host == "" {
|
||||
@@ -90,16 +95,23 @@ func (s *httpServer) OnStart() error {
|
||||
if port == 0 {
|
||||
port = 8080
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpserver: bind %s: %w", addr, err)
|
||||
}
|
||||
|
||||
s.srv = &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||
Addr: addr,
|
||||
Handler: s.Router,
|
||||
ReadTimeout: s.cfg.ReadTimeout,
|
||||
WriteTimeout: s.cfg.WriteTimeout,
|
||||
IdleTimeout: s.cfg.IdleTimeout,
|
||||
}
|
||||
s.logger.Info("httpserver: starting", "addr", s.srv.Addr)
|
||||
s.logger.Info("httpserver: starting", "addr", addr)
|
||||
go func() {
|
||||
if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
if err := s.srv.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.logger.Error("httpserver: fatal error", err)
|
||||
}
|
||||
}()
|
||||
@@ -114,7 +126,11 @@ func (s *httpServer) OnStop() error {
|
||||
return nil
|
||||
}
|
||||
s.logger.Info("httpserver: shutting down gracefully")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
timeout := s.cfg.ShutdownTimeout
|
||||
if timeout == 0 {
|
||||
timeout = 10 * time.Second
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
return s.srv.Shutdown(ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user