【问题标题】:Unable to load HTML templates with Gin无法使用 Gin 加载 HTML 模板
【发布时间】:2021-01-11 04:42:19
【问题描述】:

我在通过 r.HTMLRender 设置使用 Gin 框架加载 html 模板时遇到了一些问题。

好像没有找到模板。

我尝试使用以下助手:

在设置模板的默认路径时,这些似乎都不起作用(在我的例子中是 app/views);为了达到这个目的,我的 html 模板文件结构如下所示:

/workspace
|-app
  |-views
    |-layouts
      |-application.html
    |-test.html

这里是 Gin 加载代码的示例:

import (
    "github.com/gin-contrib/location"
    "github.com/gin-gonic/gin"
    "fmt"
    "os"
    "github.com/gin-gonic/contrib/static"
    "github.com/michelloworld/ez-gin-template"
)

//CORSMiddleware ...
func CORSMiddleware() gin.HandlerFunc {
    /** CORS middleware **/
}

func Router() {

    if os.Getenv("ENVIRONMENT") == "production" {
        gin.SetMode(gin.ReleaseMode)
    }

    // Initialize Gin object
    r := gin.Default()

    // Cors Middleware
    r.Use(CORSMiddleware())

    // Rate limiting
    rl, err := helpers.RateLimiterMiddleware()
    if err != nil {
        panic("Rate Limiting Initialization error")
    }
    r.Use(rl)

    // Asset provision
    r.Use(static.ServeRoot("/public","app/assets"))

    // Get URL information
    r.Use(location.Default())

    // Attempt with EZ Template, fails
    // I ge this error: "runtime error: invalid memory address or nil pointer dereference" when calling c.HTML(...)
    render := eztemplate.New()
    render.TemplatesDir = "app/views/" // default
    render.Layout = "layouts/application"     // default
    render.Ext = ".html"               // default
    render.Debug = true               // default
    r.HTMLRender = render.Init()


    // Attempt with GinHTMLRender, fails
    // I get this error: https://gist.github.com/madhums/4340cbeb36871e227905#file-gin_html_render-go-L110
    /* 
    htmlRender := GinHTMLRender.New()
    htmlRender.TemplatesDir = "app/views/"
    htmlRender.Debug = gin.IsDebugging()
    htmlRender.Layout = "layouts/application"

    log.Println("Dir:"+htmlRender.TemplatesDir)

    r.HTMLRender = htmlRender.Create()*/

    /** Some Routes **/

    // Start web listener

    r.Run(":8009") // listen and serve on 0.0.0.0:8080
}

对应的渲染调用代码如下:

/* c is of type *gin.Context */
c.HTML(200, "test", "")

由于某种原因,r.HTMLRender 函数似乎没有考虑模板路径;我尝试过这样做:

_, err := template.ParseFiles("app/views/test.html")
if err != nil {
    log.Println("Template Error")
} else {
    log.Println("No Template Error")
}

此代码始终显示“无模板错误”,这让我相信HTMLRender 赋值没有考虑TemplatesDir 设置变量。

我已经被这个问题困扰了一段时间,我不完全确定如何解决它。

任何帮助使其工作的帮助将不胜感激。

【问题讨论】:

  • 你试过LoadHTMLGlob()LoadHTMLFiles()gin-gonic/gin#html-rendering吗?
  • 我想在渲染页面时采用辅助方式来减少手动加载每个模板。我实际上已经找到了 EZ Gin 模板问题的根源,尽管我没有再次尝试使用 GinRenderHTML。我将发布我的发现作为我问题的答案。感谢您的建议。

标签: go go-gin


【解决方案1】:

经过进一步研究,我找到了 EZ Gin 模板问题的根源。

我希望这对遇到同样问题的人有所帮助。

深入查看helper代码后,发现模板匹配模式是严格的,不会递归搜索文件; IE。它需要一个特定的文件结构来查找模板文件:

在默认设置中,EZ Gin 模板需要以下文件结构才能工作:

/workspace
- app
  - views
    - layouts
      - some_layout.html
    - some_dir
      - template_file.html
      - _partial_template.html
    - partials
      - _some_other_partial.html

为了允许其他文件模式,需要修改辅助函数集。

就我而言,我在本地分叉了帮助程序代码以允许匹配第一级模板文件:

func (r Render) Init() Render {
    globalPartials := r.getGlobalPartials()

    layout := r.TemplatesDir + r.Layout + r.Ext

    // Match multiple levels of templates
    viewDirs, _ := filepath.Glob(r.TemplatesDir + "**" + string(os.PathSeparator) + "*" + r.Ext)
    // Added the following two lines to match for app/views/some_file.html as well as files on the **/*.html matching pattern
    tmp, _ := filepath.Glob(r.TemplatesDir + "*" + r.Ext)
    viewDirs = append(viewDirs, tmp...)
    // Can be extended by replicating those two lines above and adding search paths within the base template path.

    fullPartialDir := filepath.Join(r.TemplatesDir + r.PartialDir)
    for _, view := range viewDirs {
        templateFileName := filepath.Base(view)
        //skip partials
        if strings.Index(templateFileName, "_") != 0 && strings.Index(view, fullPartialDir) != 0 {
            localPartials := r.findPartials(filepath.Dir(view))

            renderName := r.getRenderName(view)
            if r.Debug {
                log.Printf("[GIN-debug] %-6s %-25s --> %s\n", "LOAD", view, renderName)
            }
            allFiles := []string{layout, view}
            allFiles = append(allFiles, globalPartials...)
            allFiles = append(allFiles, localPartials...)
            r.AddFromFiles(renderName, allFiles...)
        }
    }

    return r
}

我没有尝试过使用 GinHTMLRenderer 的类似解决方案,但我认为问题可能与预期的文件结构有关。

【讨论】:

    【解决方案2】:

    您还可以将模板绑定到代码中。 jessevdk/go-assets-builder 将生成一个包含指定目录中资产的 go 文件。确保生成的文件位于主包所在的位置。 Gin 在他们的DocumentationFor more Info 中也提供了这个作为示例。它还将在二进制文件中包含子文件夹及其文件(即资产)。

    获取生成器工具:

    go get github.com/jessevdk/go-assets-builder
    

    生成:

    # go-assets-builder <dir> -o <generated file name>
    go-assets-builder app -o assets.go
    

    注意&lt;generated file name&gt;也可以像cmd/client/assets.go一样指定生成文件的目的地。

    加载模板:

    package main
    
    // ... imports
    
    func main() {
        r := gin.New()
    
        t, err := loadTemplate()
        if err != nil {
            panic(err)
        }
        r.SetHTMLTemplate(t)
    
        r.GET("/", func(c *gin.Context) {
            c.HTML(http.StatusOK, "app/views/layouts/application.html", nil)
        })
        r.GET("/test", func(c *gin.Context) {
            c.HTML(http.StatusOK, "app/views/test.html", nil)
        })
        r.Run(":8080")
    }
    
    // loadTemplate loads templates embedded by go-assets-builder
    func loadTemplate() (*template.Template, error) {
        t := template.New("")
        // Assets is the templates
        for name, file := range Assets.Files {
            if file.IsDir() || !strings.HasSuffix(name, ".html") {
                continue
            }
            h, err := ioutil.ReadAll(file)
            if err != nil {
                return nil, err
            }
            t, err = t.New(name).Parse(string(h))
            if err != nil {
                return nil, err
            }
        }
        return t, nil
    }
    

    【讨论】:

      【解决方案3】:

      这是我的做法。这将遍历目录并收集标有模板后缀(即 .html)的文件,然后我只包含所有这些文件。我在任何地方都没有看到这个答案,所以我想我把它贴出来。

      // START UP THE ROUTER
      router := gin.Default()
      
      var files []string
      filepath.Walk("./views", func(path string, info os.FileInfo, err error) error {
          if strings.HasSuffix(path, ".html") {
              files = append(files, path)
          }
          return nil
      })
      
      router.LoadHTMLFiles(files...)
      
      // SERVE STATICS
      router.Use(static.Serve("/css", static.LocalFile("./css", true)))
      router.Use(static.Serve("/js", static.LocalFile("./js", true)))
      router.Use(static.Serve("/images", static.LocalFile("./images", true)))
      
      routers.LoadBaseRoutes(router)
      routers.LoadBlog(router)
      
      router.Run(":8080")
      

      现在它们不必像其他建议那样全部嵌套在确切的深度...文件结构可能不均匀

      【讨论】:

        猜你喜欢
        • 2021-10-16
        • 1970-01-01
        • 2019-11-05
        • 2011-09-21
        • 1970-01-01
        • 2012-03-29
        • 1970-01-01
        • 1970-01-01
        • 2019-05-10
        相关资源
        最近更新 更多