refactor(middlewares,router): follow http package structure and interfaces
This commit is contained in:
21
internals/middlewares/development.go
Normal file
21
internals/middlewares/development.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
47
internals/router/middleware.go
Normal file
47
internals/router/middleware.go
Normal 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
|
||||
}
|
||||
64
internals/router/router.go
Normal file
64
internals/router/router.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
22
main.go
22
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()
|
||||
|
||||
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
|
||||
r := router.NewRouter(routes.ROUTES)
|
||||
if *dev {
|
||||
r.AddMiddleware(middlewares.DevelopmentMiddleware{Logger: logger})
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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;">
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{}},
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ templ RedirectPopUp(title string, msg string, url templ.SafeURL) {
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
@Homepage()
|
||||
{ children... }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user