关于go:Go框架使用gin实现http的分块传输及原理分析

3次阅读

共计 2384 个字符,预计需要花费 6 分钟才能阅读完成。

大家好,我是渔夫子。

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

  • 分片输入的效果图
  • gin 实现分片传输代码
  • http 分片传输的根底:transfer-encoding
  • gin 实现分片传输原理

效果图

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

gin 分片传输实现代码

下面的效果图中,网页中的内容一直的输入。在 gin 中是次要是利用了 Flush 函数实现的。如下代码:

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”。

为什么通过 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 学习材料。

正文完
 0