Skip to content

Commit a42ddc1

Browse files
grivera64gaby
andauthored
πŸ”₯ 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 <[email protected]> Co-authored-by: Juan Calderon-Perez <[email protected]>
1 parent 44b971a commit a42ddc1

File tree

5 files changed

+182
-1
lines changed

5 files changed

+182
-1
lines changed

β€Žctx.go

+17
Original file line numberDiff line numberDiff line change
@@ -1986,3 +1986,20 @@ func (c *DefaultCtx) Drop() error {
19861986
//nolint:wrapcheck // error wrapping is avoided to keep the operation lightweight and focused on connection closure.
19871987
return c.RequestCtx().Conn().Close()
19881988
}
1989+
1990+
// End immediately flushes the current response and closes the underlying connection.
1991+
func (c *DefaultCtx) End() error {
1992+
ctx := c.RequestCtx()
1993+
conn := ctx.Conn()
1994+
1995+
bw := bufio.NewWriter(conn)
1996+
if err := ctx.Response.Write(bw); err != nil {
1997+
return err
1998+
}
1999+
2000+
if err := bw.Flush(); err != nil {
2001+
return err //nolint:wrapcheck // unnecessary to wrap it
2002+
}
2003+
2004+
return conn.Close() //nolint:wrapcheck // unnecessary to wrap it
2005+
}

β€Žctx_interface_gen.go

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žctx_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -5931,6 +5931,83 @@ func Test_Ctx_DropWithMiddleware(t *testing.T) {
59315931
require.Nil(t, resp)
59325932
}
59335933

5934+
// go test -run Test_Ctx_End
5935+
func Test_Ctx_End(t *testing.T) {
5936+
app := New()
5937+
5938+
app.Get("/", func(c Ctx) error {
5939+
c.SendString("Hello, World!") //nolint:errcheck // unnecessary to check error
5940+
return c.End()
5941+
})
5942+
5943+
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
5944+
require.NoError(t, err)
5945+
require.NotNil(t, resp)
5946+
require.Equal(t, StatusOK, resp.StatusCode)
5947+
body, err := io.ReadAll(resp.Body)
5948+
require.NoError(t, err, "io.ReadAll(resp.Body)")
5949+
require.Equal(t, "Hello, World!", string(body))
5950+
}
5951+
5952+
// go test -run Test_Ctx_End_after_timeout
5953+
func Test_Ctx_End_after_timeout(t *testing.T) {
5954+
app := New()
5955+
5956+
// Early flushing handler
5957+
app.Get("/", func(c Ctx) error {
5958+
time.Sleep(2 * time.Second)
5959+
return c.End()
5960+
})
5961+
5962+
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
5963+
require.ErrorIs(t, err, os.ErrDeadlineExceeded)
5964+
require.Nil(t, resp)
5965+
}
5966+
5967+
// go test -run Test_Ctx_End_with_drop_middleware
5968+
func Test_Ctx_End_with_drop_middleware(t *testing.T) {
5969+
app := New()
5970+
5971+
// Middleware that will drop connections
5972+
// that persist after c.Next()
5973+
app.Use(func(c Ctx) error {
5974+
c.Next() //nolint:errcheck // unnecessary to check error
5975+
return c.Drop()
5976+
})
5977+
5978+
// Early flushing handler
5979+
app.Get("/", func(c Ctx) error {
5980+
c.SendStatus(StatusOK) //nolint:errcheck // unnecessary to check error
5981+
return c.End()
5982+
})
5983+
5984+
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
5985+
require.NoError(t, err)
5986+
require.NotNil(t, resp)
5987+
require.Equal(t, StatusOK, resp.StatusCode)
5988+
}
5989+
5990+
// go test -run Test_Ctx_End_after_drop
5991+
func Test_Ctx_End_after_drop(t *testing.T) {
5992+
app := New()
5993+
5994+
// Middleware that ends the request
5995+
// after c.Next()
5996+
app.Use(func(c Ctx) error {
5997+
c.Next() //nolint:errcheck // unnecessary to check error
5998+
return c.End()
5999+
})
6000+
6001+
// Early flushing handler
6002+
app.Get("/", func(c Ctx) error {
6003+
return c.Drop()
6004+
})
6005+
6006+
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
6007+
require.ErrorIs(t, err, ErrTestGotEmptyResponse)
6008+
require.Nil(t, resp)
6009+
}
6010+
59346011
// go test -run Test_GenericParseTypeString
59356012
func Test_GenericParseTypeString(t *testing.T) {
59366013
t.Parallel()

β€Ždocs/api/ctx.md

+48
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,54 @@ app.Get("/", func(c fiber.Ctx) error {
484484
})
485485
```
486486

487+
## End
488+
489+
End immediately flushes the current response and closes the underlying connection.
490+
491+
```go title="Signature"
492+
func (c fiber.Ctx) End() error
493+
```
494+
495+
```go title="Example"
496+
app.Get("/", func(c fiber.Ctx) error {
497+
c.SendString("Hello World!")
498+
return c.End()
499+
})
500+
```
501+
502+
:::caution
503+
Calling `c.End()` will disallow further writes to the underlying connection.
504+
:::
505+
506+
End can be used to stop a middleware from modifying a response of a handler/other middleware down the method chain
507+
when they regain control after calling `c.Next()`.
508+
509+
```go title="Example"
510+
// Error Logging/Responding middleware
511+
app.Use(func(c fiber.Ctx) error {
512+
err := c.Next()
513+
514+
// Log errors & write the error to the response
515+
if err != nil {
516+
log.Printf("Got error in middleware: %v", err)
517+
return c.Writef("(got error %v)", err)
518+
}
519+
520+
// No errors occured
521+
return nil
522+
})
523+
524+
// Handler with simulated error
525+
app.Get("/", func(c fiber.Ctx) error {
526+
// Closes the connection instantly after writing from this handler
527+
// and disallow further modification of its response
528+
defer c.End()
529+
530+
c.SendString("Hello, ... I forgot what comes next!")
531+
return errors.New("some error")
532+
})
533+
```
534+
487535
## Format
488536

489537
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.

β€Ždocs/whats_new.md

+36
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ testConfig := fiber.TestConfig{
341341
- **String**: Similar to Express.js, converts a value to a string.
342342
- **ViewBind**: Binds data to a view, replacing the old `Bind` method.
343343
- **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.
344+
- **End**: Similar to Express.js, immediately flushes the current response and closes the underlying connection.
344345

345346
### Removed Methods
346347

@@ -403,6 +404,41 @@ app.Get("/sse", func(c fiber.Ctx) {
403404

404405
You can find more details about this feature in [/docs/api/ctx.md](./api/ctx.md).
405406

407+
### End
408+
409+
In v3, we introduced a new method to match the Express.js API's `res.end()` method.
410+
411+
```go
412+
func (c Ctx) End()
413+
```
414+
415+
With this method, you can:
416+
417+
- Stop middleware from controlling the connection after a handler further up the method chain
418+
by immediately flushing the current response and closing the connection.
419+
- Use `return c.End()` as an alternative to `return nil`
420+
421+
```go
422+
app.Use(func (c fiber.Ctx) error {
423+
err := c.Next()
424+
if err != nil {
425+
log.Println("Got error: %v", err)
426+
return c.SendString(err.Error()) // Will be unsuccessful since the response ended below
427+
}
428+
return nil
429+
})
430+
431+
app.Get("/hello", func (c fiber.Ctx) error {
432+
query := c.Query("name", "")
433+
if query == "" {
434+
c.SendString("You don't have a name?")
435+
c.End() // Closes the underlying connection
436+
return errors.New("No name provided")
437+
}
438+
return c.SendString("Hello, " + query + "!")
439+
})
440+
```
441+
406442
---
407443

408444
## 🌎 Client package

0 commit comments

Comments
Β (0)