diff --git a/internals/middlewares/development.go b/internals/middlewares/development.go new file mode 100644 index 0000000..4ce5ae3 --- /dev/null +++ b/internals/middlewares/development.go @@ -0,0 +1,21 @@ +package middlewares + +import ( + "log" + "net/http" +) + +type DevelopmentMiddleware struct { + Logger *log.Logger +} + +func (m DevelopmentMiddleware) Serve(handler http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + m.Logger.Printf("New request: %s", r.URL.Path) + + handler(w, r) + + w.Header().Del("Cache-Control") + w.Header().Add("Cache-Control", "max-age=0") + } +} diff --git a/internals/router.go b/internals/router.go deleted file mode 100644 index 34eee18..0000000 --- a/internals/router.go +++ /dev/null @@ -1,22 +0,0 @@ -package internals - -import ( - "net/http" - - "github.com/a-h/templ" -) - -type RouteHandler = func(http.ResponseWriter, *http.Request) - -type Route struct { - Pattern string - Static bool - Handler RouteHandler - Page templ.Component -} - -func RegisterAllRoutes(routes []Route, s *http.ServeMux) { - for _, r := range routes { - s.HandleFunc(r.Pattern, r.Handler) - } -} diff --git a/internals/router/middleware.go b/internals/router/middleware.go new file mode 100644 index 0000000..f36ecfb --- /dev/null +++ b/internals/router/middleware.go @@ -0,0 +1,47 @@ +package router + +import ( + "log" + "net/http" +) + +type Middleware interface { + Serve(r http.HandlerFunc) http.HandlerFunc +} + +type MiddlewaredResponse struct { + w http.ResponseWriter + status int + bodyWrites [][]byte +} + +func NewMiddlewaredResponse(w http.ResponseWriter) *MiddlewaredResponse { + return &MiddlewaredResponse{w, 200, [][]byte{[]byte("")}} +} + +func (m *MiddlewaredResponse) WriteHeader(s int) { + log.Printf("Status changed %v", s) + m.status = s +} + +func (m *MiddlewaredResponse) Header() http.Header { + return m.w.Header() +} + +func (m *MiddlewaredResponse) Write(b []byte) (int, error) { + m.bodyWrites = append(m.bodyWrites, b) + return len(b), nil +} + +func (m *MiddlewaredResponse) ReallyWriteHeader() (int, error) { + m.w.WriteHeader(m.status) + bytes := 0 + for _, b := range m.bodyWrites { + by, err := m.w.Write(b) + bytes += by + if err != nil { + return bytes, err + } + } + return bytes, nil +} diff --git a/internals/router/router.go b/internals/router/router.go new file mode 100644 index 0000000..9e71779 --- /dev/null +++ b/internals/router/router.go @@ -0,0 +1,64 @@ +package router + +import ( + "log" + "net/http" + "strings" +) + +type Route struct { + Pattern string + Handler http.Handler + Children *[]Route +} + +type Router struct { + routes []Route + middlewares []Middleware + mux *http.ServeMux + serveHTTP http.HandlerFunc +} + +func NewRouter(rs []Route) *Router { + mux := http.NewServeMux() + Router{}.registerAllRoutes("/", rs, mux) + return &Router{rs, []Middleware{}, mux, mux.ServeHTTP} +} + +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + r.serveHTTP(w, req) +} + +func (r *Router) AddMiddleware(m Middleware) { + r.middlewares = append(r.middlewares, m) + r.serveHTTP = r.wrapMiddleares(r.middlewares, r.serveHTTP) +} + +func (router Router) wrapMiddleares(ms []Middleware, h http.HandlerFunc) http.HandlerFunc { + fh := h.ServeHTTP + for _, m := range ms { + fh = m.Serve(fh) + } + + return func(w http.ResponseWriter, r *http.Request) { + mw := NewMiddlewaredResponse(w) + fh(mw, r) + _, _ = mw.ReallyWriteHeader() + } +} + +func (router Router) registerAllRoutes(p string, rs []Route, mux *http.ServeMux) { + for _, r := range rs { + pattern := strings.Join([]string{ + strings.TrimSuffix(p, "/"), + strings.TrimPrefix(r.Pattern, "/"), + }, "/") + log.Printf("registering route %s", pattern) + + mux.Handle(pattern, r.Handler) + + if r.Children != nil { + router.registerAllRoutes(pattern, *r.Children, mux) + } + } +} diff --git a/main.go b/main.go index 463d899..c4fc543 100644 --- a/main.go +++ b/main.go @@ -6,17 +6,16 @@ import ( "log" "net/http" - "extrovert/internals" + "extrovert/internals/middlewares" + "extrovert/internals/router" "extrovert/routes" ) var logger = log.Default() func main() { - staticDir := flag.String("s", "./static", "the directory to copy static files from") port := flag.Int("p", 8080, "the port to run the server") dev := flag.Bool("d", false, "if the server is in development mode") - cache := flag.Bool("c", true, "if the static files are cached") flag.Parse() @@ -24,21 +23,12 @@ func main() { log.Printf("Running server in DEVELOPMENT MODE") } - mux := http.NewServeMux() + r := router.NewRouter(routes.ROUTES) + if *dev { + r.AddMiddleware(middlewares.DevelopmentMiddleware{Logger: logger}) + } - internals.RegisterAllRoutes(routes.ROUTES, mux) - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - logger.Printf("Handling file server request. path=%s", r.URL.Path) - http.FileServer(http.Dir(*staticDir)).ServeHTTP(w, r) - return - } - }) - - logger.Printf("Running server at port: %v", *port) - - middleware := internals.NewMiddleware(mux, *dev, !*cache, log.Default()) - err := http.ListenAndServe(fmt.Sprintf(":%v", *port), middleware) + err := http.ListenAndServe(fmt.Sprintf(":%v", *port), r) if err != nil { logger.Fatalf("Server crashed due to:\n%s", err) } diff --git a/templates/pages/index.templ b/routes/index.templ similarity index 82% rename from templates/pages/index.templ rename to routes/index.templ index 7c77103..05ecfd6 100644 --- a/templates/pages/index.templ +++ b/routes/index.templ @@ -1,11 +1,19 @@ -package pages +package routes import ( "extrovert/templates/layouts" "extrovert/components" + "net/http" ) -templ Homepage() { +type Homepage struct{} + +func (h Homepage) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _ = h.page().Render(context.Background(), w) +} + +templ (h Homepage) page() { @layouts.Page("Project Extrovert") {