From 27f92894b50ffc2058c1b2f0db4d78d47a48c843 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 24 Apr 2024 13:36:57 +0200 Subject: split code into separate files --- cmd/server/filemap.go | 77 ++++++++++++++++++++++++++ cmd/server/logging.go | 55 +++++++++++++++++++ cmd/server/main.go | 38 +++++++++++++ cmd/server/server.go | 146 ++------------------------------------------------ 4 files changed, 174 insertions(+), 142 deletions(-) create mode 100644 cmd/server/filemap.go create mode 100644 cmd/server/logging.go create mode 100644 cmd/server/main.go (limited to 'cmd/server') diff --git a/cmd/server/filemap.go b/cmd/server/filemap.go new file mode 100644 index 0000000..5f7e1bb --- /dev/null +++ b/cmd/server/filemap.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "hash/fnv" + "io" + "io/fs" + "log" + "log/slog" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" +) + +type File struct { + filename string + etag string +} + +var files = map[string]File{} + +func hashFile(filename string) (string, error) { + f, err := os.Open(filename) + if err != nil { + return "", err + } + defer f.Close() + hash := fnv.New64a() + if _, err := io.Copy(hash, f); err != nil { + return "", err + } + return fmt.Sprintf(`W/"%x"`, hash.Sum(nil)), nil +} + +func registerFile(urlpath string, filepath string) error { + if files[urlpath] != (File{}) { + log.Printf("registerFile called with duplicate file, urlPath: %s", urlpath) + return nil + } + hash, err := hashFile(filepath) + if err != nil { + return err + } + files[urlpath] = File{ + filename: filepath, + etag: hash, + } + return nil +} + +func registerContentFiles(root string) error { + err := filepath.WalkDir(root, func(filePath string, f fs.DirEntry, err error) error { + if err != nil { + return errors.WithMessagef(err, "failed to access path %s", filePath) + } + relPath, err := filepath.Rel(root, filePath) + if err != nil { + return errors.WithMessagef(err, "failed to make path relative, path: %s", filePath) + } + urlPath, _ := strings.CutSuffix(relPath, "index.html") + if !f.IsDir() { + slog.Debug("registering file", "urlpath", "/"+urlPath) + return registerFile("/"+urlPath, filePath) + } + return nil + }) + if err != nil { + return err + } + return nil +} + +func GetFile(urlPath string) File { + return files[urlPath] +} diff --git a/cmd/server/logging.go b/cmd/server/logging.go new file mode 100644 index 0000000..601baab --- /dev/null +++ b/cmd/server/logging.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "io" + "net/http" +) + +type loggingResponseWriter struct { + http.ResponseWriter + statusCode int +} + +func (lrw *loggingResponseWriter) WriteHeader(code int) { + lrw.statusCode = code + // avoids warning: superfluous response.WriteHeader call + if lrw.statusCode != http.StatusOK { + lrw.ResponseWriter.WriteHeader(code) + } +} + +func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { + return &loggingResponseWriter{w, http.StatusOK} +} + +type wrappedHandlerOptions struct { + defaultHostname string + logger io.Writer +} + +func wrapHandlerWithLogging(wrappedHandler http.Handler, opts wrappedHandlerOptions) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + scheme := r.Header.Get("X-Forwarded-Proto") + if scheme == "" { + scheme = "http" + } + host := r.Header.Get("Host") + if host == "" { + host = opts.defaultHostname + } + lw := NewLoggingResponseWriter(w) + wrappedHandler.ServeHTTP(lw, r) + statusCode := lw.statusCode + fmt.Fprintf( + opts.logger, + "%s %s %d %s %s %s\n", + scheme, + r.Method, + statusCode, + host, + r.URL.Path, + lw.Header().Get("Location"), + ) + }) +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..9fb9f14 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "log" + "log/slog" + "os" + cfg "website/internal/config" + + "github.com/ardanlabs/conf/v3" + "github.com/pkg/errors" +) + +type Config struct { + Production bool `conf:"default:false"` + ListenAddress string `conf:"default:localhost"` + Port uint16 `conf:"default:3000,short:p"` + BaseURL cfg.URL `conf:"default:http://localhost:3000,short:b"` + RedirectOtherHostnames bool `conf:"default:false"` +} + +func main() { + if os.Getenv("DEBUG") != "" { + slog.SetLogLoggerLevel(slog.LevelDebug) + } + + runtimeConfig := Config{} + help, err := conf.Parse("", &runtimeConfig) + if err != nil { + if errors.Is(err, conf.ErrHelpWanted) { + fmt.Println(help) + os.Exit(1) + } + log.Panicf("parsing runtime configuration: %v", err) + } + + startServer(&runtimeConfig) +} diff --git a/cmd/server/server.go b/cmd/server/server.go index 9c9911a..10144bb 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -2,98 +2,27 @@ package main import ( "fmt" - "hash/fnv" "io" - "io/fs" "log" "log/slog" "mime" "net" "net/http" "os" - "path/filepath" "strings" "time" cfg "website/internal/config" - "github.com/ardanlabs/conf/v3" "github.com/getsentry/sentry-go" sentryhttp "github.com/getsentry/sentry-go/http" - "github.com/pkg/errors" "github.com/shengyanli1982/law" ) -type Config struct { - Production bool `conf:"default:false"` - ListenAddress string `conf:"default:localhost"` - Port uint16 `conf:"default:3000,short:p"` - BaseURL cfg.URL `conf:"default:http://localhost:3000,short:b"` - RedirectOtherHostnames bool `conf:"default:false"` -} - var Commit string var config *cfg.Config -type File struct { - filename string - etag string -} - -var files = map[string]File{} - -func hashFile(filename string) (string, error) { - f, err := os.Open(filename) - if err != nil { - return "", err - } - defer f.Close() - hash := fnv.New64a() - if _, err := io.Copy(hash, f); err != nil { - return "", err - } - return fmt.Sprintf(`W/"%x"`, hash.Sum(nil)), nil -} - -func registerFile(urlpath string, filepath string) error { - if files[urlpath] != (File{}) { - log.Printf("registerFile called with duplicate file, urlPath: %s", urlpath) - return nil - } - hash, err := hashFile(filepath) - if err != nil { - return err - } - files[urlpath] = File{ - filename: filepath, - etag: hash, - } - return nil -} - -func registerContentFiles(root string) error { - err := filepath.WalkDir(root, func(filePath string, f fs.DirEntry, err error) error { - if err != nil { - return errors.WithMessagef(err, "failed to access path %s", filePath) - } - relPath, err := filepath.Rel(root, filePath) - if err != nil { - return errors.WithMessagef(err, "failed to make path relative, path: %s", filePath) - } - urlPath, _ := strings.CutSuffix(relPath, "index.html") - if !f.IsDir() { - slog.Debug("registering file", "urlpath", "/"+urlPath) - return registerFile("/"+urlPath, filePath) - } - return nil - }) - if err != nil { - return err - } - return nil -} - type HTTPError struct { Error error Message string @@ -116,7 +45,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) *HTTPError { http.Redirect(w, r, urlPath, 302) return nil } - file := files[urlPath] + file := GetFile(urlPath) if file == (File{}) { return &HTTPError{ Message: "File not found", @@ -166,81 +95,14 @@ func fixupMIMETypes() { } } -type loggingResponseWriter struct { - http.ResponseWriter - statusCode int -} - -func (lrw *loggingResponseWriter) WriteHeader(code int) { - lrw.statusCode = code - // avoids warning: superfluous response.WriteHeader call - if lrw.statusCode != http.StatusOK { - lrw.ResponseWriter.WriteHeader(code) - } -} - -func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { - return &loggingResponseWriter{w, http.StatusOK} -} - -type wrappedHandlerOptions struct { - defaultHostname string - logger io.Writer -} - -func wrapHandlerWithLogging(wrappedHandler http.Handler, opts wrappedHandlerOptions) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - scheme := r.Header.Get("X-Forwarded-Proto") - if scheme == "" { - scheme = "http" - } - host := r.Header.Get("Host") - if host == "" { - host = opts.defaultHostname - } - lw := NewLoggingResponseWriter(w) - wrappedHandler.ServeHTTP(lw, r) - statusCode := lw.statusCode - fmt.Fprintf( - opts.logger, - "%s %s %d %s %s %s\n", - scheme, - r.Method, - statusCode, - host, - r.URL.Path, - lw.Header().Get("Location"), - ) - }) -} - -func main() { - if os.Getenv("DEBUG") != "" { - slog.SetLogLoggerLevel(slog.LevelDebug) - } - +func startServer(runtimeConfig *Config) { fixupMIMETypes() - runtimeConfig := Config{} - help, err := conf.Parse("", &runtimeConfig) - if err != nil { - if errors.Is(err, conf.ErrHelpWanted) { - fmt.Println(help) - os.Exit(1) - } - log.Panicf("parsing runtime configuration: %v", err) - } - - config, err = cfg.GetConfig() + c, err := cfg.GetConfig() if err != nil { log.Panicf("parsing configuration file: %v", err) } - - cwd, err := os.Getwd() - if err != nil { - log.Panicf("don't know where I am") - } - slog.Debug("starting at", "wd", cwd) + config = c prefix := "website/public" slog.Debug("registering content files", "prefix", prefix) -- cgit 1.4.1