a-h / templ

A language for writing HTML user interfaces in Go.
https://templ.guide/
MIT License
7.14k stars 236 forks source link

Help with intercepting middleware, and writing responses #796

Closed gfffrtt closed 2 weeks ago

gfffrtt commented 2 weeks ago

Hello everyone, I'm just building a project with templ and go, and need help with understanding something.

func isHtml(w http.ResponseWriter) bool {
    return strings.HasPrefix(w.Header().Get("content-type"), "text/html")
}

type App struct {
    Router *http.ServeMux
}

func NewApp() *App {
    return &App{
        Router: http.NewServeMux(),
    }
}

func (a *App) Middleware(next http.HandlerFunc) http.HandlerFunc {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isHtml(w) {
            next.ServeHTTP(w, r)
        }

        w.Write([]byte("<p>Loading...</p>"))
        next.ServeHTTP(w, r)
    })
}

func (a *App) Component(route string, handler http.HandlerFunc) {
    a.Router.HandleFunc(route, a.Middleware(handler))
}

func (a *App) Listen(port string) {
    http.ListenAndServe(port, a.Router)
}

func main() {
    app := NewApp()

    app.Component("/", func(w http.ResponseWriter, r *http.Request) {
        views.Component().Render(context.Background(), w)
    })

    app.Listen(":3000")
}

Here is the code I've simplified it a bit but this is the essence of it, I'm adding some middleware that intercepts "text/html" responses and writes some extra html on the, but the result is unexpected, instead of adding the extra html to the bottom of the page, I'm getting a duplication of the html.

image

Am I doing something really wrong here, or is this expected?

joerdav commented 2 weeks ago

I believe you might need to return here:

if !isHtml(w) {
            next.ServeHTTP(w, r)
            return
}
gfffrtt commented 2 weeks ago

I believe you might need to return here:

if !isHtml(w) {
          next.ServeHTTP(w, r)
          return
}

Yes it was that, but now I cant get the content type from the header, every time I print it print []map

func isHtml(w http.ResponseWriter) bool {
    return strings.HasPrefix(w.Header().Get("Content-Type"), "text/html")
}

type App struct {
    Router *chi.Mux
}

func NewApp() *App {
    router := chi.NewRouter()
    router.Use(middleware.Logger)
    return &App{
        Router: chi.NewRouter(),
    }
}

func (a *App) Middleware(next http.HandlerFunc) http.HandlerFunc {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isHtml(w) {
            next.ServeHTTP(w, r)
            return
        }

        w.Write([]byte("<p>Loading...</p>"))
        next.ServeHTTP(w, r)
    })
}

func (a *App) Component(route string, handler http.HandlerFunc) {
    a.Router.Get(route, a.Middleware(handler))
}

func (a *App) Listen(port string) {
    http.ListenAndServe(port, a.Router)
}

func main() {
    app := NewApp()

    app.Component("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Add("Content-Type", "text/html")
        views.Component().Render(context.Background(), w)
    })

    app.Listen(":3000")
}
joerdav commented 2 weeks ago

This may not be the only issue, but I can see that you are writing to the response before setting headers, you should set the header in the middleware.

gfffrtt commented 2 weeks ago

@joerdav Thank you for your help, but I figured out what I actually had to do, what I wanted is to intercept the reponse, so I had to first run the next handler than modify the response if it is html, but thank you man.

image