From 75442d261d00c4032e2dd95a94fa6f73d07202b5 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 28 Nov 2022 04:59:20 +0100 Subject: [PATCH 1/6] this is nowhere near working yet --- .gitignore | 1 + cmd/httpd/main.go | 25 +- go.mod | 5 + go.sum | 23 ++ internal/config/config.go | 83 ++++++ internal/database/database.go | 49 ++++ internal/globals/globals.go | 27 ++ .../{server => handlers}/handlehealthcheck.go | 4 +- internal/{server => handlers}/handleindex.go | 7 +- internal/{server => handlers}/handlelogin.go | 4 +- .../handlers.go => handlers/handlenow.go} | 4 +- internal/{server => handlers}/handlepanic.go | 4 +- internal/handlers/handlers.go | 61 ++++ internal/logger/logger.go | 99 +++++++ .../{server => middlewares}/middlewares.go | 10 +- internal/server/http.go | 20 +- internal/server/main.go | 269 ------------------ internal/server/routes.go | 2 +- internal/server/server.go | 178 ++++++++++++ 19 files changed, 567 insertions(+), 308 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/database/database.go create mode 100644 internal/globals/globals.go rename internal/{server => handlers}/handlehealthcheck.go (90%) rename internal/{server => handlers}/handleindex.go (78%) rename internal/{server => handlers}/handlelogin.go (64%) rename internal/{server/handlers.go => handlers/handlenow.go} (75%) rename internal/{server => handlers}/handlepanic.go (76%) create mode 100644 internal/handlers/handlers.go create mode 100644 internal/logger/logger.go rename internal/{server => middlewares}/middlewares.go (91%) delete mode 100644 internal/server/main.go create mode 100644 internal/server/server.go diff --git a/.gitignore b/.gitignore index 09c78c5..3b432fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /httpd debug.log /.env +/cmd/httpd/httpd diff --git a/cmd/httpd/main.go b/cmd/httpd/main.go index e6d0197..f9ed795 100644 --- a/cmd/httpd/main.go +++ b/cmd/httpd/main.go @@ -1,9 +1,13 @@ package main import ( - "os" - + "git.eeqj.de/sneak/gohttpserver/internal/config" + "git.eeqj.de/sneak/gohttpserver/internal/database" + "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/handlers" + "git.eeqj.de/sneak/gohttpserver/internal/logger" "git.eeqj.de/sneak/gohttpserver/internal/server" + "go.uber.org/fx" ) var ( @@ -13,5 +17,20 @@ var ( ) func main() { - os.Exit(server.Run(Appname, Version, Buildarch)) + globals.Appname = Appname + globals.Version = Version + globals.Buildarch = Buildarch + + fx.New( + fx.Provide( + config.New, + database.New, + globals.New, + handlers.New, + logger.New, + server.New, + ), + fx.Invoke(func(*server.Server) {}), + ).Run() + // os.Exit(server.Run(Appname, Version, Buildarch)) } diff --git a/go.mod b/go.mod index 0912a8a..25fc970 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,11 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.1 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/dig v1.15.0 // indirect + go.uber.org/fx v1.18.2 // indirect + go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.21.0 // indirect golang.org/x/sys v0.2.0 // indirect golang.org/x/text v0.4.0 // indirect google.golang.org/protobuf v1.28.1 // indirect diff --git a/go.sum b/go.sum index dbed49f..32c4c3c 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -279,12 +280,26 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= +go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= +go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= +go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -326,6 +341,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -359,6 +375,7 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -384,6 +401,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -423,8 +441,10 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -497,6 +517,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -605,10 +626,12 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..8418c75 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,83 @@ +package config + +import ( + "fmt" + + "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/logger" + "github.com/rs/zerolog" + "github.com/spf13/viper" + "go.uber.org/fx" + + // spooky action at a distance! + // this populates the environment + // from a ./.env file automatically + // for development configuration. + // .env contents should be things like + // `DBURL=postgres://user:pass@.../` + // (without the backticks, of course) + _ "github.com/joho/godotenv/autoload" +) + +type ConfigParams struct { + Globals globals.Globals + Logger logger.Logger +} + +type Config struct { + DBURL string + Debug bool + MaintenanceMode bool + MetricsPassword string + MetricsUsername string + Port int + SentryDSN string + params ConfigParams + log *zerolog.Logger +} + +func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { + log := params.Logger.Get() + name := params.Globals.Appname + + viper.SetConfigName(name) + viper.SetConfigType("yaml") + // path to look for the config file in: + viper.AddConfigPath(fmt.Sprintf("/etc/%s", name)) + // call multiple times to add many search paths: + viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", name)) + // viper.SetEnvPrefix(strings.ToUpper(s.appname)) + viper.AutomaticEnv() + + viper.SetDefault("DEBUG", "false") + viper.SetDefault("MAINTENANCE_MODE", "false") + viper.SetDefault("PORT", "8080") + viper.SetDefault("DBURL", "") + viper.SetDefault("SENTRY_DSN", "") + viper.SetDefault("METRICS_USERNAME", "") + viper.SetDefault("METRICS_PASSWORD", "") + + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // Config file not found; ignore error if desired + } else { + // Config file was found but another error was produced + log.Panic(). + Err(err). + Msg("config file malformed") + } + } + + s := &Config{ + DBURL: viper.GetString("DBURL"), + Debug: viper.GetBool("debug"), + Port: viper.GetInt("PORT"), + SentryDSN: viper.GetString("SENTRY_DSN"), + MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"), + MetricsUsername: viper.GetString("METRICS_USERNAME"), + MetricsPassword: viper.GetString("METRICS_PASSWORD"), + log: log, + } + + return s, nil +} diff --git a/internal/database/database.go b/internal/database/database.go new file mode 100644 index 0000000..be4b3e9 --- /dev/null +++ b/internal/database/database.go @@ -0,0 +1,49 @@ +package database + +import ( + "context" + + "git.eeqj.de/sneak/gohttpserver/internal/config" + "git.eeqj.de/sneak/gohttpserver/internal/logger" + "github.com/rs/zerolog" + "go.uber.org/fx" + + // spooky action at a distance! + // this populates the environment + // from a ./.env file automatically + // for development configuration. + // .env contents should be things like + // `DBURL=postgres://user:pass@.../` + // (without the backticks, of course) + _ "github.com/joho/godotenv/autoload" +) + +type DatabaseParams struct { + fx.In + Logger logger.Logger + Config config.Config +} + +type Database struct { + URL string + log *zerolog.Logger + params DatabaseParams +} + +func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) { + s.log.Info().Msg("Database instantiated") + s := new(Database) + s.params = params + s.log = params.Logger.Get() + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + s.log.Info().Msg("Database OnStart Hook") + // FIXME connect to db + }, + OnStop: func(ctx context.Context) error { + // FIXME disconnect from db + }, + }) + return s, nil +} diff --git a/internal/globals/globals.go b/internal/globals/globals.go new file mode 100644 index 0000000..e2ed2c8 --- /dev/null +++ b/internal/globals/globals.go @@ -0,0 +1,27 @@ +package globals + +import ( + "go.uber.org/fx" +) + +// these get populated from main() and copied into the Globals object. +var ( + Appname string + Version string + Buildarch string +) + +type Globals struct { + Appname string + Version string + Buildarch string +} + +func New(lc fx.Lifecycle) (*Globals, error) { + n := &Globals{ + Appname: Appname, + Buildarch: Buildarch, + Version: Version, + } + return n, nil +} diff --git a/internal/server/handlehealthcheck.go b/internal/handlers/handlehealthcheck.go similarity index 90% rename from internal/server/handlehealthcheck.go rename to internal/handlers/handlehealthcheck.go index d9e8b33..479e340 100644 --- a/internal/server/handlehealthcheck.go +++ b/internal/handlers/handlehealthcheck.go @@ -1,11 +1,11 @@ -package server +package handlers import ( "net/http" "time" ) -func (s *server) handleHealthCheck() http.HandlerFunc { +func (s *Handlers) handleHealthCheck() http.HandlerFunc { type response struct { Status string `json:"status"` Now string `json:"now"` diff --git a/internal/server/handleindex.go b/internal/handlers/handleindex.go similarity index 78% rename from internal/server/handleindex.go rename to internal/handlers/handleindex.go index 7c54bd3..0c79b53 100644 --- a/internal/server/handleindex.go +++ b/internal/handlers/handleindex.go @@ -1,20 +1,19 @@ -package server +package handlers import ( "html/template" - "log" "net/http" "git.eeqj.de/sneak/gohttpserver/templates" ) -func (s *server) handleIndex() http.HandlerFunc { +func (s *Handlers) HandleIndex() http.HandlerFunc { indexTemplate := template.Must(template.New("index").Parse(templates.MustString("index.html"))) return func(w http.ResponseWriter, r *http.Request) { err := indexTemplate.ExecuteTemplate(w, "index", nil) if err != nil { - log.Println(err.Error()) + s.log.Println(err.Error()) http.Error(w, http.StatusText(500), 500) } } diff --git a/internal/server/handlelogin.go b/internal/handlers/handlelogin.go similarity index 64% rename from internal/server/handlelogin.go rename to internal/handlers/handlelogin.go index 57a5268..0dad604 100644 --- a/internal/server/handlelogin.go +++ b/internal/handlers/handlelogin.go @@ -1,11 +1,11 @@ -package server +package handlers import ( "fmt" "net/http" ) -func (s *server) handleLogin() http.HandlerFunc { +func (s *Handlers) HandleLogin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello login") } diff --git a/internal/server/handlers.go b/internal/handlers/handlenow.go similarity index 75% rename from internal/server/handlers.go rename to internal/handlers/handlenow.go index 8ab0e09..140b08b 100644 --- a/internal/server/handlers.go +++ b/internal/handlers/handlenow.go @@ -1,11 +1,11 @@ -package server +package handlers import ( "net/http" "time" ) -func (s *server) handleNow() http.HandlerFunc { +func (s *Handlers) HandleNow() http.HandlerFunc { type response struct { Now time.Time `json:"now"` } diff --git a/internal/server/handlepanic.go b/internal/handlers/handlepanic.go similarity index 76% rename from internal/server/handlepanic.go rename to internal/handlers/handlepanic.go index 77d8c29..385d1ca 100644 --- a/internal/server/handlepanic.go +++ b/internal/handlers/handlepanic.go @@ -1,4 +1,4 @@ -package server +package handlers import ( "net/http" @@ -7,7 +7,7 @@ import ( // CHANGEME you probably want to remove this, // this is just a handler/route that throws a panic to test // sentry events. -func (s *server) handlePanic() http.HandlerFunc { +func (s *Handlers) HandlePanic() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { panic("y tho") } diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go new file mode 100644 index 0000000..7dcba34 --- /dev/null +++ b/internal/handlers/handlers.go @@ -0,0 +1,61 @@ +package handlers + +import ( + "context" + "encoding/json" + "net/http" + "time" + + "git.eeqj.de/sneak/gohttpserver/internal/globals" + "github.com/rs/zerolog" + "go.uber.org/fx" + "google.golang.org/genproto/googleapis/spanner/admin/database/v1" +) + +type HandlersParams struct { + fx.In + Logger *zerolog.Logger + Globals globals.Globals + Database database.Database +} + +type Handlers struct { + params HandlersParams + log *zerolog.Logger +} + +func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) { + s := new(Handlers) + s.params = params + s.log = params.Logger.Get() + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + // FIXME compile some templates here or something + }, + }) + return s, nil +} + +func (s *Handlers) HandleNow() http.HandlerFunc { + type response struct { + Now time.Time `json:"now"` + } + return func(w http.ResponseWriter, r *http.Request) { + s.respondJSON(w, r, &response{Now: time.Now()}, 200) + } +} + +func (s *Handlers) respondJSON(w http.ResponseWriter, r *http.Request, data interface{}, status int) { + w.WriteHeader(status) + w.Header().Set("Content-Type", "application/json") + if data != nil { + err := json.NewEncoder(w).Encode(data) + if err != nil { + s.log.Error().Err(err).Msg("json encode error") + } + } +} + +func (s *Handlers) decodeJSON(w http.ResponseWriter, r *http.Request, v interface{}) error { // nolint + return json.NewDecoder(r.Body).Decode(v) +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..0bde904 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,99 @@ +package server + +import ( + "io" + "os" + "time" + + "git.eeqj.de/sneak/gohttpserver/internal/globals" + "github.com/rs/zerolog" + "go.uber.org/fx" + "honnef.co/go/tools/config" +) + +type LoggerParams struct { + fx.In + Globals globals.Globals + Config config.Config +} + +type Logger struct { + log *zerolog.Logger + params LoggerParams +} + +func New(lc fx.Lifecycle, params LoggerParams) (*Logger, error) { + l := new(Logger) + + // always log in UTC + zerolog.TimestampFunc = func() time.Time { + return time.Now().UTC() + } + zerolog.SetGlobalLevel(zerolog.InfoLevel) + + tty := false + if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 { + tty = true + } + + var writers []io.Writer + + if tty { + // this does cool colorization for console/dev + consoleWriter := zerolog.NewConsoleWriter( + func(w *zerolog.ConsoleWriter) { + // Customize time format + w.TimeFormat = time.RFC3339Nano + }, + ) + + writers = append(writers, consoleWriter) + } else { + // log json in prod for the machines + writers = append(writers, os.Stdout) + } + + /* + // this is how you log to a file, if you do that + // sort of thing still + logfile := viper.GetString("Logfile") + if logfile != "" { + logfileDir := filepath.Dir(logfile) + err := goutil.Mkdirp(logfileDir) + if err != nil { + log.Error().Err(err).Msg("unable to create log dir") + } + + hp.logfh, err = os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + panic("unable to open logfile: " + err.Error()) + } + + writers = append(writers, hp.logfh) + */ + + multi := zerolog.MultiLevelWriter(writers...) + logger := zerolog.New(multi).With().Timestamp().Logger().With().Caller().Logger() + + l.log = &logger + // log.Logger = logger + + if l.params.Config.Debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + l.log.Debug().Bool("debug", true).Send() + } + + return l, nil +} + +func (l *Logger) Get() *zerolog.Logger { + return l.log +} + +func (l *Logger) Identify() { + l.log.Info(). + Str("appname", l.params.Globals.Appname). + Str("version", l.params.Globals.Version). + Str("buildarch", l.params.Globals.Buildarch). + Msg("starting") +} diff --git a/internal/server/middlewares.go b/internal/middlewares/middlewares.go similarity index 91% rename from internal/server/middlewares.go rename to internal/middlewares/middlewares.go index 4877d55..4b35c96 100644 --- a/internal/server/middlewares.go +++ b/internal/middlewares/middlewares.go @@ -45,7 +45,7 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) { // type Middleware func(http.Handler) http.Handler // this returns a Middleware that is designed to do every request through the // mux, note the signature: -func (s *server) LoggingMiddleware() func(http.Handler) http.Handler { +func (s *Server) LoggingMiddleware() func(http.Handler) http.Handler { // FIXME this should use https://github.com/google/go-cloud/blob/master/server/requestlog/requestlog.go return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -73,7 +73,7 @@ func (s *server) LoggingMiddleware() func(http.Handler) http.Handler { } } -func (s *server) CORSMiddleware() func(http.Handler) http.Handler { +func (s *Server) CORSMiddleware() func(http.Handler) http.Handler { return cors.Handler(cors.Options{ // CHANGEME! these are defaults, change them to suit your needs or // read from environment/viper. @@ -88,7 +88,7 @@ func (s *server) CORSMiddleware() func(http.Handler) http.Handler { }) } -func (s *server) AuthMiddleware() func(http.Handler) http.Handler { +func (s *Server) AuthMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // CHANGEME you'll want to change this to do stuff. @@ -98,7 +98,7 @@ func (s *server) AuthMiddleware() func(http.Handler) http.Handler { } } -func (s *server) MetricsMiddleware() func(http.Handler) http.Handler { +func (s *Server) MetricsMiddleware() func(http.Handler) http.Handler { mdlw := ghmm.New(ghmm.Config{ Recorder: metrics.NewRecorder(metrics.Config{}), }) @@ -107,7 +107,7 @@ func (s *server) MetricsMiddleware() func(http.Handler) http.Handler { } } -func (s *server) MetricsAuthMiddleware() func(http.Handler) http.Handler { +func (s *Server) MetricsAuthMiddleware() func(http.Handler) http.Handler { return basicauth.New( "metrics", map[string][]string{ diff --git a/internal/server/http.go b/internal/server/http.go index 9549330..7e814b1 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -1,13 +1,12 @@ package server import ( - "encoding/json" "fmt" "net/http" "time" ) -func (s *server) serveUntilShutdown() { +func (s *Server) serveUntilShutdown() { listenAddr := fmt.Sprintf(":%d", s.port) s.httpServer = &http.Server{ Addr: listenAddr, @@ -30,21 +29,6 @@ func (s *server) serveUntilShutdown() { } } -func (s *server) respondJSON(w http.ResponseWriter, r *http.Request, data interface{}, status int) { - w.WriteHeader(status) - w.Header().Set("Content-Type", "application/json") - if data != nil { - err := json.NewEncoder(w).Encode(data) - if err != nil { - s.log.Error().Err(err).Msg("json encode error") - } - } -} - -func (s *server) decodeJSON(w http.ResponseWriter, r *http.Request, v interface{}) error { // nolint - return json.NewDecoder(r.Body).Decode(v) -} - -func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) } diff --git a/internal/server/main.go b/internal/server/main.go deleted file mode 100644 index 2681876..0000000 --- a/internal/server/main.go +++ /dev/null @@ -1,269 +0,0 @@ -package server - -import ( - "context" - "fmt" - "io" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "github.com/spf13/viper" - - "github.com/getsentry/sentry-go" - "github.com/go-chi/chi" - - // spooky action at a distance! - // this populates the environment - // from a ./.env file automatically - // for development configuration. - // .env contents should be things like - // `DBURL=postgres://user:pass@.../` - // (without the backticks, of course) - _ "github.com/joho/godotenv/autoload" -) - -type server struct { - appname string - version string - buildarch string - databaseURL string - startupTime time.Time - port int - exitCode int - sentryEnabled bool - log *zerolog.Logger - ctx context.Context - cancelFunc context.CancelFunc - httpServer *http.Server - router *chi.Mux -} - -func newServer(options ...func(s *server)) *server { - n := new(server) - n.startupTime = time.Now() - n.version = "unknown" - for _, opt := range options { - opt(n) - } - return n -} - -// FIXME change this to use uber/fx DI and an Invoke() -// this is where we come in from package main. -func Run(appname, version, buildarch string) int { - s := newServer(func(i *server) { - i.appname = appname - if version != "" { - i.version = version - } - i.buildarch = buildarch - }) - - // this does nothing if SENTRY_DSN is unset in env. - - // TODO remove: - if s.sentryEnabled { - sentry.CaptureMessage("It works!") - } - - s.configure() - s.setupLogging() - - // logging before sentry, because sentry logs - s.enableSentry() - - s.databaseURL = viper.GetString("DBURL") - s.port = viper.GetInt("PORT") - - return s.serve() -} - -func (s *server) enableSentry() { - s.sentryEnabled = false - - if viper.GetString("SENTRY_DSN") == "" { - return - } - - err := sentry.Init(sentry.ClientOptions{ - Dsn: viper.GetString("SENTRY_DSN"), - Release: fmt.Sprintf("%s-%s", s.appname, s.version), - }) - if err != nil { - log.Fatal().Err(err).Msg("sentry init failure") - return - } - s.log.Info().Msg("sentry error reporting activated") - s.sentryEnabled = true -} - -func (s *server) serve() int { - s.ctx, s.cancelFunc = context.WithCancel(context.Background()) - - // signal watcher - go func() { - c := make(chan os.Signal, 1) - signal.Ignore(syscall.SIGPIPE) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - // block and wait for signal - sig := <-c - s.log.Info().Msgf("signal received: %+v", sig) - if s.cancelFunc != nil { - // cancelling the main context will trigger a clean - // shutdown. - s.cancelFunc() - } - }() - - go s.serveUntilShutdown() - - for range s.ctx.Done() { - // aforementioned clean shutdown upon main context - // cancellation - } - s.cleanShutdown() - return s.exitCode -} - -func (s *server) cleanupForExit() { - s.log.Info().Msg("cleaning up") - // FIXME unimplemented - // close database connections or whatever -} - -func (s *server) cleanShutdown() { - // initiate clean shutdown - s.exitCode = 0 - ctxShutdown, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) - if err := s.httpServer.Shutdown(ctxShutdown); err != nil { - s.log.Error(). - Err(err). - Msg("server clean shutdown failed") - } - if shutdownCancel != nil { - shutdownCancel() - } - - s.cleanupForExit() - - if s.sentryEnabled { - sentry.Flush(2 * time.Second) - } -} - -func (s *server) uptime() time.Duration { - return time.Since(s.startupTime) -} - -func (s *server) maintenance() bool { - return viper.GetBool("MAINTENANCE_MODE") -} - -func (s *server) configure() { - viper.SetConfigName(s.appname) - viper.SetConfigType("yaml") - // path to look for the config file in: - viper.AddConfigPath(fmt.Sprintf("/etc/%s", s.appname)) - // call multiple times to add many search paths: - viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", s.appname)) - // viper.SetEnvPrefix(strings.ToUpper(s.appname)) - viper.AutomaticEnv() - - viper.SetDefault("DEBUG", "false") - viper.SetDefault("MAINTENANCE_MODE", "false") - viper.SetDefault("PORT", "8080") - viper.SetDefault("DBURL", "") - viper.SetDefault("SENTRY_DSN", "") - viper.SetDefault("METRICS_USERNAME", "") - viper.SetDefault("METRICS_PASSWORD", "") - - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - // Config file not found; ignore error if desired - } else { - // Config file was found but another error was produced - log.Panic(). - Err(err). - Msg("config file malformed") - } - } - - // if viper.GetBool("DEBUG") { - // pp.Print(viper.AllSettings()) - // } -} - -func (s *server) setupLogging() { - // always log in UTC - zerolog.TimestampFunc = func() time.Time { - return time.Now().UTC() - } - zerolog.SetGlobalLevel(zerolog.InfoLevel) - - tty := false - if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 { - tty = true - } - - var writers []io.Writer - - if tty { - // this does cool colorization for console/dev - consoleWriter := zerolog.NewConsoleWriter( - func(w *zerolog.ConsoleWriter) { - // Customize time format - w.TimeFormat = time.RFC3339Nano - }, - ) - - writers = append(writers, consoleWriter) - } else { - // log json in prod for the machines - writers = append(writers, os.Stdout) - } - - /* - // this is how you log to a file, if you do that - // sort of thing still - logfile := viper.GetString("Logfile") - if logfile != "" { - logfileDir := filepath.Dir(logfile) - err := goutil.Mkdirp(logfileDir) - if err != nil { - log.Error().Err(err).Msg("unable to create log dir") - } - - hp.logfh, err = os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) - if err != nil { - panic("unable to open logfile: " + err.Error()) - } - - writers = append(writers, hp.logfh) - */ - - multi := zerolog.MultiLevelWriter(writers...) - logger := zerolog.New(multi).With().Timestamp().Logger().With().Caller().Logger() - - s.log = &logger - // log.Logger = logger - - if viper.GetBool("debug") { - zerolog.SetGlobalLevel(zerolog.DebugLevel) - s.log.Debug().Bool("debug", true).Send() - } - - s.identify() -} - -func (s *server) identify() { - s.log.Info(). - Str("appname", s.appname). - Str("version", s.version). - Str("buildarch", s.buildarch). - Msg("starting") -} diff --git a/internal/server/routes.go b/internal/server/routes.go index dd8d7f8..27cb874 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/viper" ) -func (s *server) routes() { +func (s *Server) SetupRoutes() { s.router = chi.NewRouter() // the mux .Use() takes a http.Handler wrapper func, like most diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..217daf4 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,178 @@ +package server + +import ( + "context" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "git.eeqj.de/sneak/gohttpserver/internal/globals" + "github.com/docker/docker/daemon/logger" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "go.uber.org/fx" + "honnef.co/go/tools/config" + + "github.com/getsentry/sentry-go" + "github.com/go-chi/chi" + + // spooky action at a distance! + // this populates the environment + // from a ./.env file automatically + // for development configuration. + // .env contents should be things like + // `DBURL=postgres://user:pass@.../` + // (without the backticks, of course) + _ "github.com/joho/godotenv/autoload" +) + +type ServerParams struct { + fx.In + Logger logger.Logger + Globals globals.Globals + Config config.Config +} + +type Server struct { + appname string + version string + buildarch string + startupTime time.Time + port int + exitCode int + sentryEnabled bool + log *zerolog.Logger + ctx context.Context + cancelFunc context.CancelFunc + httpServer *http.Server + router *chi.Mux + params ServerParams +} + +func New(lc fx.Lifecycle, params ServerParams) (*Server, error) { + s := new(Server) + s.params = params + s.log = params.Logger.Get() + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + s.startupTime = time.Now() + s.version = params.Globals.Version + s.Run() + }, + OnStop: func(ctx context.Context) error { + // FIXME do server shutdown here + }, + }) + return s, nil +} + +// FIXME change this to use uber/fx DI and an Invoke() +// this is where we come in from package main. +func (s *Server) Run() { + // this does nothing if SENTRY_DSN is unset in env. + // TODO remove: + if s.sentryEnabled { + sentry.CaptureMessage("It works!") + } + + s.configure() + + // logging before sentry, because sentry logs + s.enableSentry() + + return s.serve() +} + +func (s *Server) enableSentry() { + s.sentryEnabled = false + + if s.Config.SentryDSN == "" { + return + } + + err := sentry.Init(sentry.ClientOptions{ + Dsn: viper.GetString("SENTRY_DSN"), + Release: fmt.Sprintf("%s-%s", s.params.Globals.Appname, s.params.Globals.Version), + }) + if err != nil { + log.Fatal().Err(err).Msg("sentry init failure") + return + } + s.log.Info().Msg("sentry error reporting activated") + s.sentryEnabled = true +} + +func (s *Server) serve() int { + // FIXME fx will handle this for us + s.ctx, s.cancelFunc = context.WithCancel(context.Background()) + + // signal watcher + go func() { + c := make(chan os.Signal, 1) + signal.Ignore(syscall.SIGPIPE) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + // block and wait for signal + sig := <-c + s.log.Info().Msgf("signal received: %+v", sig) + if s.cancelFunc != nil { + // cancelling the main context will trigger a clean + // shutdown. + s.cancelFunc() + } + }() + + go s.serveUntilShutdown() + + for range s.ctx.Done() { + // aforementioned clean shutdown upon main context + // cancellation + } + s.cleanShutdown() + return s.exitCode +} + +func (s *Server) cleanupForExit() { + s.log.Info().Msg("cleaning up") + // FIXME unimplemented + // close database connections or whatever +} + +func (s *Server) cleanShutdown() { + // initiate clean shutdown + s.exitCode = 0 + ctxShutdown, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) + if err := s.httpServer.Shutdown(ctxShutdown); err != nil { + s.log.Error(). + Err(err). + Msg("server clean shutdown failed") + } + if shutdownCancel != nil { + shutdownCancel() + } + + s.cleanupForExit() + + if s.sentryEnabled { + sentry.Flush(2 * time.Second) + } +} + +func (s *Server) uptime() time.Duration { + return time.Since(s.startupTime) +} + +func (s *Server) maintenance() bool { + return s.params.Config.MaintenanceMode +} + +func (s *Server) configure() { + // FIXME move most of this to dedicated places + // if viper.GetBool("DEBUG") { + // pp.Print(viper.AllSettings()) + // } +} -- 2.40.1 From 5fc22c36b0cef5f2bb3879be1bf0b4708e43fdcb Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 28 Nov 2022 05:09:23 +0100 Subject: [PATCH 2/6] getting closer --- go.mod | 5 ++++- go.sum | 5 +++++ internal/config/config.go | 5 +++++ internal/database/database.go | 5 ++++- internal/handlers/handlers.go | 16 ++++------------ internal/logger/logger.go | 14 ++++++-------- internal/server/server.go | 6 +++--- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 25fc970..cb4c74e 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ require ( github.com/rs/zerolog v1.28.0 github.com/slok/go-http-metrics v0.10.0 github.com/spf13/viper v1.14.0 + go.uber.org/fx v1.18.2 + google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e ) require ( @@ -37,11 +39,12 @@ require ( github.com/subosito/gotenv v1.4.1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/dig v1.15.0 // indirect - go.uber.org/fx v1.18.2 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect + golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect golang.org/x/sys v0.2.0 // indirect golang.org/x/text v0.4.0 // indirect + google.golang.org/grpc v1.50.1 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 32c4c3c..dd7b16b 100644 --- a/go.sum +++ b/go.sum @@ -380,6 +380,7 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -584,6 +585,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -600,6 +603,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/internal/config/config.go b/internal/config/config.go index 8418c75..be6096c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -79,5 +79,10 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { log: log, } + if s.Debug { + params.Logger.EnableDebugLogging() + s.log = params.Logger.Get() + } + return s, nil } diff --git a/internal/database/database.go b/internal/database/database.go index be4b3e9..3450e6e 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -31,18 +31,21 @@ type Database struct { } func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) { - s.log.Info().Msg("Database instantiated") s := new(Database) s.params = params s.log = params.Logger.Get() + s.log.Info().Msg("Database instantiated") + lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { s.log.Info().Msg("Database OnStart Hook") // FIXME connect to db + return nil }, OnStop: func(ctx context.Context) error { // FIXME disconnect from db + return nil }, }) return s, nil diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 7dcba34..5dd0990 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -4,17 +4,17 @@ import ( "context" "encoding/json" "net/http" - "time" + "git.eeqj.de/sneak/gohttpserver/internal/database" "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/logger" "github.com/rs/zerolog" "go.uber.org/fx" - "google.golang.org/genproto/googleapis/spanner/admin/database/v1" ) type HandlersParams struct { fx.In - Logger *zerolog.Logger + Logger logger.Logger Globals globals.Globals Database database.Database } @@ -31,20 +31,12 @@ func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { // FIXME compile some templates here or something + return nil }, }) return s, nil } -func (s *Handlers) HandleNow() http.HandlerFunc { - type response struct { - Now time.Time `json:"now"` - } - return func(w http.ResponseWriter, r *http.Request) { - s.respondJSON(w, r, &response{Now: time.Now()}, 200) - } -} - func (s *Handlers) respondJSON(w http.ResponseWriter, r *http.Request, data interface{}, status int) { w.WriteHeader(status) w.Header().Set("Content-Type", "application/json") diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 0bde904..4f9ca53 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,4 +1,4 @@ -package server +package logger import ( "io" @@ -8,13 +8,11 @@ import ( "git.eeqj.de/sneak/gohttpserver/internal/globals" "github.com/rs/zerolog" "go.uber.org/fx" - "honnef.co/go/tools/config" ) type LoggerParams struct { fx.In Globals globals.Globals - Config config.Config } type Logger struct { @@ -78,14 +76,14 @@ func New(lc fx.Lifecycle, params LoggerParams) (*Logger, error) { l.log = &logger // log.Logger = logger - if l.params.Config.Debug { - zerolog.SetGlobalLevel(zerolog.DebugLevel) - l.log.Debug().Bool("debug", true).Send() - } - return l, nil } +func (l *Logger) EnableDebugLogging() { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + l.log.Debug().Bool("debug", true).Send() +} + func (l *Logger) Get() *zerolog.Logger { return l.log } diff --git a/internal/server/server.go b/internal/server/server.go index 217daf4..72675c3 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,19 +3,19 @@ package server import ( "context" "fmt" + "log" "net/http" "os" "os/signal" "syscall" "time" + "git.eeqj.de/sneak/gohttpserver/internal/config" "git.eeqj.de/sneak/gohttpserver/internal/globals" - "github.com/docker/docker/daemon/logger" + "git.eeqj.de/sneak/gohttpserver/internal/logger" "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "github.com/spf13/viper" "go.uber.org/fx" - "honnef.co/go/tools/config" "github.com/getsentry/sentry-go" "github.com/go-chi/chi" -- 2.40.1 From 46b67f8a6eb86ada4c1d6edefa8383a831b0ba22 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 28 Nov 2022 05:33:52 +0100 Subject: [PATCH 3/6] more progress --- internal/handlers/handlehealthcheck.go | 8 ++--- .../middleware.go} | 36 +++++++++++++++---- internal/server/http.go | 2 +- internal/server/routes.go | 18 +++++----- internal/server/server.go | 14 ++++++-- 5 files changed, 55 insertions(+), 23 deletions(-) rename internal/{middlewares/middlewares.go => middleware/middleware.go} (77%) diff --git a/internal/handlers/handlehealthcheck.go b/internal/handlers/handlehealthcheck.go index 479e340..9067d0a 100644 --- a/internal/handlers/handlehealthcheck.go +++ b/internal/handlers/handlehealthcheck.go @@ -20,10 +20,10 @@ func (s *Handlers) handleHealthCheck() http.HandlerFunc { Status: "ok", Now: time.Now().UTC().Format(time.RFC3339Nano), UptimeSeconds: int64(s.uptime().Seconds()), - UptimeHuman: s.uptime().String(), - Maintenance: s.maintenance(), - Appname: s.appname, - Version: s.version, + UptimeHuman: s.params.Server.uptime().String(), + Maintenance: s.params.Server.MaintenanceMode(), + Appname: s.params.Globals.Appname, + Version: s.params.Globals.Version, } s.respondJSON(w, req, resp, 200) } diff --git a/internal/middlewares/middlewares.go b/internal/middleware/middleware.go similarity index 77% rename from internal/middlewares/middlewares.go rename to internal/middleware/middleware.go index 4b35c96..ba189ff 100644 --- a/internal/middlewares/middlewares.go +++ b/internal/middleware/middleware.go @@ -1,19 +1,43 @@ -package server +package middleware import ( "net" "net/http" "time" + "git.eeqj.de/sneak/gohttpserver/internal/config" + "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/logger" basicauth "github.com/99designs/basicauth-go" "github.com/go-chi/chi/middleware" "github.com/go-chi/cors" + "github.com/rs/zerolog" metrics "github.com/slok/go-http-metrics/metrics/prometheus" ghmm "github.com/slok/go-http-metrics/middleware" "github.com/slok/go-http-metrics/middleware/std" "github.com/spf13/viper" + "go.uber.org/fx" ) +type MiddlewareParams struct { + fx.In + Logger logger.Logger + Globals globals.Globals + Config config.Config +} + +type Middleware struct { + log *zerolog.Logger + params MiddlewareParams +} + +func New(lc fx.Lifecycle, params MiddlewareParams) (*Middleware, error) { + s := new(Middleware) + s.params = params + s.log = params.Logger.Get() + return s, nil +} + // the following is from // https://learning-cloud-native-go.github.io/docs/a6.adding_zerolog_logger/ @@ -45,7 +69,7 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) { // type Middleware func(http.Handler) http.Handler // this returns a Middleware that is designed to do every request through the // mux, note the signature: -func (s *Server) LoggingMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) LoggingMiddleware() func(http.Handler) http.Handler { // FIXME this should use https://github.com/google/go-cloud/blob/master/server/requestlog/requestlog.go return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -73,7 +97,7 @@ func (s *Server) LoggingMiddleware() func(http.Handler) http.Handler { } } -func (s *Server) CORSMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) CORSMiddleware() func(http.Handler) http.Handler { return cors.Handler(cors.Options{ // CHANGEME! these are defaults, change them to suit your needs or // read from environment/viper. @@ -88,7 +112,7 @@ func (s *Server) CORSMiddleware() func(http.Handler) http.Handler { }) } -func (s *Server) AuthMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) AuthMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // CHANGEME you'll want to change this to do stuff. @@ -98,7 +122,7 @@ func (s *Server) AuthMiddleware() func(http.Handler) http.Handler { } } -func (s *Server) MetricsMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) MetricsMiddleware() func(http.Handler) http.Handler { mdlw := ghmm.New(ghmm.Config{ Recorder: metrics.NewRecorder(metrics.Config{}), }) @@ -107,7 +131,7 @@ func (s *Server) MetricsMiddleware() func(http.Handler) http.Handler { } } -func (s *Server) MetricsAuthMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) MetricsAuthMiddleware() func(http.Handler) http.Handler { return basicauth.New( "metrics", map[string][]string{ diff --git a/internal/server/http.go b/internal/server/http.go index 7e814b1..07efa29 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -18,7 +18,7 @@ func (s *Server) serveUntilShutdown() { // add routes // this does any necessary setup in each handler - s.routes() + s.SetupRoutes() s.log.Info().Str("listenaddr", listenAddr).Msg("http begin listen") if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { diff --git a/internal/server/routes.go b/internal/server/routes.go index 27cb874..1fb69d1 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -23,16 +23,16 @@ func (s *Server) SetupRoutes() { s.router.Use(middleware.Recoverer) s.router.Use(middleware.RequestID) - s.router.Use(s.LoggingMiddleware()) + s.router.Use(s.mw.LoggingMiddleware()) // add metrics middleware only if we can serve them behind auth if viper.GetString("METRICS_USERNAME") != "" { - s.router.Use(s.MetricsMiddleware()) + s.router.Use(s.mw.MetricsMiddleware()) } // set up CORS headers. you'll probably want to configure that // in middlewares.go. - s.router.Use(s.CORSMiddleware()) + s.router.Use(s.mw.CORSMiddleware()) // CHANGEME to suit your needs, or pull from config. // timeout for request context; your handlers must finish within @@ -57,12 +57,12 @@ func (s *Server) SetupRoutes() { // complete docs: https://github.com/go-chi/chi //////////////////////////////////////////////////////////////////////// - s.router.Get("/", s.handleIndex()) + s.router.Get("/", s.h.handleIndex()) s.router.Mount("/s", http.StripPrefix("/s", http.FileServer(http.FS(static.Static)))) s.router.Route("/api/v1", func(r chi.Router) { - r.Get("/now", s.handleNow()) + r.Get("/now", s.h.handleNow()) }) // if you want to use a general purpose middleware (http.Handler @@ -71,25 +71,25 @@ func (s *Server) SetupRoutes() { authMiddleware := s.AuthMiddleware() s.router.Get( "/login", - authMiddleware(s.handleLogin()).ServeHTTP, + authMiddleware(s.h.handleLogin()).ServeHTTP, ) // route that panics for testing // CHANGEME remove this s.router.Get( "/panic", - s.handlePanic(), + s.h.handlePanic(), ) s.router.Get( "/.well-known/healthcheck.json", - s.handleHealthCheck(), + s.h.handleHealthCheck(), ) // set up authenticated /metrics route: if viper.GetString("METRICS_USERNAME") != "" { s.router.Group(func(r chi.Router) { - r.Use(s.MetricsAuthMiddleware()) + r.Use(s.mw.MetricsAuthMiddleware()) r.Get("/metrics", http.HandlerFunc(promhttp.Handler().ServeHTTP)) }) } diff --git a/internal/server/server.go b/internal/server/server.go index 72675c3..8bf7350 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -12,7 +12,9 @@ import ( "git.eeqj.de/sneak/gohttpserver/internal/config" "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/handlers" "git.eeqj.de/sneak/gohttpserver/internal/logger" + "git.eeqj.de/sneak/gohttpserver/internal/middleware" "github.com/rs/zerolog" "github.com/spf13/viper" "go.uber.org/fx" @@ -32,9 +34,11 @@ import ( type ServerParams struct { fx.In - Logger logger.Logger - Globals globals.Globals - Config config.Config + Logger logger.Logger + Globals globals.Globals + Config config.Config + Middleware middleware.Middleware + Handlers handlers.Handlers } type Server struct { @@ -51,11 +55,15 @@ type Server struct { httpServer *http.Server router *chi.Mux params ServerParams + mw middleware.Middleware + h handlers.Handlers } func New(lc fx.Lifecycle, params ServerParams) (*Server, error) { s := new(Server) s.params = params + s.mw = params.Middleware + s.h = params.Handlers s.log = params.Logger.Get() lc.Append(fx.Hook{ -- 2.40.1 From 3f49d528e7917f040e804eab94cb5dc8978ac21c Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 28 Nov 2022 06:00:44 +0100 Subject: [PATCH 4/6] builds and runs! not sure if it works, needs testing --- cmd/httpd/main.go | 4 ++ internal/config/config.go | 8 +-- internal/database/database.go | 8 +-- internal/handlers/handlehealthcheck.go | 22 +------- internal/handlers/handleindex.go | 2 +- internal/handlers/handlers.go | 14 +++-- internal/healthcheck/healthcheck.go | 71 ++++++++++++++++++++++++++ internal/logger/logger.go | 2 +- internal/middleware/middleware.go | 10 ++-- internal/server/routes.go | 12 ++--- internal/server/server.go | 38 ++++++-------- 11 files changed, 123 insertions(+), 68 deletions(-) create mode 100644 internal/healthcheck/healthcheck.go diff --git a/cmd/httpd/main.go b/cmd/httpd/main.go index f9ed795..c105754 100644 --- a/cmd/httpd/main.go +++ b/cmd/httpd/main.go @@ -5,7 +5,9 @@ import ( "git.eeqj.de/sneak/gohttpserver/internal/database" "git.eeqj.de/sneak/gohttpserver/internal/globals" "git.eeqj.de/sneak/gohttpserver/internal/handlers" + "git.eeqj.de/sneak/gohttpserver/internal/healthcheck" "git.eeqj.de/sneak/gohttpserver/internal/logger" + "git.eeqj.de/sneak/gohttpserver/internal/middleware" "git.eeqj.de/sneak/gohttpserver/internal/server" "go.uber.org/fx" ) @@ -29,6 +31,8 @@ func main() { handlers.New, logger.New, server.New, + middleware.New, + healthcheck.New, ), fx.Invoke(func(*server.Server) {}), ).Run() diff --git a/internal/config/config.go b/internal/config/config.go index be6096c..76481ac 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,8 +20,9 @@ import ( ) type ConfigParams struct { - Globals globals.Globals - Logger logger.Logger + fx.In + Globals *globals.Globals + Logger *logger.Logger } type Config struct { @@ -32,7 +33,7 @@ type Config struct { MetricsUsername string Port int SentryDSN string - params ConfigParams + params *ConfigParams log *zerolog.Logger } @@ -77,6 +78,7 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { MetricsUsername: viper.GetString("METRICS_USERNAME"), MetricsPassword: viper.GetString("METRICS_PASSWORD"), log: log, + params: ¶ms, } if s.Debug { diff --git a/internal/database/database.go b/internal/database/database.go index 3450e6e..a996c3a 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -20,19 +20,19 @@ import ( type DatabaseParams struct { fx.In - Logger logger.Logger - Config config.Config + Logger *logger.Logger + Config *config.Config } type Database struct { URL string log *zerolog.Logger - params DatabaseParams + params *DatabaseParams } func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) { s := new(Database) - s.params = params + s.params = ¶ms s.log = params.Logger.Get() s.log.Info().Msg("Database instantiated") diff --git a/internal/handlers/handlehealthcheck.go b/internal/handlers/handlehealthcheck.go index 9067d0a..34fed69 100644 --- a/internal/handlers/handlehealthcheck.go +++ b/internal/handlers/handlehealthcheck.go @@ -2,29 +2,11 @@ package handlers import ( "net/http" - "time" ) -func (s *Handlers) handleHealthCheck() http.HandlerFunc { - type response struct { - Status string `json:"status"` - Now string `json:"now"` - UptimeSeconds int64 `json:"uptime_seconds"` - UptimeHuman string `json:"uptime_human"` - Version string `json:"version"` - Appname string `json:"appname"` - Maintenance bool `json:"maintenance_mode"` - } +func (s *Handlers) HandleHealthCheck() http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { - resp := &response{ - Status: "ok", - Now: time.Now().UTC().Format(time.RFC3339Nano), - UptimeSeconds: int64(s.uptime().Seconds()), - UptimeHuman: s.params.Server.uptime().String(), - Maintenance: s.params.Server.MaintenanceMode(), - Appname: s.params.Globals.Appname, - Version: s.params.Globals.Version, - } + resp := s.hc.Healthcheck() s.respondJSON(w, req, resp, 200) } } diff --git a/internal/handlers/handleindex.go b/internal/handlers/handleindex.go index 0c79b53..e91fedc 100644 --- a/internal/handlers/handleindex.go +++ b/internal/handlers/handleindex.go @@ -13,7 +13,7 @@ func (s *Handlers) HandleIndex() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { err := indexTemplate.ExecuteTemplate(w, "index", nil) if err != nil { - s.log.Println(err.Error()) + s.log.Error().Err(err).Msg("") http.Error(w, http.StatusText(500), 500) } } diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 5dd0990..e9d652f 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -7,6 +7,7 @@ import ( "git.eeqj.de/sneak/gohttpserver/internal/database" "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/healthcheck" "git.eeqj.de/sneak/gohttpserver/internal/logger" "github.com/rs/zerolog" "go.uber.org/fx" @@ -14,20 +15,23 @@ import ( type HandlersParams struct { fx.In - Logger logger.Logger - Globals globals.Globals - Database database.Database + Logger *logger.Logger + Globals *globals.Globals + Database *database.Database + Healthcheck *healthcheck.Healthcheck } type Handlers struct { - params HandlersParams + params *HandlersParams log *zerolog.Logger + hc *healthcheck.Healthcheck } func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) { s := new(Handlers) - s.params = params + s.params = ¶ms s.log = params.Logger.Get() + s.hc = params.Healthcheck lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { // FIXME compile some templates here or something diff --git a/internal/healthcheck/healthcheck.go b/internal/healthcheck/healthcheck.go new file mode 100644 index 0000000..c7ff3a9 --- /dev/null +++ b/internal/healthcheck/healthcheck.go @@ -0,0 +1,71 @@ +package healthcheck + +import ( + "context" + "time" + + "git.eeqj.de/sneak/gohttpserver/internal/config" + "git.eeqj.de/sneak/gohttpserver/internal/database" + "git.eeqj.de/sneak/gohttpserver/internal/globals" + "git.eeqj.de/sneak/gohttpserver/internal/logger" + "github.com/rs/zerolog" + "go.uber.org/fx" +) + +type HealthcheckParams struct { + fx.In + Globals *globals.Globals + Config *config.Config + Logger *logger.Logger + Database *database.Database +} + +type Healthcheck struct { + StartupTime time.Time + log *zerolog.Logger + params *HealthcheckParams +} + +func New(lc fx.Lifecycle, params HealthcheckParams) (*Healthcheck, error) { + s := new(Healthcheck) + s.params = ¶ms + s.log = params.Logger.Get() + + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + s.StartupTime = time.Now() + return nil + }, + OnStop: func(ctx context.Context) error { + // FIXME do server shutdown here + return nil + }, + }) + return s, nil +} + +type HealthcheckResponse struct { + Status string `json:"status"` + Now string `json:"now"` + UptimeSeconds int64 `json:"uptime_seconds"` + UptimeHuman string `json:"uptime_human"` + Version string `json:"version"` + Appname string `json:"appname"` + Maintenance bool `json:"maintenance_mode"` +} + +func (s *Healthcheck) uptime() time.Duration { + return time.Since(s.StartupTime) +} + +func (s *Healthcheck) Healthcheck() *HealthcheckResponse { + resp := &HealthcheckResponse{ + Status: "ok", + Now: time.Now().UTC().Format(time.RFC3339Nano), + UptimeSeconds: int64(s.uptime().Seconds()), + UptimeHuman: s.uptime().String(), + Appname: s.params.Globals.Appname, + Version: s.params.Globals.Version, + } + return resp +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 4f9ca53..c127ba2 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -12,7 +12,7 @@ import ( type LoggerParams struct { fx.In - Globals globals.Globals + Globals *globals.Globals } type Logger struct { diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index ba189ff..2686665 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -21,19 +21,19 @@ import ( type MiddlewareParams struct { fx.In - Logger logger.Logger - Globals globals.Globals - Config config.Config + Logger *logger.Logger + Globals *globals.Globals + Config *config.Config } type Middleware struct { log *zerolog.Logger - params MiddlewareParams + params *MiddlewareParams } func New(lc fx.Lifecycle, params MiddlewareParams) (*Middleware, error) { s := new(Middleware) - s.params = params + s.params = ¶ms s.log = params.Logger.Get() return s, nil } diff --git a/internal/server/routes.go b/internal/server/routes.go index 1fb69d1..b283a5a 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -57,33 +57,33 @@ func (s *Server) SetupRoutes() { // complete docs: https://github.com/go-chi/chi //////////////////////////////////////////////////////////////////////// - s.router.Get("/", s.h.handleIndex()) + s.router.Get("/", s.h.HandleIndex()) s.router.Mount("/s", http.StripPrefix("/s", http.FileServer(http.FS(static.Static)))) s.router.Route("/api/v1", func(r chi.Router) { - r.Get("/now", s.h.handleNow()) + r.Get("/now", s.h.HandleNow()) }) // if you want to use a general purpose middleware (http.Handler // wrapper) on a specific HandleFunc route, you need to take the // .ServeHTTP of the http.Handler to get its HandleFunc, viz: - authMiddleware := s.AuthMiddleware() + authMiddleware := s.mw.AuthMiddleware() s.router.Get( "/login", - authMiddleware(s.h.handleLogin()).ServeHTTP, + authMiddleware(s.h.HandleLogin()).ServeHTTP, ) // route that panics for testing // CHANGEME remove this s.router.Get( "/panic", - s.h.handlePanic(), + s.h.HandlePanic(), ) s.router.Get( "/.well-known/healthcheck.json", - s.h.handleHealthCheck(), + s.h.HandleHealthCheck(), ) // set up authenticated /metrics route: diff --git a/internal/server/server.go b/internal/server/server.go index 8bf7350..e85801b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,7 +3,6 @@ package server import ( "context" "fmt" - "log" "net/http" "os" "os/signal" @@ -16,7 +15,6 @@ import ( "git.eeqj.de/sneak/gohttpserver/internal/logger" "git.eeqj.de/sneak/gohttpserver/internal/middleware" "github.com/rs/zerolog" - "github.com/spf13/viper" "go.uber.org/fx" "github.com/getsentry/sentry-go" @@ -34,17 +32,14 @@ import ( type ServerParams struct { fx.In - Logger logger.Logger - Globals globals.Globals - Config config.Config - Middleware middleware.Middleware - Handlers handlers.Handlers + Logger *logger.Logger + Globals *globals.Globals + Config *config.Config + Middleware *middleware.Middleware + Handlers *handlers.Handlers } type Server struct { - appname string - version string - buildarch string startupTime time.Time port int exitCode int @@ -55,8 +50,8 @@ type Server struct { httpServer *http.Server router *chi.Mux params ServerParams - mw middleware.Middleware - h handlers.Handlers + mw *middleware.Middleware + h *handlers.Handlers } func New(lc fx.Lifecycle, params ServerParams) (*Server, error) { @@ -69,11 +64,12 @@ func New(lc fx.Lifecycle, params ServerParams) (*Server, error) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { s.startupTime = time.Now() - s.version = params.Globals.Version - s.Run() + s.Run() // background FIXME + return nil }, OnStop: func(ctx context.Context) error { // FIXME do server shutdown here + return nil }, }) return s, nil @@ -93,22 +89,22 @@ func (s *Server) Run() { // logging before sentry, because sentry logs s.enableSentry() - return s.serve() + s.serve() // FIXME deal with return value } func (s *Server) enableSentry() { s.sentryEnabled = false - if s.Config.SentryDSN == "" { + if s.params.Config.SentryDSN == "" { return } err := sentry.Init(sentry.ClientOptions{ - Dsn: viper.GetString("SENTRY_DSN"), + Dsn: s.params.Config.SentryDSN, Release: fmt.Sprintf("%s-%s", s.params.Globals.Appname, s.params.Globals.Version), }) if err != nil { - log.Fatal().Err(err).Msg("sentry init failure") + s.log.Fatal().Err(err).Msg("sentry init failure") return } s.log.Info().Msg("sentry error reporting activated") @@ -170,11 +166,7 @@ func (s *Server) cleanShutdown() { } } -func (s *Server) uptime() time.Duration { - return time.Since(s.startupTime) -} - -func (s *Server) maintenance() bool { +func (s *Server) MaintenanceMode() bool { return s.params.Config.MaintenanceMode } -- 2.40.1 From 49709ad3d2bc3f0da72242fdd354033601ef5d78 Mon Sep 17 00:00:00 2001 From: sneak Date: Sat, 28 Jan 2023 19:05:02 -0800 Subject: [PATCH 5/6] latest --- internal/handlers/handlelogin.go | 12 --- .../{handlehealthcheck.go => healthcheck.go} | 0 .../handlers/{handleindex.go => index.go} | 5 +- internal/handlers/login.go | 19 ++++ internal/handlers/{handlenow.go => now.go} | 0 .../handlers/{handlepanic.go => panic.go} | 0 internal/middleware/middleware.go | 10 +-- internal/server/http.go | 2 +- internal/server/routes.go | 21 +++-- internal/server/server.go | 2 +- templates/htmlfooter.html | 3 + templates/htmlheader.html | 4 + templates/index.html | 89 ++++--------------- templates/login.html | 80 +---------------- templates/navbar.html | 64 +++++++++++++ templates/pagefooter.html | 3 + templates/signup.html | 76 ++++++++++++++++ templates/templates.go | 14 ++- 18 files changed, 224 insertions(+), 180 deletions(-) delete mode 100644 internal/handlers/handlelogin.go rename internal/handlers/{handlehealthcheck.go => healthcheck.go} (100%) rename internal/handlers/{handleindex.go => index.go} (63%) create mode 100644 internal/handlers/login.go rename internal/handlers/{handlenow.go => now.go} (100%) rename internal/handlers/{handlepanic.go => panic.go} (100%) create mode 100644 templates/htmlfooter.html create mode 100644 templates/htmlheader.html create mode 100644 templates/navbar.html create mode 100644 templates/pagefooter.html create mode 100644 templates/signup.html diff --git a/internal/handlers/handlelogin.go b/internal/handlers/handlelogin.go deleted file mode 100644 index 0dad604..0000000 --- a/internal/handlers/handlelogin.go +++ /dev/null @@ -1,12 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" -) - -func (s *Handlers) HandleLogin() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "hello login") - } -} diff --git a/internal/handlers/handlehealthcheck.go b/internal/handlers/healthcheck.go similarity index 100% rename from internal/handlers/handlehealthcheck.go rename to internal/handlers/healthcheck.go diff --git a/internal/handlers/handleindex.go b/internal/handlers/index.go similarity index 63% rename from internal/handlers/handleindex.go rename to internal/handlers/index.go index e91fedc..7895d49 100644 --- a/internal/handlers/handleindex.go +++ b/internal/handlers/index.go @@ -1,17 +1,16 @@ package handlers import ( - "html/template" "net/http" "git.eeqj.de/sneak/gohttpserver/templates" ) func (s *Handlers) HandleIndex() http.HandlerFunc { - indexTemplate := template.Must(template.New("index").Parse(templates.MustString("index.html"))) + t := templates.GetParsed() return func(w http.ResponseWriter, r *http.Request) { - err := indexTemplate.ExecuteTemplate(w, "index", nil) + err := t.ExecuteTemplate(w, "index.html", nil) if err != nil { s.log.Error().Err(err).Msg("") http.Error(w, http.StatusText(500), 500) diff --git a/internal/handlers/login.go b/internal/handlers/login.go new file mode 100644 index 0000000..f15b847 --- /dev/null +++ b/internal/handlers/login.go @@ -0,0 +1,19 @@ +package handlers + +import ( + "net/http" + + "git.eeqj.de/sneak/gohttpserver/templates" +) + +func (s *Handlers) HandleLoginGET() http.HandlerFunc { + t := templates.GetParsed() + + return func(w http.ResponseWriter, r *http.Request) { + err := t.ExecuteTemplate(w, "login.html", nil) + if err != nil { + s.log.Error().Err(err).Msg("") + http.Error(w, http.StatusText(500), 500) + } + } +} diff --git a/internal/handlers/handlenow.go b/internal/handlers/now.go similarity index 100% rename from internal/handlers/handlenow.go rename to internal/handlers/now.go diff --git a/internal/handlers/handlepanic.go b/internal/handlers/panic.go similarity index 100% rename from internal/handlers/handlepanic.go rename to internal/handlers/panic.go diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index 2686665..a6605ed 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -69,7 +69,7 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) { // type Middleware func(http.Handler) http.Handler // this returns a Middleware that is designed to do every request through the // mux, note the signature: -func (s *Middleware) LoggingMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) Logging() func(http.Handler) http.Handler { // FIXME this should use https://github.com/google/go-cloud/blob/master/server/requestlog/requestlog.go return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -97,7 +97,7 @@ func (s *Middleware) LoggingMiddleware() func(http.Handler) http.Handler { } } -func (s *Middleware) CORSMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) CORS() func(http.Handler) http.Handler { return cors.Handler(cors.Options{ // CHANGEME! these are defaults, change them to suit your needs or // read from environment/viper. @@ -112,7 +112,7 @@ func (s *Middleware) CORSMiddleware() func(http.Handler) http.Handler { }) } -func (s *Middleware) AuthMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) Auth() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // CHANGEME you'll want to change this to do stuff. @@ -122,7 +122,7 @@ func (s *Middleware) AuthMiddleware() func(http.Handler) http.Handler { } } -func (s *Middleware) MetricsMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) Metrics() func(http.Handler) http.Handler { mdlw := ghmm.New(ghmm.Config{ Recorder: metrics.NewRecorder(metrics.Config{}), }) @@ -131,7 +131,7 @@ func (s *Middleware) MetricsMiddleware() func(http.Handler) http.Handler { } } -func (s *Middleware) MetricsAuthMiddleware() func(http.Handler) http.Handler { +func (s *Middleware) MetricsAuth() func(http.Handler) http.Handler { return basicauth.New( "metrics", map[string][]string{ diff --git a/internal/server/http.go b/internal/server/http.go index 07efa29..6c25ffc 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -7,7 +7,7 @@ import ( ) func (s *Server) serveUntilShutdown() { - listenAddr := fmt.Sprintf(":%d", s.port) + listenAddr := fmt.Sprintf(":%d", s.params.Config.Port) s.httpServer = &http.Server{ Addr: listenAddr, ReadTimeout: 10 * time.Second, diff --git a/internal/server/routes.go b/internal/server/routes.go index b283a5a..bfdffed 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -23,16 +23,16 @@ func (s *Server) SetupRoutes() { s.router.Use(middleware.Recoverer) s.router.Use(middleware.RequestID) - s.router.Use(s.mw.LoggingMiddleware()) + s.router.Use(s.mw.Logging()) // add metrics middleware only if we can serve them behind auth if viper.GetString("METRICS_USERNAME") != "" { - s.router.Use(s.mw.MetricsMiddleware()) + s.router.Use(s.mw.Metrics()) } // set up CORS headers. you'll probably want to configure that // in middlewares.go. - s.router.Use(s.mw.CORSMiddleware()) + s.router.Use(s.mw.CORS()) // CHANGEME to suit your needs, or pull from config. // timeout for request context; your handlers must finish within @@ -68,12 +68,21 @@ func (s *Server) SetupRoutes() { // if you want to use a general purpose middleware (http.Handler // wrapper) on a specific HandleFunc route, you need to take the // .ServeHTTP of the http.Handler to get its HandleFunc, viz: - authMiddleware := s.mw.AuthMiddleware() + auth := s.mw.Auth() s.router.Get( "/login", - authMiddleware(s.h.HandleLogin()).ServeHTTP, + auth(s.h.HandleLoginGET()).ServeHTTP, ) + s.router.Get( + "/signup", + auth(s.h.HandleSignupGET()).ServeHTTP, + ) + + s.router.Post( + "/signup", + auth(s.h.HandleSignupPOST()).ServeHTTP, + ) // route that panics for testing // CHANGEME remove this s.router.Get( @@ -89,7 +98,7 @@ func (s *Server) SetupRoutes() { // set up authenticated /metrics route: if viper.GetString("METRICS_USERNAME") != "" { s.router.Group(func(r chi.Router) { - r.Use(s.mw.MetricsAuthMiddleware()) + r.Use(s.mw.MetricsAuth()) r.Get("/metrics", http.HandlerFunc(promhttp.Handler().ServeHTTP)) }) } diff --git a/internal/server/server.go b/internal/server/server.go index e85801b..64e0ba6 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -64,7 +64,7 @@ func New(lc fx.Lifecycle, params ServerParams) (*Server, error) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { s.startupTime = time.Now() - s.Run() // background FIXME + go s.Run() // background FIXME return nil }, OnStop: func(ctx context.Context) error { diff --git a/templates/htmlfooter.html b/templates/htmlfooter.html new file mode 100644 index 0000000..2a60c59 --- /dev/null +++ b/templates/htmlfooter.html @@ -0,0 +1,3 @@ + + + diff --git a/templates/htmlheader.html b/templates/htmlheader.html new file mode 100644 index 0000000..2d3e82d --- /dev/null +++ b/templates/htmlheader.html @@ -0,0 +1,4 @@ + + {{ .HTMLTitle }} + + diff --git a/templates/index.html b/templates/index.html index 426c8ac..b038f44 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,84 +1,29 @@ - - Changeme: Go HTTP Server Boilerplate - - + {{ template "htmlheader.html" . }} - - + {{ template "navbar.html" .}}

Hello, world!

+

gohttpserver

This is a boilerplate application for you to use as a base for your own sites and services.

+

+ Find more info at https://git.eeqj.de/sneak/gohttpserver. +

+

+ This software is provided by @sneak + and is released unconditionally into the public domain. +

Learn more »

-
-

© No rights reserved - This is in the public domain!

-
- - - + {{ template "pagefooter.html" . }} + {{ template "htmlfooter.html" . }} diff --git a/templates/login.html b/templates/login.html index b8e654b..1b356c4 100644 --- a/templates/login.html +++ b/templates/login.html @@ -1,10 +1,7 @@ - - Changeme: Go HTTP Server Boilerplate - - + {{ template "htmlheader.html" . }} + + + + {{ template "navbar.html" .Navbar}} + +
+ + + +
+ + {{ template "pagefooter.html" .PageFooter }} + + diff --git a/templates/templates.go b/templates/templates.go index 43e93ee..0184d6a 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -2,12 +2,21 @@ package templates import ( "embed" - "strings" + "text/template" ) //go:embed *.html -var Templates embed.FS +var TemplatesRaw embed.FS +var TemplatesParsed *template.Template +func GetParsed() *template.Template { + if TemplatesParsed == nil { + TemplatesParsed = template.Must(template.ParseFS(TemplatesRaw, "*")) + } + return TemplatesParsed +} + +/* func MustString(filename string) string { bytes, error := Templates.ReadFile(filename) if error != nil { @@ -17,3 +26,4 @@ func MustString(filename string) string { out.Write(bytes) return out.String() } +*/ -- 2.40.1 From 01073aca78cfd7ffcf0454439ef040afa1a19070 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 30 Jan 2023 00:15:38 -0800 Subject: [PATCH 6/6] latest --- internal/config/config.go | 45 ++++++++++++++++++++++--------------- internal/handlers/signup.go | 34 ++++++++++++++++++++++++++++ internal/server/server.go | 1 - 3 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 internal/handlers/signup.go diff --git a/internal/config/config.go b/internal/config/config.go index 76481ac..dc7091a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,15 +26,18 @@ type ConfigParams struct { } type Config struct { - DBURL string - Debug bool - MaintenanceMode bool - MetricsPassword string - MetricsUsername string - Port int - SentryDSN string - params *ConfigParams - log *zerolog.Logger + DBURL string + Debug bool + MaintenanceMode bool + DevelopmentMode bool + DevAdminUsername string + DevAdminPassword string + MetricsPassword string + MetricsUsername string + Port int + SentryDSN string + params *ConfigParams + log *zerolog.Logger } func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { @@ -52,6 +55,9 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { viper.SetDefault("DEBUG", "false") viper.SetDefault("MAINTENANCE_MODE", "false") + viper.SetDefault("DEVELOPMENT_MODE", "false") + viper.SetDefault("DEV_ADMIN_USERNAME", "") + viper.SetDefault("DEV_ADMIN_PASSWORD", "") viper.SetDefault("PORT", "8080") viper.SetDefault("DBURL", "") viper.SetDefault("SENTRY_DSN", "") @@ -70,15 +76,18 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) { } s := &Config{ - DBURL: viper.GetString("DBURL"), - Debug: viper.GetBool("debug"), - Port: viper.GetInt("PORT"), - SentryDSN: viper.GetString("SENTRY_DSN"), - MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"), - MetricsUsername: viper.GetString("METRICS_USERNAME"), - MetricsPassword: viper.GetString("METRICS_PASSWORD"), - log: log, - params: ¶ms, + DBURL: viper.GetString("DBURL"), + Debug: viper.GetBool("debug"), + Port: viper.GetInt("PORT"), + SentryDSN: viper.GetString("SENTRY_DSN"), + MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"), + DevelopmentMode: viper.GetBool("DEVELOPMENT_MODE"), + DevAdminUsername: viper.GetString("DEV_ADMIN_USERNAME"), + DevAdminPassword: viper.GetString("DEV_ADMIN_PASSWORD"), + MetricsUsername: viper.GetString("METRICS_USERNAME"), + MetricsPassword: viper.GetString("METRICS_PASSWORD"), + log: log, + params: ¶ms, } if s.Debug { diff --git a/internal/handlers/signup.go b/internal/handlers/signup.go new file mode 100644 index 0000000..12e0243 --- /dev/null +++ b/internal/handlers/signup.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "net/http" + + "git.eeqj.de/sneak/gohttpserver/templates" +) + +func (s *Handlers) HandleSignupGET() http.HandlerFunc { + t := templates.GetParsed() + + return func(w http.ResponseWriter, r *http.Request) { + err := t.ExecuteTemplate(w, "signup.html", nil) + if err != nil { + s.log.Error().Err(err).Msg("") + http.Error(w, http.StatusText(500), 500) + } + } +} + +func (s *Handlers) HandleSignupPOST() http.HandlerFunc { + t := templates.GetParsed() + return func(w http.ResponseWriter, r *http.Request) { + + _ = r.ParseForm() + + err := t.ExecuteTemplate(w, "signup.html", nil) + + if err != nil { + s.log.Error().Err(err).Msg("") + http.Error(w, http.StatusText(500), 500) + } + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 64e0ba6..f3c6a9f 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -41,7 +41,6 @@ type ServerParams struct { type Server struct { startupTime time.Time - port int exitCode int sentryEnabled bool log *zerolog.Logger -- 2.40.1