Skip to content

[duplicate-code] Duplicate Code Pattern: HTTP Server Lifecycle in cmd/root.go and cmd/proxy.go #7080

Description

@github-actions

🔍 Duplicate Code Pattern: HTTP Server Lifecycle in cmd/root.go and cmd/proxy.go

Part of duplicate code analysis: #7078

Summary

Both run() in internal/cmd/root.go and runProxy() in internal/cmd/proxy.go contain the same HTTP server serve/wait/shutdown sequence: launch the server in a goroutine, wait for context cancellation, then perform a graceful shutdown with a 5-second timeout. The pattern is ~12 lines duplicated across two command handlers.

Duplication Details

Pattern: Inline HTTP server serve-and-graceful-shutdown sequence

  • Severity: Medium
  • Occurrences: 2 (run in root.go, runProxy in proxy.go)
  • Locations:
    • internal/cmd/root.go (lines 439–460)
    • internal/cmd/proxy.go (lines 312–329)

root.go (lines 439–460):

go func() {
    if err := httpServer.Serve(listener); err != nil && err != http.ErrServerClosed {
        log.Printf("HTTP server error: %v", err)
        cancel()
    }
}()
// ...
<-ctx.Done()
debugLog.Print("Shutdown signal received, initiating graceful shutdown")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer shutdownCancel()
if err := httpServer.Shutdown(shutdownCtx); err != nil {
    log.Printf("HTTP server shutdown error: %v", err)
}

proxy.go (lines 312–329):

if err := httpServer.Serve(listener); err != nil && err != http.ErrServerClosed {
    log.Printf("HTTP server error: %v", err)
    cancel()
}
// ...
<-ctx.Done()
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), proxyShutdownTimeout)
defer shutdownCancel()
if err := httpServer.Shutdown(shutdownCtx); err != nil {
    log.Printf("HTTP server shutdown error: %v", err)
    logger.LogError("shutdown", "HTTP server shutdown error: %v", err)
    return err
}

Note: root.go hard-codes 5*time.Second while proxy.go uses the named constant proxyShutdownTimeout = 5 * time.Second — they are the same value.

Impact Analysis

  • Maintainability: Changing the shutdown timeout, adding structured logging to the error path, or switching to a different graceful shutdown strategy requires editing two separate files.
  • Bug Risk: root.go swallows the httpServer.Shutdown error silently; proxy.go logs it and returns it. Any future decision to unify error handling must be done in both places.
  • Code Bloat: ~12 lines × 2 locations = ~24 lines of nearly identical code.

Refactoring Recommendations

  1. Extract a serveAndWait helper in internal/cmd/ (e.g., internal/cmd/serve.go)
    // serveAndWait launches httpServer.Serve(listener) in a goroutine,
    // waits for ctx cancellation, then gracefully shuts the server down
    // within timeout. The cancel func is called if Serve returns an
    // unexpected error.
    func serveAndWait(ctx context.Context, cancel context.CancelFunc,
        httpServer *http.Server, listener net.Listener,
        timeout time.Duration) error {
        go func() {
            if err := httpServer.Serve(listener); err != nil && err != http.ErrServerClosed {
                log.Printf("HTTP server error: %v", err)
                cancel()
            }
        }()
        <-ctx.Done()
        shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), timeout)
        defer shutdownCancel()
        if err := httpServer.Shutdown(shutdownCtx); err != nil {
            log.Printf("HTTP server shutdown error: %v", err)
            return err
        }
        return nil
    }
    • Extract to: new file internal/cmd/serve.go
    • Estimated effort: 60–90 minutes
    • Benefits: Single place to update timeout, logging, and error handling; consistent behaviour between gateway and proxy subcommands

Implementation Checklist

  • Create internal/cmd/serve.go with serveAndWait helper
  • Refactor run() in root.go to use the helper
  • Refactor runProxy() in proxy.go to use the helper
  • Standardize the shutdown timeout (currently 5*time.Second literal in root.go vs proxyShutdownTimeout constant in proxy.go)
  • Run make test-all to verify no regression

Parent Issue

See parent analysis report: #7078
Related to #7078

Generated by Duplicate Code Detector · sonnet46 6.1M ·

  • expires on Jun 13, 2026, 3:43 AM UTC

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions