Skip to content

fix: code quality improvements across server, matcher, and proxy#29

Merged
yesdevnull merged 10 commits into
mainfrom
fix/code-quality-improvements
Mar 15, 2026
Merged

fix: code quality improvements across server, matcher, and proxy#29
yesdevnull merged 10 commits into
mainfrom
fix/code-quality-improvements

Conversation

@yesdevnull
Copy link
Copy Markdown
Owner

Summary

  • Proxy shutdown timeout: Shutdown now splits timeout between capture drain and HTTP drain instead of blocking indefinitely on captures.Wait()
  • Extract ReconstitutedBody helper: Eliminated triple-duplicated io.MultiReader + anonymous struct pattern into internal/httputil.ReconstitutedBody
  • Typed body reader errors: lazyBodyReader returns typed error instead of bool, distinguishing I/O errors from body-too-large
  • FilePath on MatchResult: Eliminated two O(n) linear scans per request by threading file path through the matcher via NewWithPaths
  • Clear body_file errors: resolveBodyFile checks os.Stat before EvalSymlinks so missing files get "not found" instead of confusing symlink errors
  • Template error logging: renderTemplate now logs a warning when template execution fails instead of silently returning raw template source
  • Bounded proxy captures: Semaphore caps concurrent capture goroutines at 20; request fields are copied before spawning to avoid referencing *http.Request after handler returns

Test Plan

  • All existing tests pass with -race flag
  • golangci-lint run ./... — 0 issues
  • go vet ./... — clean
  • gofmt -l . — all formatted
  • New tests added for each fix:
    • TestProxyShutdownRespectsTimeoutWithPendingCaptures
    • TestReconstitutedBody, TestReconstitutedBodyEmpty
    • TestMatchBodyIOError
    • TestMatchResultFilePath
    • TestServe_BodyFile_Missing_ClearLogMessage
    • TestServe_TemplateExecutionErrorLogged
    • TestProxyCapturesConcurrencyBounded

🤖 Generated with Claude Code

yesdevnull and others added 8 commits March 15, 2026 09:36
Previously captures.Wait() ran before the timeout context was created,
so a hanging capture goroutine would block shutdown indefinitely. Now
the timeout is split between capture drain and HTTP drain, each with
its own deadline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ation

The same io.MultiReader + anonymous struct pattern for rebuilding
r.Body appeared in matcher.go and twice in server.go. Extracted to
internal/httputil.ReconstitutedBody.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The lazy body reader previously used a single bool for both I/O errors
and body-too-large. Now returns a typed error so callers can distinguish
the two cases. errBodyTooLarge is a named sentinel for the policy limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The verbose-logging and resolveBodyFile code both did linear scans over
all coats to find the source file path on every matched request. Now
FilePath is threaded through the matcher via NewWithPaths and included
in MatchResult, making both lookups O(1). The ambiguity detection is
removed since the matcher already picks deterministically by order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveBodyFile used filepath.EvalSymlinks on the target path, which
produces a confusing 'unable to resolve symlinks' error when the file
simply doesn't exist. Now checks os.Stat first and returns a clear
'body_file not found' message. The HTTP response to clients remains
generic to avoid leaking file paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously renderTemplate silently returned the raw template source
when execution failed. Now accepts a logger and logs a warning with
the error, aiding debugging when templates contain valid syntax but
fail at execution time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously each proxied request spawned an unbounded goroutine that
captured *http.Request fields after the handler returned. Now request
data is copied before the goroutine starts, and a semaphore caps
concurrent captures at 20 to prevent goroutine/disk I/O storms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 14, 2026 23:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves request matching, logging, proxy capture behavior, and shared HTTP body handling across the server/matcher/proxy components, with accompanying tests.

Changes:

  • Thread coat source file paths through the matcher (MatchResult.FilePath + NewWithPaths) to avoid per-request scans and improve logging/body_file resolution behavior.
  • Extract shared request-body “reconstitute” logic into internal/httputil.ReconstitutedBody and use it from server + matcher.
  • Improve proxy shutdown/capture behavior (timeout split, bounded capture concurrency) and expand tests around logging and body_file/template behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
internal/server/server.go Uses matcher.NewWithPaths, logs template execution failures, resolves body_file using match file path, and adopts httputil.ReconstitutedBody.
internal/server/server_test.go Updates body_file ambiguity expectations and adds new tests for clearer missing-file logs and template execution warning logging.
internal/server/verbose_test.go Updates verbose logging expectations now that MatchResult.FilePath is always available.
internal/proxy/proxy.go Adds capture semaphore, copies request fields before spawning capture goroutines, and splits shutdown timeout between capture drain and HTTP drain.
internal/proxy/proxy_test.go Adds tests intended to cover bounded capture concurrency and shutdown timeout behavior.
internal/matcher/matcher.go Adds MatchResult.FilePath, NewWithPaths, typed body read errors, and uses httputil.ReconstitutedBody.
internal/matcher/matcher_test.go Adds tests for body I/O error handling and FilePath propagation.
internal/httputil/body.go Introduces ReconstitutedBody helper.
internal/httputil/body_test.go Adds unit tests for ReconstitutedBody.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/server/verbose_test.go Outdated
Comment thread internal/server/server_test.go Outdated
Comment thread internal/matcher/matcher.go Outdated
Comment thread internal/server/server.go
Comment thread internal/proxy/proxy_test.go
Comment thread internal/proxy/proxy_test.go
- Rename TestServe_VerboseLogging_AmbiguousCoatOmitsFilePath to
  TestServe_VerboseLogging_DuplicateCoatsLogsFirstFilePath
- Rename TestServe_BodyFile_AmbiguousCoatSources to
  TestServe_BodyFile_DuplicateCoatsFirstWins
- Add panic in NewWithPaths when len(paths) != len(coats)
- Add early isSubPath check before os.Stat in resolveBodyFile to
  prevent leaking file existence for traversal paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves request matching, server response handling, and proxy capture robustness by reducing duplicated body-reconstitution code, propagating coat source paths through the matcher, and tightening shutdown/capture behavior.

Changes:

  • Thread coat source FilePath through matcher.MatchResult via matcher.NewWithPaths, removing per-request scans in the server.
  • Extract shared request-body reconstruction into internal/httputil.ReconstitutedBody and use it from server + matcher.
  • Improve operational behavior: proxy shutdown now bounds time spent waiting on captures; proxy capture uses a semaphore and copies request fields before async capture; server logs template execution failures and clarifies body_file errors.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/server/server.go Uses NewWithPaths/MatchResult.FilePath, factors body reconstitution, logs template exec failures, refines body_file resolution.
internal/server/server_test.go Updates/extends tests for duplicate coats behavior, clearer body_file errors, and template exec warning logging.
internal/server/verbose_test.go Updates verbose logging expectations for duplicate coats to log the first coat’s file path deterministically.
internal/matcher/matcher.go Adds NewWithPaths, propagates FilePath into MatchResult, and returns typed body-read errors while restoring request bodies.
internal/matcher/matcher_test.go Adds coverage for body I/O errors and MatchResult.FilePath propagation.
internal/proxy/proxy.go Bounds concurrent capture work with a semaphore, copies request fields for async capture, and splits shutdown timeout between capture drain and HTTP drain.
internal/proxy/proxy_test.go Adds tests for bounded-capture behavior under load and shutdown returning within a bounded time.
internal/httputil/body.go Introduces ReconstitutedBody helper for replaying captured bytes + delegating Close to the original body.
internal/httputil/body_test.go Adds unit tests for ReconstitutedBody behavior with captured bytes and empty capture.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/server/server.go
os.Stat follows symlinks, so a symlink inside the coat directory
pointing outside could probe external paths before EvalSymlinks runs.
os.Lstat checks the entry itself without following, leaving symlink
resolution to the subsequent EvalSymlinks + isSubPath check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yesdevnull yesdevnull merged commit 9b744fe into main Mar 15, 2026
10 checks passed
@yesdevnull yesdevnull deleted the fix/code-quality-improvements branch March 15, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants