流式http使用示例

 提示:转载请注明原文链接

 本文链接:https://360us.net/article/109.html

流式 HTTP 是指在一次 HTTP 请求/响应过程中,服务端或客户端能够逐步发送或接收数据,而不是等待整个内容准备好后一次性传输。这种模式适用于大文件传输、实时日志推送、SSE(Server-Sent Events)、视频流等场景。

下面我们将从 HTTP/1.1、HTTP/2、HTTP/3 三个协议版本的角度详细解释流式传输的机制,并结合 Go 语言 编写服务端示例代码,说明其原理和实现方式。


一、HTTP/1.1 的流式传输

1.1 原理

  • HTTP/1.1 支持 Chunked Transfer Encoding(分块传输编码)。
  • 服务端可以在不知道内容总长度的情况下,通过 Transfer-Encoding: chunked 头部逐块发送响应体。
  • 每个块以十六进制长度开头,后跟 \r\n,然后是数据,再以 \r\n 结尾。
  • 最终用一个长度为 0 的块表示结束。

1.2 Go 实现示例(HTTP/1.1 流式响应)

package main

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

func streamHandler(w http.ResponseWriter, r *http.Request) {
	// 设置响应头,启用分块传输(无需显式设置,Go 自动处理)
	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)

	// 获取底层的 flusher,用于强制刷新缓冲区
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}

	for i := 1; i <= 5; i++ {
		fmt.Fprintf(w, "Chunk %d\n", i)
		flusher.Flush() // 立即发送已写入的数据
		time.Sleep(1 * time.Second)
	}
}

func main() {
	http.HandleFunc("/stream", streamHandler)
	log.Println("Server listening on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

✅ 注意:必须使用 http.Flusher 接口调用 Flush(),否则 Go 的默认缓冲区会延迟发送数据。


二、HTTP/2 的流式传输

2.1 原理

  • HTTP/2 是基于 二进制帧(frames) 的多路复用协议。
  • 一个连接上可以同时存在多个流(streams),每个流由唯一 ID 标识。
  • 数据通过 DATA 帧发送,支持服务器主动推送(Server Push)流式响应
  • 不再需要 Transfer-Encoding: chunked,因为帧本身就是分块的。

2.2 Go 实现(自动支持 HTTP/2)

Go 的 net/http 包在 TLS 启用时自动协商 HTTP/2(需使用 http.ListenAndServeTLS)。

package main

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
	"time"
)

func streamHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}

	for i := 1; i <= 5; i++ {
		fmt.Fprintf(w, "HTTP/2 Chunk %d\n", i)
		flusher.Flush()
		time.Sleep(1 * time.Second)
	}
}

func main() {
	// 使用自签名证书(仅用于测试)
	cert := "server.crt"
	key := "server.key"

	// 启动 HTTPS 服务(HTTP/2 需要 TLS)
	server := &http.Server{
		Addr: ":8443",
		TLSConfig: &tls.Config{
			NextProtos: []string{"h2"}, // 显式启用 HTTP/2
		},
	}

	http.HandleFunc("/stream", streamHandler)
	log.Println("HTTP/2 Server listening on https://localhost:8443")
	log.Fatal(server.ListenAndServeTLS(cert, key))
}

🔐 生成自签名证书(测试用):

go run $(go env GOROOT)/src/crypto/tls/generate_cert.go --host localhost

✅ HTTP/2 下依然使用 Flusher,但底层传输是帧而非 chunked。


三、HTTP/3 的流式传输

3.1 原理

  • HTTP/3 基于 QUIC 协议(UDP + TLS 1.3 + 多路复用)。
  • 继承 HTTP/2 的语义(如流、头部压缩),但解决队头阻塞问题。
  • 每个“流”在 QUIC 层是独立的,丢包不影响其他流。
  • 同样支持流式响应,无需 chunked 编码。

3.2 Go 实现(需第三方库)

标准库 net/http 尚未原生支持 HTTP/3(截至 Go 1.23)。需使用社区库,如:

示例代码(HTTP/3 流式服务)

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/quic-go/quic-go/http3"
)

func streamHandler(w http3.ResponseWriter, r *http3.Request) {
	w.Header().Set("Content-Type", "text/plain")

	for i := 1; i <= 5; i++ {
		fmt.Fprintf(w, "HTTP/3 Chunk %d\n", i)
		w.Flush() // http3.ResponseWriter 内置 Flush
		time.Sleep(1 * time.Second)
	}
}

func main() {
	// 使用 quic-go 提供的 HTTP/3 服务器
	server := &http3.Server{
		Addr: ":4433",
	}

	http3.HandleFunc("/stream", streamHandler)
	log.Println("HTTP/3 Server listening on https://localhost:4433")
	log.Fatal(server.ListenAndServeTLS("server.crt", "server.key"))
}

⚠️ 需要安装:

go get github.com/quic-go/quic-go

🌐 客户端需支持 HTTP/3(如 Chrome、curl >= 7.66 with --http3)。


四、对比总结

特性 HTTP/1.1 HTTP/2 HTTP/3
传输层 TCP TCP + TLS UDP + QUIC
多路复用 ❌(需多个连接) ✅(单连接多流) ✅(QUIC 流)
流式机制 Chunked Encoding DATA 帧 QUIC 流 + DATA 帧
队头阻塞 存在 连接级无,但 TCP 有 无(QUIC 解决)
Go 支持 标准库原生 标准库(需 TLS) 第三方库(quic-go)
是否需 Flush ✅ 必须 ✅ 推荐 ✅(部分自动)

五、最佳实践建议

  1. 通用流式接口:无论协议版本,都应使用 http.Flusher(或对应接口)确保及时发送。
  2. 错误处理:流式过程中客户端可能断开,需检测 r.Context().Done()
  3. 性能:避免频繁小块写入,适当缓冲(如每 1KB flush 一次)。
  4. 协议升级:优先部署 HTTPS 以启用 HTTP/2/3,提升并发性能。

六、扩展:SSE(Server-Sent Events)示例(基于 HTTP/1.1)

func sseHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")

	flusher := w.(http.Flusher)

	i := 0
	for {
		select {
		case <-r.Context().Done():
			return
		default:
			fmt.Fprintf(w, "data: Message %d\n\n", i)
			flusher.Flush()
			i++
			time.Sleep(2 * time.Second)
		}
	}
}

前端可通过 EventSource 接收:

const es = new EventSource('/sse');
es.onmessage = e => console.log(e.data);

以上由AI总结而来,AI写的还不错。。。

七、关于HTTP3的本地调试

http3在本地测试不太好搞,并不能像http、 http2的https那样,生成自签名证书,然后浏览器忽略警告就可以,还需做一些其他的设置。

首先证书得是系统信任的。

为了方面这里使用了一个工具:mkcert - https://github.com/FiloSottile/mkcert

使用方法:

mkcert -install # 安装CA

mkcert localhost 127.0.0.1 ::1 #生成本地测试的证书文件

mkcert -uninstall # 写在CA

用上面生成的证书文件来启动http3本地服务器。

浏览器直接访问还没用,不管是firefox还是chrome都不会用http3来连接,会提示连接不了。

然后得让浏览器强制使用http3协议来连接。

win系统下面的命令:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --origin-to-force-quic-on=127.0.0.1:443 https://127.0.0.1:443

强制chrome使用http3连接才行,edge浏览器参数一样。firefox没找到相关启动参数,就没法了。

在142版的chrome和edge测试通过。


一些示例demo代码:https://github.com/ilaziness/gopkg/tree/main/net/http/stream


本文链接:https://360us.net/article/109.html