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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
main.go
24
main.go
@@ -6,17 +6,16 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"extrovert/internals"
|
"extrovert/internals/middlewares"
|
||||||
|
"extrovert/internals/router"
|
||||||
"extrovert/routes"
|
"extrovert/routes"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = log.Default()
|
var logger = log.Default()
|
||||||
|
|
||||||
func main() {
|
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")
|
port := flag.Int("p", 8080, "the port to run the server")
|
||||||
dev := flag.Bool("d", false, "if the server is in development mode")
|
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()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -24,21 +23,12 @@ func main() {
|
|||||||
log.Printf("Running server in DEVELOPMENT MODE")
|
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)
|
err := http.ListenAndServe(fmt.Sprintf(":%v", *port), r)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("Server crashed due to:\n%s", err)
|
logger.Fatalf("Server crashed due to:\n%s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
package pages
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"extrovert/templates/layouts"
|
"extrovert/templates/layouts"
|
||||||
"extrovert/components"
|
"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") {
|
@layouts.Page("Project Extrovert") {
|
||||||
<div style="max-width:50rem;">
|
<div style="max-width:50rem;">
|
||||||
<div style="display:flex;flex-direction:column;gap:1rem;">
|
<div style="display:flex;flex-direction:column;gap:1rem;">
|
||||||
@@ -1,65 +1,55 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
|
||||||
|
|
||||||
"extrovert/internals"
|
"extrovert/internals"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AiTxt() templ.Component {
|
type AITxt struct{}
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := io.ReadAll(aiList.Body)
|
func (_ AITxt) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = io.WriteString(w, string(bytes))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func AiTxtHandler(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("Cache-Control", "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400")
|
||||||
w.Header().Add("CDN-Cache-Control", "max-age=604800")
|
w.Header().Add("CDN-Cache-Control", "max-age=604800")
|
||||||
|
|
||||||
error := internals.HttpErrorHelper(w)
|
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) {
|
if error("Error trying to create ai block list", err, http.StatusInternalServerError) {
|
||||||
return
|
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")
|
w.Header().Add("Content-Type", "text/plain")
|
||||||
}
|
}
|
||||||
|
|
||||||
func RobotsTxt() templ.Component {
|
type RobotsTxt struct{}
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := io.ReadAll(aiList.Body)
|
func (_ RobotsTxt) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = io.WriteString(w, string(bytes))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func RobotsTxtHandler(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("Cache-Control", "max-age=604800, stale-while-revalidate=86400, stale-if-error=86400")
|
||||||
w.Header().Add("CDN-Cache-Control", "max-age=604800")
|
w.Header().Add("CDN-Cache-Control", "max-age=604800")
|
||||||
|
|
||||||
error := internals.HttpErrorHelper(w)
|
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) {
|
if error("Error trying to create robots block list", err, http.StatusInternalServerError) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,11 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"extrovert/internals/router"
|
||||||
"extrovert/internals"
|
|
||||||
"extrovert/templates/pages"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewStaticPageHandler(c templ.Component) func(w http.ResponseWriter, r *http.Request) {
|
var ROUTES = []router.Route{
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
{Pattern: "/{$}", Handler: Homepage{}},
|
||||||
err := c.Render(context.Background(), w)
|
{Pattern: "/robots.txt", Handler: RobotsTxt{}},
|
||||||
if err != nil {
|
{Pattern: "/ai.txt", Handler: AITxt{}},
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ templ RedirectPopUp(title string, msg string, url templ.SafeURL) {
|
|||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
</dialog>
|
</dialog>
|
||||||
@Homepage()
|
{ children... }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user