Golang中ListenAndServeTLS的cert.pem应该使用io.Reader而非OS *File

Golang中ListenAndServeTLS的cert.pem应该使用io.Reader而非OS *File 我刚开始学习编程和Golang。 我确实有个想要解决的问题,这让我对以下内容产生了疑问。 是的,我有个需要解决的问题,但在这里我试图理解其中的原理和原因, 而不是寻找变通方法。

我有个问题,也许有人能帮我更好地理解。

我正在尝试使用TLS启动一个Web服务器 使用net/http包

http.ListenAndServe(":443", certFile, keyFile, mux)

在上面这段代码中,certFile和keyFile都是字符串类型 但要让这个正常工作,它们需要是(*os.File)类型 (stat和lstat)

当你运行ListenAndServeTLS( , cert, key, ,)时 它会调用x509.LoadKeyPair(certFile, keyFile) 将原始函数中的字符串传递下去 然后x509LKP会尝试打开这些文件并加载它们的内容,再传回给http.LaSTLS

我的问题是:为什么这必须是一个实际的文件? 最终我们真正需要的难道不是io.Reader{}接口吗?

在你告诉我这是XY问题之前: 是的,我确实有个实际问题需要解决,我的cert.pem不是文件形式 另外,为了验证信任链(根据文档),我需要将CA和cert.pem合并 这如果不是已经在单个文件中 就需要基于内存的操作。难道我需要将其写入新文件,只为了让http.ListenAndServeTLS能再次打开吗?

所以是的,我有个问题需要解决,但我也在尝试理解 这样有一天我就不再是Go新手了


更多关于Golang中ListenAndServeTLS的cert.pem应该使用io.Reader而非OS *File的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

我不知道表达感谢的礼节是什么。在其他论坛上我曾因此受到严厉批评。

但我确实想对您友善、详尽且不居高临下的回答表达感激之情。

我对此深表感谢。

更多关于Golang中ListenAndServeTLS的cert.pem应该使用io.Reader而非OS *File的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


当我找到一个功能几乎符合需求的函数时,我会开始阅读源代码(可通过[godoc.org]便捷链接访问):

func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServeTLS(certFile, keyFile)
}

这是一个便捷函数,它不提供任何新功能,但让常见用例更易使用。

进一步展开我们看到:

func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error {
	addr := srv.Addr
	if addr == "" {
		addr = ":https"
	}
  
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
  
	defer ln.Close()
  
	return srv.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, certFile, keyFile)
}

这同样是一个便捷函数,它将我们引向:

func (srv *Server) ServeTLS(l net.Listener, certFile, keyFile string) error {
	// Setup HTTP/2 before srv.Serve, to initialize srv.TLSConfig
	// before we clone it and create the TLS Listener.
	if err := srv.setupHTTP2_ServeTLS(); err != nil {
		return err
	}
  
	config := cloneTLSConfig(srv.TLSConfig)
	if !strSliceContains(config.NextProtos, "http/1.1") {
		config.NextProtos = append(config.NextProtos, "http/1.1")
	}
  
	configHasCert := len(config.Certificates) > 0 || config.GetCertificate != nil
	if !configHasCert || certFile != "" || keyFile != "" {
		var err error
		config.Certificates = make([]tls.Certificate, 1)
		config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
		if err != nil {
			return err
		}
	}
  
	tlsListener := tls.NewListener(l, config)
	return srv.Serve(tlsListener)
}

遗憾的是这并不完全是一个便捷函数,因为它调用了非导出方法srv.setupHTTP2_ServeTLS。我记得当Server.TLSConfig正确设置时,调用Serve会正确处理这种情况(我有几个版本没看了,请阅读Serve和ServeTLS的文档来帮助理解这一点)。

这些函数的源代码充分展示了不太便捷但更灵活的http.Server API,你应该能够实现所需的功能。

要查看使用此技术的更完整示例,请参阅我关于[扩展便捷函数]的文章。

在Golang中,ListenAndServeTLS 确实要求证书和密钥文件路径作为字符串参数,这确实限制了灵活性,特别是当证书数据来自内存或其他非文件源时。你的理解是正确的:底层 x509.LoadKeyPair 最终需要读取文件内容,但理想情况下,使用 io.Reader 会更通用。

实际上,标准库的设计是为了简化常见用例,即证书存储在文件中。但如果你需要从内存或其他源加载证书,可以通过自定义 tls.Config 来实现,而不必写入临时文件。以下是示例代码,展示如何使用内存中的证书数据启动TLS服务器:

package main

import (
    "crypto/tls"
    "net/http"
)

func main() {
    // 假设 certPEM 和 keyPEM 是字节切片,包含证书和密钥的PEM数据
    certPEM := []byte(`-----BEGIN CERTIFICATE-----
... 你的证书数据 ...
-----END CERTIFICATE-----`)
    keyPEM := []byte(`-----BEGIN PRIVATE KEY-----
... 你的私钥数据 ...
-----END PRIVATE KEY-----`)

    // 从内存数据加载证书
    cert, err := tls.X509KeyPair(certPEM, keyPEM)
    if err != nil {
        panic(err)
    }

    // 配置TLS,使用加载的证书
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
    }

    // 创建自定义服务器
    server := &http.Server{
        Addr:      ":443",
        Handler:   mux, // 假设 mux 是你的HTTP处理器
        TLSConfig: tlsConfig,
    }

    // 启动服务器,注意这里传递空字符串作为证书文件路径,因为证书已在配置中
    err = server.ListenAndServeTLS("", "")
    if err != nil {
        panic(err)
    }
}

在这个例子中:

  • 使用 tls.X509KeyPair 直接从字节切片加载证书和密钥,这避免了文件系统操作。
  • 通过 tls.Config 设置证书,然后传递给自定义的 http.Server
  • 调用 ListenAndServeTLS 时,证书和密钥文件路径参数为空字符串,因为证书已通过配置提供。

这种方法允许你从任何源(如内存、网络或环境变量)加载证书数据,无需写入临时文件。这解决了你的实际问题,同时解释了为什么基于 io.Reader 的设计会更灵活——标准库选择了简单文件路径的API,但通过底层组合,你可以实现更动态的加载方式。

回到顶部