package eghttp import ( "context" "errors" "net" "net/http" "strconv" "time" "git.blauwelle.com/go/crate/log" ) const ( DefaultAddr = ":8080" DefaultReadHeaderTimeout = time.Second ) type Option interface { apply(cfg *config) } func WithPort(port int) Option { return optionFunc(func(cfg *config) { cfg.server.Addr = net.JoinHostPort("", strconv.Itoa(port)) }) } func WithHandler(handler http.Handler) Option { return optionFunc(func(cfg *config) { cfg.server.Handler = handler }) } // WithServer 指定要启动的服务器. 注意会替换原有的服务器, 导致早前配置服务器相关的 [Option] 失效. func WithServer(server *http.Server) Option { return optionFunc(func(cfg *config) { cfg.server = server }) } func WithStartFn(fn func(server *http.Server) error) Option { return optionFunc(func(cfg *config) { cfg.startFn = fn }) } // WithServerOption 使用 fn 配置 [http.Server]; func WithServerOption(fn func(server *http.Server)) Option { return optionFunc(func(cfg *config) { fn(cfg.server) }) } // WithShutdownTimeout 设置关闭服务的超时时间. <0 没有最长等待时间. func WithShutdownTimeout(timeout time.Duration) Option { return optionFunc(func(cfg *config) { cfg.shutdownTimeout = timeout }) } type config struct { server *http.Server startFn func(server *http.Server) error shutdownTimeout time.Duration } func newDefaultConfig() *config { return &config{ server: &http.Server{ Addr: DefaultAddr, ReadHeaderTimeout: DefaultReadHeaderTimeout, }, startFn: func(server *http.Server) error { return server.ListenAndServe() }, shutdownTimeout: -1, } } type optionFunc func(cfg *config) func (fn optionFunc) apply(cfg *config) { fn(cfg) } // ListenAndServe 构造1个函数, 启动 [http.Server] 并受 ctx 控制终止. func ListenAndServe(opts ...Option) func(ctx context.Context) error { cfg := newDefaultConfig() for _, opt := range opts { opt.apply(cfg) } errChan := make(chan error, 2) //nolint:gomnd return func(ctx context.Context) error { go func() { err := cfg.startFn(cfg.server) log.Tracef(ctx, "server return with %s", err) if !errors.Is(err, http.ErrServerClosed) { errChan <- err } }() go func() { <-ctx.Done() shutdownCtx := context.Background() if cfg.shutdownTimeout >= 0 { var cancel func() shutdownCtx, cancel = context.WithTimeout(shutdownCtx, cfg.shutdownTimeout) defer cancel() } errChan <- cfg.server.Shutdown(shutdownCtx) }() return <-errChan } }