about summary refs log tree commit diff stats
path: root/cmd/server
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/server')
-rw-r--r--cmd/server/filemap.go77
-rw-r--r--cmd/server/logging.go55
-rw-r--r--cmd/server/main.go38
-rw-r--r--cmd/server/server.go146
4 files changed, 174 insertions, 142 deletions
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)