mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-14 09:06:37 +08:00
优化
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
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) }
|
||||
Reference in New Issue
Block a user