🔥 feat: Support for SendEarlyHints#3483
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds HTTP 103 Early Hints support: new Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant TestClient as App.Test
participant App
participant Handler
participant Res
participant Fasthttp
Client->>TestClient: raw request (test harness)
TestClient->>App: deliver request
App->>Handler: invoke route
Handler->>Res: SendEarlyHints([]Link)
Res->>Fasthttp: append Link headers and call EarlyHints()
Fasthttp-->>Client: 103 Early Hints (Link headers) -- interim
Note right of TestClient: loop reads interim 1xx, discards body, continues
Handler->>Res: write final status & body
Res->>Fasthttp: send final response
Fasthttp-->>Client: final response (e.g., 200 OK)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
I can't figure out why the tests are failing. Something not quite right with Fasthttp |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
ctx.go (2)
1563-1566: Clarify documentation about Link header handling.The documentation states "Only Link headers already written to the response will be transmitted as Early Hints" but the implementation actually adds the provided hints as Link headers first (line 1575), then calls
EarlyHints(). This seems inconsistent.Consider updating the documentation to accurately reflect the implementation:
-// SendEarlyHints allows the server to hint to the browser what resources a page would need -// so the browser can preload them while waiting for the server's full response. Only Link -// headers already written to the response will be transmitted as Early Hints. +// SendEarlyHints allows the server to hint to the browser what resources a page would need +// so the browser can preload them while waiting for the server's full response. The provided +// hints are added as Link headers to the response before transmitting the Early Hints.
1573-1578: Consider adding input validation for better robustness.The method doesn't validate the input hints slice or individual hint formats. While the underlying fasthttp library may handle some edge cases, adding basic validation would improve robustness and provide clearer error messages.
Consider adding input validation:
func (c *DefaultCtx) SendEarlyHints(hints []string) error { + if len(hints) == 0 { + return nil // No hints to send + } for _, h := range hints { + if h == "" { + continue // Skip empty hints + } c.fasthttp.Response.Header.Add("Link", h) } return c.fasthttp.EarlyHints() }Alternatively, you could validate Link header format according to RFC 8288 if stricter validation is desired.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
ctx.go(1 hunks)ctx_interface_gen.go(1 hunks)ctx_test.go(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: repeated
- GitHub Check: Compare
🔇 Additional comments (1)
ctx_interface_gen.go (1)
296-306: Interface definition looks correct.The method signature and documentation are consistent with the implementation in
ctx.go. Since this is an auto-generated file (as noted in line 1), any documentation improvements should be made in the source file (ctx.go) and the interface will be updated accordingly when regenerated.
There was a problem hiding this comment.
Pull Request Overview
This PR introduces support for the HTTP/2+ SendEarlyHints feature allowing the server to hint to the browser which resources to preload.
- Adds a new SendEarlyHints method to the Ctx interface and its implementation in DefaultCtx.
- Provides a new test case in ctx_test.go to validate the SendEarlyHints functionality.
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| ctx_test.go | Added a unit test for the SendEarlyHints method. |
| ctx_interface_gen.go | Updated the Ctx interface to include the SendEarlyHints method. |
| ctx.go | Implemented the SendEarlyHints method using fasthttp's EarlyHints API. |
Comments suppressed due to low confidence (1)
ctx_test.go:3113
- Consider adding additional test cases with multiple early hints to ensure that all Link headers are correctly processed, including scenarios with duplicates or a mixture of different hint strings.
err := c.SendEarlyHints([]string{"<https://cdn.com>; rel=preload; as=script"})
You have a race condition in the SendEarlyHints test: === Failed
=== FAIL: . Test_Ctx_SendEarlyHints (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xcfe6f4]
goroutine 186 [running]:
testing.tRunner.func1.2({0x1097120, 0x1812bc0})
/opt/hostedtoolcache/go/1.23.9/x64/src/testing/testing.go:1632 +0x3fc
testing.tRunner.func1()
/opt/hostedtoolcache/go/1.23.9/x64/src/testing/testing.go:1635 +0x6b6
panic({0x1097120?, 0x1812bc0?})
/opt/hostedtoolcache/go/1.23.9/x64/src/runtime/panic.go:791 +0x132
github.com/valyala/fasthttp.acquireWriter(0xc0002e1208)
/home/runner/go/pkg/mod/github.com/valyala/fasthttp@v1.62.0/server.go:2736 +0x54
github.com/valyala/fasthttp.(*RequestCtx).EarlyHints(0xc0002e1208)
/home/runner/go/pkg/mod/github.com/valyala/fasthttp@v1.62.0/server.go:647 +0xab
github.com/gofiber/fiber/v3.(*DefaultCtx).SendEarlyHints(0xc0002f6308, {0xc000264370, 0x1, 0x4bdb1b?})
/home/runner/work/fiber/fiber/ctx.go:1577 +0x1d6
github.com/gofiber/fiber/v3.Test_Ctx_SendEarlyHints(0xc0003bb380)
/home/runner/work/fiber/fiber/ctx_test.go:3123 +0xf9
testing.tRunner(0xc0003bb380, 0x11a8b70)
/opt/hostedtoolcache/go/1.23.9/x64/src/testing/testing.go:1690 +0x227
created by testing.(*T).Run in goroutine 1
/opt/hostedtoolcache/go/1.23.9/x64/src/testing/testing.go:1743 +0x826 |
|
How can I fix it? It's not obvious where the race-condition is. |
Seems the failure comes from Fasthttp? It happens when you call |
I would recommend using the |
|
Something is wrong. I changed the code. app.Get("/earlyhints", func(c Ctx) error {
c.SendEarlyHints(hints)
c.Status(StatusBadRequest)
return nil
})
This is how the test should look like: https://github.com/valyala/fasthttp/blob/master/server_test.go#L1922 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3483 +/- ##
==========================================
- Coverage 91.81% 91.81% -0.01%
==========================================
Files 114 114
Lines 11498 11517 +19
==========================================
+ Hits 10557 10574 +17
- Misses 681 682 +1
- Partials 260 261 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
ctx_test.go (1)
3114-3114:⚠️ Potential issueFix error handling for SendEarlyHints call.
The error return value from
c.SendEarlyHints(hints)is not being checked, which violates Go best practices and triggers static analysis warnings.Apply this fix:
- c.SendEarlyHints(hints) + err := c.SendEarlyHints(hints) + if err != nil { + return err + }
🧹 Nitpick comments (1)
ctx_test.go (1)
3122-3123: Remove debug print statements.These debug print statements should be removed from the test as they add noise to test output.
- fmt.Println(resp.StatusCode) - fmt.Println(resp.Header) -
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
ctx_test.go(1 hunks)
🧰 Additional context used
🪛 GitHub Check: lint
ctx_test.go
[failure] 3124-3124:
Error return value of c.SendEarlyHints is not checked (errcheck)
🪛 GitHub Actions: golangci-lint
ctx_test.go
[error] 3124-3124: golangci-lint: Error return value of c.SendEarlyHints is not checked (errcheck)
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: unit (1.24.x, windows-latest)
- GitHub Check: unit (1.23.x, windows-latest)
- GitHub Check: unit (1.24.x, macos-13)
- GitHub Check: unit (1.23.x, macos-13)
- GitHub Check: repeated
- GitHub Check: Compare
Can you try |
|
Didn't work |
|
I tested it locally, and I can confirm that My thoughts are that one of the following is happening:
For example, the HTTP response: HTTP/1.1 103 Early Hints
Link: <https://cdn.com>; rel=preload; as=script
HTTP/1.1 200 OK
Content-Length: 0
Link: <https://cdn.com>; rel=preload; as=scriptCould be parsed as only: HTTP/1.1 103 Early Hints
Link: <https://cdn.com>; rel=preload; as=script
Possibly ignoring the rest of the response, or
If this is working properly locally, I believe that it's |
|
@pjebs should i push the patch changes in the branch ? |
|
i applied the patch to your branch |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
app_test.go (1)
1762-1781: Good test coverage for the Early Hints feature.The test effectively validates the integration between
SendEarlyHintsand the modifiedTestmethod. It properly:
- Tests the happy path scenario with Link headers
- Verifies that early hints are preserved in the final response
- Confirms the final response status and body are correct
- Uses appropriate assertions with proper error handling
The test follows established patterns in the file and provides good coverage for the basic functionality.
Consider adding additional test cases to enhance coverage:
func Test_App_Test_EarlyHints_Multiple(t *testing.T) { t.Parallel() app := New() app.Get("/multiple", func(c Ctx) error { c.SendEarlyHints([]string{"<https://cdn.com>; rel=preload; as=script"}) c.SendEarlyHints([]string{"<https://fonts.com>; rel=preload; as=font"}) return c.Status(StatusOK).SendString("done") }) req := httptest.NewRequest(MethodGet, "/multiple", nil) resp, err := app.Test(req) require.NoError(t, err) require.Equal(t, StatusOK, resp.StatusCode) // Verify multiple Link headers are combined require.Len(t, resp.Header["Link"], 2) } func Test_App_Test_EarlyHints_Error_After_Hints(t *testing.T) { t.Parallel() app := New() app.Get("/error", func(c Ctx) error { c.SendEarlyHints([]string{"<https://cdn.com>; rel=preload; as=script"}) return c.Status(StatusBadRequest).SendString("error") }) req := httptest.NewRequest(MethodGet, "/error", nil) resp, err := app.Test(req) require.NoError(t, err) require.Equal(t, StatusBadRequest, resp.StatusCode) // Verify early hints are still present even with error response require.NotEmpty(t, resp.Header["Link"]) }These additional tests would provide more comprehensive coverage of edge cases and multiple early hints scenarios.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app.go(1 hunks)app_test.go(1 hunks)ctx_test.go(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- ctx_test.go
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: ReneWerner87
PR: gofiber/fiber#0
File: :0-0
Timestamp: 2024-12-01T10:28:36.011Z
Learning: Feature request #3224 has been created to add support for square bracket notation and comma-separated values in multipart form data in Fiber, while maintaining binary data transfer capabilities. This would bring parity with the existing form-urlencoded functionality.
Learnt from: ReneWerner87
PR: gofiber/fiber#3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-09-25T15:57:10.221Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.
Learnt from: gaby
PR: gofiber/fiber#3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.
app_test.go (2)
Learnt from: ReneWerner87
PR: gofiber/fiber#3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
app.go (5)
Learnt from: ReneWerner87
PR: gofiber/fiber#3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/session/middleware_test.go:400-407
Timestamp: 2024-09-25T17:09:03.756Z
Learning: In the `Test_Session_Next` function in `middleware/session/middleware_test.go`, the variable `doNext` is properly synchronized with the `muNext` mutex for both read and write access.
Learnt from: sixcolors
PR: gofiber/fiber#3051
File: middleware/session/session.go:215-216
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.
Learnt from: sixcolors
PR: gofiber/fiber#3051
File: middleware/session/session.go:215-216
Timestamp: 2024-06-30T00:38:06.580Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Compare
- GitHub Check: unit (1.24.x, macos-latest)
- GitHub Check: unit (1.24.x, macos-13)
- GitHub Check: repeated
- GitHub Check: lint
- GitHub Check: unit (1.24.x, windows-latest)
- GitHub Check: unit (1.24.x, ubuntu-latest)
- GitHub Check: Analyse
🔇 Additional comments (1)
app.go (1)
1119-1143: LGTM! Excellent implementation of multi-response handling.The modification correctly implements support for processing multiple HTTP responses, which is essential for the Early Hints feature. The loop logic properly:
- Reads responses sequentially until a non-1xx response is found
- Handles resource cleanup by discarding interim response bodies
- Maintains robust error handling for edge cases
- Preserves backward compatibility with existing functionality
The implementation aligns well with the PR objective to support HTTP 103 Early Hints while maintaining the existing Test method behavior.
@ReneWerner87 @pjebs the codecov workflow wants more coverage for the Should we add a new |
|
@grivera64 can you add this ? |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
res.go (2)
695-699: Prefer using Res/RequestCtx accessors + HeaderLink constant; short-circuit empty hints.Improves encapsulation, consistency with surrounding code, and avoids emitting a bare 103 without Link values.
func (r *DefaultRes) SendEarlyHints(hints []string) error { - for _, h := range hints { - r.c.fasthttp.Response.Header.Add("Link", h) - } - return r.c.fasthttp.EarlyHints() + if len(hints) == 0 { + return nil + } + for _, h := range hints { + r.Response().Header.Add(HeaderLink, h) + } + return r.RequestCtx().EarlyHints() }
688-691: Doc nit: clarify protocol wording.“HTTP/2+” is vague. Suggest “HTTP/2 or HTTP/3” and explicitly note that some HTTP/1.1 non-browser clients may ignore interim responses.
-// This is a HTTP/2+ feature but all browsers will either understand it or safely ignore it. +// Early Hints are primarily effective with HTTP/2 or HTTP/3. Modern browsers understand them, +// while older clients will safely ignore interim 1xx responses.ctx_test.go (2)
3869-3878: Close response body and use Header.Values for robustness.Avoids resource leaks and tolerates header coalescing behavior.
req := httptest.NewRequest(MethodGet, "/earlyhints", nil) resp, err := app.Test(req) require.NoError(t, err, "app.Test(req)") +defer func() { require.NoError(t, resp.Body.Close()) }() require.Equal(t, StatusBadRequest, resp.StatusCode, "Status code") -require.Equal(t, hints, resp.Header["Link"], "Link header") +require.Equal(t, hints, resp.Header.Values(HeaderLink), "Link header") body, err := io.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, "fail", string(body))
3856-3879: Add coverage: multiple hints and empty hints.Consider two quick additions:
- A subtest that sends two Link hints and asserts both values are present (order-preserving).
- A subtest that calls SendEarlyHints([]string{}) and asserts the final response succeeds and contains no Link header.
I can draft these if you want them in this file or in app_test.go to validate end-to-end behavior with the updated App.Test.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
app.go(2 hunks)app_test.go(1 hunks)ctx_interface_gen.go(1 hunks)ctx_test.go(1 hunks)res.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- app_test.go
- ctx_interface_gen.go
- app.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-29T12:37:27.581Z
Learnt from: efectn
PR: gofiber/fiber#3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.
Applied to files:
ctx_test.go
🧬 Code Graph Analysis (2)
res.go (1)
client/response.go (1)
Response(19-25)
ctx_test.go (2)
ctx_interface_gen.go (1)
Ctx(17-416)constants.go (2)
StatusBadRequest(73-73)MethodGet(5-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: unit (1.25.x, macos-13)
- GitHub Check: repeated
- GitHub Check: Compare
🔇 Additional comments (2)
res.go (1)
684-699: Solid addition; behavior matches RFC 8297 intent.The method appends Link headers and delegates to fasthttp’s EarlyHints emitter without mutating the final status. This aligns with the design discussed in the PR and the tests.
ctx_test.go (1)
3856-3879: Exercise looks good; assert final status and Link presence.The test now validates final status and the Link header after calling SendEarlyHints. Nice.
@ReneWerner87 After taking a look, I was able to slightly increase the coverage by a bit by mocking failed Below is the diff I am thinking of applying. Let me know what you think: Click here for `git diff` output
diff --git a/app.go b/app.go
index c8c59feb..42c94321 100644
--- a/app.go
+++ b/app.go
@@ -487,6 +487,9 @@ var DefaultMethods = []string{
methodPatch: MethodPatch,
}
+// httpReadResponse - Used for test mocking http.ReadResponse
+var httpReadResponse = http.ReadResponse
+
// DefaultErrorHandler that process return errors from handlers
func DefaultErrorHandler(c Ctx, err error) error {
code := StatusInternalServerError
@@ -1119,7 +1122,7 @@ func (app *App) Test(req *http.Request, config ...TestConfig) (*http.Response, e
var res *http.Response
for {
// Convert raw http response to *http.Response
- res, err = http.ReadResponse(buffer, req)
+ res, err = httpReadResponse(buffer, req)
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
return nil, ErrTestGotEmptyResponse
diff --git a/app_test.go b/app_test.go
index c438b9ed..37f08e91 100644
--- a/app_test.go
+++ b/app_test.go
@@ -6,6 +6,7 @@
package fiber
import (
+ "bufio"
"bytes"
"context"
"crypto/tls"
@@ -1839,8 +1840,118 @@ func Test_App_Test_drop_empty_response(t *testing.T) {
require.ErrorIs(t, err, ErrTestGotEmptyResponse)
}
-func Test_App_Test_EarlyHints(t *testing.T) {
- t.Parallel()
+func Test_App_Test_response_error(t *testing.T) {
+ // Note: Test cannot run in parallel due to
+ // overriding the httpReadResponse global variable.
+ // t.Parallel()
+
+ // Override httpReadResponse temporarily
+ oldHTTPReadResponse := httpReadResponse
+ defer func() {
+ httpReadResponse = oldHTTPReadResponse
+ }()
+ httpReadResponse = func(_ *bufio.Reader, _ *http.Request) (*http.Response, error) {
+ return nil, errErrorReader
+ }
+
+ app := New()
+ app.Get("/", func(c Ctx) error {
+ return c.SendStatus(StatusOK)
+ })
+
+ _, err := app.Test(httptest.NewRequest(MethodGet, "/", nil), TestConfig{
+ Timeout: 0,
+ FailOnTimeout: false,
+ })
+ require.ErrorIs(t, err, errErrorReader)
+}
+
+type errorReadCloser int
+
+var errInvalidReadOnBody = errors.New("test: invalid Read on body")
+
+func (errorReadCloser) Read(_ []byte) (int, error) {
+ return 0, errInvalidReadOnBody
+}
+
+func (errorReadCloser) Close() error {
+ return nil
+}
+
+func Test_App_Test_ReadFail(t *testing.T) {
+ // Note: Test cannot run in parallel due to
+ // overriding the httpReadResponse global variable.
+ // t.Parallel()
+
+ // Override httpReadResponse temporarily
+ oldHTTPReadResponse := httpReadResponse
+ defer func() {
+ httpReadResponse = oldHTTPReadResponse
+ }()
+
+ httpReadResponse = func(r *bufio.Reader, req *http.Request) (*http.Response, error) {
+ resp, err := http.ReadResponse(r, req)
+ require.NoError(t, resp.Body.Close())
+ resp.Body = errorReadCloser(0)
+ return resp, err //nolint:wrapcheck // unnecessary to wrap it
+ }
+
+ app := New()
+ hints := []string{"<https://cdn.com>; rel=preload; as=script"}
+ app.Get("/early", func(c Ctx) error {
+ err := c.SendEarlyHints(hints)
+ require.NoError(t, err)
+ return c.SendStatus(StatusOK)
+ })
+
+ req := httptest.NewRequest(MethodGet, "/early", nil)
+ _, err := app.Test(req)
+
+ require.ErrorIs(t, err, errInvalidReadOnBody)
+}
+
+var errDoubleClose = errors.New("test: double close")
+
+type doubleCloseBody struct {
+ isClosed bool
+}
+
+func (b *doubleCloseBody) Read(_ []byte) (int, error) {
+ if b.isClosed {
+ return 0, errInvalidReadOnBody
+ }
+
+ // Close after reading EOF
+ _ = b.Close() //nolint:errcheck // It is fine to ignore the error here
+ return 0, io.EOF
+}
+
+func (b *doubleCloseBody) Close() error {
+ if b.isClosed {
+ return errDoubleClose
+ }
+
+ b.isClosed = true
+ return nil
+}
+
+func Test_App_Test_CloseFail(t *testing.T) {
+ // Note: Test cannot run in parallel due to
+ // overriding the httpReadResponse global variable.
+ // t.Parallel()
+
+ // Override httpReadResponse temporarily
+ oldHTTPReadResponse := httpReadResponse
+ defer func() {
+ httpReadResponse = oldHTTPReadResponse
+ }()
+
+ httpReadResponse = func(r *bufio.Reader, req *http.Request) (*http.Response, error) {
+ resp, err := http.ReadResponse(r, req)
+ _ = resp.Body.Close() //nolint:errcheck // It is fine to ignore the error here
+ resp.Body = &doubleCloseBody{}
+ return resp, err //nolint:wrapcheck // unnecessary to wrap it
+ }
app := New()
hints := []string{"<https://cdn.com>; rel=preload; as=script"}
@@ -1851,14 +1962,9 @@ func Test_App_Test_EarlyHints(t *testing.T) {
})
req := httptest.NewRequest(MethodGet, "/early", nil)
- resp, err := app.Test(req)
+ _, err := app.Test(req)
- require.NoError(t, err)
- require.Equal(t, StatusOK, resp.StatusCode)
- require.Equal(t, hints, resp.Header["Link"])
- body, err := io.ReadAll(resp.Body)
- require.NoError(t, err)
- require.Equal(t, "done", string(body))
+ require.ErrorIs(t, err, errDoubleClose)
}
func Test_App_SetTLSHandler(t *testing.T) {
|
grivera64
left a comment
There was a problem hiding this comment.
Now that the PR reports better code coverage, this LGTM! 🚀
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces support for sending Early Hints (103 responses), a feature for improving web performance by allowing browsers to preload resources. The changes include a new SendEarlyHints method on the context, updates to the Test function to handle 1xx responses, and comprehensive tests for the new functionality. The implementation is solid and well-tested. I have one minor suggestion to handle cases where SendEarlyHints is called with an empty list of hints to avoid sending a pointless 103 response.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
ctx_interface_gen.go (2)
368-378: Doc/API mismatch and clarity pass for SendEarlyHintsThe docs say “Only Link headers already written to the response will be transmitted as Early Hints”, but the API accepts hints []string. Per the PR context, the implementation appends these hints as Link headers before sending 103. Let's align the comment to the actual behavior, tighten the HTTP version note, and fix a small grammar nit (“an HTTP/2+”):
- // SendEarlyHints allows the server to hint to the browser what resources a page would need - // so the browser can preload them while waiting for the server's full response. Only Link - // headers already written to the response will be transmitted as Early Hints. - // - // This is a HTTP/2+ feature but all browsers will either understand it or safely ignore it. - // - // NOTE: Older HTTP/1.1 non-browser clients may face compatibility issues. - // - // See: https://developer.chrome.com/docs/web-platform/early-hints and - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link#syntax + // SendEarlyHints sends an HTTP 103 Early Hints informational response. + // The provided hints are appended as Link headers and sent immediately, + // allowing clients to preload resources while the final response is prepared. + // Only Link headers are sent as Early Hints; all other headers are ignored. + // + // Recommended: call before writing final response headers/body. + // Most effective over HTTP/2 or HTTP/3; some HTTP/1.1 clients and intermediaries + // may ignore or drop 103 responses. + // + // See: RFC 8297 (Early Hints), + // https://developer.chrome.com/docs/web-platform/early-hints, + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link SendEarlyHints(hints []string) error
378-378: Consider a variadic signature for ergonomics and allocation-free call sitesLinks(link ...string) already uses varargs; mirroring that here would improve ergonomics and avoid slice allocations at call sites.
-SendEarlyHints(hints []string) error +SendEarlyHints(hints ...string) errorNote: This is an interface change and will cascade to implementations and tests; only consider if you’re comfortable adjusting the rest of the PR accordingly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
ctx_interface_gen.go(1 hunks)res.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- res.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Compare
- GitHub Check: unit (1.25.x, macos-13)
- GitHub Check: repeated
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: unit (1.25.x, macos-latest)
- GitHub Check: lint
|
Someone needs to run |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
res_interface_gen.go (2)
112-122: API alignment question: name/signature vs Express writeEarlyHintsExpress uses res.writeEarlyHints(headers[, cb]). We’re exposing SendEarlyHints([]string). If Express alignment and API longevity are goals, consider whether we want a map-like input (supporting multiple headers and non-Link fields for future-proofing) or keep Fiber’s narrowly-scoped Link-only API. If keeping Link-only, the docs should explicitly state expected value format (e.g., </style.css>; rel=preload; as=style) and dedup behavior.
Would you like me to draft an alternative API proposal (backwards-compatible) that can accept either []string (Link values) or a map[string][]string, while keeping the implementation constrained to sending only Link in the 103?
112-122: Update Early Hints documentation in source and regenerate
Please update the comment onDefaultRes.SendEarlyHintsin res.go as shown below, then runmake generateto propagate it to res_interface_gen.go:@@ -691,12 +691,23 @@ -// SendEarlyHints allows the server to hint to the browser what resources a page would need -// so the browser can preload them while waiting for the server's full response. Only Link -// headers already written to the response will be transmitted as Early Hints. -// -// This is a HTTP/2+ feature but all browsers will either understand it or safely ignore it. -// -// NOTE: Older HTTP/1.1 non-browser clients may face compatibility issues. -// -// See: https://developer.chrome.com/docs/web-platform/early-hints and -// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link#syntax +// SendEarlyHints emits a 103 Early Hints response (RFC 8297). +// The provided hints are appended as Link headers and sent in the 103 response. +// Only Link headers are included in Early Hints; other headers are typically ignored by clients. +// +// Call early, before writing the final response body. If hints is empty, this is a no-op. +// Returns an error if the underlying server fails to write the 103 response. +// +// Note: RFC 8297 applies to both HTTP/1.1 and HTTP/2+. In practice, browsers act on Early +// Hints primarily over HTTP/2/HTTP/3. When using Fiber/fasthttp (HTTP/1.1), deploy behind +// a proxy that forwards 103 to clients for best results. +// +// See: https://datatracker.ietf.org/doc/html/rfc8297 and +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link#syntax• File: res.go, around line 691
• Runmake generateto update res_interface_gen.go
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
res_interface_gen.go(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Compare
- GitHub Check: lint
- GitHub Check: unit (1.25.x, macos-13)
- GitHub Check: unit (1.25.x, macos-latest)
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: repeated
Description
Please provide a clear and concise description of the changes you've made and the problem they address. Include the purpose of the change, any relevant issues it solves, and the benefits it brings to the project. If this change introduces new features or adjustments, highlight them here.
Fixes #3211