diff --git a/app.go b/app.go index 85b78be..54a8455 100644 --- a/app.go +++ b/app.go @@ -6,20 +6,14 @@ import ( "html/template" "net" "net/http" - "os" - "os/signal" "sync" - "syscall" - "time" - reuseport "github.com/kavu/go_reuseport" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" + "github.com/moonrhythm/parapet" ) // App is the hime app type App struct { - srv http.Server + srv parapet.Server handler http.Handler routes Routes globals sync.Map @@ -30,12 +24,7 @@ type App struct { component map[string]*template.Template templateFuncs []template.FuncMap - gs *GracefulShutdown - tcpKeepAlive time.Duration - reusePort bool - ETag bool - H2C bool } type ctxKeyApp struct{} @@ -44,48 +33,39 @@ type ctxKeyApp struct{} func New() *App { app := &App{} app.srv.Handler = app - app.tcpKeepAlive = 3 * time.Minute - app.H2C = true return app } // Clone clones app func (app *App) Clone() *App { x := &App{ - srv: http.Server{ - Addr: app.srv.Addr, - ReadTimeout: app.srv.ReadTimeout, - ReadHeaderTimeout: app.srv.ReadHeaderTimeout, - WriteTimeout: app.srv.WriteTimeout, - IdleTimeout: app.srv.IdleTimeout, - MaxHeaderBytes: app.srv.MaxHeaderBytes, - TLSNextProto: cloneTLSNextProto(app.srv.TLSNextProto), - ConnState: app.srv.ConnState, - ErrorLog: app.srv.ErrorLog, + srv: parapet.Server{ + Addr: app.srv.Addr, + ReadTimeout: app.srv.ReadTimeout, + ReadHeaderTimeout: app.srv.ReadHeaderTimeout, + WriteTimeout: app.srv.WriteTimeout, + IdleTimeout: app.srv.IdleTimeout, + MaxHeaderBytes: app.srv.MaxHeaderBytes, + TCPKeepAlivePeriod: app.srv.TCPKeepAlivePeriod, + GraceTimeout: app.srv.GraceTimeout, + WaitBeforeShutdown: app.srv.WaitBeforeShutdown, + ErrorLog: app.srv.ErrorLog, + TrustProxy: app.srv.TrustProxy, + H2C: app.srv.H2C, + ReusePort: app.srv.ReusePort, + ConnState: app.srv.ConnState, + TLSConfig: app.srv.TLSConfig.Clone(), + BaseContext: app.srv.BaseContext, }, handler: app.handler, routes: cloneRoutes(app.routes), globals: cloneMap(&app.globals), template: cloneTmpl(app.template), templateFuncs: cloneFuncMaps(app.templateFuncs), - tcpKeepAlive: app.tcpKeepAlive, - reusePort: app.reusePort, ETag: app.ETag, - H2C: app.H2C, } x.srv.Handler = x - if app.srv.TLSConfig != nil { - x.srv.TLSConfig = app.srv.TLSConfig.Clone() - } - - if app.gs != nil { - x.gs = &GracefulShutdown{ - timeout: app.gs.timeout, - wait: app.gs.wait, - notiFns: app.gs.notiFns, - } - } return x } @@ -108,10 +88,6 @@ func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { app.serveHandler = http.DefaultServeMux } - if app.H2C { - app.serveHandler = h2c.NewHandler(app.serveHandler, &http2.Server{}) - } - app.serveHandler = app.ServeHandler(app.serveHandler) }) @@ -128,98 +104,22 @@ func (app *App) ServeHandler(h http.Handler) http.Handler { } // Server returns server inside app -func (app *App) Server() *http.Server { +func (app *App) Server() *parapet.Server { return &app.srv } // Shutdown shutdowns server -func (app *App) Shutdown(ctx context.Context) error { - if app.gs != nil { - for _, fn := range app.gs.notiFns { - go fn() - } - - if app.gs.wait > 0 { - time.Sleep(app.gs.wait) - } - - if app.gs.timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, app.gs.timeout) - defer cancel() - } - } - - return app.srv.Shutdown(ctx) -} - -// TCPKeepAlive sets tcp keep-alive interval when using app.ListenAndServe -func (app *App) TCPKeepAlive(d time.Duration) *App { - app.tcpKeepAlive = d - return app -} - -// ReusePort uses SO_REUSEPORT when create listener using app.ListenAndServe -func (app *App) ReusePort(enable bool) *App { - app.reusePort = enable - return app -} - -func (app *App) listenAndServe() (err error) { - addr := app.srv.Addr - if addr == "" { - addr = ":http" - } - - var ln net.Listener - if app.reusePort { - ln, err = reuseport.NewReusablePortListener("tcp", addr) - } else { - ln, err = net.Listen("tcp", addr) - } - if err != nil { - return - } - - if d := app.tcpKeepAlive; d > 0 { - ln = tcpKeepAliveListener{ln.(*net.TCPListener), d} - } - - return app.Serve(ln) +func (app *App) Shutdown() error { + return app.srv.Shutdown() } // ListenAndServe starts web server func (app *App) ListenAndServe() error { - if app.gs != nil { - // graceful shutdown - errChan := make(chan error) - - go func() { - if err := app.listenAndServe(); err != http.ErrServerClosed { - errChan <- err - } - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, syscall.SIGTERM) - - select { - case err := <-errChan: - return err - case <-stop: - return app.Shutdown(context.Background()) - } - } - - return app.listenAndServe() + return app.srv.ListenAndServe() } // Serve serves listener func (app *App) Serve(l net.Listener) error { - if app.srv.TLSConfig != nil { - return app.srv.ServeTLS(l, "", "") - } - return app.srv.Serve(l) } @@ -253,15 +153,6 @@ func (app *App) SelfSign(s SelfSign) *App { return app } -// GracefulShutdown changes server to graceful shutdown mode -func (app *App) GracefulShutdown() *GracefulShutdown { - if app.gs == nil { - app.gs = &GracefulShutdown{} - } - - return app.gs -} - func getApp(ctx context.Context) *App { app, ok := ctx.Value(ctxKeyApp{}).(*App) if !ok { diff --git a/app_test.go b/app_test.go index e3bd0e9..a9433b0 100644 --- a/app_test.go +++ b/app_test.go @@ -1,17 +1,14 @@ package hime import ( - "context" "crypto/tls" "html/template" - "net" "net/http" "net/http/httptest" "testing" "time" "github.com/stretchr/testify/assert" - "golang.org/x/net/http2" ) func TestApp(t *testing.T) { @@ -30,25 +27,6 @@ func TestApp(t *testing.T) { assert.Len(t, app.templateFuncs, 2) }) - t.Run("GracefulShutdown", func(t *testing.T) { - app := New() - assert.Nil(t, app.gs) - gs := app.GracefulShutdown() - assert.NotNil(t, app.gs) - assert.Equal(t, gs, app.gs) - - gs.Timeout(10 * time.Second) - assert.Equal(t, gs.timeout, 10*time.Second) - - gs.Wait(5 * time.Second) - assert.Equal(t, gs.wait, 5*time.Second) - - gs.Notify(func() {}) - gs.Notify(func() {}) - gs.Notify(func() {}) - assert.Len(t, gs.notiFns, 3) - }) - t.Run("Address", func(t *testing.T) { app := New() app.Address(":1234") @@ -66,7 +44,6 @@ func TestApp(t *testing.T) { "w": "x", }) app.srv.TLSConfig = Compatible() - app.GracefulShutdown() app2 := app.Clone() assert.NotNil(t, app2) @@ -76,13 +53,11 @@ func TestApp(t *testing.T) { }).Globals(Globals{ "q": "p", }) - app2.GracefulShutdown().Wait(10 * time.Second) assert.NotEqual(t, app, app2) assert.NotEqual(t, app.routes, app2.routes) assert.Equal(t, app.Global("q"), "z") assert.Equal(t, app2.Global("q"), "p") - assert.NotEqual(t, app.gs, app2.gs) assert.NotNil(t, app2.srv.TLSConfig) }) @@ -132,7 +107,7 @@ func TestApp(t *testing.T) { app.Address(":8081") go app.ListenAndServe() - defer app.Shutdown(context.Background()) + defer app.Shutdown() time.Sleep(time.Second) @@ -155,7 +130,7 @@ func TestApp(t *testing.T) { app.Address(":8082") go app.ListenAndServe() - defer app.Shutdown(context.Background()) + defer app.Shutdown() time.Sleep(time.Second) @@ -167,124 +142,6 @@ func TestApp(t *testing.T) { assert.True(t, called) }) - t.Run("ListenAndServe with graceful shutdown", func(t *testing.T) { - t.Parallel() - - app := New() - app.Handler(Handler(func(ctx *Context) error { - return ctx.String("Hello") - })) - app.Address(":8083") - - gs := app.GracefulShutdown() - gs.Wait(time.Second) - gs.Timeout(time.Second) - called := false - gs.Notify(func() { - called = true - }) - - go app.ListenAndServe() - time.Sleep(time.Second) - - http.Get("http://localhost:8083") - app.Shutdown(context.Background()) - time.Sleep(time.Second) - assert.True(t, called) - }) - - t.Run("ListenAndServe with graceful shutdown and tls", func(t *testing.T) { - t.Parallel() - - app := New() - app.TLS("testdata/server.crt", "testdata/server.key") - app.Handler(Handler(func(ctx *Context) error { - return ctx.String("Hello") - })) - app.Address(":8084") - - gs := app.GracefulShutdown() - gs.Wait(time.Second) - gs.Timeout(time.Second) - called := false - gs.Notify(func() { - called = true - }) - - go app.ListenAndServe() - time.Sleep(time.Second) - - (&http.Client{Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }}).Get("https://localhost:8084") - app.Shutdown(context.Background()) - time.Sleep(time.Second) - assert.True(t, called) - }) - - t.Run("H2C", func(t *testing.T) { - t.Parallel() - - app := New() - app.H2C = true - called := false - app.Handler(Handler(func(ctx *Context) error { - called = true - assert.Equal(t, "HTTP/2.0", ctx.Request.Proto) - return ctx.String("Hello") - })) - app.Address(":8085") - - go app.ListenAndServe() - defer app.Shutdown(context.Background()) - - time.Sleep(time.Second) - - client := http.Client{ - Transport: &http2.Transport{ - AllowHTTP: true, - DisableCompression: true, - DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { - return net.Dial(network, addr) - }, - }, - } - _, err := client.Get("http://localhost:8085") - assert.NoError(t, err) - assert.True(t, called) - }) - - t.Run("Disable H2C", func(t *testing.T) { - t.Parallel() - - app := New() - app.H2C = false - app.Handler(Handler(func(ctx *Context) error { - return ctx.String("Hello") - })) - app.Address(":8086") - - go app.ListenAndServe() - defer app.Shutdown(context.Background()) - - time.Sleep(time.Second) - - client := http.Client{ - Transport: &http2.Transport{ - AllowHTTP: true, - DisableCompression: true, - DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { - return net.Dial(network, addr) - }, - }, - } - _, err := client.Get("http://localhost:8086") - assert.Error(t, err) - - _, err = http.Get("http://localhost:8086") - assert.NoError(t, err) - }) - t.Run("ServeHandler", func(t *testing.T) { t.Parallel() diff --git a/apps.go b/apps.go index 6286827..0cfebdf 100644 --- a/apps.go +++ b/apps.go @@ -2,27 +2,13 @@ package hime import ( "context" - "io/ioutil" - "net/http" - "os" - "os/signal" - "syscall" - "time" "golang.org/x/sync/errgroup" - "gopkg.in/yaml.v3" ) // Apps is the collection of App to start together type Apps struct { list []*App - gs *GracefulShutdown -} - -// AppsConfig is the hime multiple apps config -type AppsConfig struct { - GracefulShutdown *GracefulShutdown `yaml:"gracefulShutdown" json:"gracefulShutdown"` - HTTPSRedirect *HTTPSRedirect `yaml:"httpsRedirect" json:"httpsRedirect"` } // Merge merges multiple *App into *Apps @@ -30,43 +16,6 @@ func Merge(apps ...*App) *Apps { return &Apps{list: apps} } -// Config merges config into apps config -func (apps *Apps) Config(config AppsConfig) *Apps { - if config.GracefulShutdown != nil { - apps.gs = config.GracefulShutdown - } - - if rd := config.HTTPSRedirect; rd != nil { - go func() { - err := StartHTTPSRedirectServer(rd.Addr) - if err != nil { - panicf("start https redirect server error; %v", err) - } - }() - } - - return nil -} - -// ParseConfig parses config data -func (apps *Apps) ParseConfig(data []byte) *Apps { - var config AppsConfig - err := yaml.Unmarshal(data, &config) - if err != nil { - panicf("can not parse config; %v", err) - } - return apps.Config(config) -} - -// ParseConfigFile parses config from file -func (apps *Apps) ParseConfigFile(filename string) *Apps { - data, err := ioutil.ReadFile(filename) - if err != nil { - panicf("can not read config from file; %v", err) - } - return apps.ParseConfig(data) -} - func (apps *Apps) listenAndServe() error { eg, ctx := errgroup.WithContext(context.Background()) @@ -75,82 +24,24 @@ func (apps *Apps) listenAndServe() error { } <-ctx.Done() - go apps.Shutdown(context.Background()) + go apps.Shutdown() return eg.Wait() } // ListenAndServe starts web servers func (apps *Apps) ListenAndServe() error { - if apps.gs != nil { - return apps.listenAndServeGracefully() - } - return apps.listenAndServe() } -// GracefulShutdown changes apps to graceful shutdown mode -func (apps *Apps) GracefulShutdown() *GracefulShutdown { - if apps.gs == nil { - apps.gs = &GracefulShutdown{} - } - - return apps.gs -} - // Shutdown shutdowns all apps -func (apps *Apps) Shutdown(ctx context.Context) error { - if apps.gs != nil { - for _, fn := range apps.gs.notiFns { - go fn() - } - for _, app := range apps.list { - if app.gs != nil { - for _, fn := range app.gs.notiFns { - go fn() - } - } - } - - if apps.gs.wait > 0 { - time.Sleep(apps.gs.wait) - } - - if apps.gs.timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, apps.gs.timeout) - defer cancel() - } - } - +func (apps *Apps) Shutdown() error { eg := errgroup.Group{} for _, app := range apps.list { app := app - eg.Go(func() error { return app.srv.Shutdown(ctx) }) + eg.Go(func() error { return app.srv.Shutdown() }) } return eg.Wait() } - -// ListenAndServe starts web servers in graceful shutdown mode -func (apps *Apps) listenAndServeGracefully() error { - errChan := make(chan error) - - go func() { - err := apps.listenAndServe() - if err != http.ErrServerClosed { - errChan <- err - } - }() - - stop := make(chan os.Signal, 1) - signal.Notify(stop, syscall.SIGTERM) - - select { - case err := <-errChan: - return err - case <-stop: - return apps.Shutdown(context.Background()) - } -} diff --git a/apps_test.go b/apps_test.go index 0e67cf4..0335195 100644 --- a/apps_test.go +++ b/apps_test.go @@ -1,7 +1,6 @@ package hime import ( - "context" "net/http" "testing" "time" @@ -10,69 +9,6 @@ import ( ) func TestApps(t *testing.T) { - t.Run("Config", func(t *testing.T) { - var gs GracefulShutdown - gs.Timeout(10 * time.Second) - gs.Wait(5 * time.Second) - - apps := Merge() - apps.Config(AppsConfig{ - GracefulShutdown: &gs, - }) - - assert.Equal(t, apps.gs, &gs) - }) - - t.Run("ParseConfig YAML", func(t *testing.T) { - apps := Merge() - apps.ParseConfig([]byte(` -gracefulShutdown: - timeout: 5s - wait: 10s`)) - assert.NotNil(t, apps.gs) - }) - - t.Run("ParseConfig invalid YAML", func(t *testing.T) { - apps := Merge() - - assert.Panics(t, func() { - apps.ParseConfig([]byte(` - gracefulShutdown: - timeout: 5s - wait: 10s`)) - }) - }) - - t.Run("ParseConfig JSON", func(t *testing.T) { - apps := Merge() - apps.ParseConfig([]byte(`{"gracefulShutdown": {"timeout":"5s","wait":"10s"}}`)) - assert.NotNil(t, apps.gs) - }) - - t.Run("ParseConfigFile", func(t *testing.T) { - apps := Merge() - - assert.NotPanics(t, func() { - apps.ParseConfigFile("testdata/apps.yaml") - }) - }) - - t.Run("ParseConfigFile not exists", func(t *testing.T) { - apps := Merge() - - assert.Panics(t, func() { - apps.ParseConfigFile("testdata/not-exists-apps.yaml") - }) - }) - - t.Run("GracefulShutdown", func(t *testing.T) { - apps := Merge() - - assert.Nil(t, apps.gs) - apps.GracefulShutdown() - assert.NotNil(t, apps.gs) - }) - t.Run("ListenAndServe", func(t *testing.T) { t.Parallel() @@ -92,54 +28,16 @@ gracefulShutdown: http.Get("http://localhost:9091") http.Get("http://localhost:9092") - apps.Shutdown(context.Background()) + apps.Shutdown() assert.Equal(t, called, 2) }) - t.Run("ListenAndServe graceful shutdown", func(t *testing.T) { - t.Parallel() - - h := Handler(func(ctx *Context) error { - return ctx.String("Hello") - }) - - var ( - called = false - app1Called = false - app2Called = false - ) - - app1 := New().Address(":9093").Handler(h) - app1.GracefulShutdown().Notify(func() { app1Called = true }) - app2 := New().Address(":9094").Handler(h) - app2.GracefulShutdown().Notify(func() { app2Called = true }) - apps := Merge(app1, app2) - gs := apps.GracefulShutdown() - gs.Wait(time.Second) - gs.Timeout(time.Second) - gs.Notify(func() { called = true }) - - go apps.ListenAndServe() - time.Sleep(time.Second) - - http.Get("http://localhost:9093") - http.Get("http://localhost:9094") - - apps.Shutdown(context.Background()) - time.Sleep(time.Second) - - assert.True(t, called) - assert.True(t, app1Called) - assert.True(t, app2Called) - }) - - t.Run("ListenAndServe graceful shutdown error duplicate port", func(t *testing.T) { + t.Run("ListenAndServe error duplicate port", func(t *testing.T) { t.Parallel() app1 := New().Address(":9095") app2 := New().Address(":9095") apps := Merge(app1, app2) - apps.GracefulShutdown() assert.Error(t, apps.ListenAndServe()) }) diff --git a/config.go b/config.go index 71ee944..e3bc83b 100644 --- a/config.go +++ b/config.go @@ -1,8 +1,7 @@ package hime import ( - "io/ioutil" - "time" + "os" "gopkg.in/yaml.v3" ) @@ -12,32 +11,6 @@ type AppConfig struct { Globals Globals `yaml:"globals" json:"globals"` Routes Routes `yaml:"routes" json:"routes"` Templates []TemplateConfig `yaml:"templates" json:"templates"` - Server struct { - Addr string `yaml:"addr" json:"addr"` - ReadTimeout string `yaml:"readTimeout" json:"readTimeout"` - ReadHeaderTimeout string `yaml:"readHeaderTimeout" json:"readHeaderTimeout"` - WriteTimeout string `yaml:"writeTimeout" json:"writeTimeout"` - IdleTimeout string `yaml:"idleTimeout" json:"idleTimeout"` - ReusePort *bool `yaml:"reusePort" json:"reusePort"` - TCPKeepAlive string `yaml:"tcpKeepAlive" json:"tcpKeepAlive"` - ETag *bool `yaml:"eTag" json:"eTag"` - H2C *bool `yaml:"h2c" json:"h2c"` - GracefulShutdown *GracefulShutdown `yaml:"gracefulShutdown" json:"gracefulShutdown"` - TLS *TLS `yaml:"tls" json:"tls"` - HTTPSRedirect *HTTPSRedirect `yaml:"httpsRedirect" json:"httpsRedirect"` - } `yaml:"server" json:"server"` -} - -func parseDuration(s string, t *time.Duration) { - if s == "" { - return - } - - var err error - *t, err = time.ParseDuration(s) - if err != nil { - panicf("can not parse duration; %v", err) - } } // Config merges config into app's config @@ -45,33 +18,27 @@ func parseDuration(s string, t *time.Duration) { // Example: // // globals: -// data1: test +// +// data1: test +// // routes: -// index: / -// about: /about +// +// index: / +// about: /about +// // templates: -// - dir: view -// root: layout -// delims: ["{{", "}}"] -// minify: true -// preload: +// - dir: view +// root: layout +// delims: ["{{", "}}"] +// minify: true +// preload: // - comp/comp1.tmpl // - comp/comp2.tmpl -// list: +// list: // main.tmpl: -// - main.tmpl -// - _layout.tmpl +// - main.tmpl +// - _layout.tmpl // about.tmpl: [about.tmpl, _layout.tmpl] -// server: -// readTimeout: 10s -// readHeaderTimeout: 5s -// writeTimeout: 5s -// idleTimeout: 30s -// eTag: true -// h2c: true -// gracefulShutdown: -// timeout: 1m -// wait: 5s func (app *App) Config(config AppConfig) *App { app.Globals(config.Globals) app.Routes(config.Routes) @@ -80,47 +47,6 @@ func (app *App) Config(config AppConfig) *App { app.Template().Config(cfg) } - { - // server config - server := config.Server - - if server.Addr != "" { - app.srv.Addr = server.Addr - } - parseDuration(server.ReadTimeout, &app.srv.ReadTimeout) - parseDuration(server.ReadHeaderTimeout, &app.srv.ReadHeaderTimeout) - parseDuration(server.WriteTimeout, &app.srv.WriteTimeout) - parseDuration(server.IdleTimeout, &app.srv.IdleTimeout) - parseDuration(server.TCPKeepAlive, &app.tcpKeepAlive) - - if server.ReusePort != nil { - app.reusePort = *server.ReusePort - } - if server.ETag != nil { - app.ETag = *server.ETag - } - if server.H2C != nil { - app.H2C = *server.H2C - } - - if t := server.TLS; t != nil { - app.srv.TLSConfig = server.TLS.config() - } - - if server.GracefulShutdown != nil { - app.gs = server.GracefulShutdown - } - - if rd := server.HTTPSRedirect; rd != nil { - go func() { - err := StartHTTPSRedirectServer(rd.Addr) - if err != nil { - panicf("start https redirect server error; %v", err) - } - }() - } - } - return app } @@ -136,7 +62,7 @@ func (app *App) ParseConfig(data []byte) *App { // ParseConfigFile parses config from file func (app *App) ParseConfigFile(filename string) *App { - data, err := ioutil.ReadFile(filename) + data, err := os.ReadFile(filename) if err != nil { panicf("can not read config from file; %v", err) } diff --git a/config_test.go b/config_test.go index c64c378..2b2e400 100644 --- a/config_test.go +++ b/config_test.go @@ -2,7 +2,6 @@ package hime import ( "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -28,23 +27,6 @@ func TestConfig(t *testing.T) { assert.Len(t, app.template, 2) assert.Contains(t, app.template, "main") assert.Contains(t, app.template, "main2") - - // server - assert.Equal(t, app.srv.Addr, ":8080") - assert.Equal(t, app.srv.ReadTimeout, 10*time.Second) - assert.Equal(t, app.srv.ReadHeaderTimeout, 5*time.Second) - assert.Equal(t, app.srv.WriteTimeout, 6*time.Second) - assert.Equal(t, app.srv.IdleTimeout, 30*time.Second) - assert.Equal(t, app.tcpKeepAlive, time.Minute) - assert.True(t, app.reusePort) - assert.True(t, app.ETag) - assert.True(t, app.H2C) - assert.Len(t, app.srv.TLSConfig.Certificates, 1) - - // graceful - assert.NotNil(t, app.gs) - assert.Equal(t, app.gs.timeout, time.Minute) - assert.Equal(t, app.gs.wait, 5*time.Second) }) }) @@ -55,36 +37,6 @@ func TestConfig(t *testing.T) { assert.Equal(t, mapLen(&app.globals), 0) assert.Len(t, app.routes, 0) assert.Len(t, app.template, 0) - - // server - assert.Empty(t, app.srv.ReadTimeout) - assert.Empty(t, app.srv.ReadHeaderTimeout) - assert.Empty(t, app.srv.WriteTimeout) - assert.Empty(t, app.srv.IdleTimeout) - - // graceful - assert.NotNil(t, app.gs) - assert.Empty(t, app.gs.timeout) - assert.Empty(t, app.gs.wait) - }) - }) - - t.Run("Config3", func(t *testing.T) { - assert.NotPanics(t, func() { - app := New().ParseConfigFile("testdata/config3.yaml") - - assert.Equal(t, mapLen(&app.globals), 0) - assert.Len(t, app.routes, 0) - assert.Len(t, app.template, 0) - - // server - assert.Empty(t, app.srv.ReadTimeout) - assert.Empty(t, app.srv.ReadHeaderTimeout) - assert.Empty(t, app.srv.WriteTimeout) - assert.Empty(t, app.srv.IdleTimeout) - - // graceful - assert.Nil(t, app.gs) }) }) diff --git a/go.mod b/go.mod index d7bb815..4a8722e 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,20 @@ module github.com/moonrhythm/hime go 1.20 require ( - github.com/kavu/go_reuseport v1.5.0 + github.com/moonrhythm/parapet v0.13.2 github.com/stretchr/testify v1.8.4 github.com/tdewolff/minify/v2 v2.12.9 - golang.org/x/net v0.15.0 golang.org/x/sync v0.3.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/kavu/go_reuseport v1.5.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tdewolff/parse/v2 v2.6.8 // indirect + golang.org/x/net v0.15.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect ) diff --git a/go.sum b/go.sum index 262b0a5..ac69c8c 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kavu/go_reuseport v1.5.0 h1:UNuiY2OblcqAtVDE8Gsg1kZz8zbBWg907sP1ceBV+bk= @@ -6,7 +5,8 @@ github.com/kavu/go_reuseport v1.5.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWo github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/moonrhythm/parapet v0.13.2 h1:1qXu46A6Vj0b+OfFS2CKq/Kk45FhEHu0pnlUhuqcOco= +github.com/moonrhythm/parapet v0.13.2/go.mod h1:J5aNi51WMsyiIZOrWrRVhE4vJrLEzS5tjUUAm8SIOis= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/graceful.go b/graceful.go deleted file mode 100644 index d8e2332..0000000 --- a/graceful.go +++ /dev/null @@ -1,72 +0,0 @@ -package hime - -import ( - "encoding/json" - "time" -) - -// GracefulShutdown is the graceful shutdown configure -type GracefulShutdown struct { - timeout time.Duration - wait time.Duration - notiFns []func() -} - -// Timeout sets shutdown timeout for graceful shutdown, -// set to 0 to disable timeout -// -// default is 0 -func (gs *GracefulShutdown) Timeout(d time.Duration) *GracefulShutdown { - gs.timeout = d - return gs -} - -// Wait sets wait time before shutdown -func (gs *GracefulShutdown) Wait(d time.Duration) *GracefulShutdown { - gs.wait = d - return gs -} - -// Notify calls fn when receive terminate signal from os -func (gs *GracefulShutdown) Notify(fn func()) *GracefulShutdown { - if fn != nil { - gs.notiFns = append(gs.notiFns, fn) - } - return gs -} - -type gracefulConfig struct { - Timeout string `yaml:"timeout" json:"timeout"` - Wait string `yaml:"wait" json:"wait"` -} - -func (x *gracefulConfig) store(gs *GracefulShutdown) { - parseDuration(x.Timeout, &gs.timeout) - parseDuration(x.Wait, &gs.wait) -} - -// UnmarshalYAML implements yaml.Unmarshaler -func (gs *GracefulShutdown) UnmarshalYAML(unmarshal func(any) error) error { - var x gracefulConfig - err := unmarshal(&x) - if err != nil { - return err - } - - x.store(gs) - - return nil -} - -// UnmarshalJSON implements json.Unmarshaler -func (gs *GracefulShutdown) UnmarshalJSON(b []byte) error { - var x gracefulConfig - err := json.Unmarshal(b, &x) - if err != nil { - return err - } - - x.store(gs) - - return nil -} diff --git a/template.go b/template.go index 293a47b..aef281a 100644 --- a/template.go +++ b/template.go @@ -4,7 +4,7 @@ import ( "html/template" "io" "io/fs" - "io/ioutil" + "os" "path" "strings" @@ -141,7 +141,7 @@ func (tp *Template) ParseConfig(data []byte) *Template { // ParseConfigFile parses template config from file func (tp *Template) ParseConfigFile(filename string) *Template { - data, err := ioutil.ReadFile(filename) + data, err := os.ReadFile(filename) if err != nil { panicf("read template config file; %v", err) } diff --git a/testdata/apps.yaml b/testdata/apps.yaml deleted file mode 100644 index 8e4c8ae..0000000 --- a/testdata/apps.yaml +++ /dev/null @@ -1,3 +0,0 @@ -gracefulShutdown: - wait: 5s - timeout: 10s \ No newline at end of file diff --git a/testdata/config1.yaml b/testdata/config1.yaml index 18f59db..4fba91e 100644 --- a/testdata/config1.yaml +++ b/testdata/config1.yaml @@ -16,19 +16,3 @@ templates: root: root list: main2: [main.tmpl, _layout.tmpl] -server: - addr: :8080 - readTimeout: 10s - readHeaderTimeout: 5s - writeTimeout: 6s - idleTimeout: 30s - reusePort: true - eTag: true - h2c: true - tcpKeepAlive: 1m - gracefulShutdown: - timeout: 1m - wait: 5s - tls: - selfSign: {} - profile: modern diff --git a/testdata/config2.yaml b/testdata/config2.yaml index 13a668a..e69de29 100644 --- a/testdata/config2.yaml +++ b/testdata/config2.yaml @@ -1,2 +0,0 @@ -server: - gracefulShutdown: {} \ No newline at end of file diff --git a/testdata/config3.yaml b/testdata/config3.yaml deleted file mode 100644 index 9f38a64..0000000 --- a/testdata/config3.yaml +++ /dev/null @@ -1 +0,0 @@ -server: diff --git a/testdata/invalid2.yaml b/testdata/invalid2.yaml index 138ae2a..77396b4 100644 --- a/testdata/invalid2.yaml +++ b/testdata/invalid2.yaml @@ -1,5 +1,6 @@ globals: - data1: test +- name: data1 + value: test routes: index: / about: /about @@ -10,7 +11,3 @@ templates: - comp/footer.tmpl list: main.tmpl: [main.tmpl, _layout.tmpl] -server: - gracefulShutdown: - timeout: abc - wait: 5s \ No newline at end of file