gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
79.03k stars 8.03k forks source link

Gin is rendering the wrong template #4059

Closed lukepuplett closed 2 months ago

lukepuplett commented 2 months ago

Background: I'm learning Go, and using gin. I have decades of coding experience.

Gin is rendering the wrong template file. I am globbing the templates folder with the following code:

ginRoutes.LoadHTMLGlob("templates/*")

Here is the handler code for the / route, which should render index.html:

func IndexGetHandler(ctx *gin.Context) {

    log.Info().Msg("IndexGetHandler called")

    urlToAuthorized, err := routes.GenerateURL("test_page", nil)
    if err != nil {
        ctx.JSON(500, gin.H{"error": "Failed to generate URL to authorized page"})
        return
    }

    ctx.HTML(http.StatusOK, "index.html", gin.H{
        "title":           "Go Web App",
        "message":         "Welcome to the Go Web App with Templates!",
        "urlToAuthorized": urlToAuthorized,
    })
}

Here is the handler code for a test /web/authorized route which uses a different template, this is important for later:

func SignInSuccessGetHandler(ctx *gin.Context) {

    log.Info().Msg("SignInSuccessGetHandler called")

    ctx.HTML(http.StatusOK, "sign_in_success.html", gin.H{
        "title":   "My App App",
        "message": "This test page should only be visible to logged-in users!",
    })
}

Here is the output from the server logging to stdout.

You can see the routes and you can also see IndexGetHandler called in the logs, and you can see in the code above that the handler for this route does indeed log "IndexGetHandler called".

*[main][~/Git/myapp_go]$ go run cmd/server.go
6:34PM INF Project root path path=/Users/lukepuplett/Git/myapp_go
6:34PM INF Static folder path path=/Users/lukepuplett/Git/myapp_go/static
6:34PM INF Secrets folder path path=/Users/lukepuplett/Git/myapp_go/secrets
6:34PM INF Firestore key path path=/Users/lukepuplett/Git/myapp_go/secrets/cloud-run-sa.json
6:34PM INF Checking configuration
6:34PM ??? [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

6:34PM ??? [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

6:34PM INF Loading templates
6:34PM ??? [GIN-debug] Loaded HTML Templates (8): 
    - 
    - base.html
    - title
    - header
    - content
    - footer
    - index.html
    - sign_in_success.html

6:34PM INF Setting config on context
6:34PM INF Setting error handling middleware
6:34PM INF Registering routes
6:34PM ??? [GIN-debug] GET    /                         --> myapp/internal/handlers/anon/landing.IndexGetHandler (5 handlers)
6:34PM ??? [GIN-debug] GET    /static/*filepath         --> myapp/internal/handlers.StaticGetHandler (5 handlers)
6:34PM ??? [GIN-debug] GET    /test/logging             --> myapp/internal/handlers.LoggingTestGetHandler (5 handlers)
6:34PM ??? [GIN-debug] GET    /test/cookie              --> myapp/internal/handlers/anon/landing.CookieGetHandler (5 handlers)
6:34PM ??? [GIN-debug] GET    /original/:imageId        --> myapp/internal/handlers/anon/landing.OriginalImageGetHandler (5 handlers)
6:34PM ??? [GIN-debug] GET    /web/authorized           --> myapp/internal/handlers/authorized.SignInSuccessGetHandler (6 handlers)
6:34PM ??? [GIN-debug] GET    /v1/json/image            --> myapp/internal/handlers/api.ImageExistGetHandler (6 handlers)
6:34PM INF Starting server on :8080 (isDev: true)
6:34PM ??? [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
6:34PM ??? [GIN-debug] Listening and serving HTTP on :8080
6:34PM INF IndexGetHandler called
6:34PM ??? [GIN] 2024/09/19 - 18:34:40 | 200 |    1.101541ms |             ::1 | GET      "/"

Here is the output from CURL for http://localhost:8080/

[master][~/Git/myapp]$ CURL http://localhost:8080/
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Specific Page Title</title>
    <link rel="stylesheet" href="/static/styles.css">
</head>

<body>
    <header>
        <h1>Welcome to My Website</h1>
    </header>

    <main>

<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
    <h2 class="text-4xl font-bold text-blue-600 mb-4">You are now signed in.</h2>
    <p class="text-lg text-gray-700 max-w-xl text-center mb-8">
        Welcome to My App. You can now access all the features of the site.
    </p>

</div>

    </main>

    <footer>
        <p>© 2024 My Website</p>
    </footer>
</body>

</html>

Here's the template for index.html which is what should be rendered when I go to http://localhost:8080/

{{template "base.html" .}}

{{define "title"}}Specific Page Title{{end}}

{{define "content"}}
<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
    <h2 class="text-4xl font-bold text-blue-600 mb-4">This is specific content for this page.</h2>
    <p class="text-lg text-gray-700 max-w-xl text-center mb-8">
        More details here. Tailwind CSS makes styling easy with utility classes.
        This paragraph is centered, has a maximum width, and uses a larger text size.
    </p>
    <a href="{{.urlToAuthorized}}"
        class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out">
        Go to Authorized Page
    </a>
</div>
{{end}}

And here's the template that seems to be being rendered, which is sign_in_success.html which is a different handler, which is not called (not in logs).

{{template "base.html" .}}

{{define "title"}}Specific Page Title{{end}}

{{define "content"}}
<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
    <h2 class="text-4xl font-bold text-blue-600 mb-4">You are now signed in.</h2>
    <p class="text-lg text-gray-700 max-w-xl text-center mb-8">
        Welcome to Right Mood. You can now access all the features of the site.
    </p>
    <!-- <a href="{{.urlToAuthorized}}"
        class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded transition duration-300 ease-in-out">
        Go to Authorized Page
    </a> -->
</div>
{{end}}

I simply start the server with > go run cmd/server.go and then in another terminal tab run > CURL http://localhost:8080/ and I see the wrong HTML. I have stopped the server and run CURL again and it doesn't connect so it's not something silly like I left a different server running.

I'm not sure why it's not rendering the index.html template.

lukepuplett commented 2 months ago

I moved the templates into their own folders and changed the globbing loader

    ginRoutes.LoadHTMLGlob("templates/**/*")
*[main][~/Git/myapp_go/templates]$ tree
.
├── anon
│   └── index.html
├── authorized
│   └── sign_in_success.html
└── base
    └── base.html

4 directories, 3 files

And altered the path in the handler.

package landing

import (
    "net/http"
    "myapp/internal/routes"

    "github.com/gin-gonic/gin"
    "github.com/rs/zerolog/log"
)

func IndexGetHandler(ctx *gin.Context) {

    log.Info().Msg("IndexGetHandler called")

    urlToAuthorized, err := routes.GenerateURL("test_page", nil)
    if err != nil {
        ctx.JSON(500, gin.H{"error": "Failed to generate URL to authorized page"})
        return
    }

    ctx.HTML(http.StatusOK, "anon/index.html", gin.H{
        "title":           "Go Web App",
        "message":         "Welcome to the Go Web App with Templates!",
        "urlToAuthorized": urlToAuthorized,
    })
}

But despite loading the templates, it now it cannot find it when the handler runs.

*[main][~/Git/myapp_go]$ go run cmd/server.go
7:48PM INF Project root path path=/Users/lukepuplett/Git/myapp_go
7:48PM INF Static folder path path=/Users/lukepuplett/Git/myapp_go/static
7:48PM INF Secrets folder path path=/Users/lukepuplett/Git/myapp_go/secrets
7:48PM INF Firestore key path path=/Users/lukepuplett/Git/myapp_go/secrets/cloud-run-sa.json
7:48PM INF Checking configuration
7:48PM ??? [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

7:48PM ??? [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

7:48PM INF Loading templates
7:48PM ??? [GIN-debug] Loaded HTML Templates (8): 
    - base.html
    - footer
    - header
    - 
    - index.html
    - title
    - content
    - sign_in_success.html

7:48PM INF Setting config on context
7:48PM INF Setting error handling middleware
7:48PM INF Registering routes
7:48PM ??? [GIN-debug] GET    /                         --> myapp/internal/handlers/anon/landing.IndexGetHandler (5 handlers)
7:48PM ??? [GIN-debug] GET    /static/*filepath         --> myapp/internal/handlers.StaticGetHandler (5 handlers)
7:48PM ??? [GIN-debug] GET    /test/logging             --> myapp/internal/handlers.LoggingTestGetHandler (5 handlers)
7:48PM ??? [GIN-debug] GET    /test/cookie              --> myapp/internal/handlers/anon/landing.CookieGetHandler (5 handlers)
7:48PM ??? [GIN-debug] GET    /original/:imageId        --> myapp/internal/handlers/anon/landing.OriginalImageGetHandler (5 handlers)
7:48PM ??? [GIN-debug] GET    /web/authorized           --> myapp/internal/handlers/authorized.SignInSuccessGetHandler (6 handlers)
7:48PM ??? [GIN-debug] GET    /v1/json/image            --> myapp/internal/handlers/api.ImageExistGetHandler (6 handlers)
7:48PM INF Starting server on :8080 (isDev: true)
7:48PM ??? [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
7:48PM ??? [GIN-debug] Listening and serving HTTP on :8080
7:48PM INF IndexGetHandler called
7:48PM ERR Internal server error error="html/template: \"anon/index.html\" is undefined"
7:48PM ??? [GIN] 2024/09/19 - 19:48:16 | 200 |    1.021625ms |             ::1 | GET      "/"
Error #01: html/template: "anon/index.html" is undefined

And the message as seen from CURL printed by my logging middleware.

[master][~/Git/myapp]$ curl http://localhost:8080/
{"error":"html/template: \"anon/index.html\" is undefined\nmyapp/internal/hosting.SetupHostRoutes.errorHandlingMiddleware.func2\n\t/Users/lukepuplett/Git/myapp_go/internal/hosting/hosting.go:108\ngithub.com/gin-gonic/gin.(*Context).Next\n\t/Users/lukepuplett/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\nmyapp/internal/hosting.SetupHostRoutes.func1\n\t/Users/lukepuplett/Git/myapp_go/internal/hosting/hosting.go:30\ngithub.com/gin-gonic/gin.(*Context).Next\n\t/Users/lukepuplett/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.CustomRecoveryWithWriter.func1\n\t/Users/lukepuplett/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/recovery.go:102\ngithub.com/gin-gonic/gin.(*Context).Next\n\t/Users/lukepuplett/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.LoggerWithConfig.func1\n\t/Users/lukepuplett/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/logger.go:249\ngithub.com/gin-gonic/gin.(*Context).Next\n\t/Users/lukepuplett/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185\ngithub.com/gin-gonic/gin.(*Engine).handleHTTPRequest\n\t/Users/lukepuplett/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:633\ngithub.com/gin-gonic/gin.(*Engine).ServeHTTP\n\t/Users/lukepuplett/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589\nnet/http.serverHandler.ServeHTTP\n\t/opt/homebrew/Cellar/go/1.23.1/libexec/src/net/http/server.go:3210\nnet/http.(*conn).serve\n\t/opt/homebrew/Cellar/go/1.23.1/libexec/src/net/http/server.go:2092\nruntime.goexit\n\t/opt/homebrew/Cellar/go/1.23.1/libexec/src/runtime/asm_arm64.s:1223"}%           

What I have noticed is that the gin documentation doesn't really go into detail about templating. Other people's websites do, and they seem to do it differently to the gin docs.

This is from the gin docs; notice how it uses .tmpl and also how it appears to define itself with its own file path?! That's weird anyway because it could become out-of-synch with its actual location and name.

From: https://gin-gonic.com/docs/examples/html-rendering/

Apparently this is templates/posts/index.tmpl

{{ define "posts/index.tmpl" }}
<html><h1>
    {{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}
lukepuplett commented 2 months ago

Okay, so it seems that the blocks or something is not supported or I don't know.

Anyway, I followed someone's blog that instead of using defined blocks and base templates, it just created sandwiches with one of the templates simply containing </html> which is simple and works.

lukepuplett commented 2 months ago

By the way, the fact that it spat out the wrong template is concerning. This could potentially leak information the visitor is not supposed to see, if it were sitting in the "model" that's passed to the templater.