Golang中HTTP重定向到HTTPS的问题:1.10版本是否存在回归?
Golang中HTTP重定向到HTTPS的问题:1.10版本是否存在回归? 以下是将HTTP重定向到HTTPS的代码,基本上取自多个博客/问答。
我遇到的问题是最先执行其ListenAndServe[TLS]()函数的协议会阻塞或隐藏另一个协议。也就是说,如果TLS首先在443端口启动,那么我在80端口上看不到监听器,也没有响应。反之亦然。在Ubuntu 16.04和Windows 10上的行为相同。我使用的是Go 1.10。
任何帮助/建议/意见都将不胜感激, 谢谢!
package main
import (
"crypto/tls"
"fmt"
"net/http"
"log"
"time"
)
func main() {
//
// 整个站点使用HTTPS
//
smux := &http.ServeMux{}
smux.HandleFunc( "/", func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf( w, "Hello, world" ) } )
tlsConfig := &tls.Config{
// 使服务器使用Go默认的密码套件偏好,
// 这些偏好经过调优以避免攻击。对客户端无效。
PreferServerCipherSuites: true,
// 仅使用具有汇编实现的曲线
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519,
},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
// 最好禁用,因为它们不提供前向保密,
// 但可能对某些客户端是必需的
// tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
// tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
},
NextProtos: []string{"http/1.1"}, //"h2",
} // tlsConfig
cert, err := tls.LoadX509KeyPair( ".\\tiers\\staging\\server.crt",
".\\tiers\\staging\\server.key" )
if err != nil {
log.Fatal( err )
} // if
tlsConfig.Certificates = append( tlsConfig.Certificates, cert )
tlsConfig.BuildNameToCertificate()
httpsServer := newServer()
httpsServer.Addr = ":443"
httpsServer.TLSConfig = tlsConfig
httpsServer.TLSNextProto = make( map[string]func(*http.Server, *tls.Conn, http.Handler), 0 )
httpsServer.Handler = smux
go log.Println( httpsServer.ListenAndServeTLS( "", "" ) )
//
// 将所有HTTP请求重定向到HTTPS
//
httpServer := newServer()
httpServer.Addr = ":80"
httpServer.TLSConfig = &tls.Config{}
hmux := &http.ServeMux{}
hmux.HandleFunc( "/", func( w http.ResponseWriter, req *http.Request ) {
w.Header().Set( "Connection", "close" )
url := "https://" + req.Host + req.URL.String()
http.Redirect( w, req, url, http.StatusMovedPermanently )
})
httpServer.Handler = hmux
log.Println( httpServer.ListenAndServe() )
} // main
func newServer() *http.Server {
// 设置超时,以便缓慢或恶意的客户端不会永久占用资源
return &http.Server{
ReadTimeout: 5 * time.Second,
ReadHeaderTimeout: 4 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
} // newServer
更多关于Golang中HTTP重定向到HTTPS的问题:1.10版本是否存在回归?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
谢谢,问题已经解决了!
更多关于Golang中HTTP重定向到HTTPS的问题:1.10版本是否存在回归?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
John_Clarke:
go log.Println( httpsServer.ListenAndServeTLS
参数在goroutine启动前就已经被求值,然后你的goroutine会调用log.Println(监听和服务返回的任何内容)。由于监听和服务是阻塞的,所以goroutine永远不会启动。
尝试用函数包装或移除log.println。
func main() {
fmt.Println("hello world")
}
在 Go 1.10 中,你遇到的问题并非回归,而是代码执行逻辑导致的常见问题。当你在 go 语句中启动 HTTPS 服务器时,它会在后台运行,但主线程会立即继续执行并启动 HTTP 服务器。如果 HTTP 服务器的 ListenAndServe() 阻塞(这是正常行为),程序会一直等待,直到 HTTP 服务器出错或停止。如果 HTTPS 服务器在启动时遇到错误(例如权限问题或端口被占用),错误会被记录,但主线程不会捕获它,导致 HTTP 服务器看起来“隐藏”了 HTTPS 服务器。
关键点在于:ListenAndServeTLS 和 ListenAndServe 都是阻塞调用,除非在 goroutine 中运行,否则会顺序执行。在你的代码中,HTTPS 服务器在 goroutine 中启动,但 HTTP 服务器在主线程中启动并阻塞,因此如果 HTTP 服务器先启动并成功监听,它会一直运行,而 HTTPS 服务器的任何错误可能被忽略。反之,如果 HTTPS 服务器在 goroutine 中启动失败,错误可能被记录,但 HTTP 服务器仍会启动。
以下是修改后的代码示例,使用 goroutine 启动两个服务器,并添加错误处理以确保两者都能独立运行:
package main
import (
"crypto/tls"
"fmt"
"net/http"
"log"
"time"
)
func main() {
// 创建 HTTPS 服务器
smux := &http.ServeMux{}
smux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Hello, world")
})
tlsConfig := &tls.Config{
PreferServerCipherSuites: true,
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519,
},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
NextProtos: []string{"http/1.1"},
}
cert, err := tls.LoadX509KeyPair(".\\tiers\\staging\\server.crt", ".\\tiers\\staging\\server.key")
if err != nil {
log.Fatal("Failed to load TLS certificate:", err)
}
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
tlsConfig.BuildNameToCertificate()
httpsServer := newServer()
httpsServer.Addr = ":443"
httpsServer.TLSConfig = tlsConfig
httpsServer.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
httpsServer.Handler = smux
// 在 goroutine 中启动 HTTPS 服务器
go func() {
log.Println("Starting HTTPS server on :443")
if err := httpsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
log.Fatal("HTTPS server error:", err)
}
}()
// 创建 HTTP 重定向服务器
httpServer := newServer()
httpServer.Addr = ":80"
hmux := &http.ServeMux{}
hmux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
url := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, url, http.StatusMovedPermanently)
})
httpServer.Handler = hmux
// 在 goroutine 中启动 HTTP 服务器
go func() {
log.Println("Starting HTTP server on :80")
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("HTTP server error:", err)
}
}()
// 保持主程序运行以允许服务器持续监听
select {}
}
func newServer() *http.Server {
return &http.Server{
ReadTimeout: 5 * time.Second,
ReadHeaderTimeout: 4 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
}
在这个修改中,两个服务器都在独立的 goroutine 中启动,并使用 select {} 保持主程序运行。这样,两个服务器可以并行监听,不会互相阻塞。错误处理确保任何服务器启动失败都会记录并终止程序。这解决了你描述的“阻塞或隐藏”问题,与 Go 1.10 版本无关。

