github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/http/fast/ctx.go (about)

     1  // 🚀 Fast is an Express inspired web framework written in Go.
     2  
     3  package fast
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/xml"
     8  	"fmt"
     9  	"html/template"
    10  	"log"
    11  	"mime"
    12  	"mime/multipart"
    13  	"net/url"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"time"
    19  
    20  	"github.com/angenalZZZ/gofunc/f"
    21  	json "github.com/json-iterator/go"
    22  	"github.com/valyala/fasthttp"
    23  )
    24  
    25  // Ctx represents the Context which hold the HTTP request and response.
    26  // It has methods for the request query string, parameters, body, HTTP headers and so on.
    27  type Ctx struct {
    28  	C      *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
    29  	app    *Fast                // Reference to *Fast
    30  	route  *Route               // Reference to *Route
    31  	index  int                  // Index of the current stack
    32  	method string               // HTTP method
    33  	path   string               // HTTP path
    34  	values []string             // Route parameter values
    35  	err    error                // Contains error if catched
    36  }
    37  
    38  // Range struct
    39  type Range struct {
    40  	Type   string
    41  	Ranges []struct {
    42  		Start int
    43  		End   int
    44  	}
    45  }
    46  
    47  // Cookie struct
    48  type Cookie struct {
    49  	Name     string
    50  	Value    string
    51  	Path     string
    52  	Domain   string
    53  	Expires  time.Time
    54  	Secure   bool
    55  	HTTPOnly bool
    56  	SameSite string
    57  }
    58  
    59  // Ctx pool
    60  var poolCtx = sync.Pool{
    61  	New: func() interface{} {
    62  		return new(Ctx)
    63  	},
    64  }
    65  
    66  // Acquire Ctx from pool
    67  func acquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
    68  	ctx := poolCtx.Get().(*Ctx)
    69  	ctx.index = -1
    70  	ctx.path = GetString(fctx.URI().Path())
    71  	ctx.method = GetString(fctx.Request.Header.Method())
    72  	ctx.C = fctx
    73  	return ctx
    74  }
    75  
    76  // Return Ctx to pool
    77  func releaseCtx(ctx *Ctx) {
    78  	ctx.route = nil
    79  	ctx.values = nil
    80  	ctx.C = nil
    81  	ctx.err = nil
    82  	poolCtx.Put(ctx)
    83  }
    84  
    85  // Abort skips the rest of the handlers associated with the current route.
    86  // Abort is normally used when a handler handles the request normally and wants to skip the rest of the handlers.
    87  // If a handler wants to indicate an error condition, it should simply return the error without calling Abort.
    88  func (ctx *Ctx) Abort() {
    89  	ctx.index = len(ctx.app.routes)
    90  }
    91  
    92  // Accepts checks if the specified extensions or content types are acceptable.
    93  func (ctx *Ctx) Accepts(offers ...string) (offer string) {
    94  	if len(offers) == 0 {
    95  		return ""
    96  	}
    97  	h := ctx.GetHeader("Accept")
    98  	if h == "" {
    99  		return offers[0]
   100  	}
   101  
   102  	specs := strings.Split(h, ",")
   103  	for _, value := range offers {
   104  		mimeType := getMIME(value)
   105  		for _, spec := range specs {
   106  			spec = strings.TrimSpace(spec)
   107  			if strings.HasPrefix(spec, "*/*") {
   108  				return value
   109  			}
   110  
   111  			if strings.HasPrefix(spec, mimeType) {
   112  				return value
   113  			}
   114  
   115  			if strings.Contains(spec, "/*") {
   116  				if strings.HasPrefix(spec, strings.Split(mimeType, "/")[0]) {
   117  					return value
   118  				}
   119  			}
   120  		}
   121  	}
   122  	return ""
   123  }
   124  
   125  // AcceptsCharsets checks if the specified charset is acceptable.
   126  func (ctx *Ctx) AcceptsCharsets(offers ...string) (offer string) {
   127  	if len(offers) == 0 {
   128  		return ""
   129  	}
   130  
   131  	h := ctx.GetHeader("Accept-Charset")
   132  	if h == "" {
   133  		return offers[0]
   134  	}
   135  
   136  	specs := strings.Split(h, ",")
   137  	for _, value := range offers {
   138  		for _, spec := range specs {
   139  
   140  			spec = strings.TrimSpace(spec)
   141  			if strings.HasPrefix(spec, "*") {
   142  				return value
   143  			}
   144  			if strings.HasPrefix(spec, value) {
   145  				return value
   146  			}
   147  		}
   148  	}
   149  	return ""
   150  }
   151  
   152  // AcceptsEncodings checks if the specified encoding is acceptable.
   153  func (ctx *Ctx) AcceptsEncodings(offers ...string) (offer string) {
   154  	if len(offers) == 0 {
   155  		return ""
   156  	}
   157  
   158  	h := ctx.GetHeader("Accept-Encoding")
   159  	if h == "" {
   160  		return offers[0]
   161  	}
   162  
   163  	specs := strings.Split(h, ",")
   164  	for _, value := range offers {
   165  		for _, spec := range specs {
   166  			spec = strings.TrimSpace(spec)
   167  			if strings.HasPrefix(spec, "*") {
   168  				return value
   169  			}
   170  			if strings.HasPrefix(spec, value) {
   171  				return value
   172  			}
   173  		}
   174  	}
   175  	return ""
   176  }
   177  
   178  // AcceptsLanguages checks if the specified language is acceptable.
   179  func (ctx *Ctx) AcceptsLanguages(offers ...string) (offer string) {
   180  	if len(offers) == 0 {
   181  		return ""
   182  	}
   183  	h := ctx.GetHeader("Accept-Language")
   184  	if h == "" {
   185  		return offers[0]
   186  	}
   187  
   188  	specs := strings.Split(h, ",")
   189  	for _, value := range offers {
   190  		for _, spec := range specs {
   191  			spec = strings.TrimSpace(spec)
   192  			if strings.HasPrefix(spec, "*") {
   193  				return value
   194  			}
   195  			if strings.HasPrefix(spec, value) {
   196  				return value
   197  			}
   198  		}
   199  	}
   200  	return ""
   201  }
   202  
   203  // Append the specified value to the HTTP response header field.
   204  // If the header is not already set, it creates the header with the specified value.
   205  func (ctx *Ctx) Append(field string, values ...string) {
   206  	if len(values) == 0 {
   207  		return
   208  	}
   209  	h := GetString(ctx.C.Response.Header.Peek(field))
   210  	for i := range values {
   211  		if h == "" {
   212  			h += values[i]
   213  		} else {
   214  			h += ", " + values[i]
   215  		}
   216  	}
   217  	ctx.SetHeader(field, h)
   218  }
   219  
   220  // Attachment sets the HTTP response Content-Disposition header field to attachment.
   221  func (ctx *Ctx) Attachment(name ...string) {
   222  	if len(name) > 0 {
   223  		filename := filepath.Base(name[0])
   224  		ctx.Type(filepath.Ext(filename))
   225  		ctx.SetHeader("Content-Disposition", `attachment; filename="`+filename+`"`)
   226  		return
   227  	}
   228  	ctx.SetHeader("Content-Disposition", "attachment")
   229  }
   230  
   231  // BaseURL returns (protocol + host).
   232  func (ctx *Ctx) BaseURL() string {
   233  	return ctx.Protocol() + "://" + ctx.Hostname()
   234  }
   235  
   236  // Body contains the raw body submitted in a POST request.
   237  // If a key is provided, it returns the form value
   238  func (ctx *Ctx) Body(key ...string) string {
   239  	// Return request body
   240  	if len(key) == 0 {
   241  		return GetString(ctx.C.Request.Body())
   242  	}
   243  	// Return post value by key
   244  	if len(key) > 0 {
   245  		return GetString(ctx.C.Request.PostArgs().Peek(key[0]))
   246  	}
   247  	return ""
   248  }
   249  
   250  // BodyJson get a json body request on the Content-Type header: application/json.
   251  func (ctx *Ctx) BodyJson() f.Json {
   252  	ct := GetString(ctx.C.Request.Header.ContentType())
   253  	if strings.HasPrefix(ct, "application/json") && ctx.C.Request.IsBodyStream() {
   254  		if body := ctx.C.Request.Body(); body != nil {
   255  			return f.NewJson(GetString(body))
   256  		}
   257  	}
   258  	return ""
   259  }
   260  
   261  // BodyParser binds the request body to a struct.
   262  // It supports decoding the following content types based on the Content-Type header:
   263  // application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data
   264  func (ctx *Ctx) BodyParser(out interface{}) error {
   265  	// Query Params
   266  	ct := GetString(ctx.C.Request.Header.ContentType())
   267  	// application/json
   268  	if strings.HasPrefix(ct, "application/json") && ctx.C.Request.IsBodyStream() {
   269  		return json.Unmarshal(ctx.C.Request.Body(), out)
   270  	}
   271  	// application/xml text/xml
   272  	if strings.HasPrefix(ct, "application/xml") || strings.HasPrefix(ct, "text/xml") {
   273  		return xml.Unmarshal(ctx.C.Request.Body(), out)
   274  	}
   275  	// application/x-www-form-urlencoded
   276  	if strings.HasPrefix(ct, "application/x-www-form-urlencoded") {
   277  		data, err := url.ParseQuery(GetString(ctx.C.PostBody()))
   278  		if err != nil {
   279  			return err
   280  		}
   281  		return schemaDecoder.Decode(out, data)
   282  	}
   283  	// multipart/form-data
   284  	if strings.HasPrefix(ct, "multipart/form-data") {
   285  		data, err := ctx.C.MultipartForm()
   286  		if err != nil {
   287  			return err
   288  		}
   289  		return schemaDecoder.Decode(out, data.Value)
   290  	}
   291  	return fmt.Errorf("BodyParser: cannot parse content-type: %v", ct)
   292  }
   293  
   294  // ClearCookie expires a specific cookie by key.
   295  // If no key is provided it expires all cookies.
   296  func (ctx *Ctx) ClearCookie(key ...string) {
   297  	if len(key) > 0 {
   298  		for i := range key {
   299  			ctx.C.Response.Header.DelClientCookie(key[i])
   300  		}
   301  		return
   302  	}
   303  	//ctx.C.Response.Header.DelAllCookies()
   304  	ctx.C.Request.Header.VisitAllCookie(func(k, v []byte) {
   305  		ctx.C.Response.Header.DelClientCookie(GetString(k))
   306  	})
   307  }
   308  
   309  // Cookie sets a cookie by passing a cookie struct
   310  func (ctx *Ctx) Cookie(cookie *Cookie) {
   311  	c := &fasthttp.Cookie{}
   312  	c.SetKey(cookie.Name)
   313  	c.SetValue(cookie.Value)
   314  	c.SetPath(cookie.Path)
   315  	c.SetDomain(cookie.Domain)
   316  	c.SetExpire(cookie.Expires)
   317  	c.SetSecure(cookie.Secure)
   318  	if cookie.Secure {
   319  		// Secure must be paired with SameSite=None
   320  		c.SetSameSite(fasthttp.CookieSameSiteNoneMode)
   321  	}
   322  	c.SetHTTPOnly(cookie.HTTPOnly)
   323  	switch strings.ToLower(cookie.SameSite) {
   324  	case "lax":
   325  		c.SetSameSite(fasthttp.CookieSameSiteLaxMode)
   326  	case "strict":
   327  		c.SetSameSite(fasthttp.CookieSameSiteStrictMode)
   328  	case "none":
   329  		c.SetSameSite(fasthttp.CookieSameSiteNoneMode)
   330  		// Secure must be paired with SameSite=None
   331  		c.SetSecure(true)
   332  	default:
   333  		c.SetSameSite(fasthttp.CookieSameSiteDisabled)
   334  	}
   335  	ctx.C.Response.Header.SetCookie(c)
   336  }
   337  
   338  // Cookies is used for getting a cookie value by key
   339  func (ctx *Ctx) Cookies(key ...string) (value string) {
   340  	if len(key) == 0 {
   341  		return ctx.GetHeader("Cookie")
   342  	}
   343  	return GetString(ctx.C.Request.Header.Cookie(key[0]))
   344  }
   345  
   346  // Download transfers the file from path as an attachment.
   347  // Typically, browsers will prompt the user for download.
   348  // By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog).
   349  // Override this default with the filename parameter.
   350  func (ctx *Ctx) Download(file string, name ...string) {
   351  	filename := filepath.Base(file)
   352  
   353  	if len(name) > 0 {
   354  		filename = name[0]
   355  	}
   356  
   357  	ctx.SetHeader("Content-Disposition", "attachment; filename="+filename)
   358  	ctx.SendFile(file)
   359  }
   360  
   361  // Error contains the error information passed via the Next(err) method.
   362  func (ctx *Ctx) Error() error {
   363  	return ctx.err
   364  }
   365  
   366  // Format performs content-negotiation on the Accept HTTP header.
   367  // It uses Accepts to select a proper format.
   368  // If the header is not specified or there is no proper format, text/plain is used.
   369  func (ctx *Ctx) Format(body interface{}) {
   370  	var b string
   371  	accept := ctx.Accepts("html", "json")
   372  
   373  	switch val := body.(type) {
   374  	case string:
   375  		b = val
   376  	case []byte:
   377  		b = GetString(val)
   378  	default:
   379  		if t, ok := val.(fmt.Stringer); ok {
   380  			b = t.String()
   381  		} else {
   382  			b = fmt.Sprintf("%v", val)
   383  		}
   384  	}
   385  	switch accept {
   386  	case "html":
   387  		ctx.SendString(b)
   388  	case "json":
   389  		if err := ctx.JSON(body); err != nil {
   390  			log.Println("Format: error serializing json ", err)
   391  		}
   392  	default:
   393  		ctx.SendString(b)
   394  	}
   395  }
   396  
   397  // FormFile returns the first file by key from a MultipartForm.
   398  func (ctx *Ctx) FormFile(key string) (*multipart.FileHeader, error) {
   399  	return ctx.C.FormFile(key)
   400  }
   401  
   402  // FormValue returns the first value by key from a MultipartForm.
   403  func (ctx *Ctx) FormValue(key string) (value string) {
   404  	return GetString(ctx.C.FormValue(key))
   405  }
   406  
   407  // Fresh is not implemented yet, pull requests are welcome!
   408  func (ctx *Ctx) Fresh() bool {
   409  	return false
   410  }
   411  
   412  // Get makes it possible to pass interface{} values under string keys scoped to the request
   413  // and therefore available to all following routes that match the request.
   414  func (ctx *Ctx) Get(key string) (val interface{}) {
   415  	return ctx.C.UserValue(key)
   416  }
   417  
   418  // GetArg gets token from request(query url, post args, header authorization).
   419  // id := GetArg("id")
   420  func (ctx *Ctx) GetArg(key ...string) string {
   421  	k, j, l := "token", "Authorization", len(key)
   422  	if l > 0 {
   423  		k = key[0]
   424  		if l > 1 {
   425  			j = key[1]
   426  		}
   427  	}
   428  	if j != "" {
   429  		if token := ctx.C.Request.Header.Peek(j); token != nil {
   430  			t := strings.Split(GetString(token), " ")
   431  			return t[len(t)-1]
   432  		}
   433  	}
   434  	switch ctx.method {
   435  	case "POST", "PUT":
   436  		if token := ctx.C.Request.PostArgs().Peek(k); token != nil {
   437  			return GetString(token)
   438  		}
   439  	}
   440  	if token := ctx.C.QueryArgs().Peek(k); token != nil {
   441  		return GetString(token)
   442  	}
   443  	return ""
   444  }
   445  
   446  // GetHeader returns the HTTP request header specified by field.
   447  // Field names are case-insensitive.
   448  func (ctx *Ctx) GetHeader(key string) (value string) {
   449  	if key == "Referrer" {
   450  		key = "Referer"
   451  	}
   452  	return GetString(ctx.C.Request.Header.Peek(key))
   453  }
   454  
   455  // Hostname contains the hostname derived from the Host HTTP header.
   456  func (ctx *Ctx) Hostname() string {
   457  	return GetString(ctx.C.URI().Host())
   458  }
   459  
   460  // IP returns the remote IP address of the request.
   461  func (ctx *Ctx) IP() string {
   462  	return ctx.C.RemoteIP().String()
   463  }
   464  
   465  // IPs returns an string slice of IP addresses specified in the X-Forwarded-For request header.
   466  func (ctx *Ctx) IPs() []string {
   467  	ips := strings.Split(ctx.GetHeader("X-Forwarded-For"), ",")
   468  	for i := range ips {
   469  		ips[i] = strings.TrimSpace(ips[i])
   470  	}
   471  	return ips
   472  }
   473  
   474  // Is returns the matching content type,
   475  // if the incoming request’s Content-Type HTTP header field matches the MIME type specified by the type parameter
   476  func (ctx *Ctx) Is(extension string) (match bool) {
   477  	if extension[0] != '.' {
   478  		extension = "." + extension
   479  	}
   480  
   481  	items, _ := mime.ExtensionsByType(ctx.GetHeader("Content-Type"))
   482  	if len(items) > 0 {
   483  		for _, item := range items {
   484  			if item == extension {
   485  				return true
   486  			}
   487  		}
   488  	}
   489  	return
   490  }
   491  
   492  // JSON converts any interface or string to JSON.
   493  // This method also sets the content header to application/json.
   494  func (ctx *Ctx) JSON(json interface{}) error {
   495  	// GetHeader stream from pool
   496  	stream := jsonParser.BorrowStream(nil)
   497  	defer jsonParser.ReturnStream(stream)
   498  	// Write struct to stream
   499  	stream.WriteVal(&json)
   500  	// Check for errors
   501  	if stream.Error != nil {
   502  		return stream.Error
   503  	}
   504  	// SetHeader http headers
   505  	ctx.C.Response.Header.SetContentType("application/json")
   506  	ctx.C.Response.SetBodyString(GetString(stream.Buffer()))
   507  	// Success!
   508  	return nil
   509  }
   510  
   511  // JSONP sends a JSON response with JSONP support.
   512  // This method is identical to JSON, except that it opts-in to JSONP callback support.
   513  // By default, the callback name is simply callback.
   514  func (ctx *Ctx) JSONP(json interface{}, callback ...string) error {
   515  	// GetHeader stream from pool
   516  	stream := jsonParser.BorrowStream(nil)
   517  	defer jsonParser.ReturnStream(stream)
   518  	// Write struct to stream
   519  	stream.WriteVal(&json)
   520  	// Check for errors
   521  	if stream.Error != nil {
   522  		return stream.Error
   523  	}
   524  
   525  	str := "callback("
   526  	if len(callback) > 0 {
   527  		str = callback[0] + "("
   528  	}
   529  	str += GetString(stream.Buffer()) + ");"
   530  
   531  	ctx.SetHeader("X-Content-Type-Options", "nosniff")
   532  	ctx.C.Response.Header.SetContentType("application/javascript")
   533  	ctx.C.Response.SetBodyString(str)
   534  	return nil
   535  }
   536  
   537  // Links joins the links followed by the property to populate the response’s Link HTTP header field.
   538  func (ctx *Ctx) Links(link ...string) {
   539  	h := ""
   540  	for i, l := range link {
   541  		if i%2 == 0 {
   542  			h += "<" + l + ">"
   543  		} else {
   544  			h += `; rel="` + l + `",`
   545  		}
   546  	}
   547  
   548  	if len(link) > 0 {
   549  		h = strings.TrimSuffix(h, ",")
   550  		ctx.SetHeader("Link", h)
   551  	}
   552  }
   553  
   554  // Locals makes it possible to pass interface{} values under string keys scoped to the request
   555  // and therefore available to all following routes that match the request.
   556  func (ctx *Ctx) Locals(key string, value ...interface{}) (val interface{}) {
   557  	if len(value) == 0 {
   558  		return ctx.C.UserValue(key)
   559  	}
   560  	ctx.C.SetUserValue(key, value[0])
   561  	return value[0]
   562  }
   563  
   564  // Location sets the response Location HTTP header to the specified path parameter.
   565  func (ctx *Ctx) Location(path string) {
   566  	ctx.SetHeader("Location", path)
   567  }
   568  
   569  // Method contains a string corresponding to the HTTP method of the request: GET, POST, PUT and so on.
   570  func (ctx *Ctx) Method(override ...string) string {
   571  	if len(override) > 0 {
   572  		ctx.method = override[0]
   573  	}
   574  	return ctx.method
   575  }
   576  
   577  // MultipartForm parse form entries from binary.
   578  // This returns a map[string][]string, so given a key the value will be a string slice.
   579  func (ctx *Ctx) MultipartForm() (*multipart.Form, error) {
   580  	return ctx.C.MultipartForm()
   581  }
   582  
   583  // Next executes the next method in the stack that matches the current route.
   584  // You can pass an optional error for custom error handling.
   585  func (ctx *Ctx) Next(err ...error) {
   586  	ctx.route = nil
   587  	ctx.values = nil
   588  	if len(err) > 0 {
   589  		ctx.err = err[0]
   590  	}
   591  	ctx.app.nextRoute(ctx)
   592  }
   593  
   594  // OriginalURL contains the original request URL.
   595  func (ctx *Ctx) OriginalURL() string {
   596  	return GetString(ctx.C.Request.Header.RequestURI())
   597  }
   598  
   599  // Params is used to get the route parameters.
   600  // Defaults to empty string "", if the param doesn't exist.
   601  func (ctx *Ctx) Params(key string) (value string) {
   602  	if ctx.route.Params == nil {
   603  		return
   604  	}
   605  	for i := 0; i < len(ctx.route.Params); i++ {
   606  		if (ctx.route.Params)[i] == key {
   607  			return ctx.values[i]
   608  		}
   609  	}
   610  	return
   611  }
   612  
   613  // Path returns the path part of the request URL.
   614  // Optionally, you could override the path.
   615  func (ctx *Ctx) Path(override ...string) string {
   616  	if len(override) > 0 {
   617  		// Non strict routing
   618  		if !ctx.app.Settings.StrictRouting && len(override[0]) > 1 {
   619  			override[0] = strings.TrimRight(override[0], "/")
   620  		}
   621  		// Not case sensitive
   622  		if !ctx.app.Settings.CaseSensitive {
   623  			override[0] = strings.ToLower(override[0])
   624  		}
   625  		ctx.path = override[0]
   626  	}
   627  	return ctx.path
   628  }
   629  
   630  // Protocol contains the request protocol string: http or https for TLS requests.
   631  func (ctx *Ctx) Protocol() string {
   632  	if ctx.C.IsTLS() {
   633  		return "https"
   634  	}
   635  	return "http"
   636  }
   637  
   638  // Query returns the query string parameter in the url.
   639  func (ctx *Ctx) Query(key string) (value string) {
   640  	return GetString(ctx.C.QueryArgs().Peek(key))
   641  }
   642  
   643  // Range returns a struct containing the type and a slice of ranges.
   644  func (ctx *Ctx) Range(size int) (rangeData Range, err error) {
   645  	rangeStr := string(ctx.C.Request.Header.Peek("Range"))
   646  	if rangeStr == "" || !strings.Contains(rangeStr, "=") {
   647  		return rangeData, fmt.Errorf("malformed range header string")
   648  	}
   649  	data := strings.Split(rangeStr, "=")
   650  	rangeData.Type = data[0]
   651  	arr := strings.Split(data[1], ",")
   652  	for i := 0; i < len(arr); i++ {
   653  		item := strings.Split(arr[i], "-")
   654  		if len(item) == 1 {
   655  			return rangeData, fmt.Errorf("malformed range header string")
   656  		}
   657  		start, startErr := strconv.Atoi(item[0])
   658  		end, endErr := strconv.Atoi(item[1])
   659  		if startErr != nil { // -nnn
   660  			start = size - end
   661  			end = size - 1
   662  		} else if endErr != nil { // nnn-
   663  			end = size - 1
   664  		}
   665  		if end > size-1 { // limit last-byte-pos to current length
   666  			end = size - 1
   667  		}
   668  		if start > end || start < 0 {
   669  			continue
   670  		}
   671  		rangeData.Ranges = append(rangeData.Ranges, struct {
   672  			Start int
   673  			End   int
   674  		}{
   675  			start,
   676  			end,
   677  		})
   678  	}
   679  	if len(rangeData.Ranges) < 1 {
   680  		return rangeData, fmt.Errorf("unsatisfiable range")
   681  	}
   682  	return rangeData, nil
   683  }
   684  
   685  // Redirect to the URL derived from the specified path, with specified status.
   686  // If status is not specified, status defaults to 302 Found
   687  func (ctx *Ctx) Redirect(path string, status ...int) {
   688  	code := 302
   689  	if len(status) > 0 {
   690  		code = status[0]
   691  	}
   692  
   693  	ctx.SetHeader("Location", path)
   694  	ctx.C.Response.SetStatusCode(code)
   695  }
   696  
   697  // Render a template with data and sends a text/html response.
   698  // We support the following engines: html, amber, handlebars, mustache, pug
   699  func (ctx *Ctx) Render(file string, bind interface{}) error {
   700  	var err error
   701  	var raw []byte
   702  	var html string
   703  
   704  	if ctx.app.Settings.TemplateFolder != "" {
   705  		file = filepath.Join(ctx.app.Settings.TemplateFolder, file)
   706  	}
   707  	if ctx.app.Settings.TemplateExtension != "" {
   708  		file = file + ctx.app.Settings.TemplateExtension
   709  	}
   710  	if raw, err = f.ReadFile(filepath.Clean(file)); err != nil {
   711  		return err
   712  	}
   713  	if ctx.app.Settings.TemplateEngine != nil {
   714  		// Custom template engine
   715  		// https://github.com/valyala/quicktemplate
   716  		if html, err = ctx.app.Settings.TemplateEngine(GetString(raw), bind); err != nil {
   717  			return err
   718  		}
   719  	} else {
   720  		// Default template engine
   721  		// https://golang.org/pkg/text/template/
   722  		var buf bytes.Buffer
   723  		var tmpl *template.Template
   724  
   725  		if tmpl, err = template.New("").Parse(GetString(raw)); err != nil {
   726  			return err
   727  		}
   728  		if err = tmpl.Execute(&buf, bind); err != nil {
   729  			return err
   730  		}
   731  		html = buf.String()
   732  	}
   733  	ctx.SetHeader("Content-Type", "text/html")
   734  	ctx.SendString(html)
   735  	return err
   736  }
   737  
   738  // Route returns the matched Route struct.
   739  func (ctx *Ctx) Route() *Route {
   740  	return ctx.route
   741  }
   742  
   743  // SaveFile saves any multipart file to disk.
   744  func (ctx *Ctx) SaveFile(file *multipart.FileHeader, path string) error {
   745  	return fasthttp.SaveMultipartFile(file, path)
   746  }
   747  
   748  // Secure returns a boolean property, that is true, if a TLS connection is established.
   749  func (ctx *Ctx) Secure() bool {
   750  	return ctx.C.IsTLS()
   751  }
   752  
   753  // Send sets the HTTP response body. The Send body can be of any type.
   754  func (ctx *Ctx) Send(bodies ...interface{}) {
   755  	if len(bodies) > 0 {
   756  		ctx.C.Response.SetBodyString("")
   757  	}
   758  	for i := range bodies {
   759  		switch body := bodies[i].(type) {
   760  		case string:
   761  			ctx.C.Response.AppendBodyString(body)
   762  		case []byte:
   763  			ctx.C.Response.AppendBody(body) // .AppendBodyString(GetString(body))
   764  		default:
   765  			if t, ok := body.(fmt.Stringer); ok {
   766  				ctx.C.Response.AppendBodyString(t.String())
   767  			} else {
   768  				ctx.C.Response.AppendBodyString(fmt.Sprintf("%v", body))
   769  			}
   770  		}
   771  	}
   772  }
   773  
   774  // SendBytes sets the HTTP response body for []byte types
   775  // This means no type assertion, recommended for faster performance
   776  func (ctx *Ctx) SendBytes(body []byte) {
   777  	ctx.C.Response.SetBodyString(GetString(body))
   778  }
   779  
   780  // SendFile transfers the file from the given path.
   781  // The file is compressed by default
   782  // Sets the Content-Type response HTTP header field based on the filenames extension.
   783  func (ctx *Ctx) SendFile(file string, noCompression ...bool) {
   784  	// Disable gzip
   785  	if len(noCompression) > 0 && noCompression[0] {
   786  		fasthttp.ServeFileUncompressed(ctx.C, file)
   787  		return
   788  	}
   789  	fasthttp.ServeFile(ctx.C, file)
   790  }
   791  
   792  // SendStatus sets the HTTP status code and if the response body is empty,
   793  // it sets the correct status message in the body.
   794  func (ctx *Ctx) SendStatus(status int) {
   795  	ctx.C.Response.SetStatusCode(status)
   796  	// Only set status body when there is no response body
   797  	if len(ctx.C.Response.Body()) == 0 {
   798  		ctx.C.Response.SetBodyString(statusMessages[status])
   799  	}
   800  }
   801  
   802  // SendString sets the HTTP response body for string types
   803  // This means no type assertion, recommended for faster performance
   804  func (ctx *Ctx) SendString(body string) {
   805  	ctx.C.Response.SetBodyString(body)
   806  }
   807  
   808  // Set makes it possible to pass interface{} values under string keys scoped to the request
   809  // and therefore available to all following routes that match the request.
   810  func (ctx *Ctx) Set(key string, value interface{}) {
   811  	ctx.C.SetUserValue(key, value)
   812  	return
   813  }
   814  
   815  // SetCookie sets a cookie by passing a cookie struct
   816  func (ctx *Ctx) SetCookie(name, value string, expires time.Time, path, domain string, secure, httpOnly bool) {
   817  	ctx.Cookie(&Cookie{
   818  		Name:     name,
   819  		Value:    value,
   820  		Expires:  expires,
   821  		Path:     path,
   822  		Domain:   domain,
   823  		Secure:   secure,
   824  		HTTPOnly: httpOnly,
   825  	})
   826  }
   827  
   828  // SetHeader sets the response’s HTTP header field to the specified key, value.
   829  func (ctx *Ctx) SetHeader(key string, val string) {
   830  	ctx.C.Response.Header.Set(key, val)
   831  }
   832  
   833  // Subdomains returns a string of subdomains in the domain name of the request.
   834  // The sub-domain offset, which defaults to 2, is used for determining the beginning of the sub-domain segments.
   835  func (ctx *Ctx) Subdomains(offset ...int) []string {
   836  	o := 2
   837  	if len(offset) > 0 {
   838  		o = offset[0]
   839  	}
   840  	subdomains := strings.Split(ctx.Hostname(), ".")
   841  	subdomains = subdomains[:len(subdomains)-o]
   842  	return subdomains
   843  }
   844  
   845  // Stale is not implemented yet, pull requests are welcome!
   846  func (ctx *Ctx) Stale() bool {
   847  	return !ctx.Fresh()
   848  }
   849  
   850  // Status sets the HTTP status for the response.
   851  // This method is chain.
   852  func (ctx *Ctx) Status(status int) *Ctx {
   853  	ctx.C.Response.SetStatusCode(status)
   854  	return ctx
   855  }
   856  
   857  // Type sets the Content-Type HTTP header to the MIME type specified by the file extension.
   858  func (ctx *Ctx) Type(ext string) *Ctx {
   859  	ctx.C.Response.Header.SetContentType(getMIME(ext))
   860  	return ctx
   861  }
   862  
   863  // Vary adds the given header field to the Vary response header.
   864  // This will append the header, if not already listed, otherwise leaves it listed in the current location.
   865  func (ctx *Ctx) Vary(fields ...string) {
   866  	if len(fields) == 0 {
   867  		return
   868  	}
   869  
   870  	h := GetString(ctx.C.Response.Header.Peek("Vary"))
   871  	for i := range fields {
   872  		if h == "" {
   873  			h += fields[i]
   874  		} else {
   875  			h += ", " + fields[i]
   876  		}
   877  	}
   878  
   879  	ctx.SetHeader("Vary", h)
   880  }
   881  
   882  // Write appends any input to the HTTP body response.
   883  func (ctx *Ctx) Write(bodies ...interface{}) {
   884  	for i := range bodies {
   885  		switch body := bodies[i].(type) {
   886  		case string:
   887  			ctx.C.Response.AppendBodyString(body)
   888  		case []byte:
   889  			ctx.C.Response.AppendBody(body) // .AppendBodyString(GetString(body))
   890  		default:
   891  			if t, ok := body.(fmt.Stringer); ok {
   892  				ctx.C.Response.AppendBodyString(t.String())
   893  			} else {
   894  				ctx.C.Response.AppendBodyString(fmt.Sprintf("%v", body))
   895  			}
   896  		}
   897  	}
   898  }
   899  
   900  // XHR returns a Boolean property, that is true, if the request’s X-Requested-With header field is XMLHttpRequest,
   901  // indicating that the request was issued by a client library (such as jQuery).
   902  func (ctx *Ctx) XHR() bool {
   903  	return ctx.GetHeader("X-Requested-With") == "XMLHttpRequest"
   904  }
   905  
   906  // XSSProtection X-XSS-Protection...
   907  func (ctx *Ctx) XSSProtection() {
   908  	ctx.SetHeader("X-Content-Type-Options", "nosniff")
   909  	ctx.SetHeader("X-Frame-Options", "SAMEORIGIN") // or DENY
   910  	ctx.SetHeader("X-XSS-Protection", "1; mode=block")
   911  	if ctx.C.IsTLS() {
   912  		ctx.SetHeader("Strict-Transport-Security", "max-age=31536000")
   913  	}
   914  	// Also consider adding Content-Security-Policy headers
   915  	// c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
   916  }