-
Notifications
You must be signed in to change notification settings - Fork 0
pr/refactor getbody retry ci #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
fc722b3
fix(sendRequest): handle nil GetBody, close response on retry, and co…
99222ee
test: use closed localhost port for deterministic network-error test
a51743d
docs: add copilot instructions and docker-only + atomic commits guidance
ef542d1
refactor: pre-read request body when GetBody missing; improve retry d…
2d68217
fix(gosec): guard uint64<->int64 conversions for MAX_BODY_SIZE displa…
022aa68
fix(gosec): remove unused math import
98b1b33
fix(gosec): guard uint64->int64 conversion for MAX_BODY_SIZE
0a7c369
fix(gosec): validate target URLs and annotate jitter/client.Do for gosec
cee4a3a
fix(gosec): suppress SSRF warning at request creation, use http.Serve…
ec50d4e
fix(gosec): avoid log injection and handle encoder/close errors
4517c31
fix(gosec): avoid log injection and handle Close/Encode errors
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| # Copilot instructions — FanOut | ||
|
|
||
| This file gives targeted, repository-specific guidance for future Copilot sessions working on FanOut (Go, single-binary HTTP fan-out service). | ||
|
|
||
| --- | ||
|
|
||
| ## Build, test, and lint commands | ||
|
|
||
| Build (release): | ||
|
|
||
| go build -trimpath -ldflags="-w -s" -o fanout | ||
|
|
||
| Build (debug): | ||
|
|
||
| go build -tags=debug -o fanout-debug | ||
|
|
||
| Run locally (echo mode for development): | ||
|
|
||
| TARGETS=localonly go run fanout.go | ||
|
|
||
| Run with production targets: | ||
|
|
||
| TARGETS="https://a.example/,https://b.example/" PORT=8080 go run fanout.go | ||
|
|
||
| Run the full test suite (with race detector): | ||
|
|
||
| go test -v -race ./... | ||
|
|
||
| Run a single test (exact name): | ||
|
|
||
| go test -run '^TestSendRequest$' . | ||
|
|
||
| Run a single test with race and verbose output: | ||
|
|
||
| go test -run '^TestSendRequest$' -v -race . | ||
|
|
||
| Formatting / vet: | ||
|
|
||
| gofmt -w . | ||
| go vet ./... | ||
|
|
||
| Security scan (used by README/CI): | ||
|
|
||
| gosec ./... | ||
|
|
||
| Docker (local): | ||
|
|
||
| docker build -t fanout:dev . | ||
|
|
||
| Multi-arch build (CI / release): | ||
|
|
||
| docker buildx build --platform linux/amd64,linux/arm64 -t yourorg/fanout:latest . | ||
|
|
||
| CI workflows: | ||
|
|
||
| - .github/workflows/docker-image.yml | ||
| - .github/workflows/binary-release.yml | ||
|
|
||
| --- | ||
|
|
||
| ## High-level architecture (big picture) | ||
|
|
||
| - Entrypoint: `fanout.go` — sets up HTTP handlers and environment-based configuration in `init()`. | ||
|
|
||
| - Endpoints: | ||
| - `ENDPOINT_PATH` (default `/fanout`) — main fan-out endpoint. | ||
| - `/health` — simple health check. | ||
| - `/version` — binary/version metadata. | ||
| - `/metrics` — Prometheus handler (enabled when `METRICS_ENABLED=true`). | ||
|
|
||
| - Modes: | ||
| - Echo mode: `TARGETS=localonly` — inbound requests are echoed back by `echoHandler`. | ||
| - Multiplex mode: `TARGETS` contains comma-separated targets; `multiplex` spawns one goroutine per target. | ||
|
|
||
| - Dispatcher & concurrency: | ||
| - `multiplex` launches a goroutine per configured target; responses are collected via a buffered channel and WaitGroup. Response order is not guaranteed. | ||
|
|
||
| - Request forwarding (`sendRequest`): | ||
| - Re-creates the original request per target, clones headers via `cloneHeaders` (sensitive headers are logged), and sets Content-Length appropriately. | ||
| - Implements retries for network errors and server (5xx) responses using exponential backoff + jitter. | ||
| - Adds `X-Retry-Count` on retry attempts. | ||
|
|
||
| - Logging & metrics: | ||
| - Asynchronous logger: `logQueue` is a buffered channel, format controlled by `LOG_FORMAT` (json/text) and `LOG_LEVEL`. | ||
| - Prometheus metrics (prefixed `fanout_`) are recorded when `METRICS_ENABLED=true`. | ||
|
|
||
| --- | ||
|
|
||
| ## Key repository conventions and gotchas | ||
|
|
||
| - Configuration is environment-driven and read in `init()`; changing env vars requires restarting the process. | ||
|
|
||
| - Body handling / GetBody semantics: | ||
| - The code uses a pre-read body optimization: when available, `preReadBody` is used for the first attempt; subsequent attempts call `getBody()`. | ||
| - Tests use `httptest.NewRequest` which provides `GetBody`; when writing tests or mock requests, ensure `GetBody` is present or provide a pre-read body. | ||
|
|
||
| - Retry behavior: | ||
| - Controlled via `MAX_RETRIES` (default: 3). | ||
| - Network errors are detected by substring matching in `isRetryableError` (e.g., "connection refused", "timeout", "deadline exceeded", "connection reset", "no such host"). | ||
| - 5xx responses trigger retries up to the configured limit. | ||
|
|
||
| - Sensitive headers: | ||
| - Configured via `SENSITIVE_HEADERS` (default `Authorization,Cookie`). `cloneHeaders` will log a warning when those are detected. | ||
|
|
||
| - Metrics naming and labels: | ||
| - Prometheus metrics use fixed names (e.g., `fanout_requests_total`, `fanout_target_requests_total`). Avoid renaming these without updating monitoring. | ||
|
|
||
| - Concurrency expectations: | ||
| - `multiplex` returns responses as they arrive. Do not rely on responses being in the same order as `TARGETS` unless ordering is explicitly implemented. | ||
|
|
||
| - Logging behavior: | ||
| - Log entries are queued to `logQueue`; if the queue is full, entries may be dropped or logged directly when errors occur. | ||
|
|
||
| - Versioning variables: | ||
| - `Version`, `GitCommit`, and `BuildTime` are populated at build time (defaults: dev/unknown). CI/release workflows set these. | ||
|
|
||
| --- | ||
|
|
||
| ## Where to look (short pointers) | ||
|
|
||
| - Core: `fanout.go` (single-file service implementation) | ||
| - Unit tests: `fanout_test.go` | ||
| - Container: `Dockerfile`, `compose.yml` | ||
| - CI: `.github/workflows/*` | ||
|
|
||
| --- | ||
|
|
||
| ## Repository workflow preferences | ||
|
|
||
| - Docker-only execution: All development, builds, tests and linters should be executed inside Docker containers, not on the host machine. This includes local runs, single-test runs, formatting, vetting, and CI-parity commands. Examples: | ||
|
|
||
| # Run full test suite inside official Go container | ||
| docker run --rm -v $(pwd):/src -w /src golang:1.24 go test -v -race ./... | ||
|
|
||
| # Run a single test inside Docker | ||
| docker run --rm -v $(pwd):/src -w /src golang:1.24 go test -run '^TestSendRequest$' -v -race . | ||
|
|
||
| # Build inside Docker | ||
| docker run --rm -v $(pwd):/src -w /src golang:1.24 go build -trimpath -ldflags="-w -s" -o fanout | ||
|
|
||
| Prefer running via docker-compose (compose.yml) or CI-style containers so host toolchains are not required. | ||
|
|
||
| - Atomic commits: Make small, atomic commits for every logical change. Each commit should be self-contained and reversible. Use a separate branch per feature/bugfix and keep commit messages focused on a single purpose. | ||
|
|
||
| --- | ||
|
|
||
| If something important is missing or you want additional coverage (examples, more test-run tips, or CI notes), ask and this file can be expanded. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This step masks failures:
go vet ./... || truewill always succeed even when vet finds issues, andgofmt -l .won’t fail the job when files are unformatted. Make the step fail on vet/gofmt findings (e.g., remove|| truefor vet and explicitly error whengofmt -loutputs any files).