前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「Go框架」使用gin实现http的分块传输及原理分析

「Go框架」使用gin实现http的分块传输及原理分析

作者头像
Go学堂
发布2023-08-28 14:47:18
8910
发布2023-08-28 14:47:18
举报
文章被收录于专栏:Go工具箱

大家好,我是渔夫子。

今天,跟大家聊聊gin框架中是如何实现分片输出的。主要分以下4点:

  • 分片输出的效果图
  • gin实现分片传输代码
  • http分片传输的基础:transfer-encoding
  • gin实现分片传输原理

效果图

首先看下分片输出的效果图:

gin分片传输实现代码

上面的效果图中,网页中的内容不断的输出。在gin中是主要是利用了Flush函数实现的。如下代码:

代码语言:javascript
复制
package main

import (
 "fmt"
 "net/http"
 "time"

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

func main() {
 r := gin.Default()
 r.GET("/test_stream", func(c *gin.Context) {
  w := c.Writer
  header := w.Header()
  header.Set("Content-Type", "text/html")
  w.WriteHeader(http.StatusOK)
  w.Write([]byte(`
   <html>
     <body>
  `))
  w.(http.Flusher).Flush()

        // 这里对每次循环输出都进行Flush刷新输出
  for i := 0; i < 10; i++ {
   w.Write([]byte(fmt.Sprintf(`
    <h3>%d</h3>
   `, i)))
   //w.Flush()
   w.(http.Flusher).Flush()
   time.Sleep(time.Duration(1)*time.Second)
  }
        
  w.Write([]byte(`
   
     </body>
   </html>
  `))
  w.(http.Flusher).Flush()
 })

 r.Run("127.0.0.1:8080")
}

这里主要就是利用了第22行中的w.(http.Flusher).Flush()。这里的Flush本质上就是将Write的内容立即输出到客户端的意思。

那么,为什么通过Flush就能实现上述效果呢?

分块传输的基础:http的 transfer-encoding:chunked 协议

分块传输的基础就是http中的transfer-encoding:chunked协议。在http响应报文中用头字段“Transfer-Encoding: chunked”,表示响应中的body不是一次性发送完毕,而是分成了许多的块(chunk)逐个发送,直到发送完毕。

分块传输的编码规则如下:1)每个分块包含两个部分,<长度头>和&<数据块> 2) <长度头>是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度 3) <数据块>紧跟在<长度头>后,最后也用 CRLF 结尾,但数据不包含 CRLF 4)最后用一个长度为 0 的块表示数据传输结束,即“0\r\n\r\n”。

image.png

为什么通过Flush函数就能实现分块传输

到了本篇的核心部分了,为什么在gin中通过Flush函数就能实现分块传输了呢?首先,在gin框架中正常的输出是通过Context.Writer.Write函数进行输出的。而Writer是net/http包中的response对象,该response对象包含了本次http的连接对象conn。以下是从Context.Writer对象到conn对象的一个层级关系,如下:

Context.Writer对象指向了response对象,response对象中包含一个缓冲的Writer对象w,w的底层输出对象时chunkWriter对象cw,cw又指向了本次的http连接对象response.conn。

那么,基于这个层级结构,Context.Writer.Write的写入过程如下:

我们简化一些,就是Context.Writer.Write先将内容写入到缓冲区w中,然后等本次请求逻辑处理完毕,再调用缓存区w的Flush功能,将缓冲区w中的内容写入到cw中,然后调用cw的flush功能,这时就写入了http的响应头Content-Length为写入数据的长度,并且将内容通过conn.bufw.flush输出给客户端。

简化一下gin的输出过程:内容先写入到缓冲区,最后将缓冲区的内容一次性全部输出给客户端。

划重点,Content-Length头部的输出是和分块传输的主要区别

接下来再看分块输出。

其实现的思想就是通过http的Transfer-Encoding: chunked头告诉客户端,服务端的内容要分块传输了。然后服务端就将内容先写入缓冲区,然后立即使用Flush函数将缓冲区的内容输出到客户端。这就是一个块的输出。然后依次循环写入,Flush刷新输出这个过程。

下图是gin中分块传输的流程图:

在分块输出的时候,在response.cw.flush阶段,可以判定到该请求还未处理完毕(在net/http包中,本次请求处理完毕才会调用一个finishRequest的函数以标识本次请求处理完毕),所以会自动写入一个http的头信息: Transfer-Encoding: chunked。当客户端收到该响应时,检测到header中的chunked,就表示本次响应还未结束,会继续接收后续的响应内容。

简化一下gin的分块传输流程如下:

总结

当输出内容太大时,就可以使用分块传输的方式。分块传输是基于http的Transfer-Encoding: chunked协议进行的。当客户端接收到该响应头时,就知道服务端的内容还没有传输完,不能关闭本次http连接。另一方面,gin框架通过Flush函数将缓冲区的内容及时输出来实现分块传输。

---特别推荐---

特别推荐:一个专注go项目实战、项目中踩坑经验及避坑指南、各种好玩的go工具的公众号,「Go学堂」,专注实用性,非常值得大家关注。点击下方公众号卡片,直接关注。关注送《100个go常见的错误》pdf文档、经典go学习资料。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Go学堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 效果图
  • gin分片传输实现代码
  • 分块传输的基础:http的 transfer-encoding:chunked 协议
  • 为什么通过Flush函数就能实现分块传输
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档