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 }