入门
ConnectRPC(原 Connect)是一组用于构建浏览器和 gRPC 兼容 HTTP API 的库:编写简短的 Protocol Buffer schema 并实现业务逻辑,Connect 会生成代码来处理序列化、路由、压缩和内容协商,并为所有受支持的语言生成惯用、类型安全的客户端。在 Go 中,Connect 只有一个包(connect-go),短到可以一个下午读完。
多协议支持
Connect 服务器和客户端支持三种协议:
- gRPC:完全支持,包括流式、trailers 和错误详情。任何语言的 gRPC 客户端都可以调用 Connect 服务器,Connect 客户端也可以调用任何 gRPC 服务器(官方用扩展版的 Google 互操作性测试验证兼容性)
- gRPC-Web:直接支持,无需依赖 Envoy 之类的转换代理
- Connect 协议:基于 HTTP 的简单协议,可运行在 HTTP/1.1、HTTP/2 和 HTTP/3 上,默认同时支持 JSON 和二进制 Protobuf 编码
服务器默认接受全部三种协议的入口;客户端默认使用 Connect 协议,通过配置即可切换到 gRPC 或 gRPC-Web,无需修改其他代码。错误、header、trailer 和流式 API 都是协议无关的。
可以用 cURL 直接体验官方 demo 服务(Eliza 聊天机器人):
curl \
--header "Content-Type: application/json" \
--data '{"sentence": "I feel happy."}' \
https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say
压缩和序列化
Connect 支持多种压缩和序列化选项,协议层支持 identity、gzip、br、zstd 四种 Content-Encoding。默认情况下,Connect 处理程序使用标准库的 compress/gzip 提供 gzip 压缩。Connect 客户端默认发送未压缩的请求并请求 gzip 压缩的响应。如果您知道服务器支持 gzip,则还可以在客户端构建期间使用 WithSendGzip 选项来压缩请求。
// 服务端不需要进行什么选项设置 参考https://github.com/connectrpc/connect-go/issues/773
handler := greetv1connect.NewGreetServiceHandler(
&GreetServer{},
)
// 客户端配置压缩,默认
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"http://localhost:8080",
connect.WithSendGzip(),
)
自定义压缩实现:
type CustomCompressor struct{}
func (c *CustomCompressor) Name() string { return "custom" }
func (c *CustomCompressor) Compress(w io.Writer) (io.WriteCloser, error)
func (c *CustomCompressor) Decompress(r io.Reader) (io.Reader, error)
connect的brotli压缩 (第三方包,使用时通过
connect.WithSendCompression(brotli.Name)指定)
JSON 序列化注意点
- proto3 的 JSON 映射中
int64、fixed64、uint64会序列化为字符串(因为 JavaScript 的 Number 无法精确表示 64 位整数) - Connect 客户端会自动完成数值与字符串的转换,但用 cURL、浏览器
fetch等纯 HTTP 工具直接调用时要留意 - Connect 客户端和服务端会忽略未知 JSON 字段,这样 schema 可以平滑演进而不破坏旧客户端
Get 请求
Connect 支持通过 HTTP GET 进行无副作用的请求,这使得可以在浏览器、CDN 或代理中缓存某些类型的请求。
在 proto 文件中标记方法,使用[MethodOptions.IdempotencyLevel ](https://github.com/protocolbuffers/protobuf/blob/e5679c01e8f47e8a5e7172444676bda1c2ada875/src/google/protobuf/descriptor.proto#L795]
选项将其标记为无副作用。
service ElizaService {
rpc Say(SayRequest) returns (SayResponse) {
option idempotency_level = NO_SIDE_EFFECTS;
}
}
客户端启用 GET 请求:
client := elizav1connect.NewElizaServiceClient(
http.DefaultClient,
connect.WithHTTPGet(),
)
GET 请求会把消息编码进 URL query 参数(v1.20.0 起参数顺序符合规范建议):
GET /connectrpc.greet.v1.GreetService/Greet?encoding=json&message=%7B%22name%22%3A%22Buf%22%7D
仅当使用 Connect 协议(将 Connect 客户端与 Connect 服务一起使用)时,才支持此功能。将 gRPC 客户端与 Connect 服务器一起使用,或将 Connect 客户端与 gRPC 服务器一起使用时,所有请求都将使用 HTTP POST。如果您在与原版 gRPC 服务器通信时需要 HTTP GET 支持,则可以使用代理。Envoy 支持使用 Connect-gRPC Bridge 在 Connect 客户端和 gRPC 服务器之间进行转换。
进阶
HTTP/2 与 HTTP/3
- HTTP/2 支持:
服务端(h2c,明文 HTTP/2 升级)
package main
import (
"net/http"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
func main() {
mux := http.NewServeMux()
// Mount some handlers here.
server := &http.Server{
Addr: ":http",
Handler: h2c.NewHandler(mux, &http2.Server{}),
// Don't forget timeouts!
}
}
客户端
package main
import (
"crypto/tls"
"net"
"net/http"
"golang.org/x/net/http2"
)
func newInsecureClient() *http.Client {
return &http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
// If you're also using this client for non-h2c traffic, you may want
// to delegate to tls.Dial if the network isn't TCP or the addr isn't
// in an allowlist.
return net.Dial(network, addr)
},
// Don't forget timeouts!
},
}
}
Go 1.24+ 的标准库
net/http已原生支持 HTTP/2(TLS 下通过 ALPN 自动协商),connect-go v1.19.0 起也不再依赖golang.org/x/net/http2。上面的 h2c 方案仅在需要明文 HTTP/2(无 TLS)时才需要x/net/http2/h2c。
- HTTP/3 支持:
connect-go 本身不内置 HTTP/3,但 Connect 协议不依赖特定 HTTP 版本,官方 FAQ 推荐通过 quic-go
的 http3 包为服务器和客户端提供 HTTP/3 支持。
- CORS 配置:
corsHandler := cors.New(cors.Options{
AllowedMethods: []string{
http.MethodGet,
http.MethodPost,
},
AllowedHeaders: []string{
"Accept-Encoding",
"Content-Type",
"Connect-Protocol-Version",
"Connect-Timeout-Ms",
"X-User-Agent",
},
ExposedHeaders: []string{
"Grpc-Status",
"Grpc-Message",
"Grpc-Status-Details-Bin",
},
AllowedOrigins: []string{"*"},
})
handler := corsHandler.Handler(mux)
注意:
- 只允许请求头还不够,还必须 expose 响应头(
Grpc-Status、Grpc-Message、Grpc-Status-Details-Bin),否则浏览器端 Web 客户端拿不到错误码,只会看到"CORS 缺失"之类的协议错误 - 官方提供了 connectrpc.com/cors 包,可以更省心地配置
- 生产环境避免使用
*通配符(通配符在带凭据的请求下不生效)
Header 和 Trailer
处理 Headers:
func (s *Server) Greet(
ctx context.Context,
req *connect.Request[greetv1.GreetRequest],
) (*connect.Response[greetv1.GreetResponse], error) {
// 读取请求头
tenantID := req.Header().Get("Tenant-ID")
// 设置响应头
res := connect.NewResponse(&greetv1.GreetResponse{})
res.Header().Set("Version", "v1")
return res, nil
}
Headers 处理和grpc的差异和相似点:
- 相似点:
- 都使用 HTTP headers
- 都支持二进制 headers (使用 -Bin 后缀)
- 都有保留的 header 前缀限制
- 不同点:
- Connect 使用更简单的 API,直接通过
Request和Response结构访问 - gRPC 通常通过 context 传递 metadata
- Connect 的 header 命名更灵活,只要符合 HTTP header 规范即可
关键限制 Header 命名限制:
- 保留前缀:
Connect-和Grpc- - 只能包含 ASCII 字母、数字、下划线、连字符和点
- 值只能包含可打印 ASCII 和空格
二进制 Headers:
// 编码二进制头
res.Header().Set(
"Binary-Data-Bin",
connect.EncodeBinaryHeader([]byte("data")),
)
// 解码二进制头
if data, err := connect.DecodeBinaryHeader(
req.Header().Get("Binary-Data-Bin"),
); err == nil {
// 使用解码后的数据
}
Trailer
Trailer 必须在响应返回前设置
func (s *Server) Greet(
ctx context.Context,
req *connect.Request[greetv1.GreetRequest],
) (*connect.Response[greetv1.GreetResponse], error) {
res := connect.NewResponse(&greetv1.GreetResponse{})
// Trailer 必须在返回前设置
res.Trailer().Set("Greet-Version", "v1")
return res, nil
}
Trailer 限制:
- 一旦响应返回,无法再修改 Trailer
- 对于流式响应,可以在流结束前的任何时候设置 Trailer
- 建议在非流式响应中使用 Header 而不是 Trailer
和grpc的差异和相似点
- 编码差异:
// Connect 的 Trailer 处理
func (s *Server) Greet(ctx context.Context, req *connect.Request[greetv1.GreetRequest]) (*connect.Response[greetv1.GreetResponse], error) {
res := connect.NewResponse(&greetv1.GreetResponse{})
// Connect 会自动添加 Trailer- 前缀
res.Trailer().Set("Greet-Version", "v1")
return res, nil
}
- 协议差异:
- gRPC: 总是使用 HTTP trailers
- gRPC-Web: 将 trailers 编码在响应体的最后部分
- Connect:
- 对于一元调用:使用
Trailer-前缀的 HTTP headers - 对于流式调用:类似 gRPC-Web 的处理方式
- 对于一元调用:使用
- 使用建议:
- 一元调用建议使用 Headers 而不是 Trailers
- Trailers 主要用于流式调用,在发送消息后需要传递元数据的场景
关键限制
- Trailer 设置时机:
- 必须在响应返回前设置
- 一旦响应返回,无法修改 Trailer
- 流式响应可以在流结束前的任何时候设置
这些差异主要是为了提供更好的 HTTP 兼容性和更简单的 API 使用体验。
代理与负载均衡
一元 RPC 与流式 RPC 的代理要求不同:
- 一元 RPC:不需要端到端 HTTP/2,NGINX 无需特殊配置即可代理
- 流式 RPC:通常需要端到端 HTTP/2。NGINX 现在也支持,但默认未开启:
- 客户端侧启用
ngx_http_v2_module - 上游连接使用
proxy_http_version 2(NGINX 1.29.4 及以上)
- 客户端侧启用
Envoy、Apache 和 TCP 级负载均衡器(如 HAProxy)也都支持完整的 Connect 协议。
错误处理
- 标准错误处理:
if err != nil {
return nil, connect.NewError(
connect.CodeInvalidArgument,
fmt.Errorf("invalid request: %w", err),
)
}
错误模型说明:
- Connect 沿用 gRPC 的错误码体系(
Code*常量),而不是直接使用 HTTP 状态码——因为 gRPC 与 HTTP 状态码的映射是有损的,为了无缝支持 gRPC 协议只能统一 - 一元请求会映射为有意义的 HTTP 状态码(如 400、404、500);流式请求响应总是 HTTP 200,错误编码在响应体的最后一段(envelope 中)
- 错误详情:
// 创建带详情的错误
err := connect.NewError(
connect.CodeNotFound,
errors.New("resource not found"),
)
err.Meta().Set("resource-id", "123")
// 处理错误
if connectErr := new(connect.Error); errors.As(err, &connectErr) {
fmt.Println(connectErr.Code())
fmt.Println(connectErr.Meta().Get("resource-id"))
}
也可以使用标准错误详情机制(基于 google.rpc.Status 的 details 字段):
// 服务端附加结构化详情
detail := &myv1.ErrorDetail{Reason: "rate-limited"}
return nil, connect.NewError(
connect.CodeResourceExhausted,
errors.New("rate limit exceeded"),
).WithDetail(detail)
// 客户端提取
var d myv1.ErrorDetail
if connect.AsErrorDetail(err, &d) {
fmt.Println(d.Reason)
}
版本与生态现状(2026-09)
- connect-go:v1.x 稳定,最新 v1.20.0(2026-05,要求 Go 1.25+)。v1.19.0 起
protoc-gen-connect-go提供--simple标志,生成去掉 Request/Response 包装的简洁接口,并用 context 传递元数据;同时支持 Protobuf Editions 2024 - v2.0.0-alpha.1(2026-09):connect-go 的新主版本,只改 Go API 不改 wire protocol(gRPC 兼容性和生态不变),v1 继续完全支持,官方提供了自动化迁移工具
- connect-es(TypeScript/JavaScript):稳定,被 Buf 等公司在生产环境使用,支持浏览器和 Node.js
- 移动端:connect-swift 稳定;connect-kotlin 仍为 beta
- Python:connect-py 仍为 beta
- 官方 roadmap 在 GitHub discussions 置顶
注意事项
- 流式调用与服务器超时:
http.Server的ReadTimeout/WriteTimeout作用于整个请求周期,流式调用稍长就会被切断(报stream error: stream ID ...; INTERNAL_ERROR)。流式服务建议只设置ReadHeaderTimeout - 客户端超时:可用
connect.WithTimeout选项,会发送connect-timeout-msheader,服务器也会据此限制处理时间 - 保留前缀:
connect-前缀的 header 保留给 Connect 协议本身使用,业务自定义 header 应避开 - Web 端 JSON 调试:connect-es 客户端可通过
useBinaryFormat: false切换到 JSON 编码,浏览器网络面板里就能直接看到可读的 payload
Connect RPC 提供了简单而强大的 API 构建方式,既保持了与 gRPC 的兼容性,又提供了更现代的开发体验。通过合理使用其提供的功能,可以构建高效、可靠的微服务系统。