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:
parent
44b971ace5
commit
a42ddc100e
17
ctx.go
17
ctx.go
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
77
ctx_test.go
77
ctx_test.go
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user