Skip to content

[duplicate-code] Duplicate Code Pattern: HTTP request body read-and-restore logic #3208

@github-actions

Description

@github-actions

Part of duplicate code analysis: #3207

Summary

Two separate functions in internal/server/ independently implement the same "read HTTP request body, process it, restore it for downstream handlers" pattern. The guard logic (r.Method == "POST" && r.Body != nil, io.ReadAll, then r.Body = io.NopCloser(bytes.NewBuffer(...))) is duplicated across ~15 lines in two files.

Duplication Details

Pattern: Read-process-restore of http.Request.Body

  • Severity: Medium

  • Occurrences: 2 independent implementations

  • Locations:

    • internal/server/http_helpers.go lines 106–136 (logHTTPRequestBody)
    • internal/server/sdk_logging.go lines 55–76 (WithSDKLogging)
  • Shared code structure:

// http_helpers.go (logHTTPRequestBody)
if r.Method != "POST" || r.Body == nil {
    return
}
bodyBytes, err := io.ReadAll(r.Body)
if err != nil || len(bodyBytes) == 0 {
    return
}
// ... process bodyBytes ...
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

// sdk_logging.go (WithSDKLogging)
if r.Method == "POST" && r.Body != nil {
    requestBody, err = io.ReadAll(r.Body)
    if err == nil && len(requestBody) > 0 {
        r.Body = io.NopCloser(bytes.NewBuffer(requestBody))
        // ... process requestBody ...
    }
}

Impact Analysis

  • Maintainability: If the body-restore idiom needs to change (e.g., to use a bytes.Reader instead of bytes.Buffer, or to handle large bodies with limits), it must be updated in two places.
  • Bug Risk: Low today, but divergence is possible — e.g. one site already uses inverted guard logic (!= "POST" early return vs == "POST" positive guard).
  • Code Bloat: ~12 lines of near-identical plumbing repeated unnecessarily.

Refactoring Recommendations

  1. Extract a shared helper peekRequestBody(r *http.Request) ([]byte, error) in internal/server/http_helpers.go (or a new internal/httputil function) that:

    • Checks method and body presence
    • Reads the body
    • Restores it with io.NopCloser
    • Returns the bytes (caller decides what to do with them)
    // Suggested location: internal/server/http_helpers.go or internal/httputil/httputil.go
    func peekRequestBody(r *http.Request) ([]byte, error) {
        if r.Method != http.MethodPost || r.Body == nil {
            return nil, nil
        }
        b, err := io.ReadAll(r.Body)
        if err != nil {
            return nil, err
        }
        r.Body = io.NopCloser(bytes.NewBuffer(b))
        return b, nil
    }
  2. Update callers to use the helper and apply their own post-processing on the returned bytes.

  3. Estimated effort: 1–2 hours including tests.

Implementation Checklist

  • Add peekRequestBody helper (or equivalent) in a shared location
  • Refactor logHTTPRequestBody in http_helpers.go to use the helper
  • Refactor WithSDKLogging in sdk_logging.go to use the helper
  • Verify that error paths and empty-body handling remain equivalent
  • Run make test to confirm no behaviour change

Parent Issue

See parent analysis report: #3207
Related to #3207

Generated by Duplicate Code Detector ·

  • expires on Apr 12, 2026, 6:03 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions