Skip to content

feat(nethttp-mysql): add net/http + MySQL sample for replay regression#224

Open
officialasishkumar wants to merge 1 commit intomainfrom
feat/nethttp-mysql-hotloop-regression
Open

feat(nethttp-mysql): add net/http + MySQL sample for replay regression#224
officialasishkumar wants to merge 1 commit intomainfrom
feat/nethttp-mysql-hotloop-regression

Conversation

@officialasishkumar
Copy link
Copy Markdown
Member

@officialasishkumar officialasishkumar commented Apr 21, 2026

Describe the changes that are made

Adds a minimal nethttp-mysql sample — standard library net/http
plus database/sql with go-sql-driver/mysql — that exercises the
full MySQL connection and command phase end-to-end:

  • /health calls db.Ping() (forces a real command roundtrip).
  • /users SELECTs from a seeded users table.
  • /users/add INSERTs and returns the new row.

Why this sample exists: Keploy's OSS pipeline will consume it as a
regression guard for the MySQL replay command-phase read deadline.
If the proxy ever re-introduces a zero or negative deadline, the Go
driver blocks inside db.Ping() during replay, the HTTP listener
never binds :8080, and every replayed request fails with
connection reset by peer — making the regression loud in CI
rather than silently hanging on the user's side.

The companion PR in keploy/keploy (fix: prevent zero/overflow read
deadline in MySQL replay command phase) adds the pipeline script that
drives this sample and the fix for the proxy math.

Links & References

Closes: keploy/keploy#4102

🔗 Related PRs

  • keploy/keploy: companion fix + pipeline wiring (same author)

🐞 Related Issues

  • NA

📄 Related Documents

  • NA

What type of PR is this? (check all applicable)

  • 📦 Chore
  • 🍕 Feature
  • 🐞 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • 🧑‍💻 Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🔁 CI
  • ⏩ Revert

Added e2e test pipeline?

  • 👍 yes — via the companion PR in keploy/keploy which adds a
    golang_docker matrix entry nethttp-mysql and the driver script
    at .github/workflows/test_workflow_scripts/golang/nethttp_mysql/golang-docker.sh.

Added comments for hard-to-understand areas?

  • 👍 yes — the package-level doc comment in main.go explains
    why the app is shaped this way (regression guard).

Added to documentation?

  • 📜 README.md — new sample-level README with local-run and
    keploy record/test instructions.

Are there any sample code or steps to test the changes?

  • 👍 yes

Local smoke test:

cd nethttp-mysql
docker network inspect keploy-network >/dev/null 2>&1 || docker network create keploy-network
docker run -d --name mysql --network keploy-network -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=testdb mysql:8.0
docker build -t nethttp-mysql:1.0 .
docker run --rm -p 8080:8080 --network keploy-network \
  -e MYSQL_HOST=mysql -e MYSQL_USER=root -e MYSQL_PASSWORD=password -e MYSQL_DATABASE=testdb \
  nethttp-mysql:1.0
curl -s localhost:8080/health
curl -s localhost:8080/users
curl -s "localhost:8080/users/add?name=charlie&email=charlie@test.com"

Self Review done?

  • ✅ yes

Any relevant screenshots, recordings or logs?

  • NA

🧠 PR Semantics

  • PR Title: feat(nethttp-mysql): add net/http + MySQL sample for replay regression
  • Branch: feat/nethttp-mysql-hotloop-regression

Adds a minimal net/http + database/sql + go-sql-driver/mysql sample
that exercises the full MySQL connection and command phase end-to-end.

Shape of the app:
- /health issues db.Ping() (forces a real command roundtrip).
- /users SELECTs from a seeded table.
- /users/add INSERTs and returns the row.

Why: Keploy's OSS pipeline consumes this sample as a regression guard
for the MySQL replay command-phase read deadline. If the proxy ever
re-introduces a zero or negative read deadline, the Go driver blocks
inside db.Ping() during replay and the HTTP listener never binds
:8080, making the regression loud in CI rather than silently hanging.

Signed-off-by: Asish Kumar <officialasishkumar@gmail.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

Adds a new nethttp-mysql sample app (net/http + database/sql + go-sql-driver/mysql) intended to act as a CI regression guard for MySQL replay command-phase read-deadline issues by ensuring db.Ping() completes and the server successfully binds :8080.

Changes:

  • Introduces a minimal HTTP server with /health, /users, and /users/add backed by a MySQL users table.
  • Adds module files (go.mod/go.sum) for the new sample.
  • Adds Dockerfile + sample README with local-run and Keploy record/test instructions.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
nethttp-mysql/main.go New MySQL-backed HTTP server + startup schema/seed logic used to exercise command-phase traffic.
nethttp-mysql/README.md Local Docker + Keploy record/test instructions for the regression sample.
nethttp-mysql/Dockerfile Container build/runtime packaging for the sample.
nethttp-mysql/go.mod Declares the sample’s standalone Go module and MySQL driver dependency.
nethttp-mysql/go.sum Locks dependency checksums for reproducible builds.

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

Comment thread nethttp-mysql/main.go
Comment on lines +46 to +58
for attempt := 1; attempt <= 30; attempt++ {
db, err = sql.Open("mysql", dsn)
if err == nil {
err = db.Ping()
if err == nil {
break
}
}
log.Printf("waiting for mysql (attempt %d): %v", attempt, err)
time.Sleep(2 * time.Second)
}
if err != nil {
log.Fatalf("could not connect to mysql after retries: %v", err)
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

In the retry loop, a new *sql.DB handle is created on every attempt, but failed attempts aren't closed. If Ping() fails, the previous handle can keep underlying connections/resources around. Consider calling Close() before reassigning, or Open() once and just retry Ping() on the same handle.

Suggested change
for attempt := 1; attempt <= 30; attempt++ {
db, err = sql.Open("mysql", dsn)
if err == nil {
err = db.Ping()
if err == nil {
break
}
}
log.Printf("waiting for mysql (attempt %d): %v", attempt, err)
time.Sleep(2 * time.Second)
}
if err != nil {
log.Fatalf("could not connect to mysql after retries: %v", err)
db, err = sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("could not create mysql handle: %v; verify the DSN and MySQL driver configuration, then restart the service", err)
}
defer db.Close()
for attempt := 1; attempt <= 30; attempt++ {
err = db.Ping()
if err == nil {
break
}
log.Printf("waiting for mysql (attempt %d): %v; ensure the MySQL container is running and reachable at %s:%s, then retry", attempt, err, host, port)
time.Sleep(2 * time.Second)
}
if err != nil {
log.Fatalf("could not connect to mysql after retries: %v; confirm MySQL is healthy and the connection settings are correct, then restart the service", err)

Copilot uses AI. Check for mistakes.
Comment thread nethttp-mysql/main.go
}

var count int
_ = db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The result of QueryRow(...).Scan(&count) is intentionally ignored. If this query fails (e.g., missing permissions/table issues), count stays 0 and the app will try to seed, potentially masking the root cause. Please handle the Scan error and fail fast (or at least log it) so startup is deterministic.

Suggested change
_ = db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
if err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count); err != nil {
log.Fatalf("failed to count existing users; verify the users table exists and the configured MySQL user has SELECT access: %v", err)
}

Copilot uses AI. Check for mistakes.
Comment thread nethttp-mysql/main.go
Comment on lines +107 to +110
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(users)
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

After iterating rows.Next(), rows.Err() is not checked, and JSON encoding errors are ignored. This can cause partial/empty responses to be returned as 200 OK even when iteration/encoding fails. Please check rows.Err() after the loop and handle/return any Encode error.

Copilot uses AI. Check for mistakes.
Comment thread nethttp-mysql/main.go
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
id, _ := res.LastInsertId()
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

res.LastInsertId() error is ignored. If the driver doesn't support it or it fails, the handler will silently return id=0. Please handle the error and return a 500 (or similar) if the insert ID can't be retrieved.

Suggested change
id, _ := res.LastInsertId()
id, err := res.LastInsertId()
if err != nil {
http.Error(w, "failed to retrieve the inserted user ID; please retry or verify the database driver supports LastInsertId", http.StatusInternalServerError)
return
}

Copilot uses AI. Check for mistakes.
Comment thread nethttp-mysql/main.go
time.Sleep(2 * time.Second)
}
if err != nil {
log.Fatalf("could not connect to mysql after retries: %v", err)
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

This fatal error message doesn't give a clear next step for debugging (e.g., which env vars/host were used, or suggesting to check the MySQL container/network). Consider including the target host:port/db and a suggested action (verify MySQL is running/reachable and credentials are correct) to make CI/local failures faster to diagnose.

Suggested change
log.Fatalf("could not connect to mysql after retries: %v", err)
log.Fatalf("could not connect to MySQL after retries (target=%s:%s/%s): %v; verify MySQL is running and reachable at that address and that MYSQL_USER/MYSQL_PASSWORD/MYSQL_DATABASE are correct", host, port, dbname, err)

Copilot uses AI. Check for mistakes.
Comment thread nethttp-mysql/Dockerfile
Comment on lines +4 to +6
RUN go mod download || true
COPY . .
RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -o app .
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

go mod download || true will hide real module download failures and can make builds succeed with missing deps until a later step fails. Also, running go mod tidy during image build makes the build non-deterministic and can mutate the module graph at build time. Prefer failing fast on go mod download and running go build without go mod tidy here (keep tidy as a repo-side step).

Suggested change
RUN go mod download || true
COPY . .
RUN go mod tidy && CGO_ENABLED=0 GOOS=linux go build -o app .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .

Copilot uses AI. Check for mistakes.
Comment thread nethttp-mysql/README.md
keploy test -c "docker run -p 8080:8080 --name nethttp-mysql --network keploy-network \
-e MYSQL_HOST=mysql -e MYSQL_USER=root -e MYSQL_PASSWORD=password \
-e MYSQL_DATABASE=testdb nethttp-mysql" \
--containerName nethttp-mysql --network-name keploy-network \
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The Keploy CLI flag here uses --containerName, but other samples in this repo consistently use --container-name (e.g., http-postgres/README.md:58, fasthttp-postgres/README.md:25). If --containerName isn't a valid alias, the command will fail. Please align this to --container-name for consistency and correctness.

Suggested change
--containerName nethttp-mysql --network-name keploy-network \
--container-name nethttp-mysql --network-name keploy-network \

Copilot uses AI. Check for mistakes.
Comment thread nethttp-mysql/main.go
}

func getUsers(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query("SELECT id, name, email FROM users")
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The /users query has no ORDER BY, so row order is not guaranteed and can change as the table grows/changes. For a sample used in record/replay regression, this can introduce flaky diffs. Consider adding an explicit ordering (e.g., by id) to keep responses deterministic.

Suggested change
rows, err := db.Query("SELECT id, name, email FROM users")
rows, err := db.Query("SELECT id, name, email FROM users ORDER BY id")

Copilot uses AI. Check for mistakes.
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