如何在ListenAndServeTLS前路由HTTPS请求并根据Host选证书与mux
实现基于Host动态选择证书与Mux的HTTPS服务
可以仅通过Go标准库的http包实现这个需求,核心思路是利用tls.Config的GetCertificate方法动态返回证书,同时自定义主Handler根据请求Host路由到对应Mux。
核心实现思路
- 动态证书选择:
tls.Config的GetCertificate方法会在TLS握手阶段被调用,此时能获取到客户端请求的ServerName(即请求的Host),据此返回对应的证书文件。 - 多Mux路由:为不同Host创建独立的
http.ServeMux,在主Handler中根据请求的r.Host将请求转发到对应Mux处理。
代码示例
package main import ( "crypto/tls" "net/http" "sync" ) // 缓存已加载的证书,避免重复读取文件 var certCache = struct { sync.RWMutex certs map[string]*tls.Certificate }{ certs: make(map[string]*tls.Certificate), } // 加载指定Host对应的证书 func loadCert(host string) (*tls.Certificate, error) { // 先读缓存 certCache.RLock() cert, exists := certCache.certs[host] certCache.RUnlock() if exists { return cert, nil } // 根据Host匹配证书路径,实际场景按需调整规则 certFile := host + ".crt" keyFile := host + ".key" newCert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, err } // 写入缓存 certCache.Lock() certCache.certs[host] = &newCert certCache.Unlock() return &newCert, nil } func main() { // 为不同Host创建独立的路由Mux muxHost1 := http.NewServeMux() muxHost1.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello from host1.example.com")) }) muxHost2 := http.NewServeMux() muxHost2.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello from host2.example.com")) }) // 主Handler:根据请求Host转发到对应Mux mainHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Host { case "host1.example.com": muxHost1.ServeHTTP(w, r) case "host2.example.com": muxHost2.ServeHTTP(w, r) default: http.Error(w, "Unknown host", http.StatusBadRequest) } }) // 配置TLS规则,核心是GetCertificate方法 tlsConfig := &tls.Config{ GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { // 利用TLS握手时的ServerName(即请求Host)加载对应证书 return loadCert(info.ServerName) }, } // 创建并启动HTTP服务 server := &http.Server{ Addr: ":443", Handler: mainHandler, TLSConfig: tlsConfig, } // 这里证书参数传空,因为TLSConfig已通过GetCertificate动态获取证书 err := server.ListenAndServeTLS("", "") if err != nil && err != http.ErrServerClosed { panic(err) } }
关键细节说明
- 证书缓存:用带互斥锁的缓存结构,避免每次TLS握手都重复读取证书文件,提升服务性能。
- SNI依赖:
info.ServerName来自客户端TLS握手时的SNI扩展,现代浏览器和HTTP客户端均支持该特性,确保能正确获取请求Host。 - Mux隔离:不同Host对应独立的Mux,可实现完全隔离的路由规则和业务逻辑。
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

