forked from Mxmilu666/frp
feat(proxy): add HTTP to HTTPS redirection support
This commit is contained in:
@@ -275,6 +275,10 @@ localIP = "127.0.0.1"
|
||||
localPort = 8000
|
||||
subdomain = "web02"
|
||||
customDomains = ["web02.yourdomain.com"]
|
||||
# if true, frps will redirect plain HTTP requests for the domains above to HTTPS.
|
||||
# It requires vhostHTTPPort to be enabled on frps, and an HTTP proxy registered
|
||||
# on the same domain always takes precedence over the redirect.
|
||||
# httpRedirect = true
|
||||
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||
# v1 or v2 or empty
|
||||
transport.proxyProtocolVersion = "v2"
|
||||
|
||||
@@ -40,6 +40,12 @@ transport.maxPoolCount = 5
|
||||
# If negative, keep-alive probes are disabled.
|
||||
# transport.tcpKeepalive = 7200
|
||||
|
||||
# proxyIdleTimeout specifies the maximum time in seconds that a proxied user connection
|
||||
# can stay open without any traffic in either direction before frps closes it.
|
||||
# It reclaims connections whose endpoints are still open at the TCP level but will never send data again.
|
||||
# By default, this value is 0, which disables the idle timeout.
|
||||
# transport.proxyIdleTimeout = 7200
|
||||
|
||||
# transport.tls.force specifies whether to only accept TLS-encrypted connections. By default, the value is false.
|
||||
transport.tls.force = false
|
||||
|
||||
@@ -52,6 +58,12 @@ transport.tls.force = false
|
||||
vhostHTTPPort = 80
|
||||
vhostHTTPSPort = 443
|
||||
|
||||
# Port used in the Location header when redirecting HTTP requests to HTTPS for
|
||||
# proxies with httpRedirect enabled. Set it when browsers reach the HTTPS vhost
|
||||
# through a port mapping, so it differs from vhostHTTPSPort.
|
||||
# By default, this value is vhostHTTPSPort.
|
||||
# vhostHTTPSRedirectPort = 443
|
||||
|
||||
# Response header timeout(seconds) for vhost http server, default is 60s
|
||||
# vhostHTTPTimeout = 60
|
||||
|
||||
|
||||
2
go.mod
2
go.mod
@@ -97,5 +97,3 @@ require (
|
||||
|
||||
// TODO(fatedier): Temporary use the modified version, update to the official version after merging into the official repository.
|
||||
replace github.com/hashicorp/yamux => github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6
|
||||
|
||||
replace github.com/fatedier/golib => github.com/Lolia-FRP/golib v0.7.4
|
||||
|
||||
4
go.sum
4
go.sum
@@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
|
||||
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Lolia-FRP/golib v0.7.4 h1:EtUJChdgXVV9iU1xqB7PkD/1/3RgATbj3n86HbrLKMA=
|
||||
github.com/Lolia-FRP/golib v0.7.4/go.mod h1:ArUGvPg2cOw/py2RAuBt46nNZH2VQ5Z70p109MAZpJw=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
@@ -36,6 +34,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatedier/golib v0.7.0 h1:tMDF9ObcwVt59VUHroJOzHQjVFPLymZVMpGm9WAVwhY=
|
||||
github.com/fatedier/golib v0.7.0/go.mod h1:ArUGvPg2cOw/py2RAuBt46nNZH2VQ5Z70p109MAZpJw=
|
||||
github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6 h1:u92UUy6FURPmNsMBUuongRWC0rBqN6gd01Dzu+D21NE=
|
||||
github.com/fatedier/yamux v0.0.0-20250825093530-d0154be01cd6/go.mod h1:c5/tk6G0dSpXGzJN7Wk1OEie8grdSJAmeawId9Zvd34=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
|
||||
@@ -369,6 +369,12 @@ var _ ProxyConfigurer = &HTTPSProxyConfig{}
|
||||
type HTTPSProxyConfig struct {
|
||||
ProxyBaseConfig
|
||||
DomainConfig
|
||||
|
||||
// HTTPRedirect requests frps to redirect plain HTTP requests for the
|
||||
// proxy's domains to their HTTPS endpoint. It only takes effect when
|
||||
// frps has vhostHTTPPort enabled, and never overrides a real HTTP
|
||||
// proxy registered on the same domain.
|
||||
HTTPRedirect bool `json:"httpRedirect,omitempty"`
|
||||
}
|
||||
|
||||
func (c *HTTPSProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
@@ -376,6 +382,7 @@ func (c *HTTPSProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
|
||||
m.CustomDomains = c.CustomDomains
|
||||
m.SubDomain = c.SubDomain
|
||||
m.HTTPRedirect = c.HTTPRedirect
|
||||
}
|
||||
|
||||
func (c *HTTPSProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
@@ -383,6 +390,7 @@ func (c *HTTPSProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
||||
|
||||
c.CustomDomains = m.CustomDomains
|
||||
c.SubDomain = m.SubDomain
|
||||
c.HTTPRedirect = m.HTTPRedirect
|
||||
}
|
||||
|
||||
func (c *HTTPSProxyConfig) Clone() ProxyConfigurer {
|
||||
|
||||
@@ -51,6 +51,12 @@ type ServerConfig struct {
|
||||
// Vhost requests. If this value is 0, the server will not listen for HTTPS
|
||||
// requests.
|
||||
VhostHTTPSPort int `json:"vhostHTTPSPort,omitempty"`
|
||||
// VhostHTTPSRedirectPort specifies the port used in the Location header
|
||||
// when redirecting HTTP requests to HTTPS for proxies with httpRedirect
|
||||
// enabled. Set it when browsers reach the HTTPS vhost through a port
|
||||
// mapping, so it differs from VhostHTTPSPort. By default, this value is
|
||||
// VhostHTTPSPort.
|
||||
VhostHTTPSRedirectPort int `json:"vhostHTTPSRedirectPort,omitempty"`
|
||||
// TCPMuxHTTPConnectPort specifies the port that the server listens for TCP
|
||||
// HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
|
||||
// requests on one single port. If it's not - it will listen on this value for
|
||||
@@ -118,6 +124,7 @@ func (c *ServerConfig) Complete() error {
|
||||
}
|
||||
|
||||
c.VhostHTTPTimeout = util.EmptyOr(c.VhostHTTPTimeout, 60)
|
||||
c.VhostHTTPSRedirectPort = util.EmptyOr(c.VhostHTTPSRedirectPort, c.VhostHTTPSPort)
|
||||
c.DetailedErrorsToClient = util.EmptyOr(c.DetailedErrorsToClient, lo.ToPtr(true))
|
||||
c.UserConnTimeout = util.EmptyOr(c.UserConnTimeout, 10)
|
||||
c.UDPPacketSize = util.EmptyOr(c.UDPPacketSize, 1500)
|
||||
@@ -173,6 +180,12 @@ type ServerTransportConfig struct {
|
||||
// before terminating the connection. It is not recommended to change this
|
||||
// value. By default, this value is 90. Set negative value to disable it.
|
||||
HeartbeatTimeout int64 `json:"heartbeatTimeout,omitempty"`
|
||||
// ProxyIdleTimeout specifies the maximum time in seconds that a proxied
|
||||
// user connection can stay open without any traffic in either direction
|
||||
// before frps closes it. This reclaims connections whose endpoints are
|
||||
// still open at the TCP level but will never send data again. By default,
|
||||
// this value is 0, which disables the idle timeout.
|
||||
ProxyIdleTimeout int64 `json:"proxyIdleTimeout,omitempty"`
|
||||
// QUIC options.
|
||||
QUIC QUICOptions `json:"quic,omitempty"`
|
||||
// TLS specifies TLS settings for the connection from the client.
|
||||
|
||||
@@ -124,6 +124,7 @@ type NewProxy struct {
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
ResponseHeaders map[string]string `json:"response_headers,omitempty"`
|
||||
RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
|
||||
HTTPRedirect bool `json:"http_redirect,omitempty"`
|
||||
|
||||
// stcp, sudp, xtcp
|
||||
Sk string `json:"sk,omitempty"`
|
||||
|
||||
@@ -39,11 +39,15 @@ var ErrNoRouteFound = errors.New("no route found")
|
||||
|
||||
type HTTPReverseProxyOptions struct {
|
||||
ResponseHeaderTimeoutS int64
|
||||
// HTTPSRedirector, if set, redirects requests whose host has no HTTP
|
||||
// route but opted into HTTP to HTTPS redirection.
|
||||
HTTPSRedirector *HTTPSRedirector
|
||||
}
|
||||
|
||||
type HTTPReverseProxy struct {
|
||||
proxy http.Handler
|
||||
vhostRouter *Routers
|
||||
httpsRedirector *HTTPSRedirector
|
||||
|
||||
responseHeaderTimeout time.Duration
|
||||
}
|
||||
@@ -55,6 +59,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
||||
rp := &HTTPReverseProxy{
|
||||
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
||||
vhostRouter: vhostRouter,
|
||||
httpsRedirector: option.HTTPSRedirector,
|
||||
}
|
||||
proxy := &httputil.ReverseProxy{
|
||||
// Modify incoming requests by route policies.
|
||||
@@ -281,6 +286,12 @@ func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// Hosts without any real HTTP route may still ask for a redirect to
|
||||
// their HTTPS endpoint; registered HTTP routes always take precedence.
|
||||
if rc == nil && rp.httpsRedirector != nil && rp.httpsRedirector.Redirect(rw, newreq) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Method == http.MethodConnect {
|
||||
rp.connectHandler(rw, newreq)
|
||||
} else {
|
||||
|
||||
83
pkg/util/vhost/https_redirect.go
Normal file
83
pkg/util/vhost/https_redirect.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2026 The frp Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package vhost
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||
)
|
||||
|
||||
// HTTPSRedirector tracks domains of HTTPS proxies that opted into automatic
|
||||
// HTTP to HTTPS redirection. The HTTP reverse proxy consults it as a fallback
|
||||
// for hosts that have no real HTTP route, so registered HTTP proxies on the
|
||||
// same domain always take precedence over the redirect.
|
||||
type HTTPSRedirector struct {
|
||||
mu sync.RWMutex
|
||||
domains map[string]int // domain -> number of proxies requesting the redirect
|
||||
port int // port used in the redirect Location, as seen by browsers
|
||||
}
|
||||
|
||||
func NewHTTPSRedirector(port int) *HTTPSRedirector {
|
||||
return &HTTPSRedirector{
|
||||
domains: make(map[string]int),
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *HTTPSRedirector) Add(domain string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.domains[domain]++
|
||||
}
|
||||
|
||||
func (r *HTTPSRedirector) Remove(domain string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.domains[domain] <= 1 {
|
||||
delete(r.domains, domain)
|
||||
} else {
|
||||
r.domains[domain]--
|
||||
}
|
||||
}
|
||||
|
||||
func (r *HTTPSRedirector) match(host string) bool {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.domains[host] > 0
|
||||
}
|
||||
|
||||
// Redirect responds with a redirect to the HTTPS endpoint of the requested
|
||||
// host if the host opted into redirection. It reports whether the request
|
||||
// was handled.
|
||||
func (r *HTTPSRedirector) Redirect(rw http.ResponseWriter, req *http.Request) bool {
|
||||
if req.Method == http.MethodConnect {
|
||||
return false
|
||||
}
|
||||
host, err := httppkg.CanonicalHost(req.Host)
|
||||
if err != nil || !r.match(host) {
|
||||
return false
|
||||
}
|
||||
|
||||
target := "https://" + host
|
||||
if r.port != 443 {
|
||||
target += ":" + strconv.Itoa(r.port)
|
||||
}
|
||||
target += req.URL.RequestURI()
|
||||
http.Redirect(rw, req, target, http.StatusMovedPermanently)
|
||||
return true
|
||||
}
|
||||
46
pkg/util/vhost/https_redirect_test.go
Normal file
46
pkg/util/vhost/https_redirect_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package vhost
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHTTPSRedirectorRedirect(t *testing.T) {
|
||||
r := NewHTTPSRedirector(443)
|
||||
r.Add("a.example.com")
|
||||
|
||||
// registered domain, port stripped from host, path and query preserved
|
||||
req := httptest.NewRequest("GET", "http://a.example.com:8080/foo?x=1", nil)
|
||||
rw := httptest.NewRecorder()
|
||||
require.True(t, r.Redirect(rw, req))
|
||||
require.Equal(t, 301, rw.Code)
|
||||
require.Equal(t, "https://a.example.com/foo?x=1", rw.Header().Get("Location"))
|
||||
|
||||
// unknown domain is not handled
|
||||
req = httptest.NewRequest("GET", "http://b.example.com/", nil)
|
||||
require.False(t, r.Redirect(httptest.NewRecorder(), req))
|
||||
}
|
||||
|
||||
func TestHTTPSRedirectorNonDefaultPort(t *testing.T) {
|
||||
r := NewHTTPSRedirector(8443)
|
||||
r.Add("a.example.com")
|
||||
|
||||
req := httptest.NewRequest("GET", "http://a.example.com/", nil)
|
||||
rw := httptest.NewRecorder()
|
||||
require.True(t, r.Redirect(rw, req))
|
||||
require.Equal(t, "https://a.example.com:8443/", rw.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestHTTPSRedirectorRefCount(t *testing.T) {
|
||||
r := NewHTTPSRedirector(443)
|
||||
r.Add("a.example.com")
|
||||
r.Add("a.example.com")
|
||||
|
||||
r.Remove("a.example.com")
|
||||
require.True(t, r.match("a.example.com"), "domain removed while another proxy still references it")
|
||||
|
||||
r.Remove("a.example.com")
|
||||
require.False(t, r.match("a.example.com"))
|
||||
}
|
||||
@@ -50,6 +50,9 @@ type ResourceController struct {
|
||||
// For HTTP proxies, forwarding HTTP requests
|
||||
HTTPReverseProxy *vhost.HTTPReverseProxy
|
||||
|
||||
// Domains of HTTPS proxies that requested automatic HTTP to HTTPS redirection
|
||||
HTTPSRedirector *vhost.HTTPSRedirector
|
||||
|
||||
// For HTTPS proxies, route requests to different clients by hostname and other information
|
||||
VhostHTTPSMuxer *vhost.HTTPSMuxer
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ func init() {
|
||||
type HTTPSProxy struct {
|
||||
*BaseProxy
|
||||
cfg *v1.HTTPSProxyConfig
|
||||
|
||||
// domains registered for HTTP to HTTPS redirection, removed on Close
|
||||
redirectDomains []string
|
||||
}
|
||||
|
||||
func NewHTTPSProxy(baseProxy *BaseProxy) Proxy {
|
||||
@@ -66,12 +69,28 @@ func (pxy *HTTPSProxy) Run() (remoteAddr string, err error) {
|
||||
xl.Infof("https proxy listen for host [%s] group [%s]", domain, pxy.cfg.LoadBalancer.Group)
|
||||
}
|
||||
|
||||
if pxy.cfg.HTTPRedirect {
|
||||
if pxy.rc.HTTPSRedirector != nil {
|
||||
for _, domain := range domains {
|
||||
pxy.rc.HTTPSRedirector.Add(domain)
|
||||
}
|
||||
pxy.redirectDomains = domains
|
||||
xl.Infof("https proxy enabled http redirect for hosts %v", domains)
|
||||
} else {
|
||||
xl.Warnf("httpRedirect is enabled but frps has no vhostHTTPPort or vhostHTTPSPort, ignored")
|
||||
}
|
||||
}
|
||||
|
||||
pxy.startCommonTCPListenersHandler()
|
||||
remoteAddr = strings.Join(addrs, ",")
|
||||
return
|
||||
}
|
||||
|
||||
func (pxy *HTTPSProxy) Close() {
|
||||
for _, domain := range pxy.redirectDomains {
|
||||
pxy.rc.HTTPSRedirector.Remove(domain)
|
||||
}
|
||||
pxy.redirectDomains = nil
|
||||
pxy.BaseProxy.Close()
|
||||
}
|
||||
|
||||
|
||||
47
server/proxy/idle_watcher.go
Normal file
47
server/proxy/idle_watcher.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// idleWatcher closes a proxied connection pair when no bytes have flowed in
|
||||
// either direction for the configured timeout. Without it, endpoints that
|
||||
// stay open at the TCP level but never send data again would pin the join
|
||||
// goroutines and their transfer buffers forever.
|
||||
type idleWatcher struct {
|
||||
lastActive atomic.Int64 // unix nano of the last byte transferred
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
func startIdleWatcher(timeout time.Duration, closeFn func()) *idleWatcher {
|
||||
w := &idleWatcher{stopCh: make(chan struct{})}
|
||||
w.touch()
|
||||
|
||||
go func() {
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-w.stopCh:
|
||||
return
|
||||
case <-timer.C:
|
||||
idle := time.Since(time.Unix(0, w.lastActive.Load()))
|
||||
if idle >= timeout {
|
||||
closeFn()
|
||||
return
|
||||
}
|
||||
timer.Reset(timeout - idle)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *idleWatcher) touch() {
|
||||
w.lastActive.Store(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func (w *idleWatcher) stop() {
|
||||
close(w.stopCh)
|
||||
}
|
||||
52
server/proxy/idle_watcher_test.go
Normal file
52
server/proxy/idle_watcher_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIdleWatcherClosesAfterTimeout(t *testing.T) {
|
||||
closed := make(chan struct{})
|
||||
w := startIdleWatcher(50*time.Millisecond, func() {
|
||||
close(closed)
|
||||
})
|
||||
defer w.stop()
|
||||
|
||||
select {
|
||||
case <-closed:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("closeFn was not called after idle timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdleWatcherTouchPostponesClose(t *testing.T) {
|
||||
var closedFlag atomic.Bool
|
||||
w := startIdleWatcher(100*time.Millisecond, func() {
|
||||
closedFlag.Store(true)
|
||||
})
|
||||
defer w.stop()
|
||||
|
||||
// keep the connection "active" well past the timeout
|
||||
for range 6 {
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
w.touch()
|
||||
}
|
||||
require.False(t, closedFlag.Load(), "closeFn fired despite ongoing activity")
|
||||
|
||||
// once activity stops, it should still close
|
||||
require.Eventually(t, closedFlag.Load, time.Second, 10*time.Millisecond)
|
||||
}
|
||||
|
||||
func TestIdleWatcherStopPreventsClose(t *testing.T) {
|
||||
var closedFlag atomic.Bool
|
||||
w := startIdleWatcher(50*time.Millisecond, func() {
|
||||
closedFlag.Store(true)
|
||||
})
|
||||
w.stop()
|
||||
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
require.False(t, closedFlag.Load(), "closeFn fired after watcher was stopped")
|
||||
}
|
||||
@@ -317,15 +317,33 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
|
||||
|
||||
name := pxy.GetName()
|
||||
proxyType := cfg.Type
|
||||
var watcher *idleWatcher
|
||||
touch := func() {
|
||||
if watcher != nil {
|
||||
watcher.touch()
|
||||
}
|
||||
}
|
||||
local = wrapCountingReadWriteCloser(local, nil, func(bytes int64) {
|
||||
touch()
|
||||
metrics.Server.AddTrafficIn(name, proxyType, bytes)
|
||||
})
|
||||
userConn = netpkg.WrapReadWriteCloserToConn(
|
||||
wrapCountingReadWriteCloser(userConn, nil, func(bytes int64) {
|
||||
touch()
|
||||
metrics.Server.AddTrafficOut(name, proxyType, bytes)
|
||||
}),
|
||||
userConn,
|
||||
)
|
||||
if idleTimeout := time.Duration(pxy.serverCfg.Transport.ProxyIdleTimeout) * time.Second; idleTimeout > 0 {
|
||||
wrappedLocal, wrappedUserConn := local, userConn
|
||||
watcher = startIdleWatcher(idleTimeout, func() {
|
||||
xl.Infof("close user connection [%s] of proxy [%s]: no traffic in either direction for %s",
|
||||
wrappedUserConn.RemoteAddr().String(), name, idleTimeout)
|
||||
_ = wrappedUserConn.Close()
|
||||
_ = wrappedLocal.Close()
|
||||
})
|
||||
defer watcher.stop()
|
||||
}
|
||||
metrics.Server.OpenConnection(name, proxyType)
|
||||
// Traffic is counted incrementally via the counting wrappers above, so the
|
||||
// byte totals returned by joinUserConnection are intentionally discarded here.
|
||||
|
||||
@@ -291,8 +291,14 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) {
|
||||
|
||||
// Create http vhost muxer.
|
||||
if cfg.VhostHTTPPort > 0 {
|
||||
var redirector *vhost.HTTPSRedirector
|
||||
if cfg.VhostHTTPSPort > 0 {
|
||||
redirector = vhost.NewHTTPSRedirector(cfg.VhostHTTPSRedirectPort)
|
||||
svr.rc.HTTPSRedirector = redirector
|
||||
}
|
||||
rp := vhost.NewHTTPReverseProxy(vhost.HTTPReverseProxyOptions{
|
||||
ResponseHeaderTimeoutS: cfg.VhostHTTPTimeout,
|
||||
HTTPSRedirector: redirector,
|
||||
}, svr.httpVhostRouter)
|
||||
svr.rc.HTTPReverseProxy = rp
|
||||
|
||||
|
||||
Reference in New Issue
Block a user