0d84b07f68
CI / docker (push) Successful in 1m58s
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。 Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。 Co-authored-by: Cursor <cursoragent@cursor.com>
75 lines
1.6 KiB
Go
75 lines
1.6 KiB
Go
package bridge
|
|
|
|
import (
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// rateLimitedConn limits read/write throughput to bytesPerSec (simple token bucket).
|
|
type rateLimitedConn struct {
|
|
net.Conn
|
|
bytesPerSec int64
|
|
mu sync.Mutex
|
|
allowance float64
|
|
lastCheck time.Time
|
|
}
|
|
|
|
func newRateLimitedConn(conn net.Conn, bytesPerSec int64) net.Conn {
|
|
if bytesPerSec <= 0 {
|
|
return conn
|
|
}
|
|
return &rateLimitedConn{
|
|
Conn: conn,
|
|
bytesPerSec: bytesPerSec,
|
|
lastCheck: time.Now(),
|
|
allowance: float64(bytesPerSec),
|
|
}
|
|
}
|
|
|
|
func (c *rateLimitedConn) wait(n int) {
|
|
if c.bytesPerSec <= 0 || n <= 0 {
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
now := time.Now()
|
|
elapsed := now.Sub(c.lastCheck).Seconds()
|
|
c.lastCheck = now
|
|
c.allowance += elapsed * float64(c.bytesPerSec)
|
|
if c.allowance > float64(c.bytesPerSec)*2 {
|
|
c.allowance = float64(c.bytesPerSec) * 2
|
|
}
|
|
for float64(n) > c.allowance {
|
|
deficit := float64(n) - c.allowance
|
|
sleep := time.Duration(deficit/float64(c.bytesPerSec)*1e9) * time.Nanosecond
|
|
if sleep < time.Millisecond {
|
|
sleep = time.Millisecond
|
|
}
|
|
c.mu.Unlock()
|
|
time.Sleep(sleep)
|
|
c.mu.Lock()
|
|
now = time.Now()
|
|
elapsed = now.Sub(c.lastCheck).Seconds()
|
|
c.lastCheck = now
|
|
c.allowance += elapsed * float64(c.bytesPerSec)
|
|
if c.allowance > float64(c.bytesPerSec)*2 {
|
|
c.allowance = float64(c.bytesPerSec) * 2
|
|
}
|
|
}
|
|
c.allowance -= float64(n)
|
|
}
|
|
|
|
func (c *rateLimitedConn) Read(b []byte) (int, error) {
|
|
n, err := c.Conn.Read(b)
|
|
if n > 0 {
|
|
c.wait(n)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (c *rateLimitedConn) Write(b []byte) (int, error) {
|
|
c.wait(len(b))
|
|
return c.Conn.Write(b)
|
|
}
|