diff --git a/app_test.go b/app_test.go
index 13c04987420..892ef1192e4 100644
--- a/app_test.go
+++ b/app_test.go
@@ -981,8 +981,8 @@ func Test_App_Static_Custom_CacheControl(t *testing.T) {
app := New()
app.Static("/", "./.github", Static{ModifyResponse: func(c Ctx) error {
- if strings.Contains(c.GetRespHeader("Content-Type"), "text/html") {
- c.Response().Header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
+ if strings.Contains(c.Res().Get("Content-Type"), "text/html") {
+ c.Set("Cache-Control", "no-cache, no-store, must-revalidate")
}
return nil
}})
diff --git a/bind_test.go b/bind_test.go
index 476a25415d4..af08cd0e37e 100644
--- a/bind_test.go
+++ b/bind_test.go
@@ -540,21 +540,21 @@ func Test_Bind_RespHeader(t *testing.T) {
c.Context().Request.SetBody([]byte(``))
c.Context().Request.Header.SetContentType("")
- c.Response().Header.Add("id", "1")
- c.Response().Header.Add("Name", "John Doe")
- c.Response().Header.Add("Hobby", "golang,fiber")
+ c.Context().Response.Header.Add("id", "1")
+ c.Context().Response.Header.Add("Name", "John Doe")
+ c.Context().Response.Header.Add("Hobby", "golang,fiber")
q := new(Header)
require.NoError(t, c.Bind().RespHeader(q))
require.Len(t, q.Hobby, 2)
- c.Response().Header.Del("hobby")
- c.Response().Header.Add("Hobby", "golang,fiber,go")
+ c.Context().Response.Header.Del("hobby")
+ c.Context().Response.Header.Add("Hobby", "golang,fiber,go")
q = new(Header)
require.NoError(t, c.Bind().RespHeader(q))
require.Len(t, q.Hobby, 3)
empty := new(Header)
- c.Response().Header.Del("hobby")
+ c.Context().Response.Header.Del("hobby")
require.NoError(t, c.Bind().Query(empty))
require.Empty(t, empty.Hobby)
@@ -569,13 +569,13 @@ func Test_Bind_RespHeader(t *testing.T) {
No []int64
}
- c.Response().Header.Add("id", "2")
- c.Response().Header.Add("Name", "Jane Doe")
- c.Response().Header.Del("hobby")
- c.Response().Header.Add("Hobby", "go,fiber")
- c.Response().Header.Add("favouriteDrinks", "milo,coke,pepsi")
- c.Response().Header.Add("alloc", "")
- c.Response().Header.Add("no", "1")
+ c.Context().Response.Header.Add("id", "2")
+ c.Context().Response.Header.Add("Name", "Jane Doe")
+ c.Context().Response.Header.Del("hobby")
+ c.Context().Response.Header.Add("Hobby", "go,fiber")
+ c.Context().Response.Header.Add("favouriteDrinks", "milo,coke,pepsi")
+ c.Context().Response.Header.Add("alloc", "")
+ c.Context().Response.Header.Add("no", "1")
h2 := new(Header2)
h2.Bool = true
@@ -594,7 +594,7 @@ func Test_Bind_RespHeader(t *testing.T) {
Name string `respHeader:"name,required"`
}
rh := new(RequiredHeader)
- c.Response().Header.Del("name")
+ c.Context().Response.Header.Del("name")
require.Equal(t, "name is empty", c.Bind().RespHeader(rh).Error())
}
@@ -608,21 +608,21 @@ func Test_Bind_RespHeader_Map(t *testing.T) {
c.Context().Request.SetBody([]byte(``))
c.Context().Request.Header.SetContentType("")
- c.Response().Header.Add("id", "1")
- c.Response().Header.Add("Name", "John Doe")
- c.Response().Header.Add("Hobby", "golang,fiber")
+ c.Context().Response.Header.Add("id", "1")
+ c.Context().Response.Header.Add("Name", "John Doe")
+ c.Context().Response.Header.Add("Hobby", "golang,fiber")
q := make(map[string][]string, 0)
require.NoError(t, c.Bind().RespHeader(&q))
require.Len(t, q["Hobby"], 2)
- c.Response().Header.Del("hobby")
- c.Response().Header.Add("Hobby", "golang,fiber,go")
+ c.Context().Response.Header.Del("hobby")
+ c.Context().Response.Header.Add("Hobby", "golang,fiber,go")
q = make(map[string][]string, 0)
require.NoError(t, c.Bind().RespHeader(&q))
require.Len(t, q["Hobby"], 3)
empty := make(map[string][]string, 0)
- c.Response().Header.Del("hobby")
+ c.Context().Response.Header.Del("hobby")
require.NoError(t, c.Bind().Query(&empty))
require.Empty(t, empty["Hobby"])
}
@@ -789,9 +789,9 @@ func Benchmark_Bind_RespHeader(b *testing.B) {
c.Context().Request.SetBody([]byte(``))
c.Context().Request.Header.SetContentType("")
- c.Response().Header.Add("id", "1")
- c.Response().Header.Add("Name", "John Doe")
- c.Response().Header.Add("Hobby", "golang,fiber")
+ c.Context().Response.Header.Add("id", "1")
+ c.Context().Response.Header.Add("Name", "John Doe")
+ c.Context().Response.Header.Add("Hobby", "golang,fiber")
q := new(ReqHeader)
b.ReportAllocs()
@@ -811,9 +811,9 @@ func Benchmark_Bind_RespHeader_Map(b *testing.B) {
c.Context().Request.SetBody([]byte(``))
c.Context().Request.Header.SetContentType("")
- c.Response().Header.Add("id", "1")
- c.Response().Header.Add("Name", "John Doe")
- c.Response().Header.Add("Hobby", "golang,fiber")
+ c.Context().Response.Header.Add("id", "1")
+ c.Context().Response.Header.Add("Name", "John Doe")
+ c.Context().Response.Header.Add("Hobby", "golang,fiber")
q := make(map[string][]string)
b.ReportAllocs()
@@ -1540,7 +1540,7 @@ func Test_Bind_Must(t *testing.T) {
rq := new(RequiredQuery)
c.Context().URI().SetQueryString("")
err := c.Bind().Must().Query(rq)
- require.Equal(t, StatusBadRequest, c.Response().StatusCode())
+ require.Equal(t, StatusBadRequest, c.Context().Response.StatusCode())
require.Equal(t, "Bad request: name is empty", err.Error())
}
diff --git a/client/response_test.go b/client/response_test.go
index ab22ab388a8..b370e016ac5 100644
--- a/client/response_test.go
+++ b/client/response_test.go
@@ -182,7 +182,7 @@ func Test_Response_Header(t *testing.T) {
server := startTestServer(t, func(app *fiber.App) {
app.Get("/", func(c fiber.Ctx) error {
- c.Response().Header.Add("foo", "bar")
+ c.Set("foo", "bar")
return c.SendString("helo world")
})
})
diff --git a/ctx.go b/ctx.go
index 149d087ce1f..54f53f69b0f 100644
--- a/ctx.go
+++ b/ctx.go
@@ -12,11 +12,7 @@ import (
"io"
"mime/multipart"
"net"
- "path/filepath"
"strconv"
- "strings"
- "sync"
- "text/template"
"time"
"github.com/gofiber/utils/v2"
@@ -40,17 +36,15 @@ type contextKey int
const userContextKey contextKey = 0 // __local_user_context__
type DefaultCtx struct {
- app *App // Reference to *App
- req Request // Reference to *Request
- // res *Response // Reference to *Response
- indexRoute int // Index of the current route
- indexHandler int // Index of the current handler
- fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
- matched bool // Non use route matched
- viewBindMap sync.Map // Default view map to bind template engine
- bind *Bind // Default bind reference
- redirect *Redirect // Default redirect reference
- redirectionMessages []string // Messages of the previous redirect
+ app *App // Reference to *App
+ req Request // Reference to *Request
+ res Response // Reference to *Response
+ indexRoute int // Index of the current route
+ indexHandler int // Index of the current handler
+ fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
+ matched bool // Non use route matched
+ bind *Bind // Default bind reference
+ redirect *Redirect // Default redirect reference
}
// TLSHandler object
@@ -98,12 +92,6 @@ type Views interface {
Render(out io.Writer, name string, binding any, layout ...string) error
}
-// ResFmt associates a Content Type to a fiber.Handler for c.Format
-type ResFmt struct {
- MediaType string
- Handler func(Ctx) error
-}
-
// Accepts is an alias of [Request.Accepts]
func (c *DefaultCtx) Accepts(offers ...string) string {
return c.req.Accepts(offers...)
@@ -129,37 +117,14 @@ func (c *DefaultCtx) App() *App {
return c.app
}
-// Append the specified value to the HTTP response header field.
-// If the header is not already set, it creates the header with the specified value.
+// Append is an alias of [Response.Append].
func (c *DefaultCtx) Append(field string, values ...string) {
- if len(values) == 0 {
- return
- }
- h := c.app.getString(c.fasthttp.Response.Header.Peek(field))
- originalH := h
- for _, value := range values {
- if len(h) == 0 {
- h = value
- } else if h != value && !strings.HasPrefix(h, value+",") && !strings.HasSuffix(h, " "+value) &&
- !strings.Contains(h, " "+value+",") {
- h += ", " + value
- }
- }
- if originalH != h {
- c.Set(field, h)
- }
+ c.res.Append(field, values...)
}
-// Attachment sets the HTTP response Content-Disposition header field to attachment.
+// Attachment is an alias of [Response.Attachment].
func (c *DefaultCtx) Attachment(filename ...string) {
- if len(filename) > 0 {
- fname := filepath.Base(filename[0])
- c.Type(filepath.Ext(fname))
-
- c.setCanonical(HeaderContentDisposition, `attachment; filename="`+c.app.quoteString(fname)+`"`)
- return
- }
- c.setCanonical(HeaderContentDisposition, "attachment")
+ c.res.Attachment(filename...)
}
// BaseURL is an alias of [Request.BaseURL].
@@ -177,18 +142,9 @@ func (c *DefaultCtx) Body() []byte {
return c.req.Body()
}
-// ClearCookie expires a specific cookie by key on the client side.
-// If no key is provided it expires all cookies that came with the request.
+// ClearCookie is an alias of [Response.ClearCookie].
func (c *DefaultCtx) ClearCookie(key ...string) {
- if len(key) > 0 {
- for i := range key {
- c.fasthttp.Response.Header.DelClientCookie(key[i])
- }
- return
- }
- c.fasthttp.Request.Header.VisitAllCookie(func(k, _ []byte) {
- c.fasthttp.Response.Header.DelClientCookieBytes(k)
- })
+ c.res.ClearCookie(key...)
}
// Context returns *fasthttp.RequestCtx that carries a deadline
@@ -214,36 +170,9 @@ func (c *DefaultCtx) SetUserContext(ctx context.Context) {
c.fasthttp.SetUserValue(userContextKey, ctx)
}
-// Cookie sets a cookie by passing a cookie struct.
+// Cookie is an alias of [Response.Cookie].
func (c *DefaultCtx) Cookie(cookie *Cookie) {
- fcookie := fasthttp.AcquireCookie()
- fcookie.SetKey(cookie.Name)
- fcookie.SetValue(cookie.Value)
- fcookie.SetPath(cookie.Path)
- fcookie.SetDomain(cookie.Domain)
- // only set max age and expiry when SessionOnly is false
- // i.e. cookie supposed to last beyond browser session
- // refer: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie
- if !cookie.SessionOnly {
- fcookie.SetMaxAge(cookie.MaxAge)
- fcookie.SetExpire(cookie.Expires)
- }
- fcookie.SetSecure(cookie.Secure)
- fcookie.SetHTTPOnly(cookie.HTTPOnly)
-
- switch utils.ToLower(cookie.SameSite) {
- case CookieSameSiteStrictMode:
- fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
- case CookieSameSiteNoneMode:
- fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
- case CookieSameSiteDisabled:
- fcookie.SetSameSite(fasthttp.CookieSameSiteDisabled)
- default:
- fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
- }
-
- c.fasthttp.Response.Header.SetCookie(fcookie)
- fasthttp.ReleaseCookie(fcookie)
+ c.res.Cookie(cookie)
}
// Cookies is an alias of [Request.Cookies]
@@ -251,28 +180,21 @@ func (c *DefaultCtx) Cookies(key string, defaultValue ...string) string {
return c.req.Cookies(key, defaultValue...)
}
-// Download transfers the file from path as an attachment.
-// Typically, browsers will prompt the user for download.
-// By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog).
-// Override this default with the filename parameter.
+// Download is an alias of [Response.Download].
func (c *DefaultCtx) Download(file string, filename ...string) error {
- var fname string
- if len(filename) > 0 {
- fname = filename[0]
- } else {
- fname = filepath.Base(file)
- }
- c.setCanonical(HeaderContentDisposition, `attachment; filename="`+c.app.quoteString(fname)+`"`)
- return c.SendFile(file)
+ return c.res.Download(file, filename...)
}
-// Req return the *fasthttp.Req object
-// This allows you to use all fasthttp request methods
-// https://godoc.org/github.com/valyala/fasthttp#Req
+// Req returns the Request object for the current context.
func (c *DefaultCtx) Req() *Request {
return &c.req
}
+// Res returns the Response object for the current context.
+func (c *DefaultCtx) Res() *Response {
+ return &c.res
+}
+
// Response return the *fasthttp.Response object
// This allows you to use all fasthttp response methods
// https://godoc.org/github.com/valyala/fasthttp#Response
@@ -280,54 +202,9 @@ func (c *DefaultCtx) Response() *fasthttp.Response {
return &c.fasthttp.Response
}
-// Format performs content-negotiation on the Accept HTTP header.
-// It uses Accepts to select a proper format and calls the matching
-// user-provided handler function.
-// If no accepted format is found, and a format with MediaType "default" is given,
-// that default handler is called. If no format is found and no default is given,
-// StatusNotAcceptable is sent.
+// Format is an alias of [Response.Format]
func (c *DefaultCtx) Format(handlers ...ResFmt) error {
- if len(handlers) == 0 {
- return ErrNoHandlers
- }
-
- c.Vary(HeaderAccept)
-
- if c.Get(HeaderAccept) == "" {
- c.Response().Header.SetContentType(handlers[0].MediaType)
- return handlers[0].Handler(c)
- }
-
- // Using an int literal as the slice capacity allows for the slice to be
- // allocated on the stack. The number was chosen arbitrarily as an
- // approximation of the maximum number of content types a user might handle.
- // If the user goes over, it just causes allocations, so it's not a problem.
- types := make([]string, 0, 8)
- var defaultHandler Handler
- for _, h := range handlers {
- if h.MediaType == "default" {
- defaultHandler = h.Handler
- continue
- }
- types = append(types, h.MediaType)
- }
- accept := c.Accepts(types...)
-
- if accept == "" {
- if defaultHandler == nil {
- return c.SendStatus(StatusNotAcceptable)
- }
- return defaultHandler(c)
- }
-
- for _, h := range handlers {
- if h.MediaType == accept {
- c.Response().Header.SetContentType(h.MediaType)
- return h.Handler(c)
- }
- }
-
- return fmt.Errorf("%w: format: an Accept was found but no handler was called", errUnreachable)
+ return c.res.Format(handlers...)
}
// AutoFormat performs content-negotiation on the Accept HTTP header.
@@ -397,12 +274,9 @@ func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V {
return genericParseType[V](c.Req().Get(key), v, defaultValue...)
}
-// GetRespHeader returns the HTTP response header specified by field.
-// Field names are case-insensitive
-// Returned value is only valid within the handler. Do not store any references.
-// Make copies or use the Immutable setting instead.
+// GetRespHeader is an alias of [Response.Get].
func (c *DefaultCtx) GetRespHeader(key string, defaultValue ...string) string {
- return defaultString(c.app.getString(c.fasthttp.Response.Header.Peek(key)), defaultValue)
+ return c.res.Get(key, defaultValue...)
}
// GetRespHeaders returns the HTTP response headers.
@@ -463,80 +337,24 @@ func (c *DefaultCtx) Is(extension string) bool {
return c.req.Is(extension)
}
-// JSON converts any interface or string to JSON.
-// Array and slice values encode as JSON arrays,
-// except that []byte encodes as a base64-encoded string,
-// and a nil slice encodes as the null JSON value.
-// If the ctype parameter is given, this method will set the
-// Content-Type header equal to ctype. If ctype is not given,
-// The Content-Type header will be set to application/json.
+// JSON is an alias of [Response.JSON].
func (c *DefaultCtx) JSON(data any, ctype ...string) error {
- raw, err := c.app.config.JSONEncoder(data)
- if err != nil {
- return err
- }
- c.fasthttp.Response.SetBodyRaw(raw)
- if len(ctype) > 0 {
- c.fasthttp.Response.Header.SetContentType(ctype[0])
- } else {
- c.fasthttp.Response.Header.SetContentType(MIMEApplicationJSON)
- }
- return nil
+ return c.res.JSON(data, ctype...)
}
-// JSONP sends a JSON response with JSONP support.
-// This method is identical to JSON, except that it opts-in to JSONP callback support.
-// By default, the callback name is simply callback.
+// JSONP is an alias of [Response.JSONP].
func (c *DefaultCtx) JSONP(data any, callback ...string) error {
- raw, err := c.app.config.JSONEncoder(data)
- if err != nil {
- return err
- }
-
- var result, cb string
-
- if len(callback) > 0 {
- cb = callback[0]
- } else {
- cb = "callback"
- }
-
- result = cb + "(" + c.app.getString(raw) + ");"
-
- c.setCanonical(HeaderXContentTypeOptions, "nosniff")
- c.fasthttp.Response.Header.SetContentType(MIMETextJavaScriptCharsetUTF8)
- return c.SendString(result)
+ return c.res.JSONP(data, callback...)
}
-// XML converts any interface or string to XML.
-// This method also sets the content header to application/xml.
+// XML is an alias of [Response.XML].
func (c *DefaultCtx) XML(data any) error {
- raw, err := c.app.config.XMLEncoder(data)
- if err != nil {
- return err
- }
- c.fasthttp.Response.SetBodyRaw(raw)
- c.fasthttp.Response.Header.SetContentType(MIMEApplicationXML)
- return nil
+ return c.res.XML(data)
}
-// Links joins the links followed by the property to populate the response's Link HTTP header field.
+// Links is an alias of [Response.Links].
func (c *DefaultCtx) Links(link ...string) {
- if len(link) == 0 {
- return
- }
- bb := bytebufferpool.Get()
- for i := range link {
- if i%2 == 0 {
- bb.WriteByte('<')
- bb.WriteString(link[i])
- bb.WriteByte('>')
- } else {
- bb.WriteString(`; rel="` + link[i] + `",`)
- }
- }
- c.setCanonical(HeaderLink, strings.TrimRight(c.app.getString(bb.Bytes()), ","))
- bytebufferpool.Put(bb)
+ c.res.Links(link...)
}
// Locals makes it possible to pass any values under keys scoped to the request
@@ -565,14 +383,14 @@ func Locals[V any](c Ctx, key any, value ...V) V {
return v
}
-// Location sets the response Location HTTP header to the specified path parameter.
+// Location is an alias of [Response.Location].
func (c *DefaultCtx) Location(path string) {
- c.setCanonical(HeaderLocation, path)
+ c.res.Location(path)
}
-// Method is an alias of [Request.Method]
+// Method is an alias of [Request.Method].
func (c *DefaultCtx) Method(override ...string) string {
- return c.Req().Method(override...)
+ return c.req.Method(override...)
}
// MultipartForm parse form entries from binary.
@@ -725,14 +543,9 @@ func (c *DefaultCtx) Redirect() *Redirect {
return c.redirect
}
-// Bind Add vars to default view var map binding to template engine.
-// Variables are read by the Render method and may be overwritten.
+// Bind is an alias of [Response.Bind].
func (c *DefaultCtx) BindVars(vars Map) error {
- // init viewBindMap - lazy map
- for k, v := range vars {
- c.viewBindMap.Store(k, v)
- }
- return nil
+ return c.res.BindVars(vars)
}
// getLocationFromRoute get URL location from route using parameters
@@ -769,100 +582,9 @@ func (c *DefaultCtx) GetRouteURL(routeName string, params Map) (string, error) {
return c.getLocationFromRoute(c.App().GetRoute(routeName), params)
}
-// Render a template with data and sends a text/html response.
-// We support the following engines: https://github.com/gofiber/template
+// Render is an alias of [Response.Render].
func (c *DefaultCtx) Render(name string, bind Map, layouts ...string) error {
- // Get new buffer from pool
- buf := bytebufferpool.Get()
- defer bytebufferpool.Put(buf)
-
- // Initialize empty bind map if bind is nil
- if bind == nil {
- bind = make(Map)
- }
-
- // Pass-locals-to-views, bind, appListKeys
- c.renderExtensions(bind)
-
- var rendered bool
- for i := len(c.app.mountFields.appListKeys) - 1; i >= 0; i-- {
- prefix := c.app.mountFields.appListKeys[i]
- app := c.app.mountFields.appList[prefix]
- if prefix == "" || strings.Contains(c.OriginalURL(), prefix) {
- if len(layouts) == 0 && app.config.ViewsLayout != "" {
- layouts = []string{
- app.config.ViewsLayout,
- }
- }
-
- // Render template from Views
- if app.config.Views != nil {
- if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil {
- return fmt.Errorf("failed to render: %w", err)
- }
-
- rendered = true
- break
- }
- }
- }
-
- if !rendered {
- // Render raw template using 'name' as filepath if no engine is set
- var tmpl *template.Template
- if _, err := readContent(buf, name); err != nil {
- return err
- }
- // Parse template
- tmpl, err := template.New("").Parse(c.app.getString(buf.Bytes()))
- if err != nil {
- return fmt.Errorf("failed to parse: %w", err)
- }
- buf.Reset()
- // Render template
- if err := tmpl.Execute(buf, bind); err != nil {
- return fmt.Errorf("failed to execute: %w", err)
- }
- }
-
- // Set Content-Type to text/html
- c.fasthttp.Response.Header.SetContentType(MIMETextHTMLCharsetUTF8)
- // Set rendered template to body
- c.fasthttp.Response.SetBody(buf.Bytes())
-
- return nil
-}
-
-func (c *DefaultCtx) renderExtensions(bind any) {
- if bindMap, ok := bind.(Map); ok {
- // Bind view map
- c.viewBindMap.Range(func(key, value any) bool {
- keyValue, ok := key.(string)
- if !ok {
- return true
- }
- if _, ok := bindMap[keyValue]; !ok {
- bindMap[keyValue] = value
- }
- return true
- })
-
- // Check if the PassLocalsToViews option is enabled (by default it is disabled)
- if c.app.config.PassLocalsToViews {
- // Loop through each local and set it in the map
- c.fasthttp.VisitUserValues(func(key []byte, val any) {
- // check if bindMap doesn't contain the key
- if _, ok := bindMap[c.app.getString(key)]; !ok {
- // Set the key and value in the bindMap
- bindMap[c.app.getString(key)] = val
- }
- })
- }
- }
-
- if len(c.app.mountFields.appListKeys) == 0 {
- c.app.generateAppListKeys()
- }
+ return c.res.Render(name, bind, layouts...)
}
// Route is an alias of [Request.Route].
@@ -894,117 +616,29 @@ func (*DefaultCtx) SaveFileToStorage(fileheader *multipart.FileHeader, path stri
return nil
}
-// Secure returns whether a secure connection was established.
+// Secure is an alias of [Request.Secure].
func (c *DefaultCtx) Secure() bool {
- return c.Protocol() == schemeHTTPS
+ return c.req.Secure()
}
-// Send sets the HTTP response body without copying it.
-// From this point onward the body argument must not be changed.
+// Send is an alias of [Response.Send].
func (c *DefaultCtx) Send(body []byte) error {
- // Write response body
- c.fasthttp.Response.SetBodyRaw(body)
- return nil
+ return c.res.Send(body)
}
-var (
- sendFileOnce sync.Once
- sendFileFS *fasthttp.FS
- sendFileHandler fasthttp.RequestHandler
-)
-
-// SendFile transfers the file from the given path.
-// The file is not compressed by default, enable this by passing a 'true' argument
-// Sets the Content-Type response HTTP header field based on the filenames extension.
+// SendFile is an alias of [Response.SendFile].
func (c *DefaultCtx) SendFile(file string, compress ...bool) error {
- // Save the filename, we will need it in the error message if the file isn't found
- filename := file
-
- // https://github.com/valyala/fasthttp/blob/c7576cc10cabfc9c993317a2d3f8355497bea156/fs.go#L129-L134
- sendFileOnce.Do(func() {
- const cacheDuration = 10 * time.Second
- sendFileFS = &fasthttp.FS{
- Root: "",
- AllowEmptyRoot: true,
- GenerateIndexPages: false,
- AcceptByteRange: true,
- Compress: true,
- CompressedFileSuffix: c.app.config.CompressedFileSuffix,
- CacheDuration: cacheDuration,
- IndexNames: []string{"index.html"},
- PathNotFound: func(ctx *fasthttp.RequestCtx) {
- ctx.Response.SetStatusCode(StatusNotFound)
- },
- }
- sendFileHandler = sendFileFS.NewRequestHandler()
- })
-
- // Keep original path for mutable params
- c.req.pathOriginal = utils.CopyString(c.req.pathOriginal)
- // Disable compression
- if len(compress) == 0 || !compress[0] {
- // https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L55
- c.fasthttp.Request.Header.Del(HeaderAcceptEncoding)
- }
- // copy of https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L103-L121 with small adjustments
- if len(file) == 0 || !filepath.IsAbs(file) {
- // extend relative path to absolute path
- hasTrailingSlash := len(file) > 0 && (file[len(file)-1] == '/' || file[len(file)-1] == '\\')
-
- var err error
- file = filepath.FromSlash(file)
- if file, err = filepath.Abs(file); err != nil {
- return fmt.Errorf("failed to determine abs file path: %w", err)
- }
- if hasTrailingSlash {
- file += "/"
- }
- }
- // convert the path to forward slashes regardless the OS in order to set the URI properly
- // the handler will convert back to OS path separator before opening the file
- file = filepath.ToSlash(file)
-
- // Restore the original requested URL
- originalURL := utils.CopyString(c.OriginalURL())
- defer c.fasthttp.Request.SetRequestURI(originalURL)
- // Set new URI for fileHandler
- c.fasthttp.Request.SetRequestURI(file)
- // Save status code
- status := c.fasthttp.Response.StatusCode()
- // Serve file
- sendFileHandler(c.fasthttp)
- // Get the status code which is set by fasthttp
- fsStatus := c.fasthttp.Response.StatusCode()
- // Set the status code set by the user if it is different from the fasthttp status code and 200
- if status != fsStatus && status != StatusOK {
- c.Status(status)
- }
- // Check for error
- if status != StatusNotFound && fsStatus == StatusNotFound {
- return NewError(StatusNotFound, fmt.Sprintf("sendfile: file %s not found", filename))
- }
- return nil
+ return c.res.SendFile(file, compress...)
}
-// SendStatus sets the HTTP status code and if the response body is empty,
-// it sets the correct status message in the body.
+// SendStatus is an alias of [Response.SendStatus].
func (c *DefaultCtx) SendStatus(status int) error {
- c.Status(status)
-
- // Only set status body when there is no response body
- if len(c.fasthttp.Response.Body()) == 0 {
- return c.SendString(utils.StatusMessage(status))
- }
-
- return nil
+ return c.res.SendStatus(status)
}
-// SendString sets the HTTP response body for string types.
-// This means no type assertion, recommended for faster performance
+// SendString is an alias of [Response.SendString].
func (c *DefaultCtx) SendString(body string) error {
- c.fasthttp.Response.SetBodyString(body)
-
- return nil
+ return c.res.SendString(body)
}
// SendStream sets response body stream and optional body size.
@@ -1018,30 +652,14 @@ func (c *DefaultCtx) SendStream(stream io.Reader, size ...int) error {
return nil
}
-// Set sets the response's HTTP header field to the specified key, value.
+// Set is an alias of [Response.Set].
func (c *DefaultCtx) Set(key, val string) {
- c.fasthttp.Response.Header.Set(key, val)
-}
-
-func (c *DefaultCtx) setCanonical(key, val string) {
- c.fasthttp.Response.Header.SetCanonical(utils.UnsafeBytes(key), utils.UnsafeBytes(val))
+ c.res.Set(key, val)
}
-// Subdomains returns a string slice of subdomains in the domain name of the request.
-// The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments.
+// Subdomains is an alias of [Request.Subdomains].
func (c *DefaultCtx) Subdomains(offset ...int) []string {
- o := 2
- if len(offset) > 0 {
- o = offset[0]
- }
- subdomains := strings.Split(c.Host(), ".")
- l := len(subdomains) - o
- // Check index to avoid slice bounds out of range panic
- if l < 0 {
- l = len(subdomains)
- }
- subdomains = subdomains[:l]
- return subdomains
+ return c.req.Subdomains(offset...)
}
// Stale is an alias of [Request.Stale].
@@ -1049,10 +667,10 @@ func (c *DefaultCtx) Stale() bool {
return c.req.Stale()
}
-// Status sets the HTTP status for the response.
+// Status is an alias of [Response.Status].
// This method is chainable.
func (c *DefaultCtx) Status(status int) Ctx {
- c.fasthttp.Response.SetStatusCode(status)
+ c.res.Status(status)
return c
}
@@ -1095,44 +713,36 @@ func (c *DefaultCtx) String() string {
return str
}
-// Type sets the Content-Type HTTP header to the MIME type specified by the file extension.
+// Type is an alias of [Response.Type].
func (c *DefaultCtx) Type(extension string, charset ...string) Ctx {
- if len(charset) > 0 {
- c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0])
- } else {
- c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension))
- }
+ c.res.Type(extension, charset...)
return c
}
-// Vary adds the given header field to the Vary response header.
-// This will append the header, if not already listed, otherwise leaves it listed in the current location.
+// Vary is an alias of [Response.Vary].
func (c *DefaultCtx) Vary(fields ...string) {
c.Append(HeaderVary, fields...)
}
-// Write appends p into response body.
+// Write is an alias of [Response.Write].
func (c *DefaultCtx) Write(p []byte) (int, error) {
c.fasthttp.Response.AppendBody(p)
return len(p), nil
}
-// Writef appends f & a into response body writer.
+// Writef is an alias of [Response.Writef].
func (c *DefaultCtx) Writef(f string, a ...any) (int, error) {
- //nolint:wrapcheck // This must not be wrapped
- return fmt.Fprintf(c.fasthttp.Response.BodyWriter(), f, a...)
+ return c.res.Writef(f, a...)
}
-// WriteString appends s to response body.
+// WriteString is an alias of [Response.WriteString].
func (c *DefaultCtx) WriteString(s string) (int, error) {
- c.fasthttp.Response.AppendBodyString(s)
- return len(s), nil
+ return c.res.WriteString(s)
}
-// XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest,
-// indicating that the request was issued by a client library (such as jQuery).
+// XHR is an alis of [Request.XHR].
func (c *DefaultCtx) XHR() bool {
- return utils.EqualFold(c.app.getBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest"))
+ return c.req.XHR()
}
// IsProxyTrusted checks trustworthiness of remote ip.
diff --git a/ctx_interface.go b/ctx_interface.go
index 7a61e0b7600..0c3c9110048 100644
--- a/ctx_interface.go
+++ b/ctx_interface.go
@@ -83,10 +83,9 @@ type Ctx interface {
// To access the underlying fasthttp request object, use [Ctx.Context].
Req() *Request
- // Response return the *fasthttp.Response object
- // This allows you to use all fasthttp response methods
- // https://godoc.org/github.com/valyala/fasthttp#Response
- Response() *fasthttp.Response
+ // Res returns the [Response] object for the current request context.
+ // To access the underlying fasthttp response object, use [Ctx.Context].
+ Res() *Response
// Format performs content-negotiation on the Accept HTTP header.
// It uses Accepts to select a proper format and calls the matching
@@ -131,6 +130,7 @@ type Ctx interface {
// Field names are case-insensitive
// Returned value is only valid within the handler. Do not store any references.
// Make copies or use the Immutable setting instead.
+ // Deprecated: Use c.Res().Get()
GetRespHeader(key string, defaultValue ...string) string
// GetRespHeaders returns the HTTP response headers.
@@ -406,8 +406,12 @@ func NewDefaultCtx(app *App) *DefaultCtx {
req: Request{
app: app,
},
+ res: Response{
+ app: app,
+ },
}
ctx.req.ctx = ctx
+ ctx.res.ctx = ctx
return ctx
}
@@ -453,6 +457,7 @@ func (c *DefaultCtx) Reset(fctx *fasthttp.RequestCtx) {
// Attach *fasthttp.RequestCtx to ctx
c.fasthttp = fctx
c.req.fasthttp = &fctx.Request
+ c.res.fasthttp = &fctx.Response
// Set method
c.req.method = c.app.getString(fctx.Request.Header.Method())
c.req.methodINT = c.app.methodInt(c.req.method)
@@ -467,8 +472,7 @@ func (c *DefaultCtx) release() {
c.req.route = nil
c.fasthttp = nil
c.bind = nil
- c.redirectionMessages = c.redirectionMessages[:0]
- c.viewBindMap = sync.Map{}
+ c.res.viewBindMap = sync.Map{}
if c.redirect != nil {
ReleaseRedirect(c.redirect)
c.redirect = nil
diff --git a/ctx_test.go b/ctx_test.go
index b6f5f69906a..54ba21bc484 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -263,11 +263,11 @@ func Test_Ctx_Append(t *testing.T) {
// without append value
c.Append("X-Custom-Header")
- require.Equal(t, "Hello, World", string(c.Response().Header.Peek("X-Test")))
- require.Equal(t, "World, XHello, Hello", string(c.Response().Header.Peek("X2-Test")))
- require.Equal(t, "XHello, World, Hello", string(c.Response().Header.Peek("X3-Test")))
- require.Equal(t, "XHello, Hello, HelloZ, YHello", string(c.Response().Header.Peek("X4-Test")))
- require.Equal(t, "", string(c.Response().Header.Peek("x-custom-header")))
+ require.Equal(t, "Hello, World", c.Res().Get("X-Test"))
+ require.Equal(t, "World, XHello, Hello", c.Res().Get("X2-Test"))
+ require.Equal(t, "XHello, World, Hello", c.Res().Get("X3-Test"))
+ require.Equal(t, "XHello, Hello, HelloZ, YHello", c.Res().Get("X4-Test"))
+ require.Equal(t, "", c.Res().Get("x-custom-header"))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Append -benchmem -count=4
@@ -282,7 +282,7 @@ func Benchmark_Ctx_Append(b *testing.B) {
c.Append("X-Custom-Header", "World")
c.Append("X-Custom-Header", "Hello")
}
- require.Equal(b, "Hello, World", app.getString(c.Response().Header.Peek("X-Custom-Header")))
+ require.Equal(b, "Hello, World", c.Res().Get("X-Custom-Header"))
}
// go test -run Test_Ctx_Attachment
@@ -293,14 +293,14 @@ func Test_Ctx_Attachment(t *testing.T) {
// empty
c.Attachment()
- require.Equal(t, `attachment`, string(c.Response().Header.Peek(HeaderContentDisposition)))
+ require.Equal(t, `attachment`, c.Res().Get(HeaderContentDisposition))
// real filename
c.Attachment("./static/img/logo.png")
- require.Equal(t, `attachment; filename="logo.png"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
- require.Equal(t, "image/png", string(c.Response().Header.Peek(HeaderContentType)))
+ require.Equal(t, `attachment; filename="logo.png"`, c.Res().Get(HeaderContentDisposition))
+ require.Equal(t, "image/png", c.Res().Get(HeaderContentType))
// check quoting
c.Attachment("another document.pdf\"\r\nBla: \"fasel")
- require.Equal(t, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
+ require.Equal(t, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, c.Res().Get(HeaderContentDisposition))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Attachment -benchmem -count=4
@@ -314,7 +314,7 @@ func Benchmark_Ctx_Attachment(b *testing.B) {
// example with quote params
c.Attachment("another document.pdf\"\r\nBla: \"fasel")
}
- require.Equal(b, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
+ require.Equal(b, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, c.Res().Get(HeaderContentDisposition))
}
// go test -run Test_Ctx_BaseURL
@@ -886,23 +886,23 @@ func Test_Ctx_Cookie(t *testing.T) {
}
c.Cookie(cookie)
expect := "username=john; expires=" + httpdate + "; path=/; SameSite=Lax"
- require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
+ require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
expect = "username=john; expires=" + httpdate + "; path=/"
cookie.SameSite = CookieSameSiteDisabled
c.Cookie(cookie)
- require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
+ require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
expect = "username=john; expires=" + httpdate + "; path=/; SameSite=Strict"
cookie.SameSite = CookieSameSiteStrictMode
c.Cookie(cookie)
- require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
+ require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
expect = "username=john; expires=" + httpdate + "; path=/; secure; SameSite=None"
cookie.Secure = true
cookie.SameSite = CookieSameSiteNoneMode
c.Cookie(cookie)
- require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
+ require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
expect = "username=john; path=/; secure; SameSite=None"
// should remove expires and max-age headers
@@ -910,7 +910,7 @@ func Test_Ctx_Cookie(t *testing.T) {
cookie.Expires = expire
cookie.MaxAge = 10000
c.Cookie(cookie)
- require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
+ require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
expect = "username=john; path=/; secure; SameSite=None"
// should remove expires and max-age headers when no expire and no MaxAge (default time)
@@ -918,7 +918,7 @@ func Test_Ctx_Cookie(t *testing.T) {
cookie.Expires = time.Time{}
cookie.MaxAge = 0
c.Cookie(cookie)
- require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
+ require.Equal(t, expect, c.Res().Get(HeaderSetCookie))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4
@@ -934,7 +934,7 @@ func Benchmark_Ctx_Cookie(b *testing.B) {
Value: "Doe",
})
}
- require.Equal(b, "John=Doe; path=/; SameSite=Lax", app.getString(c.Response().Header.Peek("Set-Cookie")))
+ require.Equal(b, "John=Doe; path=/; SameSite=Lax", c.Res().Get("Set-Cookie"))
}
// go test -run Test_Ctx_Cookies
@@ -973,13 +973,13 @@ func Test_Ctx_Format(t *testing.T) {
require.Equal(t, "application/xhtml+xml", accepted)
require.Equal(t, "application/xhtml+xml", c.GetRespHeader(HeaderContentType))
require.NoError(t, err)
- require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode())
+ require.NotEqual(t, StatusNotAcceptable, c.Context().Response.StatusCode())
err = c.Format(formatHandlers("foo/bar;a=b")...)
require.Equal(t, "foo/bar;a=b", accepted)
require.Equal(t, "foo/bar;a=b", c.GetRespHeader(HeaderContentType))
require.NoError(t, err)
- require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode())
+ require.NotEqual(t, StatusNotAcceptable, c.Context().Response.StatusCode())
myError := errors.New("this is an error")
err = c.Format(ResFmt{"text/html", func(_ Ctx) error { return myError }})
@@ -987,7 +987,7 @@ func Test_Ctx_Format(t *testing.T) {
c.Context().Request.Header.Set(HeaderAccept, "application/json")
err = c.Format(ResFmt{"text/html", func(c Ctx) error { return c.SendStatus(StatusOK) }})
- require.Equal(t, StatusNotAcceptable, c.Response().StatusCode())
+ require.Equal(t, StatusNotAcceptable, c.Context().Response.StatusCode())
require.NoError(t, err)
err = c.Format(formatHandlers("text/html", "default")...)
@@ -1073,27 +1073,27 @@ func Test_Ctx_AutoFormat(t *testing.T) {
c.Context().Request.Header.Set(HeaderAccept, MIMETextPlain)
err := c.AutoFormat([]byte("Hello, World!"))
require.NoError(t, err)
- require.Equal(t, "Hello, World!", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!", string(c.Context().Response.Body()))
c.Context().Request.Header.Set(HeaderAccept, MIMETextHTML)
err = c.AutoFormat("Hello, World!")
require.NoError(t, err)
- require.Equal(t, "
Hello, World!
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!
", string(c.Context().Response.Body()))
c.Context().Request.Header.Set(HeaderAccept, MIMEApplicationJSON)
err = c.AutoFormat("Hello, World!")
require.NoError(t, err)
- require.Equal(t, `"Hello, World!"`, string(c.Response().Body()))
+ require.Equal(t, `"Hello, World!"`, string(c.Context().Response.Body()))
c.Context().Request.Header.Set(HeaderAccept, MIMETextPlain)
err = c.AutoFormat(complex(1, 1))
require.NoError(t, err)
- require.Equal(t, "(1+1i)", string(c.Response().Body()))
+ require.Equal(t, "(1+1i)", string(c.Context().Response.Body()))
c.Context().Request.Header.Set(HeaderAccept, MIMEApplicationXML)
err = c.AutoFormat("Hello, World!")
require.NoError(t, err)
- require.Equal(t, `Hello, World!`, string(c.Response().Body()))
+ require.Equal(t, `Hello, World!`, string(c.Context().Response.Body()))
err = c.AutoFormat(complex(1, 1))
require.Error(t, err)
@@ -1101,14 +1101,14 @@ func Test_Ctx_AutoFormat(t *testing.T) {
c.Context().Request.Header.Set(HeaderAccept, MIMETextPlain)
err = c.AutoFormat(Map{})
require.NoError(t, err)
- require.Equal(t, "map[]", string(c.Response().Body()))
+ require.Equal(t, "map[]", string(c.Context().Response.Body()))
type broken string
c.Context().Request.Header.Set(HeaderAccept, "broken/accept")
require.NoError(t, err)
err = c.AutoFormat(broken("Hello, World!"))
require.NoError(t, err)
- require.Equal(t, `Hello, World!`, string(c.Response().Body()))
+ require.Equal(t, `Hello, World!`, string(c.Context().Response.Body()))
}
func Test_Ctx_AutoFormat_Struct(t *testing.T) {
@@ -1132,7 +1132,7 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) {
require.NoError(t, err)
require.Equal(t,
`{"Recipients":["Alice","Bob"],"Sender":"Carol","Urgency":3}`,
- string(c.Response().Body()),
+ string(c.Context().Response.Body()),
)
c.Context().Request.Header.Set(HeaderAccept, MIMEApplicationXML)
@@ -1140,7 +1140,7 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) {
require.NoError(t, err)
require.Equal(t,
`AliceBob`,
- string(c.Response().Body()),
+ string(c.Context().Response.Body()),
)
}
@@ -1158,7 +1158,7 @@ func Benchmark_Ctx_AutoFormat(b *testing.B) {
err = c.AutoFormat("Hello, World!")
}
require.NoError(b, err)
- require.Equal(b, `Hello, World!`, string(c.Response().Body()))
+ require.Equal(b, `Hello, World!`, string(c.Context().Response.Body()))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_AutoFormat_HTML -benchmem -count=4
@@ -1175,7 +1175,7 @@ func Benchmark_Ctx_AutoFormat_HTML(b *testing.B) {
err = c.AutoFormat("Hello, World!")
}
require.NoError(b, err)
- require.Equal(b, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(b, "Hello, World!
", string(c.Context().Response.Body()))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_AutoFormat_JSON -benchmem -count=4
@@ -1192,7 +1192,7 @@ func Benchmark_Ctx_AutoFormat_JSON(b *testing.B) {
err = c.AutoFormat("Hello, World!")
}
require.NoError(b, err)
- require.Equal(b, `"Hello, World!"`, string(c.Response().Body()))
+ require.Equal(b, `"Hello, World!"`, string(c.Context().Response.Body()))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_AutoFormat_XML -benchmem -count=4
@@ -1209,7 +1209,7 @@ func Benchmark_Ctx_AutoFormat_XML(b *testing.B) {
err = c.AutoFormat("Hello, World!")
}
require.NoError(b, err)
- require.Equal(b, `Hello, World!`, string(c.Response().Body()))
+ require.Equal(b, `Hello, World!`, string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_FormFile
@@ -1324,17 +1324,17 @@ func Test_Ctx_Fresh(t *testing.T) {
require.False(t, c.Fresh())
c.Context().Request.Header.Set(HeaderIfNoneMatch, "a, b")
- c.Response().Header.Set(HeaderETag, "c")
+ c.Set(HeaderETag, "c")
require.False(t, c.Fresh())
- c.Response().Header.Set(HeaderETag, "a")
+ c.Set(HeaderETag, "a")
require.True(t, c.Fresh())
c.Context().Request.Header.Set(HeaderIfModifiedSince, "xxWed, 21 Oct 2015 07:28:00 GMT")
- c.Response().Header.Set(HeaderLastModified, "xxWed, 21 Oct 2015 07:28:00 GMT")
+ c.Set(HeaderLastModified, "xxWed, 21 Oct 2015 07:28:00 GMT")
require.False(t, c.Fresh())
- c.Response().Header.Set(HeaderLastModified, "Wed, 21 Oct 2015 07:28:00 GMT")
+ c.Set(HeaderLastModified, "Wed, 21 Oct 2015 07:28:00 GMT")
require.False(t, c.Fresh())
c.Context().Request.Header.Set(HeaderIfModifiedSince, "Wed, 21 Oct 2015 07:28:00 GMT")
@@ -2894,13 +2894,13 @@ func Test_Ctx_ClearCookie(t *testing.T) {
c.Context().Request.Header.Set(HeaderCookie, "john=doe")
c.ClearCookie("john")
- require.True(t, strings.HasPrefix(string(c.Response().Header.Peek(HeaderSetCookie)), "john=; expires="))
+ require.True(t, strings.HasPrefix(c.Res().Get(HeaderSetCookie), "john=; expires="))
c.Context().Request.Header.Set(HeaderCookie, "test1=dummy")
c.Context().Request.Header.Set(HeaderCookie, "test2=dummy")
c.ClearCookie()
- require.Contains(t, string(c.Response().Header.Peek(HeaderSetCookie)), "test1=; expires=")
- require.Contains(t, string(c.Response().Header.Peek(HeaderSetCookie)), "test2=; expires=")
+ require.Contains(t, c.Res().Get(HeaderSetCookie), "test1=; expires=")
+ require.Contains(t, c.Res().Get(HeaderSetCookie), "test2=; expires=")
}
// go test -race -run Test_Ctx_Download
@@ -2919,11 +2919,11 @@ func Test_Ctx_Download(t *testing.T) {
expect, err := io.ReadAll(f)
require.NoError(t, err)
- require.Equal(t, expect, c.Response().Body())
- require.Equal(t, `attachment; filename="Awesome+File%21"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
+ require.Equal(t, expect, c.Context().Response.Body())
+ require.Equal(t, `attachment; filename="Awesome+File%21"`, c.Res().Get(HeaderContentDisposition))
require.NoError(t, c.Download("ctx.go"))
- require.Equal(t, `attachment; filename="ctx.go"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
+ require.Equal(t, `attachment; filename="ctx.go"`, c.Res().Get(HeaderContentDisposition))
}
// go test -race -run Test_Ctx_SendFile
@@ -2948,8 +2948,8 @@ func Test_Ctx_SendFile(t *testing.T) {
err = c.SendFile("ctx.go")
// check expectation
require.NoError(t, err)
- require.Equal(t, expectFileContent, c.Response().Body())
- require.Equal(t, StatusOK, c.Response().StatusCode())
+ require.Equal(t, expectFileContent, c.Context().Response.Body())
+ require.Equal(t, StatusOK, c.Context().Response.StatusCode())
app.ReleaseCtx(c)
// test with custom error code
@@ -2957,8 +2957,8 @@ func Test_Ctx_SendFile(t *testing.T) {
err = c.Status(StatusInternalServerError).SendFile("ctx.go")
// check expectation
require.NoError(t, err)
- require.Equal(t, expectFileContent, c.Response().Body())
- require.Equal(t, StatusInternalServerError, c.Response().StatusCode())
+ require.Equal(t, expectFileContent, c.Context().Response.Body())
+ require.Equal(t, StatusInternalServerError, c.Context().Response.StatusCode())
app.ReleaseCtx(c)
// test not modified
@@ -2967,8 +2967,8 @@ func Test_Ctx_SendFile(t *testing.T) {
err = c.SendFile("ctx.go")
// check expectation
require.NoError(t, err)
- require.Equal(t, StatusNotModified, c.Response().StatusCode())
- require.Equal(t, []byte(nil), c.Response().Body())
+ require.Equal(t, StatusNotModified, c.Context().Response.StatusCode())
+ require.Equal(t, []byte(nil), c.Context().Response.Body())
app.ReleaseCtx(c)
}
@@ -3066,8 +3066,8 @@ func Test_Ctx_JSON(t *testing.T) {
"Age": 20,
})
require.NoError(t, err)
- require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body()))
- require.Equal(t, "application/json", string(c.Response().Header.Peek("content-type")))
+ require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Context().Response.Body()))
+ require.Equal(t, "application/json", c.Res().Get("content-type"))
// Test with ctype
err = c.JSON(Map{ // map has no order
@@ -3075,13 +3075,13 @@ func Test_Ctx_JSON(t *testing.T) {
"Age": 20,
}, "application/problem+json")
require.NoError(t, err)
- require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body()))
- require.Equal(t, "application/problem+json", string(c.Response().Header.Peek("content-type")))
+ require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Context().Response.Body()))
+ require.Equal(t, "application/problem+json", c.Res().Get("content-type"))
testEmpty := func(v any, r string) {
err := c.JSON(v)
require.NoError(t, err)
- require.Equal(t, r, string(c.Response().Body()))
+ require.Equal(t, r, string(c.Context().Response.Body()))
}
testEmpty(nil, "null")
@@ -3104,8 +3104,8 @@ func Test_Ctx_JSON(t *testing.T) {
"Age": 20,
})
require.NoError(t, err)
- require.Equal(t, `["custom","json"]`, string(c.Response().Body()))
- require.Equal(t, "application/json", string(c.Response().Header.Peek("content-type")))
+ require.Equal(t, `["custom","json"]`, string(c.Context().Response.Body()))
+ require.Equal(t, "application/json", c.Res().Get("content-type"))
})
}
@@ -3129,7 +3129,7 @@ func Benchmark_Ctx_JSON(b *testing.B) {
err = c.JSON(data)
}
require.NoError(b, err)
- require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body()))
+ require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Context().Response.Body()))
}
// go test -run=^$ -bench=Benchmark_Ctx_JSON_Ctype -benchmem -count=4
@@ -3152,8 +3152,8 @@ func Benchmark_Ctx_JSON_Ctype(b *testing.B) {
err = c.JSON(data, "application/problem+json")
}
require.NoError(b, err)
- require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body()))
- require.Equal(b, "application/problem+json", string(c.Response().Header.Peek("content-type")))
+ require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Context().Response.Body()))
+ require.Equal(b, "application/problem+json", c.Res().Get("content-type"))
}
// go test -run Test_Ctx_JSONP
@@ -3169,16 +3169,16 @@ func Test_Ctx_JSONP(t *testing.T) {
"Age": 20,
})
require.NoError(t, err)
- require.Equal(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Response().Body()))
- require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))
+ require.Equal(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Context().Response.Body()))
+ require.Equal(t, "text/javascript; charset=utf-8", c.Res().Get("content-type"))
err = c.JSONP(Map{
"Name": "Grame",
"Age": 20,
}, "john")
require.NoError(t, err)
- require.Equal(t, `john({"Age":20,"Name":"Grame"});`, string(c.Response().Body()))
- require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))
+ require.Equal(t, `john({"Age":20,"Name":"Grame"});`, string(c.Context().Response.Body()))
+ require.Equal(t, "text/javascript; charset=utf-8", c.Res().Get("content-type"))
t.Run("custom json encoder", func(t *testing.T) {
t.Parallel()
@@ -3195,8 +3195,8 @@ func Test_Ctx_JSONP(t *testing.T) {
"Age": 20,
})
require.NoError(t, err)
- require.Equal(t, `callback(["custom","json"]);`, string(c.Response().Body()))
- require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))
+ require.Equal(t, `callback(["custom","json"]);`, string(c.Context().Response.Body()))
+ require.Equal(t, "text/javascript; charset=utf-8", c.Res().Get("content-type"))
})
}
@@ -3221,7 +3221,7 @@ func Benchmark_Ctx_JSONP(b *testing.B) {
err = c.JSONP(data, callback)
}
require.NoError(b, err)
- require.Equal(b, `emit({"Name":"Grame","Age":20});`, string(c.Response().Body()))
+ require.Equal(b, `emit({"Name":"Grame","Age":20});`, string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_XML
@@ -3243,13 +3243,13 @@ func Test_Ctx_XML(t *testing.T) {
Ages: []int{1, 12, 20},
})
require.NoError(t, err)
- require.Equal(t, `GrameJohn11220`, string(c.Response().Body()))
- require.Equal(t, "application/xml", string(c.Response().Header.Peek("content-type")))
+ require.Equal(t, `GrameJohn11220`, string(c.Context().Response.Body()))
+ require.Equal(t, "application/xml", c.Res().Get("content-type"))
testEmpty := func(v any, r string) {
err := c.XML(v)
require.NoError(t, err)
- require.Equal(t, r, string(c.Response().Body()))
+ require.Equal(t, r, string(c.Context().Response.Body()))
}
testEmpty(nil, "")
@@ -3279,8 +3279,8 @@ func Test_Ctx_XML(t *testing.T) {
})
require.NoError(t, err)
- require.Equal(t, `xml`, string(c.Response().Body()))
- require.Equal(t, "application/xml", string(c.Response().Header.Peek("content-type")))
+ require.Equal(t, `xml`, string(c.Context().Response.Body()))
+ require.Equal(t, "application/xml", c.Res().Get("content-type"))
})
}
@@ -3304,7 +3304,7 @@ func Benchmark_Ctx_XML(b *testing.B) {
}
require.NoError(b, err)
- require.Equal(b, `Grame20`, string(c.Response().Body()))
+ require.Equal(b, `Grame20`, string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_Links
@@ -3314,13 +3314,13 @@ func Test_Ctx_Links(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Links()
- require.Equal(t, "", string(c.Response().Header.Peek(HeaderLink)))
+ require.Equal(t, "", c.Res().Get(HeaderLink))
c.Links(
"http://api.example.com/users?page=2", "next",
"http://api.example.com/users?page=5", "last",
)
- require.Equal(t, `; rel="next",; rel="last"`, string(c.Response().Header.Peek(HeaderLink)))
+ require.Equal(t, `; rel="next",; rel="last"`, c.Res().Get(HeaderLink))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Links -benchmem -count=4
@@ -3345,7 +3345,7 @@ func Test_Ctx_Location(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Location("http://example.com")
- require.Equal(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, "http://example.com", c.Res().Get(HeaderLocation))
}
// go test -run Test_Ctx_Next
@@ -3391,7 +3391,7 @@ func Test_Ctx_Render(t *testing.T) {
})
require.NoError(t, err)
- require.Equal(t, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!
", string(c.Context().Response.Body()))
err = c.Render("./.github/testdata/template-non-exists.html", nil)
require.Error(t, err)
@@ -3411,7 +3411,7 @@ func Test_Ctx_RenderWithoutLocals(t *testing.T) {
err := c.Render("./.github/testdata/index.tmpl", Map{})
require.NoError(t, err)
- require.Equal(t, "
", string(c.Response().Body()))
+ require.Equal(t, "
", string(c.Context().Response.Body()))
}
func Test_Ctx_RenderWithLocals(t *testing.T) {
@@ -3428,7 +3428,7 @@ func Test_Ctx_RenderWithLocals(t *testing.T) {
err := c.Render("./.github/testdata/index.tmpl", Map{})
require.NoError(t, err)
- require.Equal(t, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!
", string(c.Context().Response.Body()))
})
t.Run("NilBind", func(t *testing.T) {
@@ -3439,7 +3439,7 @@ func Test_Ctx_RenderWithLocals(t *testing.T) {
err := c.Render("./.github/testdata/index.tmpl", nil)
require.NoError(t, err)
- require.Equal(t, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!
", string(c.Context().Response.Body()))
})
}
@@ -3461,7 +3461,7 @@ func Test_Ctx_RenderWithBindVars(t *testing.T) {
defer bytebufferpool.Put(buf)
require.NoError(t, err)
- require.Equal(t, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!
", string(c.Context().Response.Body()))
}
func Test_Ctx_RenderWithOverwrittenBind(t *testing.T) {
@@ -3483,7 +3483,7 @@ func Test_Ctx_RenderWithOverwrittenBind(t *testing.T) {
buf.WriteString("overwrite")
defer bytebufferpool.Put(buf)
- require.Equal(t, "Hello from Fiber!
", string(c.Response().Body()))
+ require.Equal(t, "Hello from Fiber!
", string(c.Context().Response.Body()))
}
func Test_Ctx_RenderWithBindVarsLocals(t *testing.T) {
@@ -3503,9 +3503,9 @@ func Test_Ctx_RenderWithBindVarsLocals(t *testing.T) {
err = c.Render("./.github/testdata/template.tmpl", Map{})
require.NoError(t, err)
- require.Equal(t, "Hello, World! Test
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World! Test
", string(c.Context().Response.Body()))
- require.Equal(t, "Hello, World! Test
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World! Test
", string(c.Context().Response.Body()))
}
func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) {
@@ -3527,7 +3527,7 @@ func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) {
})
require.NoError(t, err)
- require.Equal(t, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!
", string(c.Context().Response.Body()))
}
func Benchmark_Ctx_RenderWithLocalsAndBindVars(b *testing.B) {
@@ -3554,7 +3554,7 @@ func Benchmark_Ctx_RenderWithLocalsAndBindVars(b *testing.B) {
}
require.NoError(b, err)
- require.Equal(b, "Hello, World! Test
", string(c.Response().Body()))
+ require.Equal(b, "Hello, World! Test
", string(c.Context().Response.Body()))
}
func Benchmark_Ctx_RenderLocals(b *testing.B) {
@@ -3577,7 +3577,7 @@ func Benchmark_Ctx_RenderLocals(b *testing.B) {
}
require.NoError(b, err)
- require.Equal(b, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(b, "Hello, World!
", string(c.Context().Response.Body()))
}
func Benchmark_Ctx_RenderBindVars(b *testing.B) {
@@ -3601,7 +3601,7 @@ func Benchmark_Ctx_RenderBindVars(b *testing.B) {
}
require.NoError(b, err)
- require.Equal(b, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(b, "Hello, World!
", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_RestartRouting
@@ -3711,7 +3711,7 @@ func Test_Ctx_Render_Engine(t *testing.T) {
"Title": "Hello, World!",
})
require.NoError(t, err)
- require.Equal(t, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!
", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_Render_Engine_With_View_Layout
@@ -3727,7 +3727,7 @@ func Test_Ctx_Render_Engine_With_View_Layout(t *testing.T) {
"Title": "Hello, World!",
})
require.NoError(t, err)
- require.Equal(t, "Hello, World!
I'm main
", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!
I'm main
", string(c.Context().Response.Body()))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Render_Engine -benchmem -count=4
@@ -3747,7 +3747,7 @@ func Benchmark_Ctx_Render_Engine(b *testing.B) {
})
}
require.NoError(b, err)
- require.Equal(b, "Hello, World!
", string(c.Response().Body()))
+ require.Equal(b, "Hello, World!
", string(c.Context().Response.Body()))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Get_Location_From_Route -benchmem -count=4
@@ -3887,7 +3887,7 @@ func Test_Ctx_Render_Go_Template(t *testing.T) {
err = c.Render(file.Name(), nil)
require.NoError(t, err)
- require.Equal(t, "template", string(c.Response().Body()))
+ require.Equal(t, "template", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_Send
@@ -3899,7 +3899,7 @@ func Test_Ctx_Send(t *testing.T) {
require.NoError(t, c.Send([]byte("Hello, World")))
require.NoError(t, c.Send([]byte("Don't crash please")))
require.NoError(t, c.Send([]byte("1337")))
- require.Equal(t, "1337", string(c.Response().Body()))
+ require.Equal(t, "1337", string(c.Context().Response.Body()))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Send -benchmem -count=4
@@ -3916,7 +3916,7 @@ func Benchmark_Ctx_Send(b *testing.B) {
err = c.Send(byt)
}
require.NoError(b, err)
- require.Equal(b, "Hello, World!", string(c.Response().Body()))
+ require.Equal(b, "Hello, World!", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_SendStatus
@@ -3927,8 +3927,8 @@ func Test_Ctx_SendStatus(t *testing.T) {
err := c.SendStatus(415)
require.NoError(t, err)
- require.Equal(t, 415, c.Response().StatusCode())
- require.Equal(t, "Unsupported Media Type", string(c.Response().Body()))
+ require.Equal(t, 415, c.Context().Response.StatusCode())
+ require.Equal(t, "Unsupported Media Type", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_SendString
@@ -3939,7 +3939,7 @@ func Test_Ctx_SendString(t *testing.T) {
err := c.SendString("Don't crash please")
require.NoError(t, err)
- require.Equal(t, "Don't crash please", string(c.Response().Body()))
+ require.Equal(t, "Don't crash please", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_SendStream
@@ -3950,15 +3950,15 @@ func Test_Ctx_SendStream(t *testing.T) {
err := c.SendStream(bytes.NewReader([]byte("Don't crash please")))
require.NoError(t, err)
- require.Equal(t, "Don't crash please", string(c.Response().Body()))
+ require.Equal(t, "Don't crash please", string(c.Context().Response.Body()))
err = c.SendStream(bytes.NewReader([]byte("Don't crash please")), len([]byte("Don't crash please")))
require.NoError(t, err)
- require.Equal(t, "Don't crash please", string(c.Response().Body()))
+ require.Equal(t, "Don't crash please", string(c.Context().Response.Body()))
err = c.SendStream(bufio.NewReader(bytes.NewReader([]byte("Hello bufio"))))
require.NoError(t, err)
- require.Equal(t, "Hello bufio", string(c.Response().Body()))
+ require.Equal(t, "Hello bufio", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_Set
@@ -3971,9 +3971,9 @@ func Test_Ctx_Set(t *testing.T) {
c.Set("X-2", "2")
c.Set("X-3", "3")
c.Set("X-3", "1337")
- require.Equal(t, "1", string(c.Response().Header.Peek("x-1")))
- require.Equal(t, "2", string(c.Response().Header.Peek("x-2")))
- require.Equal(t, "1337", string(c.Response().Header.Peek("x-3")))
+ require.Equal(t, "1", c.Res().Get("x-1"))
+ require.Equal(t, "2", c.Res().Get("x-2"))
+ require.Equal(t, "1337", c.Res().Get("x-3"))
}
// go test -run Test_Ctx_Set_Splitter
@@ -3983,11 +3983,11 @@ func Test_Ctx_Set_Splitter(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Set("Location", "foo\r\nSet-Cookie:%20SESSIONID=MaliciousValue\r\n")
- h := string(c.Response().Header.Peek("Location"))
+ h := c.Res().Get("Location")
require.NotContains(t, h, "\r\n")
c.Set("Location", "foo\nSet-Cookie:%20SESSIONID=MaliciousValue\n")
- h = string(c.Response().Header.Peek("Location"))
+ h = c.Res().Get("Location")
require.NotContains(t, h, "\n")
}
@@ -4011,11 +4011,11 @@ func Test_Ctx_Status(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Status(400)
- require.Equal(t, 400, c.Response().StatusCode())
+ require.Equal(t, 400, c.Context().Response.StatusCode())
err := c.Status(415).Send([]byte("Hello, World"))
require.NoError(t, err)
- require.Equal(t, 415, c.Response().StatusCode())
- require.Equal(t, "Hello, World", string(c.Response().Body()))
+ require.Equal(t, 415, c.Context().Response.StatusCode())
+ require.Equal(t, "Hello, World", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_Type
@@ -4025,16 +4025,16 @@ func Test_Ctx_Type(t *testing.T) {
c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Type(".json")
- require.Equal(t, "application/json", string(c.Response().Header.Peek("Content-Type")))
+ require.Equal(t, "application/json", c.Res().Get("Content-Type"))
c.Type("json", "utf-8")
- require.Equal(t, "application/json; charset=utf-8", string(c.Response().Header.Peek("Content-Type")))
+ require.Equal(t, "application/json; charset=utf-8", c.Res().Get("Content-Type"))
c.Type(".html")
- require.Equal(t, "text/html", string(c.Response().Header.Peek("Content-Type")))
+ require.Equal(t, "text/html", c.Res().Get("Content-Type"))
c.Type("html", "utf-8")
- require.Equal(t, "text/html; charset=utf-8", string(c.Response().Header.Peek("Content-Type")))
+ require.Equal(t, "text/html; charset=utf-8", c.Res().Get("Content-Type"))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Type -benchmem -count=4
@@ -4072,7 +4072,7 @@ func Test_Ctx_Vary(t *testing.T) {
c.Vary("Origin")
c.Vary("User-Agent")
c.Vary("Accept-Encoding", "Accept")
- require.Equal(t, "Origin, User-Agent, Accept-Encoding, Accept", string(c.Response().Header.Peek("Vary")))
+ require.Equal(t, "Origin, User-Agent, Accept-Encoding, Accept", c.Res().Get("Vary"))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Vary -benchmem -count=4
@@ -4097,7 +4097,7 @@ func Test_Ctx_Write(t *testing.T) {
require.NoError(t, err)
_, err = c.Write([]byte("World!"))
require.NoError(t, err)
- require.Equal(t, "Hello, World!", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!", string(c.Context().Response.Body()))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Write -benchmem -count=4
@@ -4125,7 +4125,7 @@ func Test_Ctx_Writef(t *testing.T) {
world := "World!"
_, err := c.Writef("Hello, %s", world)
require.NoError(t, err)
- require.Equal(t, "Hello, World!", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!", string(c.Context().Response.Body()))
}
// go test -v -run=^$ -bench=Benchmark_Ctx_Writef -benchmem -count=4
@@ -4154,7 +4154,7 @@ func Test_Ctx_WriteString(t *testing.T) {
require.NoError(t, err)
_, err = c.WriteString("World!")
require.NoError(t, err)
- require.Equal(t, "Hello, World!", string(c.Response().Body()))
+ require.Equal(t, "Hello, World!", string(c.Context().Response.Body()))
}
// go test -run Test_Ctx_XHR
@@ -4196,7 +4196,7 @@ func Benchmark_Ctx_SendString_B(b *testing.B) {
err = c.SendString(body)
}
require.NoError(b, err)
- require.Equal(b, []byte("Hello, world!"), c.Response().Body())
+ require.Equal(b, []byte("Hello, world!"), c.Context().Response.Body())
}
// go test -run Test_Ctx_Queries -v
@@ -4485,9 +4485,9 @@ func Test_Ctx_GetRespHeaders(t *testing.T) {
c.Set("test", "Hello, World 👋!")
c.Set("foo", "bar")
- c.Response().Header.Set("multi", "one")
- c.Response().Header.Add("multi", "two")
- c.Response().Header.Set(HeaderContentType, "application/json")
+ c.Context().Response.Header.Set("multi", "one")
+ c.Context().Response.Header.Add("multi", "two")
+ c.Context().Response.Header.Set(HeaderContentType, "application/json")
require.Equal(t, map[string][]string{
"Content-Type": {"application/json"},
@@ -4501,9 +4501,9 @@ func Benchmark_Ctx_GetRespHeaders(b *testing.B) {
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
- c.Response().Header.Set("test", "Hello, World 👋!")
- c.Response().Header.Set("foo", "bar")
- c.Response().Header.Set(HeaderContentType, "application/json")
+ c.Set("test", "Hello, World 👋!")
+ c.Set("foo", "bar")
+ c.Set(HeaderContentType, "application/json")
b.ReportAllocs()
b.ResetTimer()
diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go
index 0ace704e357..adec84e6298 100644
--- a/middleware/cache/cache.go
+++ b/middleware/cache/cache.go
@@ -135,14 +135,14 @@ func New(config ...Config) fiber.Handler {
e.body = manager.getRaw(key + "_body")
}
// Set response headers from cache
- c.Response().SetBodyRaw(e.body)
- c.Response().SetStatusCode(e.status)
- c.Response().Header.SetContentTypeBytes(e.ctype)
+ c.Context().Response.SetBodyRaw(e.body)
+ c.Context().Response.SetStatusCode(e.status)
+ c.Context().Response.Header.SetContentTypeBytes(e.ctype)
if len(e.cencoding) > 0 {
- c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding)
+ c.Context().Response.Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding)
}
for k, v := range e.headers {
- c.Response().Header.SetBytesV(k, v)
+ c.Context().Response.Header.SetBytesV(k, v)
}
// Set Cache-Control header if enabled
if cfg.CacheControl {
@@ -177,7 +177,7 @@ func New(config ...Config) fiber.Handler {
}
// Don't try to cache if body won't fit into cache
- bodySize := uint(len(c.Response().Body()))
+ bodySize := uint(len(c.Context().Response.Body()))
if cfg.MaxBytes > 0 && bodySize > cfg.MaxBytes {
c.Set(cfg.CacheHeader, cacheUnreachable)
return nil
@@ -193,16 +193,16 @@ func New(config ...Config) fiber.Handler {
}
// Cache response
- e.body = utils.CopyBytes(c.Response().Body())
- e.status = c.Response().StatusCode()
- e.ctype = utils.CopyBytes(c.Response().Header.ContentType())
- e.cencoding = utils.CopyBytes(c.Response().Header.Peek(fiber.HeaderContentEncoding))
+ e.body = utils.CopyBytes(c.Context().Response.Body())
+ e.status = c.Context().Response.StatusCode()
+ e.ctype = utils.CopyBytes(c.Context().Response.Header.ContentType())
+ e.cencoding = utils.CopyBytes(c.Context().Response.Header.Peek(fiber.HeaderContentEncoding))
// Store all response headers
// (more: https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1)
if cfg.StoreResponseHeaders {
e.headers = make(map[string][]byte)
- c.Response().Header.VisitAll(
+ c.Context().Response.Header.VisitAll(
func(key, value []byte) {
// create real copy
keyS := string(key)
diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go
index 8966ec7ac29..1c122c5060a 100644
--- a/middleware/cache/cache_test.go
+++ b/middleware/cache/cache_test.go
@@ -442,7 +442,7 @@ func Test_Cache_CustomNext(t *testing.T) {
app.Use(New(Config{
Next: func(c fiber.Ctx) bool {
- return c.Response().StatusCode() != fiber.StatusOK
+ return c.Context().Response.StatusCode() != fiber.StatusOK
},
CacheControl: true,
}))
@@ -509,7 +509,7 @@ func Test_CustomExpiration(t *testing.T) {
}}))
app.Get("/", func(c fiber.Ctx) error {
- c.Response().Header.Add("Cache-Time", "1")
+ c.Set("Cache-Time", "1")
return c.SendString(strconv.FormatInt(time.Now().UnixNano(), 10))
})
@@ -553,7 +553,7 @@ func Test_AdditionalE2EResponseHeaders(t *testing.T) {
}))
app.Get("/", func(c fiber.Ctx) error {
- c.Response().Header.Add("X-Foobar", "foobar")
+ c.Set("X-Foobar", "foobar")
return c.SendString("hi")
})
@@ -576,7 +576,7 @@ func Test_CacheHeader(t *testing.T) {
app.Use(New(Config{
Expiration: 10 * time.Second,
Next: func(c fiber.Ctx) bool {
- return c.Response().StatusCode() != fiber.StatusOK
+ return c.Context().Response.StatusCode() != fiber.StatusOK
},
}))
@@ -843,7 +843,7 @@ func Benchmark_Cache_AdditionalHeaders(b *testing.B) {
}))
app.Get("/demo", func(c fiber.Ctx) error {
- c.Response().Header.Add("X-Foobar", "foobar")
+ c.Set("X-Foobar", "foobar")
return c.SendStatus(418)
})
diff --git a/middleware/encryptcookie/encryptcookie.go b/middleware/encryptcookie/encryptcookie.go
index 6f905168cd7..ba14208e8b6 100644
--- a/middleware/encryptcookie/encryptcookie.go
+++ b/middleware/encryptcookie/encryptcookie.go
@@ -34,19 +34,19 @@ func New(config ...Config) fiber.Handler {
err := c.Next()
// Encrypt response cookies
- c.Response().Header.VisitAllCookie(func(key, _ []byte) {
+ c.Context().Response.Header.VisitAllCookie(func(key, _ []byte) {
keyString := string(key)
if !isDisabled(keyString, cfg.Except) {
cookieValue := fasthttp.Cookie{}
cookieValue.SetKeyBytes(key)
- if c.Response().Header.Cookie(&cookieValue) {
+ if c.Context().Response.Header.Cookie(&cookieValue) {
encryptedValue, err := cfg.Encryptor(string(cookieValue.Value()), cfg.Key)
if err != nil {
panic(err)
}
cookieValue.SetValue(encryptedValue)
- c.Response().Header.SetCookie(&cookieValue)
+ c.Context().Response.Header.SetCookie(&cookieValue)
}
}
})
diff --git a/middleware/etag/etag.go b/middleware/etag/etag.go
index 312e557887f..5b2d54acfa4 100644
--- a/middleware/etag/etag.go
+++ b/middleware/etag/etag.go
@@ -34,16 +34,16 @@ func New(config ...Config) fiber.Handler {
}
// Don't generate ETags for invalid responses
- if c.Response().StatusCode() != fiber.StatusOK {
+ if c.Context().Response.StatusCode() != fiber.StatusOK {
return nil
}
- body := c.Response().Body()
+ body := c.Context().Response.Body()
// Skips ETag if no response body is present
if len(body) == 0 {
return nil
}
// Skip ETag if header is already present
- if c.Response().Header.PeekBytes(normalizedHeaderETag) != nil {
+ if c.Context().Response.Header.PeekBytes(normalizedHeaderETag) != nil {
return nil
}
@@ -77,7 +77,7 @@ func New(config ...Config) fiber.Handler {
return c.SendStatus(fiber.StatusNotModified)
}
// W/1 != W/2 || W/1 != 2
- c.Response().Header.SetCanonical(normalizedHeaderETag, etag)
+ c.Context().Response.Header.SetCanonical(normalizedHeaderETag, etag)
return nil
}
@@ -89,7 +89,7 @@ func New(config ...Config) fiber.Handler {
return c.SendStatus(fiber.StatusNotModified)
}
// 1 != 2
- c.Response().Header.SetCanonical(normalizedHeaderETag, etag)
+ c.Context().Response.Header.SetCanonical(normalizedHeaderETag, etag)
return nil
}
diff --git a/middleware/filesystem/filesystem.go b/middleware/filesystem/filesystem.go
index 0a0393bec45..883cdb0ad17 100644
--- a/middleware/filesystem/filesystem.go
+++ b/middleware/filesystem/filesystem.go
@@ -230,14 +230,14 @@ func New(config ...Config) fiber.Handler {
if cfg.MaxAge > 0 {
c.Set(fiber.HeaderCacheControl, cacheControlStr)
}
- c.Response().SetBodyStream(file, contentLength)
+ c.Context().Response.SetBodyStream(file, contentLength)
return nil
}
if method == fiber.MethodHead {
c.Context().Request.ResetBody()
// Fasthttp should skipbody by default if HEAD?
- c.Response().SkipBody = true
- c.Response().Header.SetContentLength(contentLength)
+ c.Context().Response.SkipBody = true
+ c.Context().Response.Header.SetContentLength(contentLength)
if err := file.Close(); err != nil {
return fmt.Errorf("failed to close: %w", err)
}
@@ -305,14 +305,14 @@ func SendFile(c fiber.Ctx, filesystem fs.FS, path string) error {
method := c.Method()
if method == fiber.MethodGet {
- c.Response().SetBodyStream(file, contentLength)
+ c.Context().Response.SetBodyStream(file, contentLength)
return nil
}
if method == fiber.MethodHead {
c.Context().Request.ResetBody()
// Fasthttp should skipbody by default if HEAD?
- c.Response().SkipBody = true
- c.Response().Header.SetContentLength(contentLength)
+ c.Context().Response.SkipBody = true
+ c.Context().Response.Header.SetContentLength(contentLength)
if err := file.Close(); err != nil {
return fmt.Errorf("failed to close: %w", err)
}
diff --git a/middleware/idempotency/idempotency.go b/middleware/idempotency/idempotency.go
index 923ce5ce9a0..a24bde363ed 100644
--- a/middleware/idempotency/idempotency.go
+++ b/middleware/idempotency/idempotency.go
@@ -117,9 +117,9 @@ func New(config ...Config) fiber.Handler {
// Construct response
res := &response{
- StatusCode: c.Response().StatusCode(),
+ StatusCode: c.Context().Response.StatusCode(),
- Body: utils.CopyBytes(c.Response().Body()),
+ Body: utils.CopyBytes(c.Context().Response.Body()),
}
{
headers := make(map[string][]string)
diff --git a/middleware/limiter/limiter_fixed.go b/middleware/limiter/limiter_fixed.go
index 1e2a1aa0e55..1ec0dad8c59 100644
--- a/middleware/limiter/limiter_fixed.go
+++ b/middleware/limiter/limiter_fixed.go
@@ -83,8 +83,8 @@ func (FixedWindow) New(cfg Config) fiber.Handler {
err := c.Next()
// Check for SkipFailedRequests and SkipSuccessfulRequests
- if (cfg.SkipSuccessfulRequests && c.Response().StatusCode() < fiber.StatusBadRequest) ||
- (cfg.SkipFailedRequests && c.Response().StatusCode() >= fiber.StatusBadRequest) {
+ if (cfg.SkipSuccessfulRequests && c.Context().Response.StatusCode() < fiber.StatusBadRequest) ||
+ (cfg.SkipFailedRequests && c.Context().Response.StatusCode() >= fiber.StatusBadRequest) {
// Lock entry
mux.Lock()
e = manager.get(key)
diff --git a/middleware/limiter/limiter_sliding.go b/middleware/limiter/limiter_sliding.go
index a98593476ec..091fef6d644 100644
--- a/middleware/limiter/limiter_sliding.go
+++ b/middleware/limiter/limiter_sliding.go
@@ -114,8 +114,8 @@ func (SlidingWindow) New(cfg Config) fiber.Handler {
err := c.Next()
// Check for SkipFailedRequests and SkipSuccessfulRequests
- if (cfg.SkipSuccessfulRequests && c.Response().StatusCode() < fiber.StatusBadRequest) ||
- (cfg.SkipFailedRequests && c.Response().StatusCode() >= fiber.StatusBadRequest) {
+ if (cfg.SkipSuccessfulRequests && c.Context().Response.StatusCode() < fiber.StatusBadRequest) ||
+ (cfg.SkipFailedRequests && c.Context().Response.StatusCode() >= fiber.StatusBadRequest) {
// Lock entry
mux.Lock()
e = manager.get(key)
diff --git a/middleware/logger/default_logger.go b/middleware/logger/default_logger.go
index 5744fd1a5f7..40cabde1185 100644
--- a/middleware/logger/default_logger.go
+++ b/middleware/logger/default_logger.go
@@ -33,7 +33,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
buf.WriteString(
fmt.Sprintf("%s |%s %3d %s| %13v | %15s |%s %-7s %s| %-"+data.ErrPaddingStr+"s %s\n",
data.Timestamp.Load().(string), //nolint:forcetypeassert // Timestamp is always a string
- statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset,
+ statusColor(c.Context().Response.StatusCode(), colors), c.Context().Response.StatusCode(), colors.Reset,
data.Stop.Sub(data.Start),
c.IP(),
methodColor(c.Method(), colors), c.Method(), colors.Reset,
@@ -66,7 +66,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error {
buf.WriteString(" | ")
// Status Code with 3 fixed width, right aligned
- fixedWidth(strconv.Itoa(c.Response().StatusCode()), 3, true)
+ fixedWidth(strconv.Itoa(c.Context().Response.StatusCode()), 3, true)
buf.WriteString(" | ")
// Duration with 13 fixed width, right aligned
diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go
index 0aa517bcf56..fea5359d553 100644
--- a/middleware/logger/logger_test.go
+++ b/middleware/logger/logger_test.go
@@ -115,7 +115,7 @@ func Test_Logger_Done(t *testing.T) {
app.Use(New(Config{
Done: func(c fiber.Ctx, logString []byte) {
- if c.Response().StatusCode() == fiber.StatusOK {
+ if c.Context().Response.StatusCode() == fiber.StatusOK {
_, err := buf.Write(logString)
require.NoError(t, err)
}
diff --git a/middleware/logger/tags.go b/middleware/logger/tags.go
index cf1b49ff844..fc6b315f121 100644
--- a/middleware/logger/tags.go
+++ b/middleware/logger/tags.go
@@ -90,16 +90,16 @@ func createTagMap(cfg *Config) map[string]LogFunc {
return appendInt(output, len(c.Req().Body()))
},
TagBytesSent: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) {
- if c.Response().Header.ContentLength() < 0 {
+ if c.Context().Response.Header.ContentLength() < 0 {
return appendInt(output, 0)
}
- return appendInt(output, len(c.Response().Body()))
+ return appendInt(output, len(c.Context().Response.Body()))
},
TagRoute: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) {
return output.WriteString(c.Route().Path)
},
TagResBody: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) {
- return output.Write(c.Response().Body())
+ return output.Write(c.Context().Response.Body())
},
TagReqHeaders: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) {
out := make(map[string][]string, 0)
@@ -184,9 +184,9 @@ func createTagMap(cfg *Config) map[string]LogFunc {
TagStatus: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) {
if cfg.enableColors {
colors := c.App().Config().ColorScheme
- return output.WriteString(fmt.Sprintf("%s%3d%s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset))
+ return output.WriteString(fmt.Sprintf("%s%3d%s", statusColor(c.Context().Response.StatusCode(), colors), c.Context().Response.StatusCode(), colors.Reset))
}
- return appendInt(output, c.Response().StatusCode())
+ return appendInt(output, c.Context().Response.StatusCode())
},
TagMethod: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) {
if cfg.enableColors {
diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go
index 746ee7ab8d4..2145f908a6e 100644
--- a/middleware/proxy/proxy.go
+++ b/middleware/proxy/proxy.go
@@ -65,7 +65,7 @@ func Balancer(config Config) fiber.Handler {
// Set request and response
req := &c.Context().Request
- res := c.Response()
+ res := &c.Context().Response
// Don't proxy "Connection" header
req.Header.Del(fiber.HeaderConnection)
@@ -173,7 +173,7 @@ func doAction(
}
req := &c.Context().Request
- res := c.Response()
+ res := &c.Context().Response
originalURL := utils.CopyString(c.OriginalURL())
defer req.SetRequestURI(originalURL)
diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go
index 7f10273d57f..78e43553d71 100644
--- a/middleware/proxy/proxy_test.go
+++ b/middleware/proxy/proxy_test.go
@@ -343,7 +343,7 @@ func Test_Proxy_Modify_Response(t *testing.T) {
app.Use(Balancer(Config{
Servers: []string{addr},
ModifyResponse: func(c fiber.Ctx) error {
- c.Response().SetStatusCode(fiber.StatusOK)
+ c.Res().Status(fiber.StatusOK)
return c.SendString("modified response")
},
}))
@@ -645,7 +645,7 @@ func Test_Proxy_Do_HTTP_Prefix_URL(t *testing.T) {
if err := Do(c, url); err != nil {
return err
}
- c.Response().Header.Del(fiber.HeaderServer)
+ c.Context().Response.Header.Del(fiber.HeaderServer)
return nil
})
diff --git a/middleware/session/session.go b/middleware/session/session.go
index 259d062c3e5..921d2816f19 100644
--- a/middleware/session/session.go
+++ b/middleware/session/session.go
@@ -216,7 +216,7 @@ func (s *Session) SetExpiry(exp time.Duration) {
func (s *Session) setSession() {
if s.config.source == SourceHeader {
s.ctx.Context().Request.Header.SetBytesV(s.config.sessionName, []byte(s.id))
- s.ctx.Response().Header.SetBytesV(s.config.sessionName, []byte(s.id))
+ s.ctx.Context().Response.Header.SetBytesV(s.config.sessionName, []byte(s.id))
} else {
fcookie := fasthttp.AcquireCookie()
fcookie.SetKey(s.config.sessionName)
@@ -240,7 +240,7 @@ func (s *Session) setSession() {
default:
fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
}
- s.ctx.Response().Header.SetCookie(fcookie)
+ s.ctx.Context().Response.Header.SetCookie(fcookie)
fasthttp.ReleaseCookie(fcookie)
}
}
@@ -248,10 +248,10 @@ func (s *Session) setSession() {
func (s *Session) delSession() {
if s.config.source == SourceHeader {
s.ctx.Context().Request.Header.Del(s.config.sessionName)
- s.ctx.Response().Header.Del(s.config.sessionName)
+ s.ctx.Context().Response.Header.Del(s.config.sessionName)
} else {
s.ctx.Context().Request.Header.DelCookie(s.config.sessionName)
- s.ctx.Response().Header.DelCookie(s.config.sessionName)
+ s.ctx.Context().Response.Header.DelCookie(s.config.sessionName)
fcookie := fasthttp.AcquireCookie()
fcookie.SetKey(s.config.sessionName)
@@ -271,7 +271,7 @@ func (s *Session) delSession() {
fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
}
- s.ctx.Response().Header.SetCookie(fcookie)
+ s.ctx.Context().Response.Header.SetCookie(fcookie)
fasthttp.ReleaseCookie(fcookie)
}
}
diff --git a/middleware/session/session_test.go b/middleware/session/session_test.go
index b3172262896..30ac870015c 100644
--- a/middleware/session/session_test.go
+++ b/middleware/session/session_test.go
@@ -337,8 +337,8 @@ func Test_Session_Save(t *testing.T) {
// save session
err = sess.Save()
require.NoError(t, err)
- require.Equal(t, store.getSessionID(ctx), string(ctx.Response().Header.Peek(store.sessionName)))
- require.Equal(t, store.getSessionID(ctx), ctx.Get(store.sessionName))
+ require.Equal(t, store.getSessionID(ctx), ctx.Res().Get(store.sessionName))
+ require.Equal(t, store.getSessionID(ctx), ctx.Req().Get(store.sessionName))
})
}
@@ -434,8 +434,8 @@ func Test_Session_Destroy(t *testing.T) {
err = sess.Destroy()
require.NoError(t, err)
- require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName)))
- require.Equal(t, "", ctx.Get(store.sessionName))
+ require.Equal(t, "", ctx.Res().Get(store.sessionName))
+ require.Equal(t, "", ctx.Req().Get(store.sessionName))
})
}
@@ -468,7 +468,7 @@ func Test_Session_Cookie(t *testing.T) {
require.NoError(t, sess.Save())
// cookie should be set on Save ( even if empty data )
- require.Len(t, ctx.Response().Header.PeekCookie(store.sessionName), 84)
+ require.Len(t, ctx.Context().Response.Header.PeekCookie(store.sessionName), 84)
}
// go test -run Test_Session_Cookie_In_Response
@@ -585,8 +585,8 @@ func Test_Session_Reset(t *testing.T) {
require.NoError(t, err)
// Check that the session id is not in the header or cookie anymore
- require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName)))
- require.Equal(t, "", ctx.Get(store.sessionName))
+ require.Equal(t, "", ctx.Res().Get(store.sessionName))
+ require.Equal(t, "", ctx.Req().Get(store.sessionName))
})
}
diff --git a/middleware/session/store.go b/middleware/session/store.go
index e480164efb7..74b0baade42 100644
--- a/middleware/session/store.go
+++ b/middleware/session/store.go
@@ -123,7 +123,7 @@ func (s *Store) getSessionID(c fiber.Ctx) string {
func (s *Store) responseCookies(c fiber.Ctx) (string, error) {
// Get key from response cookie
- cookieValue := c.Response().Header.PeekCookie(s.sessionName)
+ cookieValue := c.Context().Response.Header.PeekCookie(s.sessionName)
if len(cookieValue) == 0 {
return "", nil
}
diff --git a/redirect.go b/redirect.go
index 71e71cca588..666770b79da 100644
--- a/redirect.go
+++ b/redirect.go
@@ -37,8 +37,9 @@ type Redirect struct {
c *DefaultCtx // Embed ctx
status int // Status code of redirection. Default: StatusFound
- messages []string // Flash messages
- oldInput map[string]string // Old input data
+ messages []string // Flash messages
+ redirectionMessages []string // Messages of the previous redirect
+ oldInput map[string]string // Old input data
}
// A config to use with Redirect().Route()
@@ -71,6 +72,7 @@ func ReleaseRedirect(r *Redirect) {
func (r *Redirect) release() {
r.status = 302
r.messages = r.messages[:0]
+ r.redirectionMessages = r.redirectionMessages[:0]
// reset map
for k := range r.oldInput {
delete(r.oldInput, k)
@@ -119,7 +121,7 @@ func (r *Redirect) WithInput() *Redirect {
// Get flash messages.
func (r *Redirect) Messages() map[string]string {
- msgs := r.c.redirectionMessages
+ msgs := r.redirectionMessages
flashMessages := make(map[string]string, len(msgs))
for _, msg := range msgs {
@@ -135,7 +137,7 @@ func (r *Redirect) Messages() map[string]string {
// Get flash message by key.
func (r *Redirect) Message(key string) string {
- msgs := r.c.redirectionMessages
+ msgs := r.redirectionMessages
for _, msg := range msgs {
k, v := parseMessage(msg)
@@ -149,7 +151,7 @@ func (r *Redirect) Message(key string) string {
// Get old input data.
func (r *Redirect) OldInputs() map[string]string {
- msgs := r.c.redirectionMessages
+ msgs := r.redirectionMessages
oldInputs := make(map[string]string, len(msgs))
for _, msg := range msgs {
@@ -165,7 +167,7 @@ func (r *Redirect) OldInputs() map[string]string {
// Get old input data by key.
func (r *Redirect) OldInput(key string) string {
- msgs := r.c.redirectionMessages
+ msgs := r.redirectionMessages
for _, msg := range msgs {
k, v := parseMessage(msg)
@@ -179,7 +181,7 @@ func (r *Redirect) OldInput(key string) string {
// Redirect to the URL derived from the specified path, with specified status.
func (r *Redirect) To(location string) error {
- r.c.setCanonical(HeaderLocation, location)
+ r.c.res.setCanonical(HeaderLocation, location)
r.c.Status(r.status)
return nil
@@ -279,10 +281,10 @@ func (r *Redirect) setFlash() {
for {
commaPos = findNextNonEscapedCharsetPosition(cookieValue, []byte(CookieDataSeparator))
if commaPos == -1 {
- r.c.redirectionMessages = append(r.c.redirectionMessages, strings.Trim(cookieValue, " "))
+ r.redirectionMessages = append(r.redirectionMessages, strings.Trim(cookieValue, " "))
break
}
- r.c.redirectionMessages = append(r.c.redirectionMessages, strings.Trim(cookieValue[:commaPos], " "))
+ r.redirectionMessages = append(r.redirectionMessages, strings.Trim(cookieValue[:commaPos], " "))
cookieValue = cookieValue[commaPos+1:]
}
diff --git a/redirect_test.go b/redirect_test.go
index c69e0548d71..e95cd6ba64f 100644
--- a/redirect_test.go
+++ b/redirect_test.go
@@ -25,13 +25,13 @@ func Test_Redirect_To(t *testing.T) {
err := c.Redirect().To("http://default.com")
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
- require.Equal(t, "http://default.com", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, 302, c.Context().Response.StatusCode())
+ require.Equal(t, "http://default.com", c.Res().Get(HeaderLocation))
err = c.Redirect().Status(301).To("http://example.com")
require.NoError(t, err)
- require.Equal(t, 301, c.Response().StatusCode())
- require.Equal(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, 301, c.Context().Response.StatusCode())
+ require.Equal(t, "http://example.com", c.Res().Get(HeaderLocation))
}
// go test -run Test_Redirect_Route_WithParams
@@ -49,8 +49,8 @@ func Test_Redirect_Route_WithParams(t *testing.T) {
},
})
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
- require.Equal(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, 302, c.Context().Response.StatusCode())
+ require.Equal(t, "/user/fiber", c.Res().Get(HeaderLocation))
}
// go test -run Test_Redirect_Route_WithParams_WithQueries
@@ -69,9 +69,9 @@ func Test_Redirect_Route_WithParams_WithQueries(t *testing.T) {
Queries: map[string]string{"data[0][name]": "john", "data[0][age]": "10", "test": "doe"},
})
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
+ require.Equal(t, 302, c.Context().Response.StatusCode())
// analysis of query parameters with url parsing, since a map pass is always randomly ordered
- location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation)))
+ location, err := url.Parse(c.Res().Get(HeaderLocation))
require.NoError(t, err, "url.Parse(location)")
require.Equal(t, "/user/fiber", location.Path)
require.Equal(t, url.Values{"data[0][name]": []string{"john"}, "data[0][age]": []string{"10"}, "test": []string{"doe"}}, location.Query())
@@ -92,8 +92,8 @@ func Test_Redirect_Route_WithOptionalParams(t *testing.T) {
},
})
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
- require.Equal(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, 302, c.Context().Response.StatusCode())
+ require.Equal(t, "/user/fiber", c.Res().Get(HeaderLocation))
}
// go test -run Test_Redirect_Route_WithOptionalParamsWithoutValue
@@ -107,8 +107,8 @@ func Test_Redirect_Route_WithOptionalParamsWithoutValue(t *testing.T) {
err := c.Redirect().Route("user")
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
- require.Equal(t, "/user/", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, 302, c.Context().Response.StatusCode())
+ require.Equal(t, "/user/", c.Res().Get(HeaderLocation))
}
// go test -run Test_Redirect_Route_WithGreedyParameters
@@ -126,8 +126,8 @@ func Test_Redirect_Route_WithGreedyParameters(t *testing.T) {
},
})
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
- require.Equal(t, "/user/test/routes", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, 302, c.Context().Response.StatusCode())
+ require.Equal(t, "/user/test/routes", c.Res().Get(HeaderLocation))
}
// go test -run Test_Redirect_Back
@@ -141,11 +141,11 @@ func Test_Redirect_Back(t *testing.T) {
err := c.Redirect().Back("/")
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
- require.Equal(t, "/", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, 302, c.Context().Response.StatusCode())
+ require.Equal(t, "/", c.Res().Get(HeaderLocation))
err = c.Redirect().Back()
- require.Equal(t, 500, c.Response().StatusCode())
+ require.Equal(t, 500, c.Context().Response.StatusCode())
require.ErrorAs(t, err, &ErrRedirectBackNoFallback)
}
@@ -164,9 +164,9 @@ func Test_Redirect_Back_WithReferer(t *testing.T) {
c.Context().Request.Header.Set(HeaderReferer, "/back")
err := c.Redirect().Back("/")
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
+ require.Equal(t, 302, c.Context().Response.StatusCode())
require.Equal(t, "/back", c.Get(HeaderReferer))
- require.Equal(t, "/back", string(c.Response().Header.Peek(HeaderLocation)))
+ require.Equal(t, "/back", c.Res().Get(HeaderLocation))
}
// go test -run Test_Redirect_Route_WithFlashMessages
@@ -182,7 +182,7 @@ func Test_Redirect_Route_WithFlashMessages(t *testing.T) {
err := c.Redirect().With("success", "1").With("message", "test").Route("user")
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
+ require.Equal(t, 302, c.Context().Response.StatusCode())
require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation)))
equal := c.GetRespHeader(HeaderSetCookie) == "fiber_flash=success:1,message:test; path=/; SameSite=Lax" || c.GetRespHeader(HeaderSetCookie) == "fiber_flash=message:test,success:1; path=/; SameSite=Lax"
@@ -207,7 +207,7 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) {
c.Context().URI().SetQueryString("id=1&name=tom")
err := c.Redirect().With("success", "1").With("message", "test").WithInput().Route("user")
require.NoError(t, err)
- require.Equal(t, 302, c.Response().StatusCode())
+ require.Equal(t, 302, c.Context().Response.StatusCode())
require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation)))
require.Contains(t, c.GetRespHeader(HeaderSetCookie), "fiber_flash=")
@@ -356,7 +356,7 @@ func Benchmark_Redirect_Route(b *testing.B) {
}
require.NoError(b, err)
- require.Equal(b, 302, c.Response().StatusCode())
+ require.Equal(b, 302, c.Context().Response.StatusCode())
require.Equal(b, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation)))
}
@@ -384,7 +384,7 @@ func Benchmark_Redirect_Route_WithQueries(b *testing.B) {
}
require.NoError(b, err)
- require.Equal(b, 302, c.Response().StatusCode())
+ require.Equal(b, 302, c.Context().Response.StatusCode())
// analysis of query parameters with url parsing, since a map pass is always randomly ordered
location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation)))
require.NoError(b, err, "url.Parse(location)")
@@ -411,7 +411,7 @@ func Benchmark_Redirect_Route_WithFlashMessages(b *testing.B) {
}
require.NoError(b, err)
- require.Equal(b, 302, c.Response().StatusCode())
+ require.Equal(b, 302, c.Context().Response.StatusCode())
require.Equal(b, "/user", string(c.Response().Header.Peek(HeaderLocation)))
equal := c.GetRespHeader(HeaderSetCookie) == "fiber_flash=success:1,message:test; path=/; SameSite=Lax" || c.GetRespHeader(HeaderSetCookie) == "fiber_flash=message:test,success:1; path=/; SameSite=Lax"
diff --git a/request.go b/request.go
index f719425a2aa..96648a9a213 100644
--- a/request.go
+++ b/request.go
@@ -495,7 +495,7 @@ func (r *Request) Fresh() bool {
// if-none-match
if noneMatch != "" && noneMatch != "*" {
- etag := r.app.getString(r.ctx.Response().Header.Peek(HeaderETag))
+ etag := r.ctx.Res().Get(HeaderETag)
if etag == "" {
return false
}
@@ -504,7 +504,7 @@ func (r *Request) Fresh() bool {
}
if modifiedSince != "" {
- lastModified := r.app.getString(r.ctx.Response().Header.Peek(HeaderLastModified))
+ lastModified := r.ctx.Res().Get(HeaderLastModified)
if lastModified != "" {
lastModifiedTime, err := http.ParseTime(lastModified)
if err != nil {
@@ -552,7 +552,7 @@ func (r *Request) Subdomains(offset ...int) []string {
// XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest,
// indicating that the request was issued by a client library (such as jQuery).
func (r *Request) XHR() bool {
- return utils.EqualFold(r.Get(HeaderXRequestedWith), "xmlhttprequest")
+ return utils.EqualFold(r.fasthttp.Header.Peek(HeaderXRequestedWith), []byte("xmlhttprequest"))
}
// Params is used to get the route parameters.
diff --git a/response.go b/response.go
new file mode 100644
index 00000000000..1e2b4f7e9a1
--- /dev/null
+++ b/response.go
@@ -0,0 +1,528 @@
+package fiber
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+ "sync"
+ "text/template"
+ "time"
+
+ "github.com/gofiber/utils/v2"
+ "github.com/valyala/bytebufferpool"
+ "github.com/valyala/fasthttp"
+)
+
+var (
+ sendFileOnce sync.Once
+ sendFileFS *fasthttp.FS
+ sendFileHandler fasthttp.RequestHandler
+)
+
+type Response struct {
+ app *App
+ ctx *DefaultCtx
+ fasthttp *fasthttp.Response
+ viewBindMap sync.Map // Default view map to bind template engine
+}
+
+// ResFmt associates a Content Type to a fiber.Handler for c.Format
+type ResFmt struct {
+ MediaType string
+ Handler func(Ctx) error
+}
+
+func (r *Response) App() *App {
+ return r.app
+}
+
+// Append the specified value to the HTTP response header field.
+// If the header is not already set, it creates the header with the specified value.
+func (r *Response) Append(field string, values ...string) {
+ if len(values) == 0 {
+ return
+ }
+ h := r.app.getString(r.fasthttp.Header.Peek(field))
+ originalH := h
+ for _, value := range values {
+ if len(h) == 0 {
+ h = value
+ } else if h != value && !strings.HasPrefix(h, value+",") && !strings.HasSuffix(h, " "+value) &&
+ !strings.Contains(h, " "+value+",") {
+ h += ", " + value
+ }
+ }
+ if originalH != h {
+ r.Set(field, h)
+ }
+}
+
+func (r *Response) Attachment(filename ...string) {
+ if len(filename) > 0 {
+ fname := filepath.Base(filename[0])
+ r.Type(filepath.Ext(fname))
+
+ r.setCanonical(HeaderContentDisposition, `attachment; filename="`+r.app.quoteString(fname)+`"`)
+ return
+ }
+ r.setCanonical(HeaderContentDisposition, "attachment")
+}
+
+// Bind adds vars to default view var map binding to template engine.
+// Variables are read by the Render method and may be overwritten.
+func (r *Response) BindVars(vars Map) error {
+ // init viewBindMap - lazy map
+ for k, v := range vars {
+ r.viewBindMap.Store(k, v)
+ }
+ return nil
+}
+
+// Cookie sets a cookie by passing a cookie struct.
+func (r *Response) Cookie(cookie *Cookie) {
+ fcookie := fasthttp.AcquireCookie()
+ fcookie.SetKey(cookie.Name)
+ fcookie.SetValue(cookie.Value)
+ fcookie.SetPath(cookie.Path)
+ fcookie.SetDomain(cookie.Domain)
+ // only set max age and expiry when SessionOnly is false
+ // i.e. cookie supposed to last beyond browser session
+ // refer: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie
+ if !cookie.SessionOnly {
+ fcookie.SetMaxAge(cookie.MaxAge)
+ fcookie.SetExpire(cookie.Expires)
+ }
+ fcookie.SetSecure(cookie.Secure)
+ fcookie.SetHTTPOnly(cookie.HTTPOnly)
+
+ switch utils.ToLower(cookie.SameSite) {
+ case CookieSameSiteStrictMode:
+ fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
+ case CookieSameSiteNoneMode:
+ fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode)
+ case CookieSameSiteDisabled:
+ fcookie.SetSameSite(fasthttp.CookieSameSiteDisabled)
+ default:
+ fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode)
+ }
+
+ r.fasthttp.Header.SetCookie(fcookie)
+ fasthttp.ReleaseCookie(fcookie)
+}
+
+// ClearCookie expires a specific cookie by key on the client side.
+// If no key is provided it expires all cookies that came with the request.
+func (r *Response) ClearCookie(key ...string) {
+ if len(key) > 0 {
+ for i := range key {
+ r.fasthttp.Header.DelClientCookie(key[i])
+ }
+ return
+ }
+ r.ctx.fasthttp.Request.Header.VisitAllCookie(func(k, _ []byte) {
+ r.fasthttp.Header.DelClientCookieBytes(k)
+ })
+}
+
+// Download transfers the file from path as an attachment.
+// Typically, browsers will prompt the user for download.
+// By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog).
+// Override this default with the filename parameter.
+func (r *Response) Download(file string, filename ...string) error {
+ var fname string
+ if len(filename) > 0 {
+ fname = filename[0]
+ } else {
+ fname = filepath.Base(file)
+ }
+ r.setCanonical(HeaderContentDisposition, `attachment; filename="`+r.app.quoteString(fname)+`"`)
+ return r.SendFile(file)
+}
+
+// Format performs content-negotiation on the Accept HTTP header.
+// It uses Accepts to select a proper format and calls the matching
+// user-provided handler function.
+// If no accepted format is found, and a format with MediaType "default" is given,
+// that default handler is called. If no format is found and no default is given,
+// StatusNotAcceptable is sent.
+func (r *Response) Format(handlers ...ResFmt) error {
+ if len(handlers) == 0 {
+ return ErrNoHandlers
+ }
+
+ r.Vary(HeaderAccept)
+
+ if r.ctx.Get(HeaderAccept) == "" {
+ r.fasthttp.Header.SetContentType(handlers[0].MediaType)
+ return handlers[0].Handler(r.ctx)
+ }
+
+ // Using an int literal as the slice capacity allows for the slice to be
+ // allocated on the stack. The number was chosen arbitrarily as an
+ // approximation of the maximum number of content types a user might handle.
+ // If the user goes over, it just causes allocations, so it's not a problem.
+ types := make([]string, 0, 8)
+ var defaultHandler Handler
+ for _, h := range handlers {
+ if h.MediaType == "default" {
+ defaultHandler = h.Handler
+ continue
+ }
+ types = append(types, h.MediaType)
+ }
+ accept := r.ctx.Accepts(types...)
+
+ if accept == "" {
+ if defaultHandler == nil {
+ return r.SendStatus(StatusNotAcceptable)
+ }
+ return defaultHandler(r.ctx)
+ }
+
+ for _, h := range handlers {
+ if h.MediaType == accept {
+ r.fasthttp.Header.SetContentType(h.MediaType)
+ return h.Handler(r.ctx)
+ }
+ }
+
+ return fmt.Errorf("%w: format: an Accept was found but no handler was called", errUnreachable)
+}
+
+// Get returns the HTTP response header specified by field.
+// Field names are case-insensitive.
+// Returned value is only valid within the handler. Do not store any references.
+// Make copies or use the Immutable setting instead.
+func (r *Response) Get(key string, defaultValue ...string) string {
+ return defaultString(r.app.getString(r.fasthttp.Header.Peek(key)), defaultValue)
+}
+
+// JSON converts any interface or string to JSON.
+// Array and slice values encode as JSON arrays,
+// except that []byte encodes as a base64-encoded string,
+// and a nil slice encodes as the null JSON value.
+// If the ctype parameter is given, this method will set the
+// Content-Type header equal to ctype. If ctype is not given,
+// The Content-Type header will be set to application/json.
+func (r *Response) JSON(data any, ctype ...string) error {
+ raw, err := r.app.config.JSONEncoder(data)
+ if err != nil {
+ return err
+ }
+ r.fasthttp.SetBodyRaw(raw)
+ if len(ctype) > 0 {
+ r.fasthttp.Header.SetContentType(ctype[0])
+ } else {
+ r.fasthttp.Header.SetContentType(MIMEApplicationJSON)
+ }
+ return nil
+}
+
+// JSONP sends a JSON response with JSONP support.
+// This method is identical to JSON, except that it opts-in to JSONP callback support.
+// By default, the callback name is simply callback.
+func (r *Response) JSONP(data any, callback ...string) error {
+ raw, err := r.app.config.JSONEncoder(data)
+ if err != nil {
+ return err
+ }
+
+ var result, cb string
+
+ if len(callback) > 0 {
+ cb = callback[0]
+ } else {
+ cb = "callback"
+ }
+
+ result = cb + "(" + r.app.getString(raw) + ");"
+
+ r.setCanonical(HeaderXContentTypeOptions, "nosniff")
+ r.fasthttp.Header.SetContentType(MIMETextJavaScriptCharsetUTF8)
+ return r.SendString(result)
+}
+
+// Links joins the links followed by the property to populate the response's Link HTTP header field.
+func (r *Response) Links(link ...string) {
+ if len(link) == 0 {
+ return
+ }
+ bb := bytebufferpool.Get()
+ for i := range link {
+ if i%2 == 0 {
+ bb.WriteByte('<')
+ bb.WriteString(link[i])
+ bb.WriteByte('>')
+ } else {
+ bb.WriteString(`; rel="` + link[i] + `",`)
+ }
+ }
+ r.setCanonical(HeaderLink, strings.TrimRight(r.app.getString(bb.Bytes()), ","))
+ bytebufferpool.Put(bb)
+}
+
+// Location sets the response Location HTTP header to the specified path parameter.
+func (r *Response) Location(path string) {
+ r.setCanonical(HeaderLocation, path)
+}
+
+// Render a template with data and sends a text/html response.
+// We support the following engines: https://github.com/gofiber/template
+func (r *Response) Render(name string, bind Map, layouts ...string) error {
+ // Get new buffer from pool
+ buf := bytebufferpool.Get()
+ defer bytebufferpool.Put(buf)
+
+ // Initialize empty bind map if bind is nil
+ if bind == nil {
+ bind = make(Map)
+ }
+
+ // Pass-locals-to-views, bind, appListKeys
+ r.renderExtensions(bind)
+
+ var rendered bool
+ for i := len(r.app.mountFields.appListKeys) - 1; i >= 0; i-- {
+ prefix := r.app.mountFields.appListKeys[i]
+ app := r.app.mountFields.appList[prefix]
+ if prefix == "" || strings.Contains(r.ctx.OriginalURL(), prefix) {
+ if len(layouts) == 0 && app.config.ViewsLayout != "" {
+ layouts = []string{
+ app.config.ViewsLayout,
+ }
+ }
+
+ // Render template from Views
+ if app.config.Views != nil {
+ if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil {
+ return fmt.Errorf("failed to render: %w", err)
+ }
+
+ rendered = true
+ break
+ }
+ }
+ }
+
+ if !rendered {
+ // Render raw template using 'name' as filepath if no engine is set
+ var tmpl *template.Template
+ if _, err := readContent(buf, name); err != nil {
+ return err
+ }
+ // Parse template
+ tmpl, err := template.New("").Parse(r.app.getString(buf.Bytes()))
+ if err != nil {
+ return fmt.Errorf("failed to parse: %w", err)
+ }
+ buf.Reset()
+ // Render template
+ if err := tmpl.Execute(buf, bind); err != nil {
+ return fmt.Errorf("failed to execute: %w", err)
+ }
+ }
+
+ // Set Content-Type to text/html
+ r.fasthttp.Header.SetContentType(MIMETextHTMLCharsetUTF8)
+ // Set rendered template to body
+ r.fasthttp.SetBody(buf.Bytes())
+
+ return nil
+}
+
+func (r *Response) renderExtensions(bind any) {
+ if bindMap, ok := bind.(Map); ok {
+ // Bind view map
+ r.viewBindMap.Range(func(key, value any) bool {
+ keyValue, ok := key.(string)
+ if !ok {
+ return true
+ }
+ if _, ok := bindMap[keyValue]; !ok {
+ bindMap[keyValue] = value
+ }
+ return true
+ })
+
+ // Check if the PassLocalsToViews option is enabled (by default it is disabled)
+ if r.app.config.PassLocalsToViews {
+ // Loop through each local and set it in the map
+ r.ctx.Context().VisitUserValues(func(key []byte, val any) {
+ // check if bindMap doesn't contain the key
+ if _, ok := bindMap[r.app.getString(key)]; !ok {
+ // Set the key and value in the bindMap
+ bindMap[r.app.getString(key)] = val
+ }
+ })
+ }
+ }
+
+ if len(r.app.mountFields.appListKeys) == 0 {
+ r.app.generateAppListKeys()
+ }
+}
+
+// Send sets the HTTP response body without copying it.
+// From this point onward the body argument must not be changed.
+func (r *Response) Send(body []byte) error {
+ // Write response body
+ r.fasthttp.SetBodyRaw(body)
+ return nil
+}
+
+// SendFile transfers the file from the given path.
+// The file is not compressed by default, enable this by passing a 'true' argument
+// Sets the Content-Type response HTTP header field based on the filenames extension.
+func (r *Response) SendFile(file string, compress ...bool) error {
+ // Save the filename, we will need it in the error message if the file isn't found
+ filename := file
+
+ // https://github.com/valyala/fasthttp/blob/c7576cc10cabfc9c993317a2d3f8355497bea156/fs.go#L129-L134
+ sendFileOnce.Do(func() {
+ const cacheDuration = 10 * time.Second
+ sendFileFS = &fasthttp.FS{
+ Root: "",
+ AllowEmptyRoot: true,
+ GenerateIndexPages: false,
+ AcceptByteRange: true,
+ Compress: true,
+ CompressedFileSuffix: r.app.config.CompressedFileSuffix,
+ CacheDuration: cacheDuration,
+ IndexNames: []string{"index.html"},
+ PathNotFound: func(ctx *fasthttp.RequestCtx) {
+ ctx.Response.SetStatusCode(StatusNotFound)
+ },
+ }
+ sendFileHandler = sendFileFS.NewRequestHandler()
+ })
+
+ // Keep original path for mutable params
+ r.ctx.req.pathOriginal = utils.CopyString(r.ctx.req.pathOriginal)
+ // Disable compression
+ if len(compress) == 0 || !compress[0] {
+ // https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L55
+ r.ctx.Context().Request.Header.Del(HeaderAcceptEncoding)
+ }
+ // copy of https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L103-L121 with small adjustments
+ if len(file) == 0 || !filepath.IsAbs(file) {
+ // extend relative path to absolute path
+ hasTrailingSlash := len(file) > 0 && (file[len(file)-1] == '/' || file[len(file)-1] == '\\')
+
+ var err error
+ file = filepath.FromSlash(file)
+ if file, err = filepath.Abs(file); err != nil {
+ return fmt.Errorf("failed to determine abs file path: %w", err)
+ }
+ if hasTrailingSlash {
+ file += "/"
+ }
+ }
+ // convert the path to forward slashes regardless the OS in order to set the URI properly
+ // the handler will convert back to OS path separator before opening the file
+ file = filepath.ToSlash(file)
+
+ // Restore the original requested URL
+ originalURL := utils.CopyString(r.ctx.OriginalURL())
+ defer r.ctx.fasthttp.Request.SetRequestURI(originalURL)
+ // Set new URI for fileHandler
+ r.ctx.fasthttp.Request.SetRequestURI(file)
+ // Save status code
+ status := r.fasthttp.StatusCode()
+ // Serve file
+ sendFileHandler(r.ctx.fasthttp)
+ // Get the status code which is set by fasthttp
+ fsStatus := r.fasthttp.StatusCode()
+ // Set the status code set by the user if it is different from the fasthttp status code and 200
+ if status != fsStatus && status != StatusOK {
+ r.Status(status)
+ }
+ // Check for error
+ if status != StatusNotFound && fsStatus == StatusNotFound {
+ return NewError(StatusNotFound, fmt.Sprintf("sendfile: file %s not found", filename))
+ }
+ return nil
+}
+
+// SendStatus sets the HTTP status code and if the response body is empty,
+// it sets the correct status message in the body.
+func (r *Response) SendStatus(status int) error {
+ r.Status(status)
+
+ // Only set status body when there is no response body
+ if len(r.fasthttp.Body()) == 0 {
+ return r.SendString(utils.StatusMessage(status))
+ }
+
+ return nil
+}
+
+// SendString sets the HTTP response body for string types.
+// This means no type assertion, recommended for faster performance
+func (r *Response) SendString(body string) error {
+ r.fasthttp.SetBodyString(body)
+ return nil
+}
+
+// Set sets the response's HTTP header field to the specified key, value.
+func (r *Response) Set(key, val string) {
+ r.fasthttp.Header.Set(key, val)
+}
+
+// setCanonical is the same as set, but it assumes key is already in canonical form,
+// making it more efficient.
+func (r *Response) setCanonical(key, val string) {
+ r.fasthttp.Header.SetCanonical(utils.UnsafeBytes(key), utils.UnsafeBytes(val))
+}
+
+// Status sets the HTTP status for the response.
+// This method is chainable.
+func (r *Response) Status(status int) *Response {
+ r.fasthttp.SetStatusCode(status)
+ return r
+}
+
+// Type sets the Content-Type HTTP header to the MIME type specified by the file extension.
+func (r *Response) Type(extension string, charset ...string) *Response {
+ if len(charset) > 0 {
+ r.fasthttp.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0])
+ } else {
+ r.fasthttp.Header.SetContentType(utils.GetMIME(extension))
+ }
+ return r
+}
+
+// Vary adds the given header field to the Vary response header.
+// This will append the header, if not already listed, otherwise leaves it listed in the current location.
+func (r *Response) Vary(fields ...string) {
+ r.Append(HeaderVary, fields...)
+}
+
+// Write appends p into response body.
+func (r *Response) Write(p []byte) (int, error) {
+ r.fasthttp.AppendBody(p)
+ return len(p), nil
+}
+
+// Writef appends f & a into response body writer.
+func (r *Response) Writef(f string, a ...any) (int, error) {
+ //nolint:wrapcheck // This must not be wrapped
+ return fmt.Fprintf(r.fasthttp.BodyWriter(), f, a...)
+}
+
+// WriteString appends s to response body.
+func (r *Response) WriteString(s string) (int, error) {
+ r.fasthttp.AppendBodyString(s)
+ return len(s), nil
+}
+
+// XML converts any interface or string to XML.
+// This method also sets the content header to application/xml.
+func (r *Response) XML(data any) error {
+ raw, err := r.app.config.XMLEncoder(data)
+ if err != nil {
+ return err
+ }
+ r.fasthttp.SetBodyRaw(raw)
+ r.fasthttp.Header.SetContentType(MIMEApplicationXML)
+ return nil
+}