青训营笔记2 | 豆包MarsCode AI刷题

43 阅读10分钟

GO - GIN框架学习

一、模板补充

(1)修改默认的标识符

package main

import(
    "fmt"
    "net/http"
    "html/template"
)

func index(w http.ResponseWriter,r *http.Request){
    //定义模板
    //解析模板
    t,err := template.New("index.tmpl").
    Delims("{[","]}").
    ParseFiles("./web08/index.tmpl")

    if err != nil{
        fmt.Printf("parse template failed,err:%v\n",err)
    }
    //渲染模板
    name := "寒假第一战"
    err = t.Execute(w,name)
    if err != nil{
        fmt.Printf("execute template failed,err:%v\n",err)
        return
    }


}

func main(){
    http.HandleFunc("/index", index)
    err := http.ListenAndServe(":9000", nil)
    if err != nil {
        fmt.Printf("http server failed, err:%v\n", err)
        return
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
   <title>修改模板引擎的标识符</title>
</head>
<body>
    <div>Hello {[ . ]}</div>
</body>
</html>

放在一个文件夹下运行两个文件,可以得到单独一行的输出

Hello 寒假第一战

这里是在修改默认的标识符

Go标准库的模板引擎使用的花括号{{}}作为标识,而许多前端框架(如Vue和 AngularJS)也使用{{}}作为标识符,所以当我们同时使用Go语言模板引擎和以上前端框架时就会出现冲突,这个时候我们需要修改标识符,修改前端的或者修改Go语言的。这里演示如何修改Go语言模板引擎默认的标识符:

template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")

与原来比较多了New和Delims

对应的我代码里其实是把{{}}变成了{[]},以另一种形式解析模板

Delims是一个函数


(2)text/template与html/tempalte的区别

html/template针对的是需要返回HTML内容的场景,在模板渲染过程中会对一些有风险的内容进行转义,以此来防范跨站脚本攻击。

其中常用就是<script>alert(123)</script>

这个时候传入一段JS代码并使用html/template去渲染该文件,会在页面上显示出转义后的JS内容。 <script>alert('嘿嘿嘿')</script> 这就是html/template为我们做的事。防止被无限循环等破坏服务器。

如果要相信用户输入的内容,不想被一起转义掉,就自己编写safe函数,手动返回一个template.HTML类型的内容。

package main

import (
    "fmt"
    "html/template"
    "net/http"
)



func xss(w http.ResponseWriter, r *http.Request) {
    //定义模板

    //解析模板
    //解析模板之前定义一个自定义的函数safe再解析
    t, err := template.New("xss.tmpl").Funcs(template.FuncMap{
        "safe": func(str string) template.HTML {
            return template.HTML(str)
        },
    }).ParseFiles("./web08/xss.tmpl")

    if err != nil {
        fmt.Printf("parse template failed,err:%v\n", err)
        return
    }
    //渲染模板
    str1 := "<script>alert(123);</script>"
    str2 := "<a href='http://hdufhq.cn:8888/'>hdu孵化器地址</a>"
    t.Execute(w, map[string]string{
        "str1": str1,
        "str2": str2,
    })
}

func main() {
    http.HandleFunc("/xss", xss)
    err := http.ListenAndServe(":9000", nil)
    if err != nil {
        fmt.Printf("http server failed, err:%v\n", err)
        return
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>xss</title>
</head>
<body>
    用户的评论是:{{.str1}}
    用户的评论是:{{.str2 | safe}}
</body>
</html>

这里的safe操作{{. | safe}}就是让用户输入不被转义进行调用自己编写的函数的写法。好像也可以{{safe .}}这么写

解析模板的地方先新建了xss.tmpl的模板,然后Funcs调用函数,然后定义一个函数映射集合,里面定义了safe自定义函数,用来返回我们str内容变成HTML的string,在最后才解析模板。

渲染的时候使用了映射,字符串对应字符串,可以多个数据传输过去。

这个第二个part主要讲的是go的包template自带防止被脚本攻击的转义功能,如果想要不转义,就自己编写函数并在tmpl文件里调用它。

最后的结果就可以变成如下:

前面是转义结果,后面是避免了转移,自带了链接(夹带私货证明自己学的bushi)。



二、gin框架模板渲染

(1)首个实例
package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func main(){
    //创建一个默认路由
    r := gin.Default()

    //解析模板
    r.LoadHTMLFiles("./web09/templates/index.tmpl")

    //渲染模板
    r.GET("/index",func(c *gin.Context){

        //HTTP请求要有状态码的响应被返回 三次握手 渲染上数据
        c.HTML(http.StatusOK,"index.tmpl",gin.H{
            "title" : "http://hdufhq.cn:8888/",
        })

    })

    //启动server 这里浏览器就是客户端 本机是服务器
    r.Run(":9090")
}

所有笔记都在注释里了,一步步走。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>posts/index</title>
</head>
<body>
    {{.title}}
</body>
</html>

搭配的模板里只需要在点号传入title数据就可以了。

Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染。


(2)多个模板一起渲染的情况

首先要先在templates文件夹下新建posts和users文件夹,都各自有一个index.tmpl文件

所以要区分这两个相同的文件,可以在开头加入语句{{define "users/index.tmpl"}}(若是posts文件夹就换一下),在结尾加上{{end}}

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func main(){
    //创建一个默认路由
    r := gin.Default()

    //解析模板
    //旧方法解析r.LoadHTMLFiles("./web09/templates/index.tmpl")

    r.LoadHTMLGlob("web09/***/**/*") //意思是直接加载这个web09下面的所有文件 不用具体指定路径了


    //渲染模板  在这里写上上面星号的相对路径
    r.GET("posts/index",func(c *gin.Context){

        //HTTP请求要有状态码的响应被返回 三次握手 渲染上数据
        c.HTML(http.StatusOK,"index.tmpl",gin.H{
            "title" : "http://hdufhq.cn:8888/",
        })//不可行的写法,无法展示title内容,应该是找不到文件

    })
    r.GET("users/index",func(c *gin.Context){

        //HTTP请求要有状态码的响应被返回 三次握手 渲染上数据
        c.HTML(http.StatusOK,"users/index.tmpl",gin.H{
            "title" : "https://www.hdu.edu.cn/main.htm",
        })//可行写法

    })

    //启动server 这里浏览器就是客户端 本机是服务器
    r.Run(":9090")
}

这里出现了一个很好玩的情况:

视频里是只解析到二级目录,因为仓库关系我没有新建文件夹,所以在原来文件夹里写,多了一层文件壳,我就尝试了一下用三个*来解析web09下的所有文件,结果是可行的!!虽然本来gpt跟我说最多到两级。

有个问题是,我调试的时候发现post这种HTML的name写法是读不出title的,虽然还能GET但是没有内容。但是下面那种写法是可以正常展现title的,我觉得可能是为了区分相同文件名也就是index这个文件需要至少外写一层文件夹。(后来发现可能是因为文件首行里手写define了

GET那个路径纯粹是路由相对路径,下面c.HTML函数才是需要找到自己文件的。

改进写法

r.GET("users/index", func(c *gin.Context) {

        //HTTP请求要有状态码的响应被返回 三次握手 渲染上数据
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "<a href='https://www.hdu.edu.cn/main.htm'>杭电地址</a>",
        })

     })

这个写法的显示结果是单纯文字,显然是被当成风险转义了,不想转义,让它变成可点击链接就得继续改写。

改写过程遇到一个问题就是引包。

要区分这两个,否则template.HTML会显示是undefined。

html/templatetext/template是Go语言中用于处理模板的两个包,它们之间的区别在于对待输出的方式。

  1. text/template:这个包主要用于处理纯文本模板,不会对输出进行特殊处理。所有的变量在输出时都会被转义,以确保安全性。这意味着如果您在模板中使用类似{{ .Content }}的表达式,其中.Content包含HTML代码,那么输出将会把HTML标签转义为实体字符,而不会被当作HTML代码渲染。
  2. html/template:相比于text/templatehtml/template提供了更多的HTML相关功能。它会自动对输出进行HTML转义,以避免XSS(跨站脚本攻击)等安全问题。在html/template中,通过使用template.HTML类型来指示某个输出是安全的HTML内容,不需要进行转义。您可以使用template.FuncMap注册自定义函数,并在函数中返回template.HTML类型的值。

在避免转移时候用的是第二个加上自定义函数,前面编写用的是第一个。

package main

import (
    "net/http"

    "html/template"
    "github.com/gin-gonic/gin"
)

func main() {
    //创建一个默认路由
    r := gin.Default()

    //解析模板
    //先给gin框架中模板添加自定义函数
    r.SetFuncMap(template.FuncMap{
        "safe" : func(str string) template.HTML{
            return template.HTML(str)
        },
    })

    //旧方法解析r.LoadHTMLFiles("./web09/templates/index.tmpl")

    r.LoadHTMLGlob("web09/***/**/*") //意思是直接加载这个web09下面的所有文件 不用具体指定路径了

    //渲染模板  在这里写上上面星号的相对路径
    r.GET("posts/index", func(c *gin.Context) {

        //HTTP请求要有状态码的响应被返回 三次握手 渲染上数据
        c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
            "title": "<a href='http://hdufhq.cn:8888/'>孵化器地址</a>",
        })

    })
    r.GET("users/index", func(c *gin.Context) {

        //HTTP请求要有状态码的响应被返回 三次握手 渲染上数据
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "<a href='https://www.hdu.edu.cn/main.htm'>杭电地址</a>",
        })

    })

    //启动server 这里浏览器就是客户端 本机是服务器
    r.Run(":9090")
}
na

那边文件里写成{{.title | safe}}可以展示为两个链接


(3)静态文件处理

静态文件:html页面上用到的样式文件 如css js文件 图片

当我们渲染的HTML文件中引用了静态文件时,我们只需要按照以下方式在渲染页面前调用gin.Static方法即可。

在解析之前插入

//加载静态文件 意思是直接去右边这个目录找文件 
//然后在浏览器f12打开的代码是左边这个链接,右边是被指代隐藏了吧
    r.Static("/xxx","./web09/statics")
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/xxx/index.css">
    <title>users/index</title>
</head>

用link来插入连接的样式表

我感觉/xxx这个就是在指代index的长长的文件夹路径,方便前端链接书写。

r 是一个 Gin 的实例,用于处理 HTTP 请求和路由。

.Static() 是 Gin 框架提供的方法之一,用于配置静态文件服务。

"/xxx" 是在服务器上暴露的 URL 路径,即访问静态文件时需要使用的路径。例如,如果设置为 /xxx,那么访问静态文件时的 URL 就是 http://yourdomain/xxx/filename.ext

"./web09/statics" 是本地文件系统上的静态文件目录路径。在这个例子中,静态文件存储在项目根目录下的 "web09/statics" 文件夹中。

通过以上代码,当用户在浏览器中请求 "/xxx/filename.ext" 时,Gin 框架会自动去 "./web09/statics/filename.ext" 路径下寻找对应的静态文件,并将其返回给客户端。

请注意,/xxx 可以自定义为其他路径,而 ./web09/statics 则应该根据实际情况修改为您的静态文件目录路径。

.

其中:终端

[GIN] 2024/02/15 - 22:55:28 | 200 | 1.0184ms | 127.0.0.1 | GET "/users/index" [GIN] 2024/02/15 - 22:55:28 | 200 | 74.0016ms | 127.0.0.1 | GET "/xxx/index.css"

浏览器访问的时候发送了两次请求,在渲染的时候额外需要一个css,浏览器会又发一次请求获得css。

若添加js文件

alert(123);
<body>
    {{.title | safe}}

    <script src="/xxx/index.js"></script>

</body>

会在页面上先弹窗出123的响应,可见上传附带图片4。

[GIN] 2024/02/16 - 21:02:37 | 200 | 2.2963ms | 127.0.0.1 | GET "/users/index" [GIN] 2024/02/16 - 21:02:38 | 200 | 77.6227ms | 127.0.0.1 | GET "/xxx/index.js" [GIN] 2024/02/16 - 21:02:38 | 200 | 93.6283ms | 127.0.0.1 | GET "/xxx/index.css"


(4)使用模板继承

Gin框架默认都是使用单模板,如果需要使用block template功能,可以通过"github.com/gin-contrib/multitemplate"库实现,具体示例如下:

首先,假设我们项目目录下的templates文件夹下有以下模板文件,其中home.tmplindex.tmpl继承了base.tmpl

templates
├── includes
│   ├── home.tmpl
│   └── index.tmpl
├── layouts
│   └── base.tmpl
└── scripts.tmpl

犯了通配符的路径的错误,搞了半天搜不到解决方法,找了哥,最后可能还是glob函数使用不能三个*的原因。

package main

import (
    "net/http"

    "html/template"
    "github.com/gin-gonic/gin"
)

func main() {
    gin.SetMode(gin.ReleaseMode)
    //创建一个默认路由
    r := gin.Default()

    //解析模板

       //加载静态文件
    r.Static("statics","/statics")

    //先给gin框架中模板添加自定义函数
    r.SetFuncMap(template.FuncMap{
        "safe" : func(str string) template.HTML{
            return template.HTML(str)
        },
    })


    //旧方法解析r.LoadHTMLFiles("./web09/templates/index.tmpl")

    r.LoadHTMLGlob("templates/**/*") //意思是直接加载这个web09下面的所有文件 不用具体指定路径了

    //渲染模板  在这里写上浏览器上url后小截
    r.GET("/posts/index", func(c *gin.Context) {

        //HTTP请求要有状态码的响应被返回 三次握手 渲染上数据
        c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
            "title": "<a href='http://hdufhq.cn:8888/'>孵化器地址</a>",
        })

    })
    r.GET("/users/index", func(c *gin.Context) {

        //HTTP请求要有状态码的响应被返回 三次握手 渲染上数据
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "<a href='https://www.hdu.edu.cn/main.htm'>杭电地址</a>",
        })

    })

    //返回从网上下载的模板,没有需要上传的数据,所以是nil
    r.GET("/home",func(c *gin.Context){
        c.HTML(http.StatusOK,"posts/home.html",nil)
    })

    //启动server 这里浏览器就是客户端 本机是服务器
    r.Run(":9090")
}

把这个整个文件夹拿出来单独运行,保证最多就两级目录,这样就可以运行了,gin框架安装和初始没有问题。

这样就可以实现前端模板的一个全栈应用了。