HTTP 400 的问题
以上一篇笔记为例,将 HTTP 协议改成了 HTTPS 协议,可以使得数据的传输更安全。但开启 HTTPS 后,在某些场景下会出现 HTTP 400 的问题。
当客户端(浏览器)使用 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,效果如下:
PS:我 fork 了这个项目后做了一些小调整,重定向状态码改为了 307,以实现更好的兼容性(302 会把 POST 请求重定向成 GET 请求)。
如果你也有和我类似的需求,可以使用我 fork 的库 go get github.com/dbkuaizi/hlfhr
,否则使用 go get github.com/bddjr/hlfhr
就好。
现在版本更新到1.1.2了,是否使用自定义handler的占用是一样的,readme也包括了307的handler,直接复制就能用
感谢宣传,关注你github了
很好用的库