diff --git a/internal/acp/distributed_forwarder.go b/internal/acp/distributed_forwarder.go index 037f844..cd89850 100644 --- a/internal/acp/distributed_forwarder.go +++ b/internal/acp/distributed_forwarder.go @@ -94,9 +94,7 @@ func newDistributedTaskRouter(config distributedTaskRouterConfig) *distributedTa routes: distributedRouteMap(distributed.Forwarding.Routes), routeStore: newDistributedSessionRouteStore(defaultSessionRouteTTL), roundRobin: make(map[string]int), - httpClient: &http.Client{ - Timeout: openClawAgentWaitMaxTimeout + openClawAgentWaitHTTPMargin, - }, + httpClient: shared.NewHTTPClient(openClawAgentWaitMaxTimeout + openClawAgentWaitHTTPMargin), } } diff --git a/internal/acp/gateway.go b/internal/acp/gateway.go index 301034f..9ee72d0 100644 --- a/internal/acp/gateway.go +++ b/internal/acp/gateway.go @@ -115,12 +115,12 @@ func applyProductionGatewayRouting( gatewayURL := resolveURL(server.config.Upstream.GatewayURL, "GATEWAY_RPC_URL") if gatewayURL == "" { - return request + return withDefaultOpenClawGatewayEndpoint(request) } parsed, err := url.Parse(gatewayURL) if err != nil || parsed.Hostname() == "" { - return request + return withDefaultOpenClawGatewayEndpoint(request) } tls := strings.ToLower(parsed.Scheme) == "https" || strings.ToLower(parsed.Scheme) == "wss" @@ -145,6 +145,45 @@ func applyProductionGatewayRouting( request.ConnectAuthFields = []string{"token"} request.ConnectAuthSources = []string{"bridge"} request.HasSharedAuth = request.Auth.Token != "" + return withDefaultOpenClawGatewayEndpoint(request) +} + +func gatewayStatusForManager(manager *gatewayruntime.Manager) string { + if manager != nil && manager.HasConnectedSession() { + return "connected" + } + return "disconnected" +} + +func (s *Server) gatewayStatusForSystemLogs() string { + if gatewayStatusForManager(s.gateway) == "connected" { + return "connected" + } + if rpcErr := ensureProductionGatewayConnected(s, "openclaw", nil); rpcErr != nil { + return "disconnected" + } + return gatewayStatusForManager(s.gateway) +} + +func withDefaultOpenClawGatewayEndpoint( + request gatewayruntime.ConnectRequest, +) gatewayruntime.ConnectRequest { + if !isOpenClawMode(request.Mode) { + return request + } + if strings.TrimSpace(request.Endpoint.Host) == "" { + request.Endpoint.Host = "127.0.0.1" + } + if request.Endpoint.Port <= 0 { + if strings.TrimSpace(request.Endpoint.Host) == "127.0.0.1" || + strings.TrimSpace(request.Endpoint.Host) == "localhost" { + request.Endpoint.Port = 18789 + } else if request.Endpoint.TLS { + request.Endpoint.Port = 443 + } else { + request.Endpoint.Port = 80 + } + } return request } diff --git a/internal/acp/gateway_test.go b/internal/acp/gateway_test.go index db04baf..f63b579 100644 --- a/internal/acp/gateway_test.go +++ b/internal/acp/gateway_test.go @@ -1,9 +1,11 @@ package acp import ( + "path/filepath" "testing" "xworkmate-bridge/internal/gatewayruntime" + "xworkmate-bridge/internal/shared" ) func TestApplyProductionGatewayRoutingPreservesGatewayURLPath(t *testing.T) { @@ -35,3 +37,74 @@ func TestApplyProductionGatewayRoutingPreservesGatewayURLPath(t *testing.T) { t.Fatalf("expected gateway URL path to be preserved, got %#v", request.Endpoint) } } + +func TestApplyProductionGatewayRoutingDefaultsOpenClawEndpoint(t *testing.T) { + t.Setenv("GATEWAY_RPC_URL", "") + t.Setenv("BRIDGE_CONFIG_PATH", filepath.Join(t.TempDir(), "missing-config.yaml")) + server := NewServer() + + request := applyProductionGatewayRouting( + server, + gatewayruntime.ConnectRequest{Mode: "openclaw"}, + ) + + if request.Endpoint.Host != "127.0.0.1" { + t.Fatalf("expected built-in gateway host, got %#v", request.Endpoint) + } + if request.Endpoint.Port != 18789 { + t.Fatalf("expected built-in gateway port, got %#v", request.Endpoint) + } + if request.Endpoint.TLS { + t.Fatalf("expected built-in gateway endpoint to use local plaintext, got %#v", request.Endpoint) + } +} + +func TestSystemLogsConnectsProductionGatewayForStatus(t *testing.T) { + gateway := newAcpFakeOpenClawGateway(t) + defer gateway.Close() + + t.Setenv("GATEWAY_RPC_URL", gateway.URL()) + t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") + t.Setenv("BRIDGE_CONFIG_PATH", filepath.Join(t.TempDir(), "missing-config.yaml")) + t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", filepath.Join(t.TempDir(), "openclaw-device.json")) + resetBridgeGatewayIdentityForTest() + t.Cleanup(resetBridgeGatewayIdentityForTest) + + server := NewServer() + + result, rpcErr := server.handleRequest( + shared.RPCRequest{ + ID: "status", + Method: "system.logs", + Params: map[string]any{}, + }, + func(map[string]any) {}, + ) + if rpcErr != nil { + t.Fatalf("system.logs returned rpc error: %#v", rpcErr) + } + if got := result["gatewayStatus"]; got != "connected" { + t.Fatalf("expected gatewayStatus connected, got %#v", result) + } + if got := gateway.ConnectCount(); got != 1 { + t.Fatalf("expected one gateway connect attempt, got %d", got) + } + + result, rpcErr = server.handleRequest( + shared.RPCRequest{ + ID: "status-again", + Method: "system.logs", + Params: map[string]any{}, + }, + func(map[string]any) {}, + ) + if rpcErr != nil { + t.Fatalf("second system.logs returned rpc error: %#v", rpcErr) + } + if got := result["gatewayStatus"]; got != "connected" { + t.Fatalf("expected second gatewayStatus connected, got %#v", result) + } + if got := gateway.ConnectCount(); got != 1 { + t.Fatalf("expected connected status to reuse gateway session, got %d connect attempts", got) + } +} diff --git a/internal/acp/provider_compat.go b/internal/acp/provider_compat.go index d19dae8..973d17f 100644 --- a/internal/acp/provider_compat.go +++ b/internal/acp/provider_compat.go @@ -78,9 +78,7 @@ func newProviderCompat(provider syncedProvider) ProviderCompat { endpoint: resolveSingleAgentForwardEndpoint(provider), authHeader: provider.AuthorizationHeader, category: providerCategory(provider.ProviderID), - client: &http.Client{ - Timeout: 5 * time.Minute, - }, + client: shared.NewHTTPClient(5 * time.Minute), } switch provider.ProviderID { case "gemini": diff --git a/internal/acp/rpc_handler.go b/internal/acp/rpc_handler.go index 154e217..fd0a728 100644 --- a/internal/acp/rpc_handler.go +++ b/internal/acp/rpc_handler.go @@ -21,16 +21,9 @@ func (s *Server) handleRequest(request shared.RPCRequest, notify func(map[string return map[string]any{"status": "ok", "version": "0.7.0", "role": "acp-control-plane"}, nil case "system.logs": - gatewayStatus := "disconnected" - if s.gateway != nil { - if s.gateway.HasConnectedSession() { - gatewayStatus = "connected" - } - } - return map[string]any{ "bridgeStatus": "ok", - "gatewayStatus": gatewayStatus, + "gatewayStatus": s.gatewayStatusForSystemLogs(), "bridgeLogs": shared.GlobalLogBuffer.GetLines(), }, nil @@ -323,7 +316,7 @@ func (s *Server) handleDesktopMethod(ctx context.Context, method string, params cfg := desktop.PipelineConfig{ Display: display, - Port: 5004, + Port: desktop.DefaultRTPPort, Width: width, Height: height, FPS: fps, diff --git a/internal/desktop/input.go b/internal/desktop/input.go index 078b067..024b98c 100644 --- a/internal/desktop/input.go +++ b/internal/desktop/input.go @@ -6,9 +6,12 @@ import ( "log" "os" "os/exec" + "path/filepath" + "sort" "strconv" "strings" "sync" + "time" ) // InputEvent represents a client mouse or keyboard action @@ -29,6 +32,9 @@ type XdotoolInjector struct { width int height int isStarted bool + + moveChan chan InputEvent + stopChan chan struct{} } func NewXdotoolInjector(display string) *XdotoolInjector { @@ -36,9 +42,10 @@ func NewXdotoolInjector(display string) *XdotoolInjector { display = ":0.0" } return &XdotoolInjector{ - display: display, - width: 1280, // Default fallbacks - height: 720, + display: display, + width: 1280, // Default fallbacks + height: 720, + moveChan: make(chan InputEvent, 1), } } @@ -79,6 +86,11 @@ func (xi *XdotoolInjector) Start() error { xi.stdin = stdin xi.isStarted = true + if xi.stopChan == nil { + xi.stopChan = make(chan struct{}) + go xi.mouseMoveWorker() + } + return nil } @@ -95,9 +107,15 @@ func (xi *XdotoolInjector) Inject(event InputEvent) error { switch event.Type { case "mouse_move": - absX := int(event.X * float64(xi.width)) - absY := int(event.Y * float64(xi.height)) - cmdStr = fmt.Sprintf("mousemove %d %d\n", absX, absY) + select { + case <-xi.moveChan: + default: + } + select { + case xi.moveChan <- event: + default: + } + return nil case "mouse_down": btn := xi.mapButton(event.Button) @@ -161,12 +179,51 @@ func (xi *XdotoolInjector) Close() error { xi.stdin = nil xi.cmd = nil + + if xi.stopChan != nil { + close(xi.stopChan) + xi.stopChan = nil + } return nil } +func (xi *XdotoolInjector) mouseMoveWorker() { + ticker := time.NewTicker(16 * time.Millisecond) // ~60fps + defer ticker.Stop() + + var lastEvent *InputEvent + + for { + select { + case <-xi.stopChan: + return + case event := <-xi.moveChan: + lastEvent = &event + case <-ticker.C: + if lastEvent != nil { + xi.mu.Lock() + if xi.isStarted && xi.stdin != nil { + absX := int(lastEvent.X * float64(xi.width)) + absY := int(lastEvent.Y * float64(xi.height)) + cmdStr := fmt.Sprintf("mousemove %d %d\n", absX, absY) + if _, err := xi.stdin.Write([]byte(cmdStr)); err != nil { + log.Printf("xdotool mousemove write error: %v", err) + } + } + xi.mu.Unlock() + lastEvent = nil + } + } + } +} + func (xi *XdotoolInjector) queryDisplayGeometry() (int, int, error) { + return queryDisplayGeometry(xi.display) +} + +func queryDisplayGeometry(display string) (int, int, error) { cmd := exec.Command("xdotool", "getdisplaygeometry") - cmd.Env = desktopCommandEnv(xi.display) + cmd.Env = desktopCommandEnv(display) out, err := cmd.Output() if err != nil { return 0, 0, err @@ -186,19 +243,142 @@ func (xi *XdotoolInjector) queryDisplayGeometry() (int, int, error) { return w, h, nil } +func ResolveDesktopDisplay(requested string) string { + resolved, ok := resolveDesktopDisplayWithProber( + requested, + os.Getenv("DISPLAY"), + x11SocketDisplays("/tmp/.X11-unix"), + func(display string) bool { + _, _, err := queryDisplayGeometry(display) + return err == nil + }, + ) + if ok { + if strings.TrimSpace(requested) != "" && strings.TrimSpace(requested) != resolved { + log.Printf("Resolved remote desktop display %q to active X11 display %s", requested, resolved) + } + return resolved + } + + requested = strings.TrimSpace(requested) + if requested != "" { + return requested + } + if envDisplay := strings.TrimSpace(os.Getenv("DISPLAY")); envDisplay != "" { + return envDisplay + } + return ":0.0" +} + +func resolveDesktopDisplayWithProber( + requested string, + envDisplay string, + socketDisplays []string, + probe func(string) bool, +) (string, bool) { + requested = strings.TrimSpace(requested) + envDisplay = strings.TrimSpace(envDisplay) + + if requested != "" && !isAutoDesktopDisplay(requested) { + return requested, true + } + + candidates := make([]string, 0, len(socketDisplays)+3) + if envDisplay != "" { + candidates = append(candidates, envDisplay) + } + candidates = append(candidates, socketDisplays...) + if requested != "" { + candidates = append(candidates, requested) + } + candidates = append(candidates, ":0.0", ":0") + + for _, candidate := range uniqueDisplayCandidates(candidates) { + if probe(candidate) { + return candidate, true + } + } + return "", false +} + +func isAutoDesktopDisplay(display string) bool { + switch strings.TrimSpace(display) { + case "", ":0", ":0.0": + return true + default: + return false + } +} + +func x11SocketDisplays(dir string) []string { + entries, err := os.ReadDir(dir) + if err != nil { + return nil + } + displays := make([]int, 0, len(entries)) + for _, entry := range entries { + name := entry.Name() + if entry.IsDir() || !strings.HasPrefix(name, "X") { + continue + } + value, err := strconv.Atoi(strings.TrimPrefix(name, "X")) + if err != nil { + continue + } + displays = append(displays, value) + } + sort.Sort(sort.Reverse(sort.IntSlice(displays))) + + result := make([]string, 0, len(displays)) + for _, display := range displays { + result = append(result, fmt.Sprintf(":%d", display)) + } + return result +} + +func uniqueDisplayCandidates(candidates []string) []string { + seen := make(map[string]struct{}, len(candidates)) + result := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + candidate = strings.TrimSpace(candidate) + if candidate == "" { + continue + } + if _, exists := seen[candidate]; exists { + continue + } + seen[candidate] = struct{}{} + result = append(result, candidate) + } + return result +} + func desktopCommandEnv(display string) []string { env := os.Environ() if strings.TrimSpace(display) == "" { return env } filtered := make([]string, 0, len(env)+1) + hasXauthority := false for _, item := range env { if strings.HasPrefix(item, "DISPLAY=") { continue } + if strings.HasPrefix(item, "XAUTHORITY=") { + hasXauthority = true + } filtered = append(filtered, item) } - return append(filtered, "DISPLAY="+display) + filtered = append(filtered, "DISPLAY="+display) + if !hasXauthority { + if home := strings.TrimSpace(os.Getenv("HOME")); home != "" { + xauthority := filepath.Join(home, ".Xauthority") + if _, err := os.Stat(xauthority); err == nil { + filtered = append(filtered, "XAUTHORITY="+xauthority) + } + } + } + return filtered } func (xi *XdotoolInjector) mapButton(btn int) int { diff --git a/internal/desktop/input_test.go b/internal/desktop/input_test.go index 683184e..13ee25b 100644 --- a/internal/desktop/input_test.go +++ b/internal/desktop/input_test.go @@ -1,6 +1,8 @@ package desktop import ( + "os" + "path/filepath" "strings" "testing" ) @@ -26,6 +28,56 @@ func TestDesktopCommandEnvPreservesProcessEnvironmentAndOverridesDisplay(t *test } } +func TestDesktopCommandEnvAddsHomeXauthorityWhenAvailable(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("PATH", "/usr/local/bin:/usr/bin") + t.Setenv("DISPLAY", ":old") + if err := os.WriteFile(filepath.Join(home, ".Xauthority"), []byte("cookie"), 0o600); err != nil { + t.Fatalf("failed to create Xauthority fixture: %v", err) + } + + env := desktopCommandEnv(":12") + + if !envContains(env, "DISPLAY=:12") { + t.Fatalf("expected DISPLAY override, got %#v", env) + } + if !envContains(env, "XAUTHORITY="+filepath.Join(home, ".Xauthority")) { + t.Fatalf("expected XAUTHORITY from HOME, got %#v", env) + } +} + +func TestResolveDesktopDisplayWithProberUsesRequestedExplicitDisplay(t *testing.T) { + got, ok := resolveDesktopDisplayWithProber( + ":12", + "", + []string{":11"}, + func(display string) bool { + t.Fatalf("explicit display should not be probed, got %s", display) + return false + }, + ) + + if !ok || got != ":12" { + t.Fatalf("expected explicit display :12, got %q ok=%v", got, ok) + } +} + +func TestResolveDesktopDisplayWithProberSelectsActiveSocketDisplay(t *testing.T) { + got, ok := resolveDesktopDisplayWithProber( + ":0.0", + "", + []string{":12", ":11", ":10"}, + func(display string) bool { + return display == ":11" + }, + ) + + if !ok || got != ":11" { + t.Fatalf("expected first probed active display :11, got %q ok=%v", got, ok) + } +} + func envContains(env []string, expected string) bool { for _, item := range env { if item == expected { diff --git a/internal/desktop/pipeline.go b/internal/desktop/pipeline.go index b6ad9dd..5a91caa 100644 --- a/internal/desktop/pipeline.go +++ b/internal/desktop/pipeline.go @@ -11,6 +11,15 @@ import ( "time" ) +const DefaultRTPPort = 5004 + +func normalizeRTPPort(port int) int { + if port <= 0 { + return DefaultRTPPort + } + return port +} + // PipelineManager manages the screen capture process lifecycle type PipelineManager struct { cmd *exec.Cmd @@ -49,9 +58,7 @@ func (pm *PipelineManager) Start(cfg PipelineConfig) error { cfg.Display = ":0.0" } } - if cfg.Port <= 0 { - cfg.Port = 5004 - } + cfg.Port = normalizeRTPPort(cfg.Port) if cfg.Width <= 0 { cfg.Width = 1280 } @@ -74,8 +81,7 @@ func (pm *PipelineManager) Start(cfg PipelineConfig) error { pm.cancel = cancel cmd := exec.CommandContext(ctx, tool, args...) - // Set X11 display environment variable - cmd.Env = append(os.Environ(), "DISPLAY="+cfg.Display) + cmd.Env = desktopCommandEnv(cfg.Display) // Capture stdout/stderr for logging cmd.Stderr = os.Stderr @@ -189,7 +195,7 @@ func (pm *PipelineManager) buildGStreamer(cfg PipelineConfig) (string, []string, pipelineStr := strings.Join(pipelineParts, " ! ") args := []string{"-v"} args = append(args, strings.Split(pipelineStr, " ")...) - + var cleanArgs []string for _, arg := range args { trimmed := strings.TrimSpace(arg) @@ -223,6 +229,7 @@ func (pm *PipelineManager) buildFFmpeg(cfg PipelineConfig) (string, []string, er "-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency", + "-threads", "0", "-g", "30", ) } diff --git a/internal/desktop/service.go b/internal/desktop/service.go index 227c4bd..8890011 100644 --- a/internal/desktop/service.go +++ b/internal/desktop/service.go @@ -10,6 +10,7 @@ import ( type DesktopSession struct { SessionID string + Port int Pipeline *PipelineManager Injector *XdotoolInjector WebRTC *WebRTCServer @@ -38,10 +39,11 @@ func (s *Service) StartSession(sessionID string, cfg PipelineConfig, iceServers s.mu.Lock() defer s.mu.Unlock() - // Stop old session if exists - if old, exists := s.sessions[sessionID]; exists { - s.stopSessionLocked(old) - } + cfg.Port = normalizeRTPPort(cfg.Port) + cfg.Display = ResolveDesktopDisplay(cfg.Display) + + s.stopSessionByIDLocked(sessionID) + s.stopSessionsOnPortLocked(cfg.Port) log.Printf("Starting Remote Desktop session: %s", sessionID) @@ -80,6 +82,7 @@ func (s *Service) StartSession(sessionID string, cfg PipelineConfig, iceServers sess := &DesktopSession{ SessionID: sessionID, + Port: cfg.Port, Pipeline: pipeline, Injector: injector, WebRTC: webrtcSrv, @@ -104,12 +107,30 @@ func (s *Service) StopSession(sessionID string) { s.mu.Lock() defer s.mu.Unlock() + s.stopSessionByIDLocked(sessionID) +} + +func (s *Service) stopSessionByIDLocked(sessionID string) { if sess, exists := s.sessions[sessionID]; exists { s.stopSessionLocked(sess) delete(s.sessions, sessionID) } } +func (s *Service) stopSessionsOnPortLocked(port int) { + if port <= 0 { + return + } + for sessionID, sess := range s.sessions { + if sess.Port != port { + continue + } + log.Printf("Stopping Remote Desktop session on RTP port %d: %s", port, sessionID) + s.stopSessionLocked(sess) + delete(s.sessions, sessionID) + } +} + func (s *Service) stopSessionLocked(sess *DesktopSession) { log.Printf("Stopping Remote Desktop session: %s", sess.SessionID) if sess.Pipeline != nil { diff --git a/internal/desktop/service_test.go b/internal/desktop/service_test.go new file mode 100644 index 0000000..aeab0d1 --- /dev/null +++ b/internal/desktop/service_test.go @@ -0,0 +1,39 @@ +package desktop + +import "testing" + +func TestNormalizeRTPPortUsesDesktopDefault(t *testing.T) { + if got := normalizeRTPPort(0); got != DefaultRTPPort { + t.Fatalf("expected default RTP port %d, got %d", DefaultRTPPort, got) + } + if got := normalizeRTPPort(-1); got != DefaultRTPPort { + t.Fatalf("expected default RTP port %d for negative port, got %d", DefaultRTPPort, got) + } + if got := normalizeRTPPort(6004); got != 6004 { + t.Fatalf("expected explicit RTP port to be preserved, got %d", got) + } +} + +func TestStopSessionsOnPortReleasesOnlyMatchingRTPPort(t *testing.T) { + svc := &Service{ + sessions: map[string]*DesktopSession{ + "old-5004": { + SessionID: "old-5004", + Port: DefaultRTPPort, + }, + "other-6004": { + SessionID: "other-6004", + Port: 6004, + }, + }, + } + + svc.stopSessionsOnPortLocked(DefaultRTPPort) + + if _, ok := svc.sessions["old-5004"]; ok { + t.Fatalf("expected old session on RTP port %d to be removed", DefaultRTPPort) + } + if _, ok := svc.sessions["other-6004"]; !ok { + t.Fatalf("expected session on a different RTP port to remain") + } +} diff --git a/internal/opencodeadapter/http_client.go b/internal/opencodeadapter/http_client.go index 2766072..7656caf 100644 --- a/internal/opencodeadapter/http_client.go +++ b/internal/opencodeadapter/http_client.go @@ -29,7 +29,7 @@ func newOpenCodeHTTPClient(command string, args []string) *opencodeHTTPClient { command: strings.TrimSpace(command), args: append([]string(nil), args...), baseURL: "http://127.0.0.1:38993", - client: &http.Client{Timeout: 5 * time.Minute}, + client: shared.NewHTTPClient(5 * time.Minute), } } diff --git a/internal/shared/http.go b/internal/shared/http.go index 176ff16..151bf2d 100644 --- a/internal/shared/http.go +++ b/internal/shared/http.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" "strings" + "time" "github.com/gorilla/websocket" ) @@ -74,3 +75,18 @@ func ParseAllowedOrigins(raw string) []string { } return result } + +// NewHTTPClient returns an http.Client with a customized high-performance transport. +// It uses a significantly larger connection pool to prevent socket exhaustion and +// performance degradation when hitting the same backend hosts heavily. +func NewHTTPClient(timeout time.Duration) *http.Client { + t := http.DefaultTransport.(*http.Transport).Clone() + t.MaxIdleConns = 1000 + t.MaxIdleConnsPerHost = 100 + t.IdleConnTimeout = 90 * time.Second + + return &http.Client{ + Timeout: timeout, + Transport: t, + } +} diff --git a/internal/shared/tools.go b/internal/shared/tools.go index 38af464..f925390 100644 --- a/internal/shared/tools.go +++ b/internal/shared/tools.go @@ -276,7 +276,7 @@ func CallOpenAICompatibleCtx( request.Header.Set("Content-Type", "application/json") request.Header.Set("Authorization", "Bearer "+apiKey) - client := &http.Client{Timeout: 120 * time.Second} + client := NewHTTPClient(120 * time.Second) response, err := client.Do(request) if err != nil { return "", err diff --git a/internal/shared/vault.go b/internal/shared/vault.go index 6fbd3fd..fc51bdf 100644 --- a/internal/shared/vault.go +++ b/internal/shared/vault.go @@ -203,7 +203,7 @@ func doVaultRequest( if payload != nil { httpRequest.Header.Set("Content-Type", "application/json") } - client := &http.Client{Timeout: 30 * time.Second} + client := NewHTTPClient(30 * time.Second) response, err := client.Do(httpRequest) if err != nil { return nil, err