diff --git a/README.md b/README.md index 42315fc..d8522be 100644 --- a/README.md +++ b/README.md @@ -124,3 +124,5 @@ Set up the web server with some necessary/nice to have middlewares. - Recovery, Logger (already included in Default mode) - CORS - RequestId + +Using channel and signal to gracefully shutdown the server. diff --git a/configs/howmuch.yml b/configs/howmuch.yml index 7b2008b..4fa3b29 100644 --- a/configs/howmuch.yml +++ b/configs/howmuch.yml @@ -22,7 +22,9 @@ dev-mode: true -addr: :8080 +web: + addr: :8080 + shutdown-timeout: 10 db: # DB host diff --git a/go.mod b/go.mod index 23ca2b0..5dac838 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.1 require ( github.com/fsnotify/fsnotify v1.7.0 + github.com/gin-contrib/cors v1.7.2 github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.6.0 github.com/gosuri/uitable v0.0.4 @@ -12,6 +13,7 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 + golang.org/x/net v0.25.0 ) require ( @@ -22,7 +24,6 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.14.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/cors v1.7.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -55,7 +56,6 @@ require ( golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect diff --git a/go.sum b/go.sum index 4475d27..8241896 100644 --- a/go.sum +++ b/go.sum @@ -143,8 +143,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/howmuch/howmuch.go b/internal/howmuch/howmuch.go index 01ca733..d7cc8ac 100644 --- a/internal/howmuch/howmuch.go +++ b/internal/howmuch/howmuch.go @@ -23,8 +23,13 @@ package howmuch import ( + "errors" "fmt" "net/http" + "os" + "os/signal" + "syscall" + "time" "git.vinchent.xyz/vinchent/howmuch/internal/pkg/log" "git.vinchent.xyz/vinchent/howmuch/internal/pkg/middleware" @@ -33,6 +38,7 @@ import ( "github.com/gin-gonic/gin" "github.com/spf13/cobra" "github.com/spf13/viper" + "golang.org/x/net/context" ) var cfgFile string @@ -107,12 +113,43 @@ func run() error { r.Use(middleware.RequestID()) r.GET("/", func(ctx *gin.Context) { + // time.Sleep(10 * time.Second) // Test shutdown ctx.JSON(http.StatusOK, gin.H{ "message": "how much?", }) }) - r.Run(viper.GetString("addr")) + server := http.Server{ + Addr: viper.GetString("web.addr"), + Handler: r, + } + + go func() { + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + log.FatalLog(err.Error()) + } + }() + + signalChan := make(chan os.Signal, 1) + + signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT) + <-signalChan + + shutdownTimeout := viper.GetDuration("web.shutdown-timeout") + log.DebugLog("Shutdown", "timeout", shutdownTimeout) + + ctx, cancel := context.WithTimeout( + context.Background(), + shutdownTimeout*time.Second, + ) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + log.ErrorLog("Server forced shutdown", "err", err) + return err + } + + log.InfoLog("Ciao!") return nil } diff --git a/internal/pkg/middleware/requestid.go b/internal/pkg/middleware/requestid.go index 2d080be..dc1ac15 100644 --- a/internal/pkg/middleware/requestid.go +++ b/internal/pkg/middleware/requestid.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + package middleware import ( diff --git a/internal/pkg/middleware/requestid_test.go b/internal/pkg/middleware/requestid_test.go index 7e22cd5..9ead114 100644 --- a/internal/pkg/middleware/requestid_test.go +++ b/internal/pkg/middleware/requestid_test.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + package middleware import (