refactor(middlewares,router): follow http package structure and interfaces

This commit is contained in:
Gustavo "Guz" L. de Mello
2024-07-22 11:24:14 -03:00
parent 294b943353
commit 9dd4681857
9 changed files with 182 additions and 117 deletions

View File

@@ -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")
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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)
}
}
}

24
main.go
View File

@@ -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)
}

View File

@@ -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") {
<div style="max-width:50rem;">
<div style="display:flex;flex-direction:column;gap:1rem;">

View File

@@ -1,65 +1,55 @@
package routes
import (
"context"
"io"
"net/http"
"github.com/a-h/templ"
"extrovert/internals"
)
func AiTxt() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
aiList, err := http.Get("https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/main/ai.txt")
if err != nil {
return err
}
type AITxt struct{}
bytes, err := io.ReadAll(aiList.Body)
if err != nil {
return err
}
_, err = io.WriteString(w, string(bytes))
return err
})
}
func AiTxtHandler(w http.ResponseWriter, r *http.Request) {
func (_ AITxt) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-Control", "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400")
w.Header().Add("CDN-Cache-Control", "max-age=604800")
error := internals.HttpErrorHelper(w)
err := AiTxt().Render(context.Background(), w)
aiList, err := http.Get("https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/main/ai.txt")
if error("Error trying to create ai block list", err, http.StatusInternalServerError) {
return
}
bytes, err := io.ReadAll(aiList.Body)
if error("Error trying to create ai block list", err, http.StatusInternalServerError) {
return
}
_, err = io.WriteString(w, string(bytes))
if error("Error trying to create ai block list", err, http.StatusInternalServerError) {
return
}
w.Header().Add("Content-Type", "text/plain")
}
func RobotsTxt() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
aiList, err := http.Get("https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/main/robots.txt")
if err != nil {
return err
}
type RobotsTxt struct{}
bytes, err := io.ReadAll(aiList.Body)
if err != nil {
return err
}
_, err = io.WriteString(w, string(bytes))
return err
})
}
func RobotsTxtHandler(w http.ResponseWriter, r *http.Request) {
func (_ RobotsTxt) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-Control", "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400")
w.Header().Add("CDN-Cache-Control", "max-age=604800")
error := internals.HttpErrorHelper(w)
err := RobotsTxt().Render(context.Background(), w)
aiList, err := http.Get("https://raw.githubusercontent.com/ai-robots-txt/ai.robots.txt/main/robots.txt")
if error("Error trying to create robots block list", err, http.StatusInternalServerError) {
return
}
bytes, err := io.ReadAll(aiList.Body)
if error("Error trying to create robots block list", err, http.StatusInternalServerError) {
return
}
_, err = io.WriteString(w, string(bytes))
if error("Error trying to create robots block list", err, http.StatusInternalServerError) {
return
}

View File

@@ -1,44 +1,11 @@
package routes
import (
"context"
"extrovert/internals"
"extrovert/templates/pages"
"log"
"net/http"
"github.com/a-h/templ"
"extrovert/internals/router"
)
func NewStaticPageHandler(c templ.Component) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
err := c.Render(context.Background(), w)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Fatalf("TODO-ERR trying to render static page:\n%s", err)
return
}
w.WriteHeader(http.StatusOK)
}
}
var ROUTES = []internals.Route{
{
Pattern: "/index.html",
Static: true,
Page: pages.Homepage(),
Handler: NewStaticPageHandler(pages.Homepage()),
},
{
Pattern: "/robots.txt",
Static: true,
Page: RobotsTxt(),
Handler: RobotsTxtHandler,
},
{
Pattern: "/ai.txt",
Static: true,
Page: AiTxt(),
Handler: AiTxtHandler,
},
var ROUTES = []router.Route{
{Pattern: "/{$}", Handler: Homepage{}},
{Pattern: "/robots.txt", Handler: RobotsTxt{}},
{Pattern: "/ai.txt", Handler: AITxt{}},
}

View File

@@ -21,6 +21,6 @@ templ RedirectPopUp(title string, msg string, url templ.SafeURL) {
</footer>
</article>
</dialog>
@Homepage()
{ children... }
}
}