You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Go语言中根据主机名在同一端口监听HTTP和HTTPS?

同一端口同时处理HTTP和HTTPS请求的实现方案

可以在同一端口同时处理HTTP和HTTPS请求,但Go标准库的http.ListenAndServe与ListenAndServeTLS无法直接实现该需求,需要手动处理底层TCP连接的协议检测,根据连接初始数据判断请求类型后分发给对应逻辑处理。

你当前代码的问题

你的registerSite逻辑对同一端口仅启动单一类型服务器(HTTP或HTTPS),当同一端口下不同主机需要分别使用HTTP和HTTPS时,先注册的主机决定了端口的服务器类型,后注册的站点会被跳过,自然无法满足需求。

实现思路

  1. 手动监听TCP端口:替代标准库的自动监听逻辑,自行接收所有传入的TCP连接
  2. 协议检测:读取连接的第一个字节,判断是HTTP(起始为GET/POST等ASCII字符)还是HTTPS(起始字节为0x16,对应TLS握手的ClientHello)
  3. 连接分发:
    • 若为HTTPS请求,用autocert.Manager的TLS配置包装连接,再交给HTTP服务器处理
    • 若为HTTP请求,直接将连接交给HTTP服务器处理
  4. 保留原有路由逻辑:原有的handleRequest和sitesManager逻辑可以继续使用,仅需调整服务器启动方式

修改后的代码示例

import (
    "errors"
    "log"
    "net"
    "net/http"
    "strings"
    "crypto/tls"

    "golang.org/x/crypto/acme/autocert"
    // 导入你的sites包
)

var sitesManager *sites.SiteManager = sites.CreateSiteManager()

func handleRequest(res http.ResponseWriter, req *http.Request) {
    ctx := req.Context()
    svrAddr := ctx.Value(http.LocalAddrContextKey).(net.Addr)
    host := strings.Split(req.Host, ":")[0]
    port := strings.Split(svrAddr.String(), ":")[1]

    origin := host + ":" + port

    site, exists := sitesManager.GetSite(origin)
    if !exists {
        http.Error(res, "Site not found", http.StatusNotFound)
        return
    }
    site.HandleRequest(res, req)
}

func main() {
    manager := &autocert.Manager{
        Cache:      autocert.DirCache("certsCache"),
        Prompt:     autocert.AcceptTOS,
        HostPolicy: sitesManager.HostPolicy(),
    }

    // 直接注册所有站点,不再通过registerSite启动服务器
    sitesManager.RegisterSite("example1.com:8000", &sites.ReverseProxySite{
        Destination: "localhost:3000",
        HTTPS:       false,
    })
    sitesManager.RegisterSite("example2.com:8000", &sites.ReverseProxySite{
        Destination: "localhost:3001",
        HTTPS:       true,
    })
    sitesManager.RegisterSite("example.com:80", &sites.RedirectSite{
        Destination: "https://example.com",
        HTTPS:       true,
    })
    sitesManager.RegisterSite("example.com:443", &sites.ReverseProxySite{
        Destination: "http://localhost:8000",
        HTTPS:       true,
    })

    // 启动标准80端口HTTP服务
    go func() {
        err := http.ListenAndServe(":80", manager.HTTPHandler(http.HandlerFunc(handleRequest)))
        if err != nil && !errors.Is(err, http.ErrServerClosed) {
            log.Fatalf("HTTP server on 80 failed: %v", err)
        }
    }()

    // 启动标准443端口HTTPS服务
    go func() {
        tlsServer := &http.Server{
            Addr:      ":443",
            Handler:   http.HandlerFunc(handleRequest),
            TLSConfig: manager.TLSConfig(),
        }
        err := tlsServer.ListenAndServeTLS("", "")
        if err != nil && !errors.Is(err, http.ErrServerClosed) {
            log.Fatalf("HTTPS server on 443 failed: %v", err)
        }
    }()

    // 处理自定义8000端口的混合协议请求
    ln, err := net.Listen("tcp", ":8000")
    if err != nil {
        log.Fatalf("Failed to listen on 8000: %v", err)
    }
    defer ln.Close()

    handler := http.HandlerFunc(handleRequest)
    tlsConfig := manager.TLSConfig()

    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Printf("Accept error: %v", err)
            continue
        }
        go func(c net.Conn) {
            defer c.Close()

            // 读取第一个字节判断协议类型
            buf := make([]byte, 1)
            _, err := c.Read(buf)
            if err != nil {
                log.Printf("Read error: %v", err)
                return
            }

            // 将读取的字节放回连接,保证后续处理能拿到完整请求数据
            if err := c.(*net.TCPConn).UnreadByte(); err != nil {
                log.Printf("Unread error: %v", err)
                return
            }

            if buf[0] == 0x16 {
                // TLS握手起始字节,交给TLS包装后的服务处理
                tlsConn := tls.Server(c, tlsConfig)
                if err := http.Serve(tlsConn, handler); err != nil {
                    log.Printf("TLS serve error: %v", err)
                }
            } else {
                // 普通HTTP请求,直接交给HTTP服务处理
                if err := http.Serve(c, handler); err != nil {
                    log.Printf("HTTP serve error: %v", err)
                }
            }
        }(conn)
    }
}

关键细节说明

  • 协议检测:通过读取第一个字节判断是否为TLS握手(0x16是TLS Record Layer的Handshake类型标识),再用UnreadByte将字节放回连接,确保后续服务能读取完整请求
  • autocert集成:autocert.Manager的TLSConfig()会自动为请求的主机名获取并管理证书,无需手动为每个站点配置证书
  • 并发处理:每个连接启动独立goroutine处理,保证服务器的并发能力
  • 端口兼容:标准80/443端口仍可使用原有逻辑,自定义端口采用混合协议处理逻辑

内容的提问来源于stack exchange,提问作者PortalGamesMais

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 19:31:07