http 协议的chunck概念

有时候,Web服务器生成HTTP Response是无法在Header就确定消息大小的,这时一般来说服务器将不会提供Content-Length的头信息,而采用Chunked编码动态的提供body内容的长度。 进行Chunked编码传输的HTTP Response会在消息头部设置:

Transfer-Encoding: chunked

表示Content Body将用Chunked编码传输内容。

Chunked编码使用若干个Chunk串连而成,由一个标明长度为0的chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定下一段正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF)隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些附加的Header信息

golang net/http 实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package main

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

	"github.com/xxjwxc/public/tools"
)

// NlpResp 返回信息
type NlpResp struct {
	// bool state = 1; // 状态
	MatchID    int      `json:"matchId"`
	Audio      string   `json:"audio"`
	Hai        string   `json:"hai"`
	Text       string   ` json:"text"`
	RemainText []string `json:"remainText"`
}

func httpServer() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		flusher, ok := w.(http.Flusher)
		if !ok {
			panic("expected http.ResponseWriter to be an http.Flusher")
		}
		w.Header().Set("X-Content-Type-Options", "nosniff")
		for i := 1; i <= 20; i++ {
			tmp := NlpResp{
				MatchID: i,
				Audio:   "aaaaaaaaaaaaaaaaaaaaa",
				Hai:     "hhhhhhhhhhhhhhhhhhhh",
				Text:    "test",
			}
			fmt.Fprintf(w, tools.GetJSONStr(tmp, false)+"\n")
			flusher.Flush() // Trigger "chunked" encoding and send a chunk...
			time.Sleep(1 * time.Second)
		}
	})

	log.Print("Listening on localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func httpClien() {
	resp, err := http.Get("http://localhost:8080")
	if err != nil {
		// handle error
	}

	defer resp.Body.Close()

	// chunkedReader := httputil.NewChunkedReader(resp.Body)

	buf := make([]byte, 40960)
	for {
		n, err := resp.Body.Read(buf)
		fmt.Println(n, err)
		if n != 0 || err != io.EOF { // simplified
			fmt.Println(string(buf[:n]))
		}
		time.Sleep(1 * time.Second)
	}
}

func main() {
	go httpServer()
	go httpClien()

	time.Sleep(100 * time.Second)
}

gin 实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package main

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

	"github.com/gin-gonic/gin"
	"github.com/xxjwxc/public/tools"
)

// NlpResp 返回信息
type NlpResp struct {
	// bool state = 1; // 状态
	MatchID    int      `json:"matchId"`
	Audio      string   `json:"audio"`
	Hai        string   `json:"hai"`
	Text       string   ` json:"text"`
	RemainText []string `json:"remainText"`
}

func httpServer() {
	r := gin.Default()
	// your working routes
	r.GET("/", func(c *gin.Context) {
		w := c.Writer
		flusher, ok := w.(http.Flusher)
		if !ok {
			panic("expected http.ResponseWriter to be an http.Flusher")
		}
		w.Header().Set("X-Content-Type-Options", "nosniff")
		for i := 1; i <= 20; i++ {
			tmp := NlpResp{
				MatchID: i,
				Audio:   "aaaaaaaaaaaaaaaaaaaaa",
				Hai:     "hhhhhhhhhhhhhhhhhhhh",
				Text:    "test",
			}
			fmt.Fprintf(w, tools.GetJSONStr(tmp, false)+"\n")
			flusher.Flush() // Trigger "chunked" encoding and send a chunk...
			time.Sleep(1 * time.Second)
		}
	})

	log.Print("Listening on localhost:8080")
	r.Run(":8080")
}

func httpClien() {
	resp, err := http.Get("http://localhost:8080")
	if err != nil {
		// handle error
	}

	defer resp.Body.Close()

	// chunkedReader := httputil.NewChunkedReader(resp.Body)

	buf := make([]byte, 40960)
	for {
		n, err := resp.Body.Read(buf)
		fmt.Println(n, err)
		if n != 0 || err != io.EOF { // simplified
			fmt.Println(string(buf[:n]))
		}
		time.Sleep(1 * time.Second)
	}
}

func main() {
	go httpServer()
	go httpClien()

	time.Sleep(100 * time.Second)
}

更多:

xxjwxc