1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-06 10:23:55 +00:00

🔥 feat: Add End() method to Ctx (#3280)

* 🔥 Feature(v3): Add End() method to Ctx

* 🎨 Style(Ctx): Respect linter in tests

* 🚨 Test(End): Add timeout test for c.End()

* 📚 Doc: Update End() documentation examples to use 4 spaces

* 🚨 Test: Update `c.End()` tests to use StatusOK

---------

Co-authored-by: Giovanni Rivera <grivera64@users.noreply.github.com>
Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
This commit is contained in:
Giovanni Rivera 2025-01-16 02:54:46 -08:00 committed by GitHub
parent 44b971ace5
commit a42ddc100e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 182 additions and 1 deletions

17
ctx.go
View File

@ -1986,3 +1986,20 @@ func (c *DefaultCtx) Drop() error {
//nolint:wrapcheck // error wrapping is avoided to keep the operation lightweight and focused on connection closure.
return c.RequestCtx().Conn().Close()
}
// End immediately flushes the current response and closes the underlying connection.
func (c *DefaultCtx) End() error {
ctx := c.RequestCtx()
conn := ctx.Conn()
bw := bufio.NewWriter(conn)
if err := ctx.Response.Write(bw); err != nil {
return err
}
if err := bw.Flush(); err != nil {
return err //nolint:wrapcheck // unnecessary to wrap it
}
return conn.Close() //nolint:wrapcheck // unnecessary to wrap it
}

View File

@ -12,7 +12,8 @@ import (
"github.com/valyala/fasthttp"
)
// Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on.
// Ctx represents the Context which hold the HTTP request and response.
// It has methods for the request query string, parameters, body, HTTP headers and so on.
type Ctx interface {
// Accepts checks if the specified extensions or content types are acceptable.
Accepts(offers ...string) string
@ -353,4 +354,6 @@ type Ctx interface {
// This can be useful for silently terminating client connections, such as in DDoS mitigation
// or when blocking access to sensitive endpoints.
Drop() error
// End immediately flushes the current response and closes the underlying connection.
End() error
}

View File

@ -5931,6 +5931,83 @@ func Test_Ctx_DropWithMiddleware(t *testing.T) {
require.Nil(t, resp)
}
// go test -run Test_Ctx_End
func Test_Ctx_End(t *testing.T) {
app := New()
app.Get("/", func(c Ctx) error {
c.SendString("Hello, World!") //nolint:errcheck // unnecessary to check error
return c.End()
})
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err, "io.ReadAll(resp.Body)")
require.Equal(t, "Hello, World!", string(body))
}
// go test -run Test_Ctx_End_after_timeout
func Test_Ctx_End_after_timeout(t *testing.T) {
app := New()
// Early flushing handler
app.Get("/", func(c Ctx) error {
time.Sleep(2 * time.Second)
return c.End()
})
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
require.Nil(t, resp)
}
// go test -run Test_Ctx_End_with_drop_middleware
func Test_Ctx_End_with_drop_middleware(t *testing.T) {
app := New()
// Middleware that will drop connections
// that persist after c.Next()
app.Use(func(c Ctx) error {
c.Next() //nolint:errcheck // unnecessary to check error
return c.Drop()
})
// Early flushing handler
app.Get("/", func(c Ctx) error {
c.SendStatus(StatusOK) //nolint:errcheck // unnecessary to check error
return c.End()
})
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, StatusOK, resp.StatusCode)
}
// go test -run Test_Ctx_End_after_drop
func Test_Ctx_End_after_drop(t *testing.T) {
app := New()
// Middleware that ends the request
// after c.Next()
app.Use(func(c Ctx) error {
c.Next() //nolint:errcheck // unnecessary to check error
return c.End()
})
// Early flushing handler
app.Get("/", func(c Ctx) error {
return c.Drop()
})
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
require.ErrorIs(t, err, ErrTestGotEmptyResponse)
require.Nil(t, resp)
}
// go test -run Test_GenericParseTypeString
func Test_GenericParseTypeString(t *testing.T) {
t.Parallel()

View File

@ -484,6 +484,54 @@ app.Get("/", func(c fiber.Ctx) error {
})
```
## End
End immediately flushes the current response and closes the underlying connection.
```go title="Signature"
func (c fiber.Ctx) End() error
```
```go title="Example"
app.Get("/", func(c fiber.Ctx) error {
c.SendString("Hello World!")
return c.End()
})
```
:::caution
Calling `c.End()` will disallow further writes to the underlying connection.
:::
End can be used to stop a middleware from modifying a response of a handler/other middleware down the method chain
when they regain control after calling `c.Next()`.
```go title="Example"
// Error Logging/Responding middleware
app.Use(func(c fiber.Ctx) error {
err := c.Next()
// Log errors & write the error to the response
if err != nil {
log.Printf("Got error in middleware: %v", err)
return c.Writef("(got error %v)", err)
}
// No errors occured
return nil
})
// Handler with simulated error
app.Get("/", func(c fiber.Ctx) error {
// Closes the connection instantly after writing from this handler
// and disallow further modification of its response
defer c.End()
c.SendString("Hello, ... I forgot what comes next!")
return errors.New("some error")
})
```
## Format
Performs content-negotiation on the [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. It uses [Accepts](ctx.md#accepts) to select a proper format from the supplied offers. A default handler can be provided by setting the `MediaType` to `"default"`. If no offers match and no default is provided, a 406 (Not Acceptable) response is sent. The Content-Type is automatically set when a handler is selected.

View File

@ -341,6 +341,7 @@ testConfig := fiber.TestConfig{
- **String**: Similar to Express.js, converts a value to a string.
- **ViewBind**: Binds data to a view, replacing the old `Bind` method.
- **CBOR**: Introducing [CBOR](https://cbor.io/) binary encoding format for both request & response body. CBOR is a binary data serialization format which is both compact and efficient, making it ideal for use in web applications.
- **End**: Similar to Express.js, immediately flushes the current response and closes the underlying connection.
### Removed Methods
@ -403,6 +404,41 @@ app.Get("/sse", func(c fiber.Ctx) {
You can find more details about this feature in [/docs/api/ctx.md](./api/ctx.md).
### End
In v3, we introduced a new method to match the Express.js API's `res.end()` method.
```go
func (c Ctx) End()
```
With this method, you can:
- Stop middleware from controlling the connection after a handler further up the method chain
by immediately flushing the current response and closing the connection.
- Use `return c.End()` as an alternative to `return nil`
```go
app.Use(func (c fiber.Ctx) error {
err := c.Next()
if err != nil {
log.Println("Got error: %v", err)
return c.SendString(err.Error()) // Will be unsuccessful since the response ended below
}
return nil
})
app.Get("/hello", func (c fiber.Ctx) error {
query := c.Query("name", "")
if query == "" {
c.SendString("You don't have a name?")
c.End() // Closes the underlying connection
return errors.New("No name provided")
}
return c.SendString("Hello, " + query + "!")
})
```
---
## 🌎 Client package