官方文档:https://gin-gonic.com/zh-cn/docs/
注:强烈建议没用过Gin的读者先阅读第一节:第一个Gin应用。
目录
- 一、HTTP/2 推送
- 二、JSONP
- 三、PureJSON
- 四、SecureJSON
- 五、总结
一、HTTP/2 推送
首先,以“04HTTP2server推送”为根目录,创建如下目录结构:
.
├── assets
│ └── app.js # 静态文件目录,存放 JavaScript 文件
├── main.go # Gin 示例代码
└── testdata
这个案例涉及openssl的安装,读者可自行搜索,这里仅给出其中一个教程:openssl安装教程。
注意:此示例下载light版本足够。
填充代码:
main.go,相对官方文档增加了对status变量的引用。
package main
import (
"html/template" // 引入 HTML 模板包
"log" // 引入日志包
"github.com/gin-gonic/gin" // 引入 Gin 框架
)
// 定义 HTML 模板,相对官方文档加了status的引用
var html = template.Must(template.New("https").Parse(`
<html>
<head>
<title>Https Test</title>
<script src="/assets/app.js"></script>
</head>
<body>
<h1 style="color:red;">Welcome, Ginner!</h1>
<p>Status: {{.status}}</p> <!-- 在页面中显示 status 变量 -->
</body>
</html>
`))
func main() {
r := gin.Default() // 创建 Gin 引擎,初始化默认中间件(如 Logger 和 Recovery)
// 指定静态文件目录
r.Static("/assets", "./assets")
// 解释:将 URL 路径 "/assets" 映射到本地文件系统的 "./assets" 目录。
// 当客户端请求 "/assets/app.js" 时,Gin 会在 "./assets/app.js" 文件中查找并返回该文件的内容。
r.SetHTMLTemplate(html) // 设置 HTML 模板,允许使用 c.HTML() 渲染模板
r.GET("/", func(c *gin.Context) { // 定义根路由的处理逻辑
if pusher := c.Writer.Pusher(); pusher != nil { // 检查当前请求是否支持服务器推送
// 使用 pusher.Push() 做服务器推送
if err := pusher.Push("/assets/app.js", nil); err != nil { // 推送 JavaScript 文件到客户端
log.Printf("Failed to push: %v", err) // 记录推送失败的日志
}
}
c.HTML(200, "https", gin.H{ // 返回 HTML 响应,状态码为 200,使用之前设置的模板
"status": "success",
})
})
// 监听并在 https://127.0.0.1:8080 上启动服务
r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") // 启动 HTTPS 服务
}
app.js
console.log("JavaScript file is loaded!"); // 输出加载成功的消息
在这个案例中,服务器使用 Gin 框架运行一个启用了 HTTPS 和 HTTP/2 推送的服务。客户端通过浏览器发出请求,服务器通过 HTTP/2 推送机制,将静态资源推送给客户端。你可以在浏览器的开发者工具中的 Console 看到日志输出,证明服务器推送资源成功。
HTTP/2 推送的目的是提高性能,减少客户端加载页面时的等待时间。它允许服务器主动发送资源(如 JavaScript、CSS 等),而不需要客户端先请求这些资源,如果不使用 HTTP/2 推送,客户端需要先加载 HTML 页面,然后再向服务器发出请求以获取其他资源。
为什么HTTP/2 推送需要openssl?
HTTP/2 在许多情况下只在安全的连接(HTTPS)上工作,OpenSSL 用于生成和管理 SSL/TLS 证书,以确保客户端和服务器之间的通信是加密的。简单来说,OpenSSL 让我们能够创建安全的 HTTPS 连接,这对于启用 HTTP/2 推送功能是必需的。
然后cd到testdata目录下,执行以下命令来生成自签名证书和私钥:
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.pem -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=IT/CN=localhost"
该命令中的字段解释:
C: 国家代码(CN 代表中国)
ST: 州/省(Beijing)
L: 城市(Beijing)
O: 组织(MyCompany)
OU: 部门(IT)
CN: 通用名称,通常是域名或本地主机名(localhost)
此命令会自动填写所有需要的信息,跳过所有提示。
返回到根目录,执行main,然后打开浏览器,访问 https://localhost:8080
或https://127.0.0.1:8080
。此时会出现安全警告,谷歌浏览器的话点高级-继续访问即可成功加载页面,然后按F12或右击-检查,点击控制台即可看到服务器向本地推送的信息,最终效果如图:
二、JSONP
以“05JSONP”为根目录,创建main.go,填充代码:
package main
import (
"net/http" // 导入 net/http 包,用于处理 HTTP 请求
"github.com/gin-gonic/gin" // 导入 Gin 框架
)
func main() {
// 创建一个默认的 Gin 路由引擎
r := gin.Default()
// 定义处理 /JSONP 路由的 GET 请求
r.GET("/JSONP", func(c *gin.Context) {
// 创建一个 map,表示要返回的数据
data := map[string]interface{}{
"foo": "bar", // 这里的 foo 键对应的值是 bar
}
// 判断请求中是否有 callback 查询参数
// 如果存在,将回调函数名添加到响应体中
// 如果请求为 /JSONP?callback=x,返回 x({"foo":"bar"})
c.JSONP(http.StatusOK, data) // 使用 JSONP 响应,状态码为 200
})
// 启动服务器,监听 0.0.0.0:8080
// 任何网络接口上的请求都会被接受
r.Run(":8080")
}
效果:
JSONP 技术到底有什么用?
JSONP(JSON with Padding)是一种解决浏览器同源政策(Same-Origin Policy)限制的技术,旨在允许网页从不同域的服务器获取数据。由于同源政策的安全限制,浏览器通常会阻止从一个域(例如 example.com)向另一个域(例如 api.example.net)发起 AJAX 请求,从而保护用户免受跨站脚本攻击(XSS)等安全问题。而JSONP 允许开发者绕过这种限制,通过动态插入 <script>
标签来请求外部数据。
JSONP 的关键作用在于使得开发者能够灵活地访问外部 API 或服务,尤其是在 CORS(跨域资源共享)尚未普及的时期。不过,由于 JSONP 存在如XSS 攻击等安全风险,现代开发中更推荐使用 CORS 来处理跨域请求。
三、PureJSON
以“08PureJSON”为根目录,创建main.go,填充代码:
package main
import (
"github.com/gin-gonic/gin" // 导入 Gin 框架
)
func main() {
// 创建一个默认的 Gin 路由引擎
r := gin.Default()
// 定义处理 /json 路由的 GET 请求
r.GET("/json", func(c *gin.Context) {
// 使用 JSON 响应,特殊 HTML 字符会被转义为 Unicode
// 例如,< 会变为 \u003c
c.JSON(200, gin.H{
"html": "<b>Hello, world!</b>", // 返回包含 HTML 标签的字符串
})
})
// 定义处理 /purejson 路由的 GET 请求
r.GET("/purejson", func(c *gin.Context) {
// 使用 PureJSON 响应,返回字面字符而非 Unicode
// 返回值中的 HTML 标签不会被转义
c.PureJSON(200, gin.H{
"html": "<b>Hello, world!</b>", // 返回的 HTML 标签按字面字符形式保留
})
})
// 启动服务器,监听 0.0.0.0:8080
r.Run(":8080")
}
功能很简单,不做赘述,直接上效果:
四、SecureJSON
以“10SecureJSON”为根目录,创建main.go,填充代码:
package main
import (
"net/http" // 导入 net/http 包
"github.com/gin-gonic/gin" // 导入 Gin 框架
)
func main() {
// 创建一个默认的 Gin 路由引擎
r := gin.Default()
// 你也可以使用自己的 SecureJSON 前缀
// r.SecureJsonPrefix(")]}',\n")
// 定义处理 /someJSON 路由的 GET 请求
r.GET("/someJSON", func(c *gin.Context) {
// 定义一个字符串切片,包含一些名称
names := []string{"lena", "austin", "foo"}
// 使用 SecureJSON 响应,将数组值返回,并在前面预置 "while(1);"
// 这可以防止 JSON 劫持攻击
// 将输出:while(1);["lena","austin","foo"]
c.SecureJSON(http.StatusOK, names)
})
// 启动服务器,监听 0.0.0.0:8080
r.Run(":8080")
}
c.SecureJSON(http.StatusOK, names)
返回 SecureJSON 格式的响应。当返回的结构是数组时,Gin 会自动在响应体前添加 while(1);
,这可以防止 JSON 劫持攻击。最终的输出格式为:while(1);["lena","austin","foo"]
。
JSON 劫持是一种攻击方式,攻击者可以通过在网页中嵌入恶意脚本来盗取用户数据。通过在 JSON 响应前添加特定的前缀,可以确保响应在被浏览器解析时不被执行。
效果:
五、总结
在本系列示例中,我们深入探讨了 Gin 框架中多种 JSON 处理技术以及 HTTP/2 的服务器推送功能。HTTP/2
允许服务器主动向客户端推送资源,减少页面加载时间,并通过使用 SSL/TLS 加密确保数据安全。JSONP
实现了跨域请求但存在安全隐患,现代应用通常推荐使用 CORS。PureJSON
则让我们能够返回字面字符而非 Unicode 转义的特殊 HTML 字符,适合需要直接呈现 HTML 内容的场景,尤其是处理用户生成的内容时。SecureJSON
用于防止 JSON 劫持攻击,提高了数据在浏览器中的安全性。这些技术各具特色,开发者应根据具体需求选择合适的处理方式,以确保功能性与安全性的平衡。