Skip to content

响应

Quote

既然请求可以使用不同的content-type,响应也如此。通常响应会有html,text,plain,json和xml等。 gin提供了很优雅的渲染方法。

JSON/XML/YAML/ProtoBuf 方式渲染

// H是map[string]接口{}的快捷方式
r.GET("/resJson", func(context *gin.Context) {
    context.JSON(http.StatusOK, gin.H{"msg": "response is success", "status": "status is ok"})
})
// response
jartin@macbookpro1 elastic_notes % curl localhost/resJson
{"msg":"response is success","status":"status is ok"}%

// 也可以使用结构体返回
r.GET("/moreJson", func(context *gin.Context) {
    var msg struct {
        Name string `json:"user"`
        Msg  string
        Code int
    }
    msg.Name = "Jartin"
    msg.Msg = "结构体返回成功"
    msg.Code = 200
    // 注意 msg.Name 变成了 "user" 字段
    //以下方式都会输出 :   {"user": "Jartin", "Msg": "结构体返回成功", "Code": 200}
    context.JSON(http.StatusOK, msg)
})
// response
jartin@macbookpro1 elastic_notes % curl localhost/moreJson
{"user":"Jartin","Msg":"结构体返回成功","Code":200}% 

// 返回xml
r.GET("someXML", func(context *gin.Context) {
    context.XML(http.StatusOK, gin.H{"user": "jartin", "status": http.StatusOK})
})
// response
jartin@macbookpro1 elastic_notes % curl localhost/someXML 
<map><user>jartin</user><status>200</status></map>% 

r.GET("/someYAML", func(context *gin.Context) {
    context.YAML(http.StatusOK, gin.H{"user": "jartin", "code": http.StatusOK})
})
// response
code: 200
user: jartin

r.GET("/someProtoBuf", func(context *gin.Context) {
    reps := []int64{int64(1), int64(2)}
    label := "test"
    //protobuf的具体定义写在testdata/protoexample文件中
    data := &protoexample.Test{
        Label: &label,
        Reps:  reps,
    }
    // 请注意,数据在响应中变为二进制数据
    // 将输出原型。测试序列化数据的原型
    context.ProtoBuf(http.StatusOK, data)
})
package main

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

func main() {
    r := gin.Default()
    // H是map[string]接口{}的快捷方式
    r.GET("/resJson", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{"msg": "response is success", "status": "status is ok"})
    })
    // 也可以使用结构体返回
    r.GET("/moreJson", func(context *gin.Context) {
        var msg struct {
            Name string `json:"user"`
            Msg  string
            Code int
        }
        msg.Name = "Jartin"
        msg.Msg = "结构体返回成功"
        msg.Code = 200
        // 注意 msg.Name 变成了 "user" 字段
        //以下方式都会输出 :   {"user": "Jartin", "Msg": "结构体返回成功", "Code": 200}
        context.JSON(http.StatusOK, msg)
    })
    // 返回xml
    r.GET("someXML", func(context *gin.Context) {
        context.XML(http.StatusOK, gin.H{"user": "jartin", "status": http.StatusOK})
    })
    // 返回YAML
    r.GET("/someProtoBuf", func(context *gin.Context) {
        context.YAML(http.StatusOK, gin.H{"user": "jartin", "code": http.StatusOK})
    })
    // 返回protoBuf
    r.GET("/someProtoBuf", func(context *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        label := "test"
        //protobuf的具体定义写在testdata/protoexample文件中
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        // 请注意,数据在响应中变为二进制数据
        // 将输出原型。测试序列化数据的原型
        context.ProtoBuf(http.StatusOK, data)
    })
    r.Run(":80")
}

HTML模板渲染

  • gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据
  • 先要使用 LoadHTMLGlob() 或者 LoadHTMLFiles()方法来加载模板文件,LoadHTMLFiles方法不会脚本注入
package main

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

func main() {
    r := gin.Default()
    //加载模版
    r.LoadHTMLGlob("templates/*")
    //r.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
    // 定义路由
    r.GET("/index", func(context *gin.Context) {
        // 根据完整文件名渲染模版,并传递参数
        context.HTML(http.StatusOK, "index.tmpl", gin.H{"title": "弹力笔记"})
    })
    r.Run(":80")
}

<html>
    <h1>
        {{ .title }}
    </h1>
</html>
// response
jartin@macbookpro1 elastic_notes % curl localhost/index   
<html>
    <h1>
        弹力笔记
    </h1>
</html>% 

不同文件夹下模板名字可以相同,此时需要 LoadHTMLGlob() 加载两层模板路径。

package main

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

func main() {
    r := gin.Default()
    // 加载模版
    /**
    这个**代表的是所有目录, *代表的是所有文件 ;
    假如templates文件夹下面没有posts文件夹与users文件夹,模板文件都是直接放在templates文件夹下,则写成`templates/*` 即可
    */
    r.LoadHTMLGlob("templates/**/*")
    r.GET("/posts", func(context *gin.Context) {
        context.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
            "title": "Posts",
        })
    })
    r.GET("/users", func(context *gin.Context) {
        context.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "Users",
        })
    })
    r.Run(":80")
}
jartin@macbookpro1 elastic_notes % curl localhost/posts

<!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>posts/index</title>
</head>
<body>
    Posts
</body>
</html>
jartin@macbookpro1 elastic_notes % curl localhost/users

<!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>users/index</title>
</head>
<body>
Users
</body>
</html>

{{define "posts/index.tmpl"}}
    <!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>posts/index</title>
    </head>
    <body>
        {{.title}}
    </body>
    </html>
{{end}}
{{define "users/index.tmpl"}}
    <!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>users/index</title>
    </head>
    <body>
    {{.title}}
    </body>
    </html>
{{end}}

文件响应

  • 静态文件服务
    • 可以向客户端展示本地的一些文件信息,例如显示某路径下地文件。服务端代码是:
package main

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

func main() {
    r := gin.Default()
    // 下面测试静态文件服务
    // 显示当前文件夹下所有文件/或指定文件
    r.StaticFS("/showDir", http.Dir("."))
    r.StaticFS("/files", http.Dir("/bin"))
    // static提供给定文件系统根目录中的文件
    // r.Static("/files", "/bin")
    r.StaticFile("/image", "./assets/demo.png")
    r.Run(":80")
}
http://localhost/showDir/
.DS_Store
.idea/
assets/
bin/
go.mod
...


http://localhost/files/
bash
cat
chmod
cp
csh
dash
...

重定向

package main

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

func main() {
    r := gin.Default()

    r.GET("/redirect", func(context *gin.Context) {
        // 支持内部和外部重定向
        context.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
    })

    r.Run(":80")
}

同步异步

  • goroutine 机制可以方便地实现异步处理。当在中间件或处理程序中启动新的Goroutines时,你不应该在原始上下文使用它,你必须使用只读的副本。
    package main
    
    import (
        "github.com/gin-gonic/gin"
        "log"
        "time"
    )
    
    func main() {
        r := gin.Default()
    
        // 异步
        r.GET("/long_async", func(context *gin.Context) {
            // goroutine 中只能使用只读上下文 c.Copy()
            cCp := context.Copy()
            go func() {
                time.Sleep(5 * time.Second)
    
                // 注意使用只读上下文
                log.Println("Done! in path" + cCp.Request.URL.Path)
            }()
        })
    
        // 异步
        r.GET("/long_sync", func(context *gin.Context) {
            time.Sleep(5 * time.Second)
    
            // 注意可以使用原始上下文
            log.Println("Done! in path" + context.Request.URL.Path)
        })
    
        r.Run(":80")
    }
    
    jartin@macbookpro1 elastic_notes % curl localhost/long_async
    [GIN] 2023/10/07 - 18:27:20 | 200 |      25.802µs |             ::1 | GET      "/long_async"
    2023/10/07 18:27:25 Done! in path/long_async
    
    
    jartin@macbookpro1 elastic_notes % curl localhost/long_sync
    2023/10/07 18:28:34 Done! in path/long_sync
    [GIN] 2023/10/07 - 18:28:34 | 200 |  5.001236448s |             ::1 | GET      "/long_sync"