在 Go 1.24 中为服务器使用 HTTP/2 Cleartext
Using HTTP/2 Cleartext for a server in Go 1.24

原始链接: https://www.clarityboss.com/blog/go-http2-cleartext-h2c-cloud-run

为了解决 Google Cloud Run 中 Server-Sent Event (SSE) 数据流丢失客户端断开信号的问题,作者将服务迁移到了 HTTP/2。由于 Cloud Run 在边缘层终止了 TLS,因此后端需要使用“基于先验知识的 HTTP/2”(h2c)。 此前,在 Go 中实现 h2c 需要外部的 `golang.org/x/net/http2/h2c` 包以及繁琐的包装配置。但随着 Go 1.24 的发布,这一过程得到了简化。开发者现在只需在标准的 `http.Server` 配置中直接设置 `srv.Protocols.SetUnencryptedHTTP2(true)`,即可原生启用 h2c。 更新 Go 代码后,必须配置 Cloud Run 服务以支持 h2c 协议,这可以通过 Terraform 进行管理。使用 `curl --http2-prior-knowledge` 可以直接测试该实现。此次迁移确保了 HTTP/2 的持久连接特性能够将断开事件正确地传回服务器,从而提高了长连接 SSE 数据流的可靠性。

Hacker News 最新 | 往日 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 在 Go 1.24 版本中为服务器使用 HTTP/2 Cleartext (clarityboss.com) 9 点,由 dan_sbl 在 1 小时前发布 | 隐藏 | 往日 | 收藏 | 讨论 | 帮助 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

In our application, we use long-lived server-sent event streams (SSE). These are set up to have a really long timeout and lifetime - 15 minutes in our setup. However, Google Cloud Run has a known issue in that client disconnects are not propagated to Cloud Run when using HTTP/1.1 to communicate with the backend service. Thus, I started looking into using HTTP/2 for services.

Cloud Run terminates TLS at the frontend, but can forward traffic as either HTTP/1.1 or HTTP/2 cleartext (h2c) traffic. Normally, HTTP/2 always uses TLS, but HTTP clients and servers can often be configured to use a cleartext version of the protocol. Cloud Run also makes you select the protocol to be used. This is basically the “HTTP/2 with Prior Knowledge” setup noted in RFC 9113, section 3.3.

Configuring a Go server for HTTP/2 cleartext

Our first cut of h2c support predated the Go 1.24 changes I will go into more detail below. Most posts and guides on the internet will point you toward the old outdated approach, but I’ve included it below so it is easier to see the before and after, and migrate your own code if you need to.

Before Go 1.24 (old approach)

To use h2c, you had to use the golang.org/x/net/http package, and go through a convoluted setup.

import (
	"net/http"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"
)

handler := ...

h2s := &http2.Server{}
handler = h2c.NewHandler(handler, h2s)
srv := &http.Server{
	Addr:              fmt.Sprintf("%s:%d", "", 9888),
	Handler:           handler,
	ReadHeaderTimeout: 5 * time.Second,
	ReadTimeout:       10 * time.Second,
	WriteTimeout:      35 * time.Second,
	
	IdleTimeout: 620 * time.Second,
}
err = http2.ConfigureServer(srv, h2s)
if err != nil {
    ...
}

Go 1.24+ (new approach)

With Go 1.24, no x/net/http2/h2c wrapper is needed anymore, and the setup is a lot more readable. You can configure protocols directly on http.Server.

Reference: Go 1.24 net/http release notes.

handler := ...
srv := &http.Server{
	Addr:              fmt.Sprintf("%s:%d", "", 9888),
	Handler:           handler,
	ReadHeaderTimeout: 5 * time.Second,
	ReadTimeout:       10 * time.Second,
	WriteTimeout:      35 * time.Second,
	
	IdleTimeout: 620 * time.Second,
}
srv.Protocols = new(http.Protocols)
srv.Protocols.SetHTTP1(true)
srv.Protocols.SetUnencryptedHTTP2(true)

Testing locally before deployment

Testing is pretty simple. This command will fail if HTTP/2 cleartext isn’t set up properly.

curl -i --http2-prior-knowledge http://localhost:9888

Terraform Cloud Run configuration

Once your Go service can speak HTTP/2 cleartext, it needs to be configured in Cloud Run appropriately. If you’re doing it via the Cloud Run console, follow the HTTP/2 for services documentation. We’re doing it via terraform - this snippet below gives a general idea of the configuration.


resource "google_cloud_run_v2_service" "api" {
  name                 = "api"
  location             = var.primary_region
  invoker_iam_disabled = true
  ingress              = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"

  template {
    containers {
      ...
      ports {
        name           = "h2c"
        container_port = 9888
      }
    }

    
    max_instance_request_concurrency = 200
    
    timeout = "900s"
  }

  lifecycle {
    ignore_changes = [client, client_version, template[0].revision]
  }
}

No real changes were needed at the load balancer level, since HTTPS is used to communicate with Serverless NEGs, and it seems like it properly upgrades the connection to HTTP/2 during that negotiation. Also, the default timeouts for serverless backends is 60 minutes, not 30 seconds like some other backends.

联系我们 contact @ memedata.com