mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
Fix linting issues and refactor for improved code quality
This commit is contained in:
+81
-108
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user