Skip to content

Conversation

@JasonLovesDoggo
Copy link
Owner

@JasonLovesDoggo JasonLovesDoggo commented May 22, 2025

Adds a utility function to retrieve a flushable writer from the http.ResponseWriter. This allows flushing the response after writing each chunk of data, ensuring timely delivery to the client, especially in tarpit scenarios.

Closes #84


Note

Unwraps ResponseWriter to obtain a flusher and flushes each streamed chunk in the tarpit responder, with improved disconnect handling and minor tooling/deps updates.

  • Backend – tarpit responder:
    • Introduces getFlushableWriter to unwrap http.ResponseWriter and find an http.Flusher.
    • Uses the flushable writer in ServeHTTP to write the initial buffer and subsequent chunks, flushing after each write; logs a warning if flushing isn’t possible.
    • Handles client disconnects by returning early instead of breaking the loop; minor error/timeout handling tweaks.
  • Tooling/Dependencies:
    • Adds tool github.com/caddyserver/xcaddy/cmd/xcaddy and related indirect dependencies in go.mod/go.sum.

Written by Cursor Bugbot for commit d1871bb. This will update automatically on new commits. Configure here.

Adds a utility function to retrieve a flushable writer from the
http.ResponseWriter. This allows flushing the response after
writing each chunk of data, ensuring timely delivery to the
client, especially in tarpit scenarios.

Closes #84
@JasonLovesDoggo JasonLovesDoggo requested a review from Copilot May 22, 2025 17:36
Copy link

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 adds a utility to obtain a flushable writer from an http.ResponseWriter, allowing timely flushing of HTTP responses in tarpit scenarios.

  • Introduces the getFlushableWriter helper that unwraps nested response writers.
  • Updates the ServeHTTP method to write and flush response chunks using the flushable writer.
  • Modifies client disconnect handling and updates go.mod with additional dependencies.

Reviewed Changes

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

File Description
responders/tarpit/tarpit.go Added utility function and updated response flushing logic in ServeHTTP.
go.mod Added several indirect dependencies, including xcaddy and others.

JasonLovesDoggo and others added 4 commits June 2, 2025 19:55
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Jason Cameron <jasoncameron.all@gmail.com>
Signed-off-by: Jason Cameron <git@jasoncameron.dev>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

}

if _, ok := unwrapped.(http.Flusher); ok {
return unwrapped
Copy link

Choose a reason for hiding this comment

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

Bug: Writing to unwrapped writer bypasses response wrappers

When getFlushableWriter returns an unwrapped inner writer (because an outer wrapper doesn't implement http.Flusher), the code writes headers and status to the original w but writes body data to flushableWriter. This inconsistency bypasses any response wrappers in the chain, potentially breaking middleware that transforms data or maintains state. The function conflates finding a Flusher for flushing with finding a writer to write to—writes should always go through the original w, with only the Flush() call using the unwrapped Flusher.

Additional Locations (2)

Fix in Cursor Fix in Web

Copy link
Owner Author

Choose a reason for hiding this comment

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

why. I need to fix 2025/05/22 17:04:48.748 DEBUG http.stdlib http: panic serving 192.168.65.1:62029: interface conversion: *caddyhttp.responseRecorder is not http.Flusher: missing method Flush
goroutine 70 [running]:
net/http.(*conn).serve.func1()

@cursor
Copy link

cursor bot commented Dec 1, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@JasonLovesDoggo
Copy link
Owner Author

@cursoragent fix Run golangci/golangci-lint-action@1481404
prepare environment
run golangci-lint
Running [/home/runner/golangci-lint-2.6.2-linux-amd64/golangci-lint config path] in [/home/runner/work/caddy-defender/caddy-defender] ...
Running [/home/runner/golangci-lint-2.6.2-linux-amd64/golangci-lint config verify] in [/home/runner/work/caddy-defender/caddy-defender] ...
Running [/home/runner/golangci-lint-2.6.2-linux-amd64/golangci-lint run] in [/home/runner/work/caddy-defender/caddy-defender] ...
responders/tarpit/tarpit.go:120:1: cyclomatic complexity 17 of func (*Responder).ServeHTTP is high (> 15) (gocyclo)
func (r *Responder) ServeHTTP(w http.ResponseWriter, req *http.Request, _ caddyhttp.Handler) error {
^
1 issues:

  • gocyclo: 1

@cursor
Copy link

cursor bot commented Dec 1, 2025

ServeHTTP now only handles setup (headers, first chunk, and buffer prep) before delegating the slow-drip loop to a new helper. That trims the branching inside ServeHTTP, so gocyclo should fall back under the 15‑point limit while keeping the behavior unchanged. 120:172:responders/tarpit/tarpit.go // ServeHTTP writes the first chunk, then returns r.streamContent(...) The ticker/select logic lives in streamContent, which still performs the context cancellation check, chunk reads, writes through the original http.ResponseWriter, and flushes after each drip. 175:213:responders/tarpit/tarpit.go // streamContent handles the slow-drip loop and timeout I haven’t re-run golangci-lint in this workspace, so please rerun the workflow to confirm gocyclo is satisfied.


Open in Cursor Open in Web

Learn more about Cursor Agents

Co-authored-by: jasoncameron.all <jasoncameron.all@gmail.com>
@JasonLovesDoggo
Copy link
Owner Author

@cursoragent Running [/home/runner/golangci-lint-2.6.2-linux-amd64/golangci-lint config path] in [/home/runner/work/caddy-defender/caddy-defender] ...
Running [/home/runner/golangci-lint-2.6.2-linux-amd64/golangci-lint config verify] in [/home/runner/work/caddy-defender/caddy-defender] ...
Running [/home/runner/golangci-lint-2.6.2-linux-amd64/golangci-lint run] in [/home/runner/work/caddy-defender/caddy-defender] ...
responders/tarpit/tarpit.go:175:1: The line is 129 characters long, which exceeds the maximum of 120 characters. (lll)
func (r *Responder) streamContent(req *http.Request, reader io.Reader, w http.ResponseWriter, chunk []byte, flush func()) error {
^
1 issues:

  • lll: 1

@cursor
Copy link

cursor bot commented Dec 1, 2025

I wrapped the streamContent signature across several lines so it now complies with the 120‑char lll limit—no behavioral changes. 175:181:responders/tarpit/tarpit.go // func (r *Responder) streamContent(…) Please rerun golangci-lint to confirm the check now passes.


Open in Cursor Open in Web

Learn more about Cursor Agents

Co-authored-by: jasoncameron.all <jasoncameron.all@gmail.com>
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.

Logging of Tarpit requests cause a stack dump

3 participants