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

3 回复

谢谢,问题已经解决了!

更多关于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 服务器。

关键点在于:ListenAndServeTLSListenAndServe 都是阻塞调用,除非在 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 版本无关。

回到顶部