Skip to content

release: v0.1.0#20

Merged
whoisclebs merged 3 commits into
mainfrom
release/v0.1.0
Jul 20, 2026
Merged

release: v0.1.0#20
whoisclebs merged 3 commits into
mainfrom
release/v0.1.0

Conversation

@whoisclebs

Copy link
Copy Markdown
Collaborator

Summary

  • harden response error handling and committed-state tracking
  • freeze application configuration and validate route registration
  • unify the handler API and enforce bounded lazy request bodies
  • add first-class HTTP QUERY support per RFC 10008
  • publish permanent OpenSpec contracts and migration documentation

Validation

  • go test -count=1 ./...
  • go test -race -shuffle=on -count=1 ./...
  • go vet ./...
  • go build ./...
  • go mod verify
  • openspec validate --all --strict

Release

  • version.go: 0.1.0
  • prerelease: v0.1.0-rc.1

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request hardens the Golpher web framework for production by synchronizing route registration, freezing the route table on the first request, tracking response committed state, masking unknown errors, and making request body reading lazy and bounded. It also unifies the handler API to a single HandlerFunc signature and introduces first-class support for the HTTP QUERY method (RFC 10008). The review feedback identifies critical issues, including an observability regression in UseHTTP where standard middleware completes before error responses are written, a potential memory leak in pooled requests due to uncleared parameter values, and an invalid "//" static route registration when the root path is registered. Additionally, it recommends updating test assertions to verify correct error status observation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread golpher.go
Comment on lines +198 to +207
var handlerErr error
h := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handlerErr = next(req, res)
}))
h.ServeHTTP(res.Raw(), req.http)
if handlerErr != nil {
app.reportError(req, res, handlerErr)
}
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Standard Middleware Observability Regression

In the current implementation, app.reportError is called after the adapted standard middleware's ServeHTTP method returns:

h.ServeHTTP(res.Raw(), req.http)
if handlerErr != nil {
    app.reportError(req, res, handlerErr)
}

This means any standard logging, metrics, or response-modifying middleware (e.g., gzip, Prometheus metrics, Apache loggers) wrapped via UseHTTP will execute and complete before the error response (status code and body) is actually written to the wire. Consequently, they will observe an unwritten response (defaulting to 200 OK with 0 bytes), leading to incorrect logs and metrics.

To restore full interoperability with standard middleware, we should invoke app.reportError inside the http.HandlerFunc callback if next returns an error. This ensures the error response is fully written before the standard middleware's ServeHTTP returns, while setting handlerErr = nil prevents double-reporting.

				var handlerErr error
				h := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					handlerErr = next(req, res)
					if handlerErr != nil {
						app.reportError(req, res, handlerErr)
						handlerErr = nil
					}
				}))
				h.ServeHTTP(res.Raw(), req.http)
				if handlerErr != nil {
					app.reportError(req, res, handlerErr)
				}
				return nil
			}

Comment thread router.go
request.http = nil
request.params = nil
request.paramNames = nil
request.paramValues = request.paramValues[:0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Prevent Reference Retention / Memory Leak in Pooled Requests

request.paramValues is a slice of strings. When resetting it via request.paramValues = request.paramValues[:0], the elements beyond the new length (0) are not cleared in the underlying array. Because Request structs are pooled in requestPool (sync.Pool), references to these parameter strings (which can be unique and numerous, such as UUIDs or user IDs) will persist in memory indefinitely, causing a slow memory leak.

Using Go 1.21's built-in clear() function before slicing to [:0] sets all elements to their zero value (""), allowing the matched parameter strings to be garbage collected.

Suggested change
request.paramValues = request.paramValues[:0]
clear(request.paramValues)
request.paramValues = request.paramValues[:0]

Comment thread golpher_test.go
Comment on lines +474 to 479
// Errors flow through the tracking writer after the stdlib
// middleware wrapper returns. Use ErrorObserver for
// programmatic error observation.
if observedStatus != http.StatusOK {
t.Fatalf("expected stdlib middleware to see initial OK status, got %d", observedStatus)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Update Test Assertion to Verify Correct Error Observation

With the fix applied to UseHTTP to write the error response inside the standard middleware's execution context, the adapted middleware will now correctly observe the actual error status code (418 StatusTeapot) instead of the initial unwritten status (200 OK).

Update this assertion to verify that standard middleware can successfully observe the correct error status code.

	// Standard middleware should correctly observe the written error status code.
	if observedStatus != http.StatusTeapot {
		t.Fatalf("expected stdlib middleware to observe status %d, got %d", http.StatusTeapot, observedStatus)
	}

Comment thread router.go
Comment on lines +416 to 429
trimmed := strings.Trim(pattern, "/")
canonical := "/" + trimmed
trailing := canonical + "/"
if !strings.HasSuffix(pattern, "/") {
byPath[canonical] = index
if _, exists := byPath[trailing]; !exists {
byPath[trailing] = index
}
} else {
byPath[trailing] = index
if _, exists := byPath[canonical]; !exists {
byPath[canonical] = index
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Avoid Registering Invalid "//" Static Route for Root Path

When pattern is "/", trimmed becomes "", canonical becomes "/", and trailing becomes "//". Under the current logic, this results in registering "//" as a static route in the byPath map:

byPath["//"] = index

To prevent registering this invalid path, we should explicitly handle the root path case (trimmed == "") by registering only "/" and returning early.

	trimmed := strings.Trim(pattern, "/")
	canonical := "/" + trimmed
	if trimmed == "" {
		byPath["/"] = index
		return
	}
	trailing := canonical + "/"
	if !strings.HasSuffix(pattern, "/") {
		byPath[canonical] = index
		if _, exists := byPath[trailing]; !exists {
			byPath[trailing] = index
		}
	} else {
		byPath[trailing] = index
		if _, exists := byPath[canonical]; !exists {
			byPath[canonical] = index
		}
	}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 874288b611

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread golpher.go
Comment on lines +199 to +200
h := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handlerErr = next(req, res)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the request and writer passed by HTTP middleware

When standard middleware calls next.ServeHTTP with a wrapped writer or cloned request, this callback discards both w and r and invokes the Golpher handler with the originals. Consequently common middleware such as compression/status recorders cannot intercept downstream writes, and authentication or tracing context added with r.WithContext is invisible to the handler; the adapter must propagate these arguments into the pooled request/response path.

Useful? React with 👍 / 👎.

Comment thread context.go
func FromHTTPHandler(handler http.Handler) HandlerFunc {
return func(req *Request, res *Response) error {
return handler(req.acquireCtx(res), req, res)
handler.ServeHTTP(res.Raw(), req.http)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Wrap request bodies before mounted handlers read them

For routes mounted with FromHTTPHandler, a handler that reads r.Body directly receives the unwrapped stream because req.body() is never called. This bypasses both the new 1 MiB default and any configured MaxRequestBodyBytes or BodyLimit, allowing mounted handlers to consume arbitrarily large bodies despite the application's declared limit.

Useful? React with 👍 / 👎.

Comment thread golpher.go
Comment on lines +194 to +196
for _, mw := range middlewares {
middleware := mw
app.Use(func(next HandlerFunc) HandlerFunc {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Register all UseHTTP middleware under one lifecycle lock

When UseHTTP receives multiple middleware values while the server is starting, each iteration calls Use under a separate lock. A first request can freeze the app after one iteration, causing the public UseHTTP call to panic on the next iteration while leaving only a prefix of the requested middleware installed; the entire variadic registration should be one locked mutation so it either completes before freeze or makes no change.

Useful? React with 👍 / 👎.

Comment thread router.go
Comment on lines +156 to +157
if strings.HasPrefix(part, "*") {
name := part[1:]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject wildcard names that duplicate earlier parameters

The duplicate-name set is checked only for :param segments, so a pattern such as /files/:path/*path is accepted. Both captured values then share the same name, and Request.Param("path") always returns the first value, making the wildcard capture inaccessible; wildcard names should also be checked against and added to seenParams.

Useful? React with 👍 / 👎.

@whoisclebs

Copy link
Copy Markdown
Collaborator Author

Emergency merge exception approved due to the confirmed GitHub Actions critical outage on 2026-07-20.

Evidence before bypass:

  • Local: go test, race+shuffle, vet, build, go mod verify, golangci-lint v2.11.4, govulncheck with Go 1.26.5, OpenSpec strict validation, and git diff --check all passed.
  • Remote: Ubuntu on Go 1.23.6 and 1.26.5, golangci-lint, govulncheck, coverage, Snyk, and the primary CodeQL checks passed.
  • Remaining macOS/Windows jobs and one analyzer failed or timed out while GitHub Actions and API Requests were in partial outage.

The PR is mergeable without conflicts. It will be merged with a merge commit to preserve release/develop ancestry. Failed infrastructure checks must be rerun after GitHub Actions recovers.

@whoisclebs
whoisclebs merged commit 815b8d7 into main Jul 20, 2026
9 of 14 checks passed
@whoisclebs
whoisclebs deleted the release/v0.1.0 branch July 20, 2026 02:01
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.

1 participant