-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce a fast proxy mode to improve HTTP/1.1 performances with bac…
…kends
- Loading branch information
1 parent
a398536
commit aa710d3
Showing
29 changed files
with
2,838 additions
and
367 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package proxy | ||
|
||
import ( | ||
"crypto/tls" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/traefik/traefik/v3/pkg/config/dynamic" | ||
"github.com/traefik/traefik/v3/pkg/metrics" | ||
"github.com/traefik/traefik/v3/pkg/proxy/fast" | ||
"github.com/traefik/traefik/v3/pkg/proxy/httputil" | ||
) | ||
|
||
// TransportManager manages transport used for backend communications. | ||
// FIXME duplicate?? | ||
type TransportManager interface { | ||
Get(name string) (*dynamic.ServersTransport, error) | ||
GetRoundTripper(name string) (http.RoundTripper, error) | ||
GetTLSConfig(name string) (*tls.Config, error) | ||
} | ||
|
||
// Builder is a proxy builder which returns a fasthttp or httputil proxy corresponding | ||
// to the ServersTransport configuration. | ||
type Builder struct { | ||
fastProxyBuilder *fast.ProxyBuilder | ||
httputilBuilder *httputil.ProxyBuilder | ||
|
||
transportManager httputil.TransportManager | ||
fastProxy bool | ||
} | ||
|
||
// NewBuilder creates and returns a new Builder instance. | ||
func NewBuilder(transportManager TransportManager, semConvMetricsRegistry *metrics.SemConvMetricsRegistry, fastProxy bool) *Builder { | ||
return &Builder{ | ||
fastProxyBuilder: fast.NewProxyBuilder(transportManager), | ||
httputilBuilder: httputil.NewProxyBuilder(transportManager, semConvMetricsRegistry), | ||
transportManager: transportManager, | ||
fastProxy: fastProxy, | ||
} | ||
} | ||
|
||
// Update is the handler called when the dynamic configuration is updated. | ||
func (b *Builder) Update(newConfigs map[string]*dynamic.ServersTransport) { | ||
b.fastProxyBuilder.Update(newConfigs) | ||
} | ||
|
||
// Build builds an HTTP proxy for the given URL using the ServersTransport with the given name. | ||
func (b *Builder) Build(configName string, targetURL *url.URL, shouldObserve, passHostHeader bool, flushInterval time.Duration) (http.Handler, error) { | ||
serversTransport, err := b.transportManager.Get(configName) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting ServersTransport: %w", err) | ||
} | ||
|
||
if !b.fastProxy || !serversTransport.DisableHTTP2 && (targetURL.Scheme == "https" || targetURL.Scheme == "h2c") { | ||
return b.httputilBuilder.Build(configName, targetURL, shouldObserve, passHostHeader, flushInterval) | ||
} | ||
|
||
return b.fastProxyBuilder.Build(configName, targetURL, passHostHeader) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package fast | ||
|
||
import ( | ||
"crypto/tls" | ||
"fmt" | ||
"net" | ||
"net/http" | ||
"net/url" | ||
"reflect" | ||
"time" | ||
|
||
"github.com/traefik/traefik/v3/pkg/config/dynamic" | ||
) | ||
|
||
// TransportManager manages transport used for backend communications. | ||
type TransportManager interface { | ||
Get(name string) (*dynamic.ServersTransport, error) | ||
GetTLSConfig(name string) (*tls.Config, error) | ||
} | ||
|
||
// ProxyBuilder handles the connection pools for the FastProxy proxies. | ||
type ProxyBuilder struct { | ||
transportManager TransportManager | ||
|
||
// lock isn't needed because ProxyBuilder is not called concurrently. | ||
pools map[string]map[string]*connPool | ||
proxy func(*http.Request) (*url.URL, error) | ||
|
||
// not goroutine safe. | ||
configs map[string]*dynamic.ServersTransport | ||
} | ||
|
||
// NewProxyBuilder creates a new ProxyBuilder. | ||
func NewProxyBuilder(transportManager TransportManager) *ProxyBuilder { | ||
return &ProxyBuilder{ | ||
transportManager: transportManager, | ||
pools: make(map[string]map[string]*connPool), | ||
proxy: http.ProxyFromEnvironment, | ||
configs: make(map[string]*dynamic.ServersTransport), | ||
} | ||
} | ||
|
||
// Update updates all the round-tripper corresponding to the given configs. | ||
// This method must not be used concurrently. | ||
func (r *ProxyBuilder) Update(newConfigs map[string]*dynamic.ServersTransport) { | ||
for configName := range r.configs { | ||
if _, ok := newConfigs[configName]; !ok { | ||
delete(r.pools, configName) | ||
} | ||
} | ||
|
||
for newConfigName, newConfig := range newConfigs { | ||
if !reflect.DeepEqual(newConfig, r.configs[newConfigName]) { | ||
delete(r.pools, newConfigName) | ||
} | ||
} | ||
|
||
r.configs = newConfigs | ||
} | ||
|
||
// Build builds a new ReverseProxy with the given configuration. | ||
func (r *ProxyBuilder) Build(cfgName string, targetURL *url.URL, passHostHeader bool) (http.Handler, error) { | ||
proxyURL, err := r.proxy(&http.Request{URL: targetURL}) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting proxy: %w", err) | ||
} | ||
|
||
cfg, err := r.transportManager.Get(cfgName) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting ServersTransport: %w", err) | ||
} | ||
|
||
var responseHeaderTimeout time.Duration | ||
if cfg.ForwardingTimeouts != nil { | ||
responseHeaderTimeout = time.Duration(cfg.ForwardingTimeouts.ResponseHeaderTimeout) | ||
} | ||
|
||
tlsConfig, err := r.transportManager.GetTLSConfig(cfgName) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting TLS config: %w", err) | ||
} | ||
|
||
pool := r.getPool(cfgName, cfg, tlsConfig, targetURL, proxyURL) | ||
return NewReverseProxy(targetURL, proxyURL, passHostHeader, responseHeaderTimeout, pool) | ||
} | ||
|
||
func (r *ProxyBuilder) getPool(cfgName string, config *dynamic.ServersTransport, tlsConfig *tls.Config, targetURL *url.URL, proxyURL *url.URL) *connPool { | ||
pool, ok := r.pools[cfgName] | ||
if !ok { | ||
pool = make(map[string]*connPool) | ||
r.pools[cfgName] = pool | ||
} | ||
|
||
if connPool, ok := pool[targetURL.String()]; ok { | ||
return connPool | ||
} | ||
|
||
idleConnTimeout := 90 * time.Second | ||
dialTimeout := 30 * time.Second | ||
if config.ForwardingTimeouts != nil { | ||
idleConnTimeout = time.Duration(config.ForwardingTimeouts.IdleConnTimeout) | ||
dialTimeout = time.Duration(config.ForwardingTimeouts.DialTimeout) | ||
} | ||
|
||
proxyDialer := newDialer(dialerConfig{ | ||
DialKeepAlive: 0, | ||
DialTimeout: dialTimeout, | ||
HTTP: true, | ||
TLS: targetURL.Scheme == "https", | ||
ProxyURL: proxyURL, | ||
}, tlsConfig) | ||
|
||
connPool := newConnPool(config.MaxIdleConnsPerHost, idleConnTimeout, func() (net.Conn, error) { | ||
return proxyDialer.Dial("tcp", addrFromURL(targetURL)) | ||
}) | ||
|
||
r.pools[cfgName][targetURL.String()] = connPool | ||
|
||
return connPool | ||
} |
Oops, something went wrong.