Golang实现HTTPS、多主机与自动证书管理的最佳实践
Golang实现HTTPS、多主机与自动证书管理的最佳实践 我在此寻求帮助,是因为我难以找到关于如何将 autocert 用于 net/http 多主机的示例。我并非想要一个复制粘贴的解决方案,而是真心希望看到并探索实现此目标的方法。
使用 certbot 的 NGINX 反向代理方案不符合我的需求。我需要通过 net/http + autocert 或类似的 ACME2 autocert 等效方案来实现。
为什么 NGINX 不是解决方案: 我需要“实时”处理请求日志记录,并且不能中断请求的流程。通过 Alice 中间件,我启动一个 Go 协程来独立处理请求日志记录(传递给一个独立的微服务),这与正常的 HTTP 流程分开。这就是 NGINX 方案行不通的原因。
我花了数周时间研究 chi、echo(作为新用户无法添加更多链接)等框架的文档、示例和讨论,但它们似乎主要面向 API 开发。虽然我将在其他服务中使用 chi 来处理任何 API 需求,但要找到一个使用 autocert 实现 net/http + 多主机的解决方案确实很困难。
多个主机通过 http.FileServer 提供静态内容服务,任何 API 路径则被重定向到其他服务。
像 Caddy 等方案在性能上相较于 net/http 有所不足,因此也不是可行的解决方案。
创建一个 HTTP 解决方案对我来说没有问题,但在升级到 HTTPS 解决方案时,我的“多主机”需求遇到了瓶颈,因此我向 Go 社区寻求帮助。
我接触 Go 的时间相对较短(约 1 年经验),尽管我从事软件开发已有 20 多年。
任何指点、链接或建议都将不胜感激。
更多关于Golang实现HTTPS、多主机与自动证书管理的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我上面写的大部分内容,根据我的理解,结果都是错误的。我将保留之前的示例,因为它们可能仍然有助于理解这个问题。
本质上,我问题中的“多主机”部分是我误解的根源:
func main() {
var m *autocert.Manager
var httpsSrv *http.Server
// 设置中间件
reqProcRoute := alice.New(middleware.VMonitor).Then(router.Router)
prod := true
log.Printf("PROD: %v",prod )
if prod {
m = &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("site1.dab", "www.site1.dab", "site2.dab", "www.site2.dab"),
Cache: autocert.DirCache("./certs-cache"),
// 如何使用 Letsencrypt 的测试服务器进行测试
Client: &acme.Client{
DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory",
},
}
httpsSrv = makeHTTPServer()
httpsSrv.Addr = ":443"
httpsSrv.Handler = reqProcRoute
httpsSrv.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
go func() {
fmt.Printf("Starting HTTPS server on %s\n", httpsSrv.Addr)
err := httpsSrv.ListenAndServeTLS("", "")
if err != nil {
log.Fatalf("HTTP TLS Server failed with %s", err)
}
}()
}
// 端口 :80 上的传入请求将在此处处理
var httpSrv = &http.Server{
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 120 * time.Second,
Addr: ":80",
Handler: reqProcRoute,
}
handleRedirect := func(w http.ResponseWriter, r *http.Request) {
newURI := "https://" + r.Host + r.URL.String()
http.Redirect(w, r, newURI, http.StatusMovedPermanently)
}
//如果 Autocert.Manager != nil,则处理 letsencrypt 的 :80 流量
if m != nil {
httpSrv.Handler = m.HTTPHandler(httpSrv.Handler)
} else {
// 将所有 HTTP 重定向到 HTTPS。注意:这对于 Letsencrypt 的挑战将不起作用
httpSrv.Handler = http.HandlerFunc(handleRedirect)
}
log.Fatal(httpSrv.ListenAndServe())
} // main 函数结束。
请注意 redirectHandler,它将解析任何请求的 ‘host’ 值,无论有多少个主机,因此一个适用于所有主机的单一重定向处理程序就能工作。我之前误以为需要单独处理“每个主机”!
现在,“多主机”问题已经解决,我现在只剩下一直以来的核心问题:
如何将所有 HTTP 流量重定向到 HTTPS,同时仍然允许 Letsencrypt 通过 HTTP 访问以进行其 HTTP 挑战?
虽然感谢你的回复,但和我见过的大多数类似例子一样,例如这个示例,它没有处理多个主机从HTTP到HTTPS的重定向问题。
对于那些可能关注这个讨论的人,以下是我目前已有的部分代码片段和结构说明:
每个主机的根目录/public_html目录都位于一个sites目录下:sites -> siteA,sites -> siteB 等等。这是为每个主机提供静态内容的地方。
我创建了我的路由器/mux:package router
package router
type dmux struct {
StaticRoutes map[string]http.Handler
APIRoutes map[string]http.HandlerFunc
}
var Router = dmux{
StaticRoutes: make(map[string]http.Handler),
APIRoutes: make(map[string]http.HandlerFunc),
}
dmux结构体上的LoadStaticSites方法:
func (mx dmux) LoadStaticSites() error {
fileServer := http.FileServer(http.Dir(path.Join("/var/www/sites", "siteA")))
mx.StaticRoutes["siteA"] = fileServer
fileServer = http.FileServer(http.Dir(path.Join("/var/www/sites", "siteB")))
mx.StaticRoutes["siteB"] = fileServer
return nil
}
路由器/mux的ServeHTTP方法是:
func (mx dmux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if staticHandler := mx.StaticRoutes[r.Host]; staticHandler != nil {
// This check is done here so that every hosted site will have access to the API handlers
if APIHandler := mx.APIRoutes[r.URL.Path]; APIHandler != nil {
// Check for an API Route
APIHandler.ServeHTTP(w, r)
} else {
staticHandler.ServeHTTP(w, r)
}
} else {
// Handle host names for which no handler is registered
http.Error(w, "Forbidden", 403) // Or Redirect?
}
}
来自main.go:package main
func init() {
err := router.Router.LoadStaticSites()
if err != nil {
fmt.Println("Failed to load sites.")
panic(err)
}
// Load API routes
router.Router.APIRoutes[`/email`] = apiHandlers.Email
router.Router.APIRoutes[`/samples`] = apiHandlers.Samples
}
func main() {
var m *autocert.Manager
var httpsSrv *http.Server
prod := true
log.Printf("PROD: %v",prod )
if prod {
m = &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("site1.dab", "www.site1.dab", "site2.dab", "www.site2.dab"),
Cache: autocert.DirCache("./certs-cache"),
// Use Letsencrypts staging server for testing
Client: &acme.Client{
DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory",
},
}
httpsSrv = makeHTTPServer()
httpsSrv.Addr = ":443"
httpsSrv.Handler = router.Router
httpsSrv.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
go func() {
fmt.Printf("Starting HTTPS server on %s\n", httpsSrv.Addr)
err := httpsSrv.ListenAndServeTLS("", "")
if err != nil {
log.Fatalf("HTTP TLS Server failed with %s", err)
}
}()
}
var httpSrv *http.Server
if prod {
httpSrv = makeHTTPToHTTPSRedirectServer()
} else {
log.Println("Running HTTP")
httpSrv = makeHTTPServer()
httpSrv.Handler = router.Router
}
//allow autocert handle Let's Encrypt callbacks over http
if m != nil {
httpSrv.Handler = m.HTTPHandler(httpSrv.Handler)
}
httpSrv.Addr = ":80"
log.Fatal(httpSrv.ListenAndServe())
} // End of main
func makeHTTPServer() *http.Server {
return &http.Server{
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 120 * time.Second,
}
}
当我尝试运行HTTPS时,这个函数会出现各种问题,例如循环重定向……HTTP运行正常。
func makeHTTPToHTTPSRedirectServer() *http.Server {
handleRedirect := func(w http.ResponseWriter, r *http.Request) {
newURI := "https://" + r.Host + r.URL.String()
http.Redirect(w, r, newURI, http.StatusMovedPermanently)
}
router.Router.APIRoutes[`/`] = handleRedirect
router.Router.StaticRoutes[`/`] = http.HandlerFunc(handleRedirect)
return makeHTTPServer()
}
正如我在原帖中所写,我接触Go语言相对较新,大约有一年经验,但这是我第一次接触“面向公众”的HTTP/HTTPS服务器。
package main
import (
"context"
"crypto/tls"
"fmt"
"log"
"net/http"
"time"
"golang.org/x/crypto/acme/autocert"
)
// 多主机证书管理器
type multiHostManager struct {
hosts []string
manager *autocert.Manager
}
func newMultiHostManager(hosts []string, cacheDir string) *multiHostManager {
m := &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(hosts...),
Cache: autocert.DirCache(cacheDir),
RenewBefore: 30 * 24 * time.Hour, // 证书到期前30天自动续期
}
return &multiHostManager{
hosts: hosts,
manager: m,
}
}
// 静态文件处理器
func staticHandler(host string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 验证主机头
if r.Host != host {
http.Error(w, "Invalid host", http.StatusBadRequest)
return
}
// 静态文件服务
fs := http.FileServer(http.Dir("./static/" + host))
fs.ServeHTTP(w, r)
})
}
// API处理器示例
func apiHandler(host string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 异步日志记录
go func() {
logEntry := fmt.Sprintf("[%s] %s %s %s",
time.Now().Format(time.RFC3339),
r.Host,
r.Method,
r.URL.Path)
// 这里可以发送到日志微服务
fmt.Println("Async log:", logEntry)
}()
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"host": "%s", "status": "ok"}`, host)
})
}
// 主路由处理器
func mainHandler(host string) http.Handler {
mux := http.NewServeMux()
// 静态文件路由
mux.Handle("/static/", http.StripPrefix("/static/", staticHandler(host)))
// API路由
mux.HandleFunc("/api/", apiHandler(host).ServeHTTP)
// 默认路由
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprintf(w, "Welcome to %s", host)
})
return mux
}
func main() {
// 配置多个主机
hosts := []string{
"example1.com",
"example2.com",
"example3.com",
}
// 创建证书管理器
certManager := newMultiHostManager(hosts, "./certs")
// 创建主服务器
mainServer := &http.Server{
Addr: ":443",
TLSConfig: &tls.Config{
GetCertificate: certManager.manager.GetCertificate,
MinVersion: tls.VersionTLS12, // 强制TLS 1.2+
},
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
// 为每个主机注册处理器
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
host := r.Host
// 根据主机选择处理器
switch host {
case "example1.com":
mainHandler("example1.com").ServeHTTP(w, r)
case "example2.com":
mainHandler("example2.com").ServeHTTP(w, r)
case "example3.com":
mainHandler("example3.com").ServeHTTP(w, r)
default:
http.Error(w, "Host not found", http.StatusNotFound)
}
})
mainServer.Handler = handler
// HTTP重定向到HTTPS
go func() {
redirectServer := &http.Server{
Addr: ":80",
Handler: certManager.manager.HTTPHandler(nil),
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
if err := redirectServer.ListenAndServe(); err != nil {
log.Fatalf("HTTP redirect server failed: %v", err)
}
}()
// 启动HTTPS服务器
log.Println("Starting HTTPS server on :443")
if err := mainServer.ListenAndServeTLS("", ""); err != nil {
log.Fatalf("HTTPS server failed: %v", err)
}
}
// 使用中间件的增强版本
package main
import (
"net/http"
"time"
"github.com/justinas/alice"
)
// 日志中间件
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 异步处理日志
go func() {
logData := map[string]interface{}{
"timestamp": time.Now().UTC(),
"host": r.Host,
"method": r.Method,
"path": r.URL.Path,
"user_agent": r.UserAgent(),
"duration": time.Since(start).Seconds(),
}
// 发送到日志微服务
sendToLogService(logData)
}()
next.ServeHTTP(w, r)
})
}
// 主机验证中间件
func hostValidationMiddleware(allowedHosts []string) func(http.Handler) http.Handler {
hostMap := make(map[string]bool)
for _, host := range allowedHosts {
hostMap[host] = true
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !hostMap[r.Host] {
http.Error(w, "Invalid host", http.StatusBadRequest)
return
}
next.ServeHTTP(w, r)
})
}
}
// 使用Alice中间件链
func createHandlerChain(host string) http.Handler {
common := alice.New(
loggingMiddleware,
hostValidationMiddleware([]string{host}),
)
mux := http.NewServeMux()
mux.HandleFunc("/api/data", apiHandler)
mux.Handle("/static/", http.StripPrefix("/static/", staticHandler(host)))
return common.Then(mux)
}
func sendToLogService(data map[string]interface{}) {
// 实现日志发送逻辑
fmt.Printf("Log entry: %+v\n", data)
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status": "success"}`))
}
// 生产环境配置示例
package config
import (
"os"
"strconv"
)
type Config struct {
Hosts []string
CacheDir string
ReadTimeout int
WriteTimeout int
IdleTimeout int
}
func LoadConfig() *Config {
hosts := []string{
os.Getenv("HOST1"),
os.Getenv("HOST2"),
os.Getenv("HOST3"),
}
// 过滤空值
validHosts := []string{}
for _, host := range hosts {
if host != "" {
validHosts = append(validHosts, host)
}
}
readTimeout, _ := strconv.Atoi(os.Getenv("READ_TIMEOUT"))
if readTimeout == 0 {
readTimeout = 5
}
return &Config{
Hosts: validHosts,
CacheDir: os.Getenv("CERT_CACHE_DIR"),
ReadTimeout: readTimeout,
WriteTimeout: 10,
IdleTimeout: 120,
}
}
这个实现提供了:
- 多主机自动证书管理
- 异步请求日志处理
- 静态文件和API路由分离
- HTTP到HTTPS自动重定向
- 中间件支持
- 生产环境配置管理
证书会自动从Let’s Encrypt获取并缓存,支持多个主机名的TLS证书管理。


