From dcf2e29c16e6861ce87711a9d8a6ededc842de95 Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sat, 14 Feb 2026 12:36:22 +0100 Subject: [PATCH] Fix linting issues and refactor for improved code quality --- cmd/soundtouch-service/main.go | 189 +++++++++----------- pkg/service/handlers/recorder_middleware.go | 6 + pkg/service/proxy/patterns.go | 4 + pkg/service/proxy/recorder.go | 129 ++++++++----- 4 files changed, 172 insertions(+), 156 deletions(-) diff --git a/cmd/soundtouch-service/main.go b/cmd/soundtouch-service/main.go index b8f7717..c2705ec 100644 --- a/cmd/soundtouch-service/main.go +++ b/cmd/soundtouch-service/main.go @@ -35,12 +35,14 @@ func main() { recorder := proxy.NewRecorder(config.dataDir) recorder.Redact = config.redact patternsPath := filepath.Join(config.dataDir, "patterns.json") + patterns, err := proxy.LoadPatterns(patternsPath) if err == nil && len(patterns) > 0 { recorder.Patterns = patterns } else if err != nil { log.Printf("Warning: Failed to load patterns from %s: %v", patternsPath, err) } + server.SetRecorder(recorder) tlsConfig, err := cm.GetServerTLSConfig(config.domains) @@ -80,16 +82,7 @@ type serviceConfig struct { func loadConfig() serviceConfig { // Define flags - fPort := flag.String("port", "", "Port to bind the service to (env: PORT)") - fBindAddr := flag.String("bind", "", "Network interface to bind to (env: BIND_ADDR)") - fTargetURL := flag.String("target-url", "", "URL for Python-based service components (env: PYTHON_BACKEND_URL)") - fDataDir := flag.String("data-dir", "", "Directory for persistent data (env: DATA_DIR)") - fServerURL := flag.String("server-url", "", "External URL of this service (env: SERVER_URL)") - fHttpsPort := flag.String("https-port", "", "HTTPS port to bind the service to (env: HTTPS_PORT)") - fHttpsServerURL := flag.String("https-server-url", "", "External HTTPS URL (env: HTTPS_SERVER_URL)") - fRedact := flag.String("redact-logs", "", "Redact sensitive data in proxy logs (true/false, env: REDACT_PROXY_LOGS)") - fLogBody := flag.String("log-bodies", "", "Log full request/response bodies (true/false, env: LOG_PROXY_BODY)") - fRecord := flag.String("record-interactions", "", "Record HTTP interactions to disk (true/false, env: RECORD_INTERACTIONS)") + fPort, fBindAddr, fTargetURL, fDataDir, fServerURL, fHTTPSPort, fHTTPSServerURL, fRedact, fLogBody, fRecord := defineFlags() flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of soundtouch-service:\n") @@ -99,78 +92,16 @@ func loadConfig() serviceConfig { flag.Parse() - port := *fPort - if port == "" { - port = os.Getenv("PORT") - } - if port == "" { - port = "8000" - } - - bindAddr := *fBindAddr - if bindAddr == "" { - bindAddr = os.Getenv("BIND_ADDR") - } + port := getEnvOrDefault("PORT", *fPort, "8000") + bindAddr := getEnvOrDefault("BIND_ADDR", *fBindAddr, "") addr := bindAddr + ":" + port if bindAddr == "" { addr = ":" + port } - targetURL := *fTargetURL - if targetURL == "" { - targetURL = os.Getenv("PYTHON_BACKEND_URL") - } - if targetURL == "" { - targetURL = "http://localhost:8001" - } - - dataDir := *fDataDir - if dataDir == "" { - dataDir = os.Getenv("DATA_DIR") - } - if dataDir == "" { - dataDir = "data" - } - - serverURL := *fServerURL - if serverURL == "" { - serverURL = os.Getenv("SERVER_URL") - } - if serverURL == "" { - hostname, _ := os.Hostname() - if hostname == "" { - hostname = "localhost" - } - - serverURL = "http://" + strings.ToLower(hostname) + ":" + port - } - - httpsPort := *fHttpsPort - if httpsPort == "" { - httpsPort = os.Getenv("HTTPS_PORT") - } - if httpsPort == "" { - httpsPort = "8443" - } - - httpsAddr := bindAddr + ":" + httpsPort - if bindAddr == "" { - httpsAddr = ":" + httpsPort - } - - httpsServerURL := *fHttpsServerURL - if httpsServerURL == "" { - httpsServerURL = os.Getenv("HTTPS_SERVER_URL") - } - if httpsServerURL == "" { - hostname, _ := os.Hostname() - if hostname == "" { - hostname = "localhost" - } - - httpsServerURL = "https://" + strings.ToLower(hostname) + ":" + httpsPort - } + targetURL := getEnvOrDefault("PYTHON_BACKEND_URL", *fTargetURL, "http://localhost:8001") + dataDir := getEnvOrDefault("DATA_DIR", *fDataDir, "data") hostname, _ := os.Hostname() if hostname == "" { @@ -179,6 +110,79 @@ func loadConfig() serviceConfig { hostname = strings.ToLower(hostname) + serverURL := getEnvOrDefault("SERVER_URL", *fServerURL, "") + if serverURL == "" { + serverURL = "http://" + hostname + ":" + port + } + + httpsPort := getEnvOrDefault("HTTPS_PORT", *fHTTPSPort, "8443") + + httpsAddr := bindAddr + ":" + httpsPort + if bindAddr == "" { + httpsAddr = ":" + httpsPort + } + + httpsServerURL := getEnvOrDefault("HTTPS_SERVER_URL", *fHTTPSServerURL, "") + if httpsServerURL == "" { + httpsServerURL = "https://" + hostname + ":" + httpsPort + } + + domains := getDomains(serverURL, httpsServerURL, hostname) + + redactVal := getEnvOrDefault("REDACT_PROXY_LOGS", *fRedact, "") + redact := redactVal != "false" + + logBodyVal := getEnvOrDefault("LOG_PROXY_BODY", *fLogBody, "") + logBody := logBodyVal == "true" + + recordVal := getEnvOrDefault("RECORD_INTERACTIONS", *fRecord, "") + record := recordVal != "false" + + return serviceConfig{ + port: port, + bindAddr: bindAddr, + addr: addr, + targetURL: targetURL, + dataDir: dataDir, + serverURL: serverURL, + httpsServerURL: httpsServerURL, + httpsAddr: httpsAddr, + redact: redact, + logBody: logBody, + record: record, + domains: domains, + } +} + +func defineFlags() (port, bind, target, data, server, httpsPort, httpsServer, redact, logBody, record *string) { + port = flag.String("port", "", "Port to bind the service to (env: PORT)") + bind = flag.String("bind", "", "Network interface to bind to (env: BIND_ADDR)") + target = flag.String("target-url", "", "URL for Python-based service components (env: PYTHON_BACKEND_URL)") + data = flag.String("data-dir", "", "Directory for persistent data (env: DATA_DIR)") + server = flag.String("server-url", "", "External URL of this service (env: SERVER_URL)") + httpsPort = flag.String("https-port", "", "HTTPS port to bind the service to (env: HTTPS_PORT)") + httpsServer = flag.String("https-server-url", "", "External HTTPS URL (env: HTTPS_SERVER_URL)") + redact = flag.String("redact-logs", "", "Redact sensitive data in proxy logs (true/false, env: REDACT_PROXY_LOGS)") + logBody = flag.String("log-bodies", "", "Log full request/response bodies (true/false, env: LOG_PROXY_BODY)") + record = flag.String("record-interactions", "", "Record HTTP interactions to disk (true/false, env: RECORD_INTERACTIONS)") + + return +} + +func getEnvOrDefault(envKey, flagVal, defaultVal string) string { + if flagVal != "" { + return flagVal + } + + val := os.Getenv(envKey) + if val != "" { + return val + } + + return defaultVal +} + +func getDomains(serverURL, httpsServerURL, hostname string) []string { domainsMap := map[string]bool{ "streaming.bose.com": true, "updates.bose.com": true, @@ -204,38 +208,7 @@ func loadConfig() serviceConfig { domains = append(domains, d) } - redactVal := *fRedact - if redactVal == "" { - redactVal = os.Getenv("REDACT_PROXY_LOGS") - } - redact := redactVal != "false" - - logBodyVal := *fLogBody - if logBodyVal == "" { - logBodyVal = os.Getenv("LOG_PROXY_BODY") - } - logBody := logBodyVal == "true" - - recordVal := *fRecord - if recordVal == "" { - recordVal = os.Getenv("RECORD_INTERACTIONS") - } - record := recordVal != "false" - - return serviceConfig{ - port: port, - bindAddr: bindAddr, - addr: addr, - targetURL: targetURL, - dataDir: dataDir, - serverURL: serverURL, - httpsServerURL: httpsServerURL, - httpsAddr: httpsAddr, - redact: redact, - logBody: logBody, - record: record, - domains: domains, - } + return domains } func initDataStore(dataDir string) *datastore.DataStore { diff --git a/pkg/service/handlers/recorder_middleware.go b/pkg/service/handlers/recorder_middleware.go index 312de46..10101f8 100644 --- a/pkg/service/handlers/recorder_middleware.go +++ b/pkg/service/handlers/recorder_middleware.go @@ -19,8 +19,10 @@ func (s *Server) RecordMiddleware(next http.Handler) http.Handler { // Buffer the request body if it exists var reqBody []byte + if r.Body != nil { var err error + reqBody, err = io.ReadAll(r.Body) if err == nil { r.Body = io.NopCloser(bytes.NewBuffer(reqBody)) @@ -37,6 +39,9 @@ func (s *Server) RecordMiddleware(next http.Handler) http.Handler { // Create a response object for the recorder res := rw.getRecordedResponse(r) + if res.Body != nil { + defer func() { _ = res.Body.Close() }() + } // Put back the original request body for recording r.Body = io.NopCloser(bytes.NewBuffer(reqBody)) @@ -89,5 +94,6 @@ func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if h, ok := rw.ResponseWriter.(http.Hijacker); ok { return h.Hijack() } + return nil, nil, fmt.Errorf("ResponseWriter does not support Hijacker") } diff --git a/pkg/service/proxy/patterns.go b/pkg/service/proxy/patterns.go index e949c02..c525c46 100644 --- a/pkg/service/proxy/patterns.go +++ b/pkg/service/proxy/patterns.go @@ -25,6 +25,7 @@ func LoadPatterns(path string) (PathPatterns, error) { if os.IsNotExist(err) { return PathPatterns{}, nil } + return nil, err } @@ -38,6 +39,7 @@ func LoadPatterns(path string) (PathPatterns, error) { if err != nil { return nil, fmt.Errorf("invalid regex in pattern %s: %w", patterns[i].Name, err) } + patterns[i].compiled = re } @@ -51,6 +53,7 @@ func (pp PathPatterns) Sanitize(segment string) (string, string) { return p.Replacement, p.Replacement } } + return segment, "" } @@ -63,5 +66,6 @@ func DefaultPatterns() PathPatterns { } re, _ := regexp.Compile(p.Regexp) p.compiled = re + return PathPatterns{p} } diff --git a/pkg/service/proxy/recorder.go b/pkg/service/proxy/recorder.go index 4b7a966..7506412 100644 --- a/pkg/service/proxy/recorder.go +++ b/pkg/service/proxy/recorder.go @@ -29,6 +29,7 @@ type Recorder struct { // NewRecorder creates a new HTTP interaction recorder. func NewRecorder(baseDir string) *Recorder { sessionID := time.Now().Format("20060102-150405") + "-" + fmt.Sprintf("%d", os.Getpid()) + return &Recorder{ BaseDir: baseDir, SessionID: sessionID, @@ -43,10 +44,35 @@ func (r *Recorder) Record(category string, req *http.Request, res *http.Response return nil } - // Group by URL path, sanitizing variable segments like IP addresses - pathSegments := strings.Split(strings.Trim(req.URL.Path, "/"), "/") + sanitizedSegments, replacements := r.getSanitizedSegments(req.URL.Path) + dir := r.getRecordingDir(category, sanitizedSegments) + + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + + path := r.getRecordingPath(dir, req.Method) + + var buf bytes.Buffer + + r.writeRequest(&buf, req, replacements) + + if res != nil { + r.writeResponse(&buf, res) + } + + if err := os.WriteFile(path, buf.Bytes(), 0644); err != nil { + return err + } + + return r.updateEnvFile(replacements) +} + +func (r *Recorder) getSanitizedSegments(path string) ([]string, map[string]string) { + pathSegments := strings.Split(strings.Trim(path, "/"), "/") sanitizedSegments := make([]string, 0, len(pathSegments)) replacements := make(map[string]string) + for _, segment := range pathSegments { if segment == "" { continue @@ -54,53 +80,63 @@ func (r *Recorder) Record(category string, req *http.Request, res *http.Response sanitized, replacement := r.Patterns.Sanitize(segment) sanitizedSegments = append(sanitizedSegments, sanitized) + if replacement != "" { replacements[segment] = replacement } } + return sanitizedSegments, replacements +} + +func (r *Recorder) getRecordingDir(category string, sanitizedSegments []string) string { subDir := "root" if len(sanitizedSegments) > 0 { subDir = filepath.Join(sanitizedSegments...) } - dir := filepath.Join(r.BaseDir, "interactions", r.SessionID, category, subDir) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("failed to create directory %s: %w", dir, err) - } + return filepath.Join(r.BaseDir, "interactions", r.SessionID, category, subDir) +} +func (r *Recorder) getRecordingPath(dir, method string) string { timestamp := time.Now().Format("15-04-05.000") count := atomic.AddUint64(&r.counter, 1) - filename := fmt.Sprintf("%04d-%s-%s.http", count, timestamp, req.Method) - path := filepath.Join(dir, filename) + filename := fmt.Sprintf("%04d-%s-%s.http", count, timestamp, method) - var buf bytes.Buffer + return filepath.Join(dir, filename) +} - // Write Request +func (r *Recorder) writeRequest(buf *bytes.Buffer, req *http.Request, replacements map[string]string) { displayURL := req.URL.String() for orig, repl := range replacements { displayURL = strings.ReplaceAll(displayURL, orig, "{{"+strings.Trim(repl, "{}")+"}}") } - buf.WriteString(fmt.Sprintf("### %s %s\n", req.Method, displayURL)) + fmt.Fprintf(buf, "### %s %s\n", req.Method, displayURL) + for orig, repl := range replacements { key := strings.Trim(repl, "{}") - buf.WriteString(fmt.Sprintf("// %s: %s\n", key, orig)) + fmt.Fprintf(buf, "// %s: %s\n", key, orig) } - buf.WriteString(fmt.Sprintf("%s %s\n", req.Method, displayURL)) + + fmt.Fprintf(buf, "%s %s\n", req.Method, displayURL) + for k, vv := range req.Header { if r.Redact && isSensitive(k) { - buf.WriteString(fmt.Sprintf("%s: [REDACTED]\n", k)) + fmt.Fprintf(buf, "%s: [REDACTED]\n", k) continue } + for _, v := range vv { val := v for orig, repl := range replacements { val = strings.ReplaceAll(val, orig, "{{"+strings.Trim(repl, "{}")+"}}") } - buf.WriteString(fmt.Sprintf("%s: %s\n", k, val)) + + fmt.Fprintf(buf, "%s: %s\n", k, val) } } + buf.WriteString("\n") if req.Body != nil { @@ -111,46 +147,42 @@ func (r *Recorder) Record(category string, req *http.Request, res *http.Response buf.WriteString("\n") } } +} - // Write Response if available - if res != nil { - buf.WriteString("\n") - buf.WriteString("> {% \n") - buf.WriteString(fmt.Sprintf(" // Response: %d %s\n", res.StatusCode, http.StatusText(res.StatusCode))) - buf.WriteString(" // Headers:\n") - for k, vv := range res.Header { - if r.Redact && isSensitive(k) { - buf.WriteString(fmt.Sprintf(" // %s: [REDACTED]\n", k)) - continue - } - for _, v := range vv { - buf.WriteString(fmt.Sprintf(" // %s: %s\n", k, v)) - } +func (r *Recorder) writeResponse(buf *bytes.Buffer, res *http.Response) { + buf.WriteString("\n") + buf.WriteString("> {% \n") + fmt.Fprintf(buf, " // Response: %d %s\n", res.StatusCode, http.StatusText(res.StatusCode)) + buf.WriteString(" // Headers:\n") + + for k, vv := range res.Header { + if r.Redact && isSensitive(k) { + fmt.Fprintf(buf, " // %s: [REDACTED]\n", k) + continue } - buf.WriteString("%}\n") - if res.Body != nil { - bodyBytes, err := io.ReadAll(res.Body) - if err == nil { - res.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - - contentType := res.Header.Get("Content-Type") - if strings.Contains(contentType, "xml") || strings.Contains(contentType, "json") || strings.Contains(contentType, "text") { - buf.WriteString("\n/*\n") - buf.Write(bodyBytes) - buf.WriteString("\n*/\n") - } else { - buf.WriteString(fmt.Sprintf("\n// [Binary response body: %d bytes]\n", len(bodyBytes))) - } - } + for _, v := range vv { + fmt.Fprintf(buf, " // %s: %s\n", k, v) } } - if err := os.WriteFile(path, buf.Bytes(), 0644); err != nil { - return err - } + buf.WriteString("%}\n") - return r.updateEnvFile(replacements) + if res.Body != nil { + bodyBytes, err := io.ReadAll(res.Body) + if err == nil { + res.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + + contentType := res.Header.Get("Content-Type") + if strings.Contains(contentType, "xml") || strings.Contains(contentType, "json") || strings.Contains(contentType, "text") { + buf.WriteString("\n/*\n") + buf.Write(bodyBytes) + buf.WriteString("\n*/\n") + } else { + fmt.Fprintf(buf, "\n// [Binary response body: %d bytes]\n", len(bodyBytes)) + } + } + } } func (r *Recorder) updateEnvFile(newVars map[string]string) error { @@ -162,6 +194,7 @@ func (r *Recorder) updateEnvFile(newVars map[string]string) error { defer r.mu.Unlock() changed := false + for orig, repl := range newVars { key := strings.Trim(repl, "{}") if r.variables[key] != orig {