mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-14 09:06:37 +08:00
130 lines
3.7 KiB
Go
130 lines
3.7 KiB
Go
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"net"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// 开启 HTTPS 后,同一个端口会同时面对两类连接:
|
||
//
|
||
// - TLS 客户端(浏览器、播放器)——正常提供 HTTPS 服务;
|
||
// - 明确访问 http:// 的客户端——以前只会撞出一句
|
||
// "Client sent an HTTP request to an HTTPS server.",用户看不懂。
|
||
//
|
||
// 一个端口上只有 TLS 监听时无法区分两者,所以在 Accept 之后先偷看第一个字节:
|
||
// TLS 握手的记录类型固定是 0x16(handshake),而 HTTP 请求行必然是可见字符。
|
||
// 据此把连接分流成两路,明文那一路交给只做跳转的 http.Server,于是 http:// 会被
|
||
// 自动重定向到 https://(见 server_manager.go 的 httpsRedirectHandler)。
|
||
const tlsRecordHandshake = 0x16
|
||
|
||
// defaultPeekTimeout 限制"偷看首字节"阶段的等待,避免只连不发的半开连接长期占住
|
||
// goroutine。分流器按实例持有该值,便于测试用更短的超时。
|
||
const defaultPeekTimeout = 10 * time.Second
|
||
|
||
// splitListener 是监听分流器:Accept 到底层连接后按首字节投递给对应的一路。
|
||
type splitListener struct {
|
||
ln net.Listener
|
||
tlsCh chan net.Conn
|
||
plainCh chan net.Conn
|
||
quit chan struct{}
|
||
once sync.Once
|
||
peek time.Duration
|
||
}
|
||
|
||
func newSplitListener(ln net.Listener) *splitListener {
|
||
return newSplitListenerWithTimeout(ln, defaultPeekTimeout)
|
||
}
|
||
|
||
func newSplitListenerWithTimeout(ln net.Listener, peek time.Duration) *splitListener {
|
||
s := &splitListener{
|
||
ln: ln,
|
||
tlsCh: make(chan net.Conn),
|
||
plainCh: make(chan net.Conn),
|
||
quit: make(chan struct{}),
|
||
peek: peek,
|
||
}
|
||
go s.acceptLoop()
|
||
return s
|
||
}
|
||
|
||
// tlsListener 返回只收到 TLS 连接的 listener。
|
||
func (s *splitListener) tlsListener() net.Listener {
|
||
return &channelListener{addr: s.ln.Addr(), conns: s.tlsCh, quit: s.quit}
|
||
}
|
||
|
||
// plainListener 返回只收到明文 HTTP 连接的 listener。
|
||
func (s *splitListener) plainListener() net.Listener {
|
||
return &channelListener{addr: s.ln.Addr(), conns: s.plainCh, quit: s.quit}
|
||
}
|
||
|
||
func (s *splitListener) acceptLoop() {
|
||
for {
|
||
conn, err := s.ln.Accept()
|
||
if err != nil {
|
||
// 底层监听关闭(换证、关停)后两路 Accept 一起返回 net.ErrClosed,
|
||
// 由持有它们的 http.Server 自行收尾。
|
||
s.stop()
|
||
return
|
||
}
|
||
go s.dispatch(conn)
|
||
}
|
||
}
|
||
|
||
func (s *splitListener) dispatch(conn net.Conn) {
|
||
br := bufio.NewReader(conn)
|
||
_ = conn.SetReadDeadline(time.Now().Add(s.peek))
|
||
first, err := br.Peek(1)
|
||
_ = conn.SetReadDeadline(time.Time{})
|
||
if err != nil {
|
||
_ = conn.Close()
|
||
return
|
||
}
|
||
|
||
peeked := &peekedConn{Conn: conn, r: br}
|
||
ch := s.plainCh
|
||
if first[0] == tlsRecordHandshake {
|
||
ch = s.tlsCh
|
||
}
|
||
select {
|
||
case ch <- peeked: // 交给对应的 http.Server,由它负责读写与关闭
|
||
case <-s.quit:
|
||
_ = conn.Close()
|
||
}
|
||
}
|
||
|
||
func (s *splitListener) stop() {
|
||
s.once.Do(func() { close(s.quit) })
|
||
}
|
||
|
||
// channelListener 把一路 channel 包装成 net.Listener,供 http.Server.Serve 使用。
|
||
type channelListener struct {
|
||
addr net.Addr
|
||
conns <-chan net.Conn
|
||
quit <-chan struct{}
|
||
}
|
||
|
||
func (l *channelListener) Accept() (net.Conn, error) {
|
||
select {
|
||
case conn := <-l.conns:
|
||
return conn, nil
|
||
case <-l.quit:
|
||
return nil, net.ErrClosed
|
||
}
|
||
}
|
||
|
||
// Close 是空实现:真正要释放的端口在 splitListener 的底层监听上,它一旦关闭,
|
||
// 这里的 Accept 就会返回 net.ErrClosed,http.Server 随之退出。
|
||
func (l *channelListener) Close() error { return nil }
|
||
|
||
func (l *channelListener) Addr() net.Addr { return l.addr }
|
||
|
||
// peekedConn 把已经读进 bufio 缓冲的首字节还给上层读取者。
|
||
type peekedConn struct {
|
||
net.Conn
|
||
r *bufio.Reader
|
||
}
|
||
|
||
func (c *peekedConn) Read(p []byte) (int, error) { return c.r.Read(p) }
|