package fiber
import (
"bytes"
"context"
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"text/template"
"time"
"github.com/gofiber/fiber/v2/internal/schema"
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/bytebufferpool"
"github.com/valyala/fasthttp"
)
const (
schemeHTTP = "http"
schemeHTTPS = "https"
)
const maxParams = 30
const (
queryTag = "query"
reqHeaderTag = "reqHeader"
bodyTag = "form"
paramsTag = "params"
cookieTag = "cookie"
)
const userContextKey = "__local_user_context__"
var (
decoderPoolMap = map [string ]*sync .Pool {}
tags = []string {queryTag , bodyTag , reqHeaderTag , paramsTag , cookieTag }
)
func init() {
for _ , tag := range tags {
decoderPoolMap [tag ] = &sync .Pool {New : func () interface {} {
return decoderBuilder (ParserConfig {
IgnoreUnknownKeys : true ,
ZeroEmpty : true ,
})
}}
}
}
func SetParserDecoder (parserConfig ParserConfig ) {
for _ , tag := range tags {
decoderPoolMap [tag ] = &sync .Pool {New : func () interface {} {
return decoderBuilder (parserConfig )
}}
}
}
type Ctx struct {
app *App
route *Route
indexRoute int
indexHandler int
method string
methodINT int
baseURI string
path string
pathBuffer []byte
detectionPath string
detectionPathBuffer []byte
treePath string
pathOriginal string
values [maxParams ]string
fasthttp *fasthttp .RequestCtx
matched bool
viewBindMap sync .Map
}
type TLSHandler struct {
clientHelloInfo *tls .ClientHelloInfo
}
func (t *TLSHandler ) GetClientInfo (info *tls .ClientHelloInfo ) (*tls .Certificate , error ) {
t .clientHelloInfo = info
return nil , nil
}
type Range struct {
Type string
Ranges []struct {
Start int
End int
}
}
type Cookie struct {
Name string `json:"name"`
Value string `json:"value"`
Path string `json:"path"`
Domain string `json:"domain"`
MaxAge int `json:"max_age"`
Expires time .Time `json:"expires"`
Secure bool `json:"secure"`
HTTPOnly bool `json:"http_only"`
SameSite string `json:"same_site"`
SessionOnly bool `json:"session_only"`
}
type Views interface {
Load () error
Render (io .Writer , string , interface {}, ...string ) error
}
type ParserType struct {
Customtype interface {}
Converter func (string ) reflect .Value
}
type ParserConfig struct {
IgnoreUnknownKeys bool
SetAliasTag string
ParserType []ParserType
ZeroEmpty bool
}
func (app *App ) AcquireCtx (fctx *fasthttp .RequestCtx ) *Ctx {
c , ok := app .pool .Get ().(*Ctx )
if !ok {
panic (fmt .Errorf ("failed to type-assert to *Ctx" ))
}
c .app = app
c .indexRoute = -1
c .indexHandler = 0
c .matched = false
c .pathOriginal = app .getString (fctx .URI ().PathOriginal ())
c .method = app .getString (fctx .Request .Header .Method ())
c .methodINT = app .methodInt (c .method )
c .fasthttp = fctx
c .baseURI = ""
c .configDependentPaths ()
return c
}
func (app *App ) ReleaseCtx (c *Ctx ) {
c .route = nil
c .fasthttp = nil
c .viewBindMap = sync .Map {}
app .pool .Put (c )
}
func (c *Ctx ) Accepts (offers ...string ) string {
return getOffer (c .Get (HeaderAccept ), acceptsOfferType , offers ...)
}
func (c *Ctx ) AcceptsCharsets (offers ...string ) string {
return getOffer (c .Get (HeaderAcceptCharset ), acceptsOffer , offers ...)
}
func (c *Ctx ) AcceptsEncodings (offers ...string ) string {
return getOffer (c .Get (HeaderAcceptEncoding ), acceptsOffer , offers ...)
}
func (c *Ctx ) AcceptsLanguages (offers ...string ) string {
return getOffer (c .Get (HeaderAcceptLanguage ), acceptsOffer , offers ...)
}
func (c *Ctx ) App () *App {
return c .app
}
func (c *Ctx ) 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 )
}
}
func (c *Ctx ) 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" )
}
func (c *Ctx ) BaseURL () string {
if c .baseURI != "" {
return c .baseURI
}
c .baseURI = c .Protocol () + "://" + c .Hostname ()
return c .baseURI
}
func (c *Ctx ) BodyRaw () []byte {
return c .fasthttp .Request .Body ()
}
func (c *Ctx ) tryDecodeBodyInOrder (
originalBody *[]byte ,
encodings []string ,
) ([]byte , uint8 , error ) {
var (
err error
body []byte
decodesRealized uint8
)
for index , encoding := range encodings {
decodesRealized ++
switch encoding {
case StrGzip :
body , err = c .fasthttp .Request .BodyGunzip ()
case StrBr , StrBrotli :
body , err = c .fasthttp .Request .BodyUnbrotli ()
case StrDeflate :
body , err = c .fasthttp .Request .BodyInflate ()
default :
decodesRealized --
if len (encodings ) == 1 {
body = c .fasthttp .Request .Body ()
}
return body , decodesRealized , nil
}
if err != nil {
return nil , decodesRealized , err
}
if index < len (encodings )-1 && decodesRealized > 0 {
if index == 0 {
tempBody := c .fasthttp .Request .Body ()
*originalBody = make ([]byte , len (tempBody ))
copy (*originalBody , tempBody )
}
c .fasthttp .Request .SetBodyRaw (body )
}
}
return body , decodesRealized , nil
}
func (c *Ctx ) Body () []byte {
var (
err error
body , originalBody []byte
headerEncoding string
encodingOrder = []string {"" , "" , "" }
)
c .Request ().Header .VisitAll (func (key , value []byte ) {
if c .app .getString (key ) == HeaderContentEncoding {
headerEncoding = c .app .getString (value )
}
})
encodingOrder = getSplicedStrList (headerEncoding , encodingOrder )
if len (encodingOrder ) == 0 {
return c .fasthttp .Request .Body ()
}
var decodesRealized uint8
body , decodesRealized , err = c .tryDecodeBodyInOrder (&originalBody , encodingOrder )
if originalBody != nil && decodesRealized > 0 {
c .fasthttp .Request .SetBodyRaw (originalBody )
}
if err != nil {
return []byte (err .Error())
}
return body
}
func decoderBuilder(parserConfig ParserConfig ) interface {} {
decoder := schema .NewDecoder ()
decoder .IgnoreUnknownKeys (parserConfig .IgnoreUnknownKeys )
if parserConfig .SetAliasTag != "" {
decoder .SetAliasTag (parserConfig .SetAliasTag )
}
for _ , v := range parserConfig .ParserType {
decoder .RegisterConverter (reflect .ValueOf (v .Customtype ).Interface (), v .Converter )
}
decoder .ZeroEmpty (parserConfig .ZeroEmpty )
return decoder
}
func (c *Ctx ) BodyParser (out interface {}) error {
ctype := utils .ToLower (c .app .getString (c .fasthttp .Request .Header .ContentType ()))
ctype = utils .ParseVendorSpecificContentType (ctype )
if strings .HasPrefix (ctype , MIMEApplicationJSON ) {
return c .app .config .JSONDecoder (c .Body (), out )
}
if strings .HasPrefix (ctype , MIMEApplicationForm ) {
data := make (map [string ][]string )
var err error
c .fasthttp .PostArgs ().VisitAll (func (key , val []byte ) {
if err != nil {
return
}
k := c .app .getString (key )
v := c .app .getString (val )
if strings .Contains (k , "[" ) {
k , err = parseParamSquareBrackets (k )
}
if c .app .config .EnableSplittingOnParsers && strings .Contains (v , "," ) && equalFieldType (out , reflect .Slice , k , bodyTag ) {
values := strings .Split (v , "," )
for i := 0 ; i < len (values ); i ++ {
data [k ] = append (data [k ], values [i ])
}
} else {
data [k ] = append (data [k ], v )
}
})
return c .parseToStruct (bodyTag , out , data )
}
if strings .HasPrefix (ctype , MIMEMultipartForm ) {
data , err := c .fasthttp .MultipartForm ()
if err != nil {
return err
}
return c .parseToStruct (bodyTag , out , data .Value )
}
if strings .HasPrefix (ctype , MIMETextXML ) || strings .HasPrefix (ctype , MIMEApplicationXML ) {
if err := xml .Unmarshal (c .Body (), out ); err != nil {
return fmt .Errorf ("failed to unmarshal: %w" , err )
}
return nil
}
return ErrUnprocessableEntity
}
func (c *Ctx ) 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 , v []byte ) {
c .fasthttp .Response .Header .DelClientCookieBytes (k )
})
}
func (c *Ctx ) Context () *fasthttp .RequestCtx {
return c .fasthttp
}
func (c *Ctx ) UserContext () context .Context {
ctx , ok := c .fasthttp .UserValue (userContextKey ).(context .Context )
if !ok {
ctx = context .Background ()
c .SetUserContext (ctx )
}
return ctx
}
func (c *Ctx ) SetUserContext (ctx context .Context ) {
c .fasthttp .SetUserValue (userContextKey , ctx )
}
func (c *Ctx ) Cookie (cookie *Cookie ) {
fcookie := fasthttp .AcquireCookie ()
fcookie .SetKey (cookie .Name )
fcookie .SetValue (cookie .Value )
fcookie .SetPath (cookie .Path )
fcookie .SetDomain (cookie .Domain )
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 )
}
func (c *Ctx ) Cookies (key string , defaultValue ...string ) string {
return defaultString (c .app .getString (c .fasthttp .Request .Header .Cookie (key )), defaultValue )
}
func (c *Ctx ) CookieParser (out interface {}) error {
data := make (map [string ][]string )
var err error
c .fasthttp .Request .Header .VisitAllCookie (func (key , val []byte ) {
if err != nil {
return
}
k := c .app .getString (key )
v := c .app .getString (val )
if strings .Contains (k , "[" ) {
k , err = parseParamSquareBrackets (k )
}
if c .app .config .EnableSplittingOnParsers && strings .Contains (v , "," ) && equalFieldType (out , reflect .Slice , k , cookieTag ) {
values := strings .Split (v , "," )
for i := 0 ; i < len (values ); i ++ {
data [k ] = append (data [k ], values [i ])
}
} else {
data [k ] = append (data [k ], v )
}
})
if err != nil {
return err
}
return c .parseToStruct (cookieTag , out , data )
}
func (c *Ctx ) 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 )
}
func (c *Ctx ) Request () *fasthttp .Request {
return &c .fasthttp .Request
}
func (c *Ctx ) Response () *fasthttp .Response {
return &c .fasthttp .Response
}
func (c *Ctx ) Format (body interface {}) error {
accept := c .Accepts ("html" , "json" , "txt" , "xml" )
c .Type (accept )
var b string
switch val := body .(type ) {
case string :
b = val
case []byte :
b = c .app .getString (val )
default :
b = fmt .Sprintf ("%v" , val )
}
switch accept {
case "html" :
return c .SendString ("<p>" + b + "</p>" )
case "json" :
return c .JSON (body )
case "txt" :
return c .SendString (b )
case "xml" :
return c .XML (body )
}
return c .SendString (b )
}
func (c *Ctx ) FormFile (key string ) (*multipart .FileHeader , error ) {
return c .fasthttp .FormFile (key )
}
func (c *Ctx ) FormValue (key string , defaultValue ...string ) string {
return defaultString (c .app .getString (c .fasthttp .FormValue (key )), defaultValue )
}
func (c *Ctx ) Fresh () bool {
modifiedSince := c .Get (HeaderIfModifiedSince )
noneMatch := c .Get (HeaderIfNoneMatch )
if modifiedSince == "" && noneMatch == "" {
return false
}
cacheControl := c .Get (HeaderCacheControl )
if cacheControl != "" && isNoCache (cacheControl ) {
return false
}
if noneMatch != "" && noneMatch != "*" {
etag := c .app .getString (c .fasthttp .Response .Header .Peek (HeaderETag ))
if etag == "" {
return false
}
if c .app .isEtagStale (etag , c .app .getBytes (noneMatch )) {
return false
}
if modifiedSince != "" {
lastModified := c .app .getString (c .fasthttp .Response .Header .Peek (HeaderLastModified ))
if lastModified != "" {
lastModifiedTime , err := http .ParseTime (lastModified )
if err != nil {
return false
}
modifiedSinceTime , err := http .ParseTime (modifiedSince )
if err != nil {
return false
}
return lastModifiedTime .Before (modifiedSinceTime )
}
}
}
return true
}
func (c *Ctx ) Get (key string , defaultValue ...string ) string {
return defaultString (c .app .getString (c .fasthttp .Request .Header .Peek (key )), defaultValue )
}
func (c *Ctx ) GetRespHeader (key string , defaultValue ...string ) string {
return defaultString (c .app .getString (c .fasthttp .Response .Header .Peek (key )), defaultValue )
}
func (c *Ctx ) GetReqHeaders () map [string ][]string {
headers := make (map [string ][]string )
c .Request ().Header .VisitAll (func (k , v []byte ) {
key := c .app .getString (k )
headers [key ] = append (headers [key ], c .app .getString (v ))
})
return headers
}
func (c *Ctx ) GetRespHeaders () map [string ][]string {
headers := make (map [string ][]string )
c .Response ().Header .VisitAll (func (k , v []byte ) {
key := c .app .getString (k )
headers [key ] = append (headers [key ], c .app .getString (v ))
})
return headers
}
func (c *Ctx ) Hostname () string {
if c .IsProxyTrusted () {
if host := c .Get (HeaderXForwardedHost ); len (host ) > 0 {
commaPos := strings .Index (host , "," )
if commaPos != -1 {
return host [:commaPos ]
}
return host
}
}
return c .app .getString (c .fasthttp .Request .URI ().Host ())
}
func (c *Ctx ) Port () string {
tcpaddr , ok := c .fasthttp .RemoteAddr ().(*net .TCPAddr )
if !ok {
panic (fmt .Errorf ("failed to type-assert to *net.TCPAddr" ))
}
return strconv .Itoa (tcpaddr .Port )
}
func (c *Ctx ) IP () string {
if c .IsProxyTrusted () && len (c .app .config .ProxyHeader ) > 0 {
return c .extractIPFromHeader (c .app .config .ProxyHeader )
}
return c .fasthttp .RemoteIP ().String ()
}
func (c *Ctx ) extractIPsFromHeader (header string ) []string {
headerValue := c .Get (header )
const maxEstimatedCount = 8
estimatedCount := len (headerValue ) / maxEstimatedCount
if estimatedCount > maxEstimatedCount {
estimatedCount = maxEstimatedCount
}
ipsFound := make ([]string , 0 , estimatedCount )
i := 0
j := -1
iploop :
for {
var v4 , v6 bool
i , j = j +1 , j +2
if j > len (headerValue ) {
break
}
for j < len (headerValue ) && headerValue [j ] != ',' {
if headerValue [j ] == ':' {
v6 = true
} else if headerValue [j ] == '.' {
v4 = true
}
j ++
}
for i < j && (headerValue [i ] == ' ' || headerValue [i ] == ',' ) {
i ++
}
s := utils .TrimRight (headerValue [i :j ], ' ' )
if c .app .config .EnableIPValidation {
if (!v6 && !v4 ) || (v6 && !utils .IsIPv6 (s )) || (v4 && !utils .IsIPv4 (s )) {
continue iploop
}
}
ipsFound = append (ipsFound , s )
}
return ipsFound
}
func (c *Ctx ) extractIPFromHeader (header string ) string {
if c .app .config .EnableIPValidation {
headerValue := c .Get (header )
i := 0
j := -1
iploop :
for {
var v4 , v6 bool
i , j = j +1 , j +2
if j > len (headerValue ) {
break
}
for j < len (headerValue ) && headerValue [j ] != ',' {
if headerValue [j ] == ':' {
v6 = true
} else if headerValue [j ] == '.' {
v4 = true
}
j ++
}
for i < j && headerValue [i ] == ' ' {
i ++
}
s := utils .TrimRight (headerValue [i :j ], ' ' )
if c .app .config .EnableIPValidation {
if (!v6 && !v4 ) || (v6 && !utils .IsIPv6 (s )) || (v4 && !utils .IsIPv4 (s )) {
continue iploop
}
}
return s
}
return c .fasthttp .RemoteIP ().String ()
}
return c .Get (c .app .config .ProxyHeader )
}
func (c *Ctx ) IPs () []string {
return c .extractIPsFromHeader (HeaderXForwardedFor )
}
func (c *Ctx ) Is (extension string ) bool {
extensionHeader := utils .GetMIME (extension )
if extensionHeader == "" {
return false
}
return strings .HasPrefix (
utils .TrimLeft (c .app .getString (c .fasthttp .Request .Header .ContentType ()), ' ' ),
extensionHeader ,
)
}
func (c *Ctx ) JSON (data interface {}) error {
raw , err := c .app .config .JSONEncoder (data )
if err != nil {
return err
}
c .fasthttp .Response .SetBodyRaw (raw )
c .fasthttp .Response .Header .SetContentType (MIMEApplicationJSON )
return nil
}
func (c *Ctx ) JSONP (data interface {}, 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 )
}
func (c *Ctx ) XML (data interface {}) 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
}
func (c *Ctx ) 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 , utils .TrimRight (c .app .getString (bb .Bytes ()), ',' ))
bytebufferpool .Put (bb )
}
func (c *Ctx ) Locals (key interface {}, value ...interface {}) interface {} {
if len (value ) == 0 {
return c .fasthttp .UserValue (key )
}
c .fasthttp .SetUserValue (key , value [0 ])
return value [0 ]
}
func (c *Ctx ) Location (path string ) {
c .setCanonical (HeaderLocation , path )
}
func (c *Ctx ) Method (override ...string ) string {
if len (override ) == 0 {
return c .method
}
method := utils .ToUpper (override [0 ])
mINT := c .app .methodInt (method )
if mINT == -1 {
return c .method
}
c .method = method
c .methodINT = mINT
return c .method
}
func (c *Ctx ) MultipartForm () (*multipart .Form , error ) {
return c .fasthttp .MultipartForm ()
}
func (c *Ctx ) ClientHelloInfo () *tls .ClientHelloInfo {
if c .app .tlsHandler != nil {
return c .app .tlsHandler .clientHelloInfo
}
return nil
}
func (c *Ctx ) Next () error {
c .indexHandler ++
var err error
if c .indexHandler < len (c .route .Handlers ) {
err = c .route .Handlers [c .indexHandler ](c )
} else {
_, err = c .app .next (c )
}
return err
}
func (c *Ctx ) RestartRouting () error {
c .indexRoute = -1
_ , err := c .app .next (c )
return err
}
func (c *Ctx ) OriginalURL () string {
return c .app .getString (c .fasthttp .Request .Header .RequestURI ())
}
func (c *Ctx ) Params (key string , defaultValue ...string ) string {
if key == "*" || key == "+" {
key += "1"
}
for i := range c .route .Params {
if len (key ) != len (c .route .Params [i ]) {
continue
}
if c .route .Params [i ] == key || (!c .app .config .CaseSensitive && utils .EqualFold (c .route .Params [i ], key )) {
if len (c .values ) <= i || len (c .values [i ]) == 0 {
break
}
return c .values [i ]
}
}
return defaultString ("" , defaultValue )
}
func (c *Ctx ) AllParams () map [string ]string {
params := make (map [string ]string , len (c .route .Params ))
for _ , param := range c .route .Params {
params [param ] = c .Params (param )
}
return params
}
func (c *Ctx ) ParamsParser (out interface {}) error {
params := make (map [string ][]string , len (c .route .Params ))
for _ , param := range c .route .Params {
params [param ] = append (params [param ], c .Params (param ))
}
return c .parseToStruct (paramsTag , out , params )
}
func (c *Ctx ) ParamsInt (key string , defaultValue ...int ) (int , error ) {
value , err := strconv .Atoi (c .Params (key ))
if err != nil {
if len (defaultValue ) > 0 {
return defaultValue [0 ], nil
}
return 0 , fmt .Errorf ("failed to convert: %w" , err )
}
return value , nil
}
func (c *Ctx ) Path (override ...string ) string {
if len (override ) != 0 && c .path != override [0 ] {
c .pathOriginal = override [0 ]
c .fasthttp .Request .URI ().SetPath (c .pathOriginal )
c .configDependentPaths ()
}
return c .path
}
func (c *Ctx ) Protocol () string {
if c .fasthttp .IsTLS () {
return schemeHTTPS
}
if !c .IsProxyTrusted () {
return schemeHTTP
}
scheme := schemeHTTP
const lenXHeaderName = 12
c .fasthttp .Request .Header .VisitAll (func (key , val []byte ) {
if len (key ) < lenXHeaderName {
return
}
switch {
case bytes .HasPrefix (key , []byte ("X-Forwarded-" )):
if bytes .Equal (key , []byte (HeaderXForwardedProto )) ||
bytes .Equal (key , []byte (HeaderXForwardedProtocol )) {
v := c .app .getString (val )
commaPos := strings .Index (v , "," )
if commaPos != -1 {
scheme = v [:commaPos ]
} else {
scheme = v
}
} else if bytes .Equal (key , []byte (HeaderXForwardedSsl )) && bytes .Equal (val , []byte ("on" )) {
scheme = schemeHTTPS
}
case bytes .Equal (key , []byte (HeaderXUrlScheme )):
scheme = c .app .getString (val )
}
})
return scheme
}
func (c *Ctx ) Query (key string , defaultValue ...string ) string {
return defaultString (c .app .getString (c .fasthttp .QueryArgs ().Peek (key )), defaultValue )
}
func (c *Ctx ) Queries () map [string ]string {
m := make (map [string ]string , c .Context ().QueryArgs ().Len ())
c .Context ().QueryArgs ().VisitAll (func (key , value []byte ) {
m [c .app .getString (key )] = c .app .getString (value )
})
return m
}
func (c *Ctx ) QueryInt (key string , defaultValue ...int ) int {
value , err := strconv .Atoi (c .app .getString (c .fasthttp .QueryArgs ().Peek (key )))
if err != nil {
if len (defaultValue ) > 0 {
return defaultValue [0 ]
}
return 0
}
return value
}
func (c *Ctx ) QueryBool (key string , defaultValue ...bool ) bool {
value , err := strconv .ParseBool (c .app .getString (c .fasthttp .QueryArgs ().Peek (key )))
if err != nil {
if len (defaultValue ) > 0 {
return defaultValue [0 ]
}
return false
}
return value
}
func (c *Ctx ) QueryFloat (key string , defaultValue ...float64 ) float64 {
value , err := strconv .ParseFloat (c .app .getString (c .fasthttp .QueryArgs ().Peek (key )), 64 )
if err != nil {
if len (defaultValue ) > 0 {
return defaultValue [0 ]
}
return 0
}
return value
}
func (c *Ctx ) QueryParser (out interface {}) error {
data := make (map [string ][]string )
var err error
c .fasthttp .QueryArgs ().VisitAll (func (key , val []byte ) {
if err != nil {
return
}
k := c .app .getString (key )
v := c .app .getString (val )
if strings .Contains (k , "[" ) {
k , err = parseParamSquareBrackets (k )
}
if c .app .config .EnableSplittingOnParsers && strings .Contains (v , "," ) && equalFieldType (out , reflect .Slice , k , queryTag ) {
values := strings .Split (v , "," )
for i := 0 ; i < len (values ); i ++ {
data [k ] = append (data [k ], values [i ])
}
} else {
data [k ] = append (data [k ], v )
}
})
if err != nil {
return err
}
return c .parseToStruct (queryTag , out , data )
}
func parseParamSquareBrackets(k string ) (string , error ) {
bb := bytebufferpool .Get ()
defer bytebufferpool .Put (bb )
kbytes := []byte (k )
for i , b := range kbytes {
if b == '[' && kbytes [i +1 ] != ']' {
if err := bb .WriteByte ('.' ); err != nil {
return "" , fmt .Errorf ("failed to write: %w" , err )
}
}
if b == '[' || b == ']' {
continue
}
if err := bb .WriteByte (b ); err != nil {
return "" , fmt .Errorf ("failed to write: %w" , err )
}
}
return bb .String (), nil
}
func (c *Ctx ) ReqHeaderParser (out interface {}) error {
data := make (map [string ][]string )
c .fasthttp .Request .Header .VisitAll (func (key , val []byte ) {
k := c .app .getString (key )
v := c .app .getString (val )
if c .app .config .EnableSplittingOnParsers && strings .Contains (v , "," ) && equalFieldType (out , reflect .Slice , k , reqHeaderTag ) {
values := strings .Split (v , "," )
for i := 0 ; i < len (values ); i ++ {
data [k ] = append (data [k ], values [i ])
}
} else {
data [k ] = append (data [k ], v )
}
})
return c .parseToStruct (reqHeaderTag , out , data )
}
func (*Ctx ) parseToStruct (aliasTag string , out interface {}, data map [string ][]string ) error {
schemaDecoder , ok := decoderPoolMap [aliasTag ].Get ().(*schema .Decoder )
if !ok {
panic (fmt .Errorf ("failed to type-assert to *schema.Decoder" ))
}
defer decoderPoolMap [aliasTag ].Put (schemaDecoder )
schemaDecoder .SetAliasTag (aliasTag )
if err := schemaDecoder .Decode (out , data ); err != nil {
return fmt .Errorf ("failed to decode: %w" , err )
}
return nil
}
func equalFieldType(out interface {}, kind reflect .Kind , key , tag string ) bool {
outTyp := reflect .TypeOf (out ).Elem ()
key = utils .ToLower (key )
if outTyp .Kind () != reflect .Struct {
return false
}
outVal := reflect .ValueOf (out ).Elem ()
for i := 0 ; i < outTyp .NumField (); i ++ {
structField := outVal .Field (i )
if !structField .CanSet () {
continue
}
typeField := outTyp .Field (i )
structFieldKind := structField .Kind ()
if structFieldKind != kind {
continue
}
inputFieldName := typeField .Tag .Get (tag )
if inputFieldName == "" {
inputFieldName = typeField .Name
} else {
inputFieldName = strings .Split (inputFieldName , "," )[0 ]
}
if utils .ToLower (inputFieldName ) == key {
return true
}
}
return false
}
var (
ErrRangeMalformed = errors .New ("range: malformed range header string" )
ErrRangeUnsatisfiable = errors .New ("range: unsatisfiable range" )
)
func (c *Ctx ) Range (size int ) (Range , error ) {
var rangeData Range
rangeStr := c .Get (HeaderRange )
if rangeStr == "" || !strings .Contains (rangeStr , "=" ) {
return rangeData , ErrRangeMalformed
}
data := strings .Split (rangeStr , "=" )
const expectedDataParts = 2
if len (data ) != expectedDataParts {
return rangeData , ErrRangeMalformed
}
rangeData .Type = data [0 ]
arr := strings .Split (data [1 ], "," )
for i := 0 ; i < len (arr ); i ++ {
item := strings .Split (arr [i ], "-" )
if len (item ) == 1 {
return rangeData , ErrRangeMalformed
}
start , startErr := strconv .Atoi (item [0 ])
end , endErr := strconv .Atoi (item [1 ])
if startErr != nil {
start = size - end
end = size - 1
} else if endErr != nil {
end = size - 1
}
if end > size -1 {
end = size - 1
}
if start > end || start < 0 {
continue
}
rangeData .Ranges = append (rangeData .Ranges , struct {
Start int
End int
}{
start ,
end ,
})
}
if len (rangeData .Ranges ) < 1 {
return rangeData , ErrRangeUnsatisfiable
}
return rangeData , nil
}
func (c *Ctx ) Redirect (location string , status ...int ) error {
c .setCanonical (HeaderLocation , location )
if len (status ) > 0 {
c .Status (status [0 ])
} else {
c .Status (StatusFound )
}
return nil
}
func (c *Ctx ) Bind (vars Map ) error {
for k , v := range vars {
c .viewBindMap .Store (k , v )
}
return nil
}
func (c *Ctx ) getLocationFromRoute (route Route , params Map ) (string , error ) {
buf := bytebufferpool .Get ()
for _ , segment := range route .routeParser .segs {
if !segment .IsParam {
_ , err := buf .WriteString (segment .Const )
if err != nil {
return "" , fmt .Errorf ("failed to write string: %w" , err )
}
continue
}
for key , val := range params {
isSame := key == segment .ParamName || (!c .app .config .CaseSensitive && utils .EqualFold (key , segment .ParamName ))
isGreedy := segment .IsGreedy && len (key ) == 1 && isInCharset (key [0 ], greedyParameters )
if isSame || isGreedy {
_ , err := buf .WriteString (utils .ToString (val ))
if err != nil {
return "" , fmt .Errorf ("failed to write string: %w" , err )
}
}
}
}
location := buf .String ()
bytebufferpool .Put (buf )
return location , nil
}
func (c *Ctx ) GetRouteURL (routeName string , params Map ) (string , error ) {
return c .getLocationFromRoute (c .App ().GetRoute (routeName ), params )
}
func (c *Ctx ) RedirectToRoute (routeName string , params Map , status ...int ) error {
location , err := c .getLocationFromRoute (c .App ().GetRoute (routeName ), params )
if err != nil {
return err
}
if queries , ok := params ["queries" ].(map [string ]string ); ok {
queryText := bytebufferpool .Get ()
defer bytebufferpool .Put (queryText )
i := 1
for k , v := range queries {
_, _ = queryText .WriteString (k + "=" + v )
if i != len (queries ) {
_, _ = queryText .WriteString ("&" )
}
i ++
}
return c .Redirect (location +"?" +queryText .String (), status ...)
}
return c .Redirect (location , status ...)
}
func (c *Ctx ) RedirectBack (fallback string , status ...int ) error {
location := c .Get (HeaderReferer )
if location == "" {
location = fallback
}
return c .Redirect (location , status ...)
}
func (c *Ctx ) Render (name string , bind interface {}, layouts ...string ) error {
buf := bytebufferpool .Get ()
defer bytebufferpool .Put (buf )
if bind == nil {
bind = make (Map )
}
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 ,
}
}
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 {
var tmpl *template .Template
if _ , err := readContent (buf , name ); err != nil {
return err
}
tmpl , err := template .New ("" ).Parse (c .app .getString (buf .Bytes ()))
if err != nil {
return fmt .Errorf ("failed to parse: %w" , err )
}
buf .Reset ()
if err := tmpl .Execute (buf , bind ); err != nil {
return fmt .Errorf ("failed to execute: %w" , err )
}
}
c .fasthttp .Response .Header .SetContentType (MIMETextHTMLCharsetUTF8 )
c .fasthttp .Response .SetBody (buf .Bytes ())
return nil
}
func (c *Ctx ) renderExtensions (bind interface {}) {
if bindMap , ok := bind .(Map ); ok {
c .viewBindMap .Range (func (key , value interface {}) bool {
keyValue , ok := key .(string )
if !ok {
return true
}
if _ , ok := bindMap [keyValue ]; !ok {
bindMap [keyValue ] = value
}
return true
})
if c .app .config .PassLocalsToViews {
c .fasthttp .VisitUserValues (func (key []byte , val interface {}) {
if _ , ok := bindMap [c .app .getString (key )]; !ok {
bindMap [c .app .getString (key )] = val
}
})
}
}
if len (c .app .mountFields .appListKeys ) == 0 {
c .app .generateAppListKeys ()
}
}
func (c *Ctx ) Route () *Route {
if c .route == nil {
return &Route {
path : c .pathOriginal ,
Path : c .pathOriginal ,
Method : c .method ,
Handlers : make ([]Handler , 0 ),
Params : make ([]string , 0 ),
}
}
return c .route
}
func (*Ctx ) SaveFile (fileheader *multipart .FileHeader , path string ) error {
return fasthttp .SaveMultipartFile (fileheader , path )
}
func (*Ctx ) SaveFileToStorage (fileheader *multipart .FileHeader , path string , storage Storage ) error {
file , err := fileheader .Open ()
if err != nil {
return fmt .Errorf ("failed to open: %w" , err )
}
content , err := io .ReadAll (file )
if err != nil {
return fmt .Errorf ("failed to read: %w" , err )
}
if err := storage .Set (path , content , 0 ); err != nil {
return fmt .Errorf ("failed to store: %w" , err )
}
return nil
}
func (c *Ctx ) Secure () bool {
return c .Protocol () == schemeHTTPS
}
func (c *Ctx ) Send (body []byte ) error {
c .fasthttp .Response .SetBodyRaw (body )
return nil
}
var (
sendFileOnce sync .Once
sendFileFS *fasthttp .FS
sendFileHandler fasthttp .RequestHandler
)
func (c *Ctx ) SendFile (file string , compress ...bool ) error {
filename := file
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 ()
})
c .pathOriginal = utils .CopyString (c .pathOriginal )
if len (compress ) == 0 || !compress [0 ] {
c .fasthttp .Request .Header .Del (HeaderAcceptEncoding )
}
if len (file ) == 0 || !filepath .IsAbs (file ) {
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 += "/"
}
}
file = filepath .ToSlash (file )
originalURL := utils .CopyString (c .OriginalURL ())
defer c .fasthttp .Request .SetRequestURI (originalURL )
c .fasthttp .Request .SetRequestURI (file )
status := c .fasthttp .Response .StatusCode ()
sendFileHandler (c .fasthttp )
fsStatus := c .fasthttp .Response .StatusCode ()
if status != fsStatus && status != StatusOK {
c .Status (status )
}
if status != StatusNotFound && fsStatus == StatusNotFound {
return NewError (StatusNotFound , fmt .Sprintf ("sendfile: file %s not found" , filename ))
}
return nil
}
func (c *Ctx ) SendStatus (status int ) error {
c .Status (status )
if len (c .fasthttp .Response .Body ()) == 0 {
return c .SendString (utils .StatusMessage (status ))
}
return nil
}
func (c *Ctx ) SendString (body string ) error {
c .fasthttp .Response .SetBodyString (body )
return nil
}
func (c *Ctx ) SendStream (stream io .Reader , size ...int ) error {
if len (size ) > 0 && size [0 ] >= 0 {
c .fasthttp .Response .SetBodyStream (stream , size [0 ])
} else {
c .fasthttp .Response .SetBodyStream (stream , -1 )
}
return nil
}
func (c *Ctx ) Set (key , val string ) {
c .fasthttp .Response .Header .Set (key , val )
}
func (c *Ctx ) setCanonical (key , val string ) {
c .fasthttp .Response .Header .SetCanonical (c .app .getBytes (key ), c .app .getBytes (val ))
}
func (c *Ctx ) Subdomains (offset ...int ) []string {
o := 2
if len (offset ) > 0 {
o = offset [0 ]
}
subdomains := strings .Split (c .Hostname (), "." )
l := len (subdomains ) - o
if l < 0 {
l = len (subdomains )
}
subdomains = subdomains [:l ]
return subdomains
}
func (c *Ctx ) Stale () bool {
return !c .Fresh ()
}
func (c *Ctx ) Status (status int ) *Ctx {
c .fasthttp .Response .SetStatusCode (status )
return c
}
func (c *Ctx ) String () string {
return fmt .Sprintf (
"#%016X - %s <-> %s - %s %s" ,
c .fasthttp .ID (),
c .fasthttp .LocalAddr (),
c .fasthttp .RemoteAddr (),
c .fasthttp .Request .Header .Method (),
c .fasthttp .URI ().FullURI (),
)
}
func (c *Ctx ) 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 ))
}
return c
}
func (c *Ctx ) Vary (fields ...string ) {
c .Append (HeaderVary , fields ...)
}
func (c *Ctx ) Write (p []byte ) (int , error ) {
c .fasthttp .Response .AppendBody (p )
return len (p ), nil
}
func (c *Ctx ) Writef (f string , a ...interface {}) (int , error ) {
return fmt .Fprintf (c .fasthttp .Response .BodyWriter (), f , a ...)
}
func (c *Ctx ) WriteString (s string ) (int , error ) {
c .fasthttp .Response .AppendBodyString (s )
return len (s ), nil
}
func (c *Ctx ) XHR () bool {
return utils .EqualFoldBytes (c .app .getBytes (c .Get (HeaderXRequestedWith )), []byte ("xmlhttprequest" ))
}
func (c *Ctx ) configDependentPaths () {
c .pathBuffer = append (c .pathBuffer [0 :0 ], c .pathOriginal ...)
if c .app .config .UnescapePath {
c .pathBuffer = fasthttp .AppendUnquotedArg (c .pathBuffer [:0 ], c .pathBuffer )
}
c .path = c .app .getString (c .pathBuffer )
c .detectionPathBuffer = append (c .detectionPathBuffer [0 :0 ], c .pathBuffer ...)
if !c .app .config .CaseSensitive {
c .detectionPathBuffer = utils .ToLowerBytes (c .detectionPathBuffer )
}
if !c .app .config .StrictRouting && len (c .detectionPathBuffer ) > 1 && c .detectionPathBuffer [len (c .detectionPathBuffer )-1 ] == '/' {
c .detectionPathBuffer = utils .TrimRightBytes (c .detectionPathBuffer , '/' )
}
c .detectionPath = c .app .getString (c .detectionPathBuffer )
c .treePath = c .treePath [0 :0 ]
const maxDetectionPaths = 3
if len (c .detectionPath ) >= maxDetectionPaths {
c .treePath = c .detectionPath [:maxDetectionPaths ]
}
}
func (c *Ctx ) IsProxyTrusted () bool {
if !c .app .config .EnableTrustedProxyCheck {
return true
}
ip := c .fasthttp .RemoteIP ()
if _ , trusted := c .app .config .trustedProxiesMap [ip .String ()]; trusted {
return true
}
for _ , ipNet := range c .app .config .trustedProxyRanges {
if ipNet .Contains (ip ) {
return true
}
}
return false
}
var localHosts = [...]string {"127.0.0.1" , "::1" }
func (*Ctx ) isLocalHost (address string ) bool {
for _ , h := range localHosts {
if address == h {
return true
}
}
return false
}
func (c *Ctx ) IsFromLocal () bool {
return c .isLocalHost (c .fasthttp .RemoteIP ().String ())
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .