From afc979ef30b718baa380ccb2c9e63f4f907ab2a9 Mon Sep 17 00:00:00 2001 From: ThisIsDara <1380katana@gmail.com> Date: Thu, 14 May 2026 12:44:56 +0330 Subject: [PATCH] V1.4.0 Update --- .github/workflows/build.yml | 23 +++++---- cmd/mhr-cfw/main.go | 1 + config.json | 10 ++-- internal/constants/constants.go | 6 ++- internal/fronter/fronter.go | 48 +++++++++++++++++++ internal/h2/h2_transport.go | 2 + internal/proxy/proxy_server.go | 85 +++++++++++++++++++++++++++++++++ internal/tui/menu.go | 6 ++- 8 files changed, 166 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0f4d8e..3a29221 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,6 +47,10 @@ jobs: run: | export GOOS=${{ matrix.goos }} export GOARCH=${{ matrix.goarch }} + if [ "$GOOS" = "windows" ] && [ "$GOARCH" = "arm64" ]; then + echo "Skipping unsupported windows/arm64" + exit 0 + fi go build -ldflags "-s -w" -o mhr-cfw-go-${{ matrix.goos }}-${{ matrix.goarch }} ./cmd/mhr-cfw - name: Rename Windows executable @@ -55,6 +59,7 @@ jobs: run: mv mhr-cfw-go-windows-${{ matrix.goarch }} mhr-cfw-go-windows-${{ matrix.goarch }}.exe - name: Upload artifact + if: matrix.goos != 'windows' || matrix.goarch != 'arm64' uses: actions/upload-artifact@v4 with: name: mhr-cfw-go-${{ matrix.goos }}-${{ matrix.goarch }} @@ -84,13 +89,15 @@ jobs: with: files: ./binaries/* body: | - ## Bug Fixes - - Fixed Telegram API connectivity issues - - Fixed encoding issues on some websites (brotli/zstd decompression) - - ## Improvements - - Better compatibility with Google Apps Script relay - + Changelog + Improvements + - Added startup prewarm to keep Apps Script containers hot for faster first requests. + - Added periodic keepalive ping to reduce idle latency spikes. + - Tuned HTTP/2 transport with idle and ping timeouts for more stable performance. + - Enabled static asset cache fast-path to reduce repeated relay calls. + Fixes + - Updated version to 1.4.0. + - Improved graceful shutdown by closing active client connections on stop. ðŸĶĒ env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/cmd/mhr-cfw/main.go b/cmd/mhr-cfw/main.go index 78e4a02..8bed9a5 100644 --- a/cmd/mhr-cfw/main.go +++ b/cmd/mhr-cfw/main.go @@ -91,6 +91,7 @@ func main() { func runMenu(a *args) error { menu := &tui.Menu{ Title: "mhr-cfw", + Version: constants.Version, Options: []tui.Option{ {Key: 1, Label: "Start proxy", Handler: func() error { return runProxy(a) }}, {Key: 2, Label: "Setup wizard", Handler: func() error { return setup.RunInteractiveWizard(a.configPath) }}, diff --git a/config.json b/config.json index 1e9a15a..6f9f2c7 100644 --- a/config.json +++ b/config.json @@ -1,21 +1,21 @@ { "auth_key": "changeme", - "chunked_download_chunk_size": 524288, + "script_id": "changeme", "chunked_download_max_chunks": 256, "chunked_download_max_parallel": 8, "chunked_download_min_size": 5242880, "front_domain": "www.google.com", "google_ip": "216.239.38.120", "hosts": {}, - "lan_sharing": true, - "listen_host": "0.0.0.0", + "lan_sharing": false, + "listen_host": "127.0.0.1", "listen_port": 8085, "log_level": "INFO", "max_response_body_bytes": 209715200, "mode": "apps_script", "relay_timeout": 25, - "script_id": "changeme", - "socks5_enabled": false, + "chunked_download_chunk_size": 524288, + "socks5_enabled": true, "socks5_port": 1080, "tcp_connect_timeout": 10, "tls_connect_timeout": 15, diff --git a/internal/constants/constants.go b/internal/constants/constants.go index d826eb2..ef6ca61 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -1,6 +1,6 @@ package constants -const Version = "1.1.0" +const Version = "1.4.0" const ( MaxRequestBodyBytes = 100 * 1024 * 1024 @@ -86,6 +86,10 @@ const ( StatsLogTopN = 10 ) +const ( + KeepaliveInterval = 240.0 +) + var GoogleDirectExactExclude = map[string]struct{}{ "gemini.google.com": {}, "aistudio.google.com": {}, diff --git a/internal/fronter/fronter.go b/internal/fronter/fronter.go index 422c776..f4cd96a 100644 --- a/internal/fronter/fronter.go +++ b/internal/fronter/fronter.go @@ -72,6 +72,8 @@ type DomainFronter struct { coalesce map[string][]chan []byte statsStop chan struct{} + keepalive *time.Ticker + warmOnce sync.Once } type pooledConn struct { @@ -128,6 +130,8 @@ func New(cfg config.Config) *DomainFronter { f.h2 = h2.New(f.connectHost, f.sniHosts, f.verifySSL) go f.statsLoop() + go f.keepaliveLoop() + go f.prewarm() return f } @@ -164,6 +168,9 @@ func buildSNIPool(frontDomain string, overrides []string) []string { func (f *DomainFronter) Close() error { close(f.statsStop) + if f.keepalive != nil { + f.keepalive.Stop() + } if f.h2 != nil { _ = f.h2.Close() } @@ -715,6 +722,47 @@ func (f *DomainFronter) statsLoop() { } } +func (f *DomainFronter) keepaliveLoop() { + f.keepalive = time.NewTicker(time.Duration(constants.KeepaliveInterval) * time.Second) + defer f.keepalive.Stop() + for { + select { + case <-f.statsStop: + return + case <-f.keepalive.C: + if strings.TrimSpace(f.authKey) == "" { + continue + } + payload := map[string]any{ + "m": "HEAD", + "u": "http://example.com/", + "r": false, + "k": f.authKey, + } + jsonBody, _ := json.Marshal(payload) + path := f.execPath("example.com") + _, _, _, _ = f.h2.Request(context.Background(), "POST", path, f.httpHost, map[string]string{"content-type": "application/json"}, jsonBody, 20*time.Second) + } + } +} + +func (f *DomainFronter) prewarm() { + f.warmOnce.Do(func() { + if strings.TrimSpace(f.authKey) == "" { + return + } + payload := map[string]any{ + "m": "HEAD", + "u": "http://example.com/", + "r": false, + "k": f.authKey, + } + jsonBody, _ := json.Marshal(payload) + path := f.execPath("example.com") + _, _, _, _ = f.h2.Request(context.Background(), "POST", path, f.httpHost, map[string]string{"content-type": "application/json"}, jsonBody, 20*time.Second) + }) +} + func (f *DomainFronter) logStats() { if len(f.perSite) == 0 { return diff --git a/internal/h2/h2_transport.go b/internal/h2/h2_transport.go index aea4cb3..602863a 100644 --- a/internal/h2/h2_transport.go +++ b/internal/h2/h2_transport.go @@ -52,6 +52,8 @@ func (t *Transport) ensure() { } tr := &http2.Transport{ AllowHTTP: false, + ReadIdleTimeout: 90 * time.Second, + PingTimeout: 15 * time.Second, DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) { sni := t.nextSNI() tlsCfg := &tls.Config{ diff --git a/internal/proxy/proxy_server.go b/internal/proxy/proxy_server.go index b97b595..553cd38 100644 --- a/internal/proxy/proxy_server.go +++ b/internal/proxy/proxy_server.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "net" + "net/url" "net/textproto" "regexp" "strconv" @@ -153,6 +154,8 @@ type Server struct { mu sync.Mutex servers []net.Listener + conns map[net.Conn]struct{} + connMu sync.Mutex wg sync.WaitGroup ctx context.Context } @@ -177,6 +180,7 @@ func NewServer(cfg config.Config) (*Server, error) { mitm: mitm.NewManager(), cache: NewResponseCache(constants.CacheMaxMB), directFailUntil: map[string]time.Time{}, + conns: map[net.Conn]struct{}{}, }, nil } @@ -214,6 +218,7 @@ func (s *Server) Start(ctx context.Context) error { for _, l := range s.servers { _ = l.Close() } + s.closeAllConns() _ = s.fronter.Close() s.wg.Wait() log.Infof("Server stopped") @@ -233,11 +238,37 @@ func (s *Server) acceptLoop(ln net.Listener, handler func(net.Conn)) { s.wg.Add(1) go func() { defer s.wg.Done() + s.trackConn(conn) + defer s.untrackConn(conn) handler(conn) }() } } +func (s *Server) trackConn(conn net.Conn) { + s.connMu.Lock() + s.conns[conn] = struct{}{} + s.connMu.Unlock() +} + +func (s *Server) untrackConn(conn net.Conn) { + s.connMu.Lock() + delete(s.conns, conn) + s.connMu.Unlock() +} + +func (s *Server) closeAllConns() { + s.connMu.Lock() + conns := make([]net.Conn, 0, len(s.conns)) + for conn := range s.conns { + conns = append(conns, conn) + } + s.connMu.Unlock() + for _, conn := range conns { + _ = conn.Close() + } +} + func (s *Server) handleHTTPConn(conn net.Conn) { defer conn.Close() conn.SetDeadline(time.Now().Add(30 * time.Second)) @@ -341,7 +372,23 @@ func (s *Server) relayHTTPStream(host string, port int, conn net.Conn) { continue } + if s.cacheAllowed(method, urlStr, headerMap, body) { + if cached := s.cache.Get(urlStr); cached != nil { + if origin != "" { + cached = injectCORSHeaders(cached, origin) + } + _, _ = conn.Write(cached) + continue + } + } + response := s.fronter.Relay(method, urlStr, headerMap, body) + if s.cacheAllowed(method, urlStr, headerMap, body) { + ttl := s.cache.ParseTTL(response, urlStr) + if ttl > 0 { + s.cache.Put(urlStr, response, ttl) + } + } if origin != "" { response = injectCORSHeaders(response, origin) } @@ -367,7 +414,23 @@ func (s *Server) handlePlainHTTP(conn net.Conn, reader *bufio.Reader, headers [] } urlStr := path + if s.cacheAllowed(method, urlStr, headerMap, body) { + if cached := s.cache.Get(urlStr); cached != nil { + if origin != "" { + cached = injectCORSHeaders(cached, origin) + } + _, _ = conn.Write(cached) + return + } + } + response := s.fronter.Relay(method, urlStr, headerMap, body) + if s.cacheAllowed(method, urlStr, headerMap, body) { + ttl := s.cache.ParseTTL(response, urlStr) + if ttl > 0 { + s.cache.Put(urlStr, response, ttl) + } + } if origin != "" { response = injectCORSHeaders(response, origin) } @@ -520,6 +583,28 @@ func headerValue(headers map[string]string, name string) string { return "" } +func (s *Server) cacheAllowed(method, urlStr string, headers map[string]string, body []byte) bool { + if strings.ToUpper(method) != "GET" || len(body) > 0 { + return false + } + for _, name := range constants.UncacheableHeaderNames { + if headerValue(headers, name) != "" { + return false + } + } + parsed, err := url.Parse(urlStr) + if err != nil { + return false + } + path := strings.ToLower(parsed.Path) + for _, ext := range constants.StaticExts { + if strings.HasSuffix(path, ext) { + return true + } + } + return false +} + func corsPreflight(origin, acrMethod, acrHeaders string) []byte { allowOrigin := origin if allowOrigin == "" { diff --git a/internal/tui/menu.go b/internal/tui/menu.go index 650636f..b1a8951 100644 --- a/internal/tui/menu.go +++ b/internal/tui/menu.go @@ -16,6 +16,7 @@ type Option struct { type Menu struct { Title string + Version string Options []Option } @@ -58,7 +59,10 @@ func (m *Menu) render() { borderMid := "╠" + strings.Repeat("═", width+2) + "â•Ģ" borderBot := "╚ " + strings.Repeat("═", width) + " ╝" inner := "║" + strings.Repeat(" ", width+2) + "║" - tag := "Mhr-Cfw-Go V1.0" + tag := "Mhr-Cfw-Go" + if strings.TrimSpace(m.Version) != "" { + tag = fmt.Sprintf("Mhr-Cfw-Go v%s", m.Version) + } link := "https://github.com/ThisIsDara/" centerText := func(text string) string {