fasthttp 中如何使用`Transfer-Encoding: chunked` 方式的流式内容输出

发布时间 2023-10-26 16:04:50作者: ahfuzhang

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


具体的思路是这样:通过 RequestCtx 的 Conn() 方法,获得 tcp 套接字。然后直接在 tcp 套接字上 Write 流式内容就行。
上代码:

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	syslog "log"
	"net"
	"os"
	"strconv"
	"time"

	"github.com/valyala/bytebufferpool"
	"github.com/valyala/fasthttp"
)

func FasthttpHandler(ctx *fasthttp.RequestCtx) {
    ctx.HijackSetNoResponse(true)  // 不使用  fasthttp 框架的输出
	ctx.Hijack(func(c net.Conn) {  // 这里会  go 出来一个协程执行  hijack 回调函数
		log.Println("do nothing")
	})
	//
	buf := bytebufferpool.Get()
	defer bytebufferpool.Put(buf)
	buf.Reset()
	buf.B = append(buf.B, "HTTP/1.1 200 OK\r\n"+
		"Server: fasthttp\r\n"+
		"Date: Thu, 26 Oct 2023 01:09:23 GMT\r\n"+
		"Content-Type: text/plain\r\n"+
		"Cache-Control: no-cache, no-store, must-revalidate\r\n"+
		"Transfer-Encoding: chunked\r\n"+
		"\r\n"...)
	conn := ctx.Conn()
	_, _ = conn.Write(buf.B)  // 写  http 头
    //
    for i := 0; i < 9; i++ {
           _, _ = conn.Write([]byte(fmt.Sprintf("%x\r\n%d \r\n", 1, i)))
			time.Sleep(1 * time.Second)
	}
    // 写入结束信息
    _, _ = conn.Write([]byte("0\r\n\r\n")
}

func main() {
	syslog.SetFlags(syslog.Lshortfile | syslog.LstdFlags)
	server := &fasthttp.Server{
		Handler: FasthttpHandler,
	}
	syslog.Fatalln(server.ListenAndServe(":8089"))
}

通过上述的方式虽然可以实现流式输出,但实际测试发现性能很差。
性能差的原因是每次请求都会 go 出来协程去执行 hijack 函数。

为此我改了一个版本,只要不设置 hijack 函数就不会产生协程去调用:
在 go.mod 中加上这样一句:

replace (
	github.com/valyala/fasthttp => github.com/ahfuzhang/compress v1.49.2
)

然后注释掉设置 hijack 的那几行:

    ctx.HijackSetNoResponse(true) 
	//ctx.Hijack(func(c net.Conn) {
	//	log.Println("do nothing")
	//})

希望对大家有用,have fun ?