MENU

Go Server HTTP 302 跳转 HTTPS

August 3, 2024 • Read: 2862 • 编码,Go

HTTP 400 的问题

以上一篇笔记为例,将 HTTP 协议改成了 HTTPS 协议,可以使得数据的传输更安全。但开启 HTTPS 后,在某些场景下会出现 HTTP 400 的问题。

2024-08-03T08:07:55.webp

当客户端(浏览器)使用 HTTP 协议请求实际是 HTTPS 的服务,服务端就会返回 400 的错误码。

如何产生的

以 上图为例,Go Server 监听了 2333 端口,我服务的 IP 是 192.168.0.11

服务运行后,用浏览器访问这个服务提供的 Web 页面,所以我打开浏览器,在地址栏输入了 192.168.0.11:2333,并按下了回车键。

这时,因为我输入的并不是一个标准的 URL 地址(没有指明请求协议 HTTPS / HTTP),所以浏览器会自动帮我补上一个请求协议(目前大部分是补 HTTP,不同浏览器策略不同会补充 HTTPS)。然后我的请求地址就变成了 http://192,168.0.11:2333,与 Go Server 监听的协议不同,导致最终返回了 "Client sent an HTTP request to an HTTPS server.(http code 400)"。

如何解决?

最简单的办法就是访问报错之后,人为修改浏览器的请求协议为 HTTPS 就行,但每次出现 400 都需要这么操作一些,感觉有点蠢。我们需要一个使用体验更好的解决方案。

我们先明确一下需要实现的需求:当用户错误的访问 HTTP 协议时,自动重定向到 HTTPS 协议。

这个需求如果是 Nginx、Apache 之类的 Web 服务器,就很容易实现了,只需用简单的配置即可实现 HTTP 302 重定向 HTTPS 的功能,例如 Nginx 的实现方式:

error_page 497 https://$http_host$request_uri;

如果是 Go Server 的话就比较麻烦了,Go 官方没有提供这样的功能,只能自己实现。

最初我参考的这篇文章:如何让HTTP和HTTPs监听同一个端口?,计划在Gin 中加一个中间件,如果请求是 HTTP 就重定向到 HTTPS 协议。但实际使用中发现,多次请求之后 PeekConn.Peek() 读不到 TCP 的 Record Header,导致无法判断是是不是 TLS 报文。

前几天,在 GitHub 中看到了 https://github.com/bddjr/hlfhr 这个小项目,通过劫持 net.Conn 重定向到 HTTPS,可以完美实现这个功能,且需要修改的代码量也很少。

代码如下:

...
// 载入 embed 中的证书文件,返回证书连
    cert, _ := tls.X509KeyPair(SSLCertFile, SSLKeyFile)
    // 因为 Gin 不支持从 embed 中载入证书,所以需要自定义证书
    Server := hlfhr.New(&http.Server{
        Addr:    ":2333",
        Handler: App, // 加载 Gin 路由
        TLSConfig: &tls.Config{
            // 加载自签名证书
            Certificates: []tls.Certificate{cert},
        },
        ReadHeaderTimeout: 10 * time.Second,
        IdleTimeout:       10 * time.Second,
    })

    // HTTPS,启动!
    Server.ListenAndServeTLS("", "")
...

修改之后再访问 HTTP 时,会自动转发至 HTTPS,效果如下:

2024-08-03T08:08:29.webp

PS:我 fork 了这个项目后做了一些小调整,重定向状态码改为了 307,以实现更好的兼容性(302 会把 POST 请求重定向成 GET 请求)。

如果你也有和我类似的需求,可以使用我 fork 的库 go get github.com/dbkuaizi/hlfhr,否则使用 go get github.com/bddjr/hlfhr 就好。

Leave a Comment

3 Comments
  1. bddjr bddjr

    现在版本更新到1.1.2了,是否使用自定义handler的占用是一样的,readme也包括了307的handler,直接复制就能用

    1. bddjr bddjr

      @bddjr感谢宣传,关注你github了

    2. @bddjr很好用的库