github.com/astaxie/beego@v1.12.3/controller.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package beego 16 17 import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "html/template" 22 "io" 23 "mime/multipart" 24 "net/http" 25 "net/url" 26 "os" 27 "reflect" 28 "strconv" 29 "strings" 30 31 "github.com/astaxie/beego/context" 32 "github.com/astaxie/beego/context/param" 33 "github.com/astaxie/beego/session" 34 ) 35 36 var ( 37 // ErrAbort custom error when user stop request handler manually. 38 ErrAbort = errors.New("user stop run") 39 // GlobalControllerRouter store comments with controller. pkgpath+controller:comments 40 GlobalControllerRouter = make(map[string][]ControllerComments) 41 ) 42 43 // ControllerFilter store the filter for controller 44 type ControllerFilter struct { 45 Pattern string 46 Pos int 47 Filter FilterFunc 48 ReturnOnOutput bool 49 ResetParams bool 50 } 51 52 // ControllerFilterComments store the comment for controller level filter 53 type ControllerFilterComments struct { 54 Pattern string 55 Pos int 56 Filter string // NOQA 57 ReturnOnOutput bool 58 ResetParams bool 59 } 60 61 // ControllerImportComments store the import comment for controller needed 62 type ControllerImportComments struct { 63 ImportPath string 64 ImportAlias string 65 } 66 67 // ControllerComments store the comment for the controller method 68 type ControllerComments struct { 69 Method string 70 Router string 71 Filters []*ControllerFilter 72 ImportComments []*ControllerImportComments 73 FilterComments []*ControllerFilterComments 74 AllowHTTPMethods []string 75 Params []map[string]string 76 MethodParams []*param.MethodParam 77 } 78 79 // ControllerCommentsSlice implements the sort interface 80 type ControllerCommentsSlice []ControllerComments 81 82 func (p ControllerCommentsSlice) Len() int { return len(p) } 83 func (p ControllerCommentsSlice) Less(i, j int) bool { return p[i].Router < p[j].Router } 84 func (p ControllerCommentsSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 85 86 // Controller defines some basic http request handler operations, such as 87 // http context, template and view, session and xsrf. 88 type Controller struct { 89 // context data 90 Ctx *context.Context 91 Data map[interface{}]interface{} 92 93 // route controller info 94 controllerName string 95 actionName string 96 methodMapping map[string]func() //method:routertree 97 AppController interface{} 98 99 // template data 100 TplName string 101 ViewPath string 102 Layout string 103 LayoutSections map[string]string // the key is the section name and the value is the template name 104 TplPrefix string 105 TplExt string 106 EnableRender bool 107 108 // xsrf data 109 _xsrfToken string 110 XSRFExpire int 111 EnableXSRF bool 112 113 // session 114 CruSession session.Store 115 } 116 117 // ControllerInterface is an interface to uniform all controller handler. 118 type ControllerInterface interface { 119 Init(ct *context.Context, controllerName, actionName string, app interface{}) 120 Prepare() 121 Get() 122 Post() 123 Delete() 124 Put() 125 Head() 126 Patch() 127 Options() 128 Trace() 129 Finish() 130 Render() error 131 XSRFToken() string 132 CheckXSRFCookie() bool 133 HandlerFunc(fn string) bool 134 URLMapping() 135 } 136 137 // Init generates default values of controller operations. 138 func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) { 139 c.Layout = "" 140 c.TplName = "" 141 c.controllerName = controllerName 142 c.actionName = actionName 143 c.Ctx = ctx 144 c.TplExt = "tpl" 145 c.AppController = app 146 c.EnableRender = true 147 c.EnableXSRF = true 148 c.Data = ctx.Input.Data() 149 c.methodMapping = make(map[string]func()) 150 } 151 152 // Prepare runs after Init before request function execution. 153 func (c *Controller) Prepare() {} 154 155 // Finish runs after request function execution. 156 func (c *Controller) Finish() {} 157 158 // Get adds a request function to handle GET request. 159 func (c *Controller) Get() { 160 http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 161 } 162 163 // Post adds a request function to handle POST request. 164 func (c *Controller) Post() { 165 http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 166 } 167 168 // Delete adds a request function to handle DELETE request. 169 func (c *Controller) Delete() { 170 http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 171 } 172 173 // Put adds a request function to handle PUT request. 174 func (c *Controller) Put() { 175 http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 176 } 177 178 // Head adds a request function to handle HEAD request. 179 func (c *Controller) Head() { 180 http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 181 } 182 183 // Patch adds a request function to handle PATCH request. 184 func (c *Controller) Patch() { 185 http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 186 } 187 188 // Options adds a request function to handle OPTIONS request. 189 func (c *Controller) Options() { 190 http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 191 } 192 193 // Trace adds a request function to handle Trace request. 194 // this method SHOULD NOT be overridden. 195 // https://tools.ietf.org/html/rfc7231#section-4.3.8 196 // The TRACE method requests a remote, application-level loop-back of 197 // the request message. The final recipient of the request SHOULD 198 // reflect the message received, excluding some fields described below, 199 // back to the client as the message body of a 200 (OK) response with a 200 // Content-Type of "message/http" (Section 8.3.1 of [RFC7230]). 201 func (c *Controller) Trace() { 202 ts := func(h http.Header) (hs string) { 203 for k, v := range h { 204 hs += fmt.Sprintf("\r\n%s: %s", k, v) 205 } 206 return 207 } 208 hs := fmt.Sprintf("\r\nTRACE %s %s%s\r\n", c.Ctx.Request.RequestURI, c.Ctx.Request.Proto, ts(c.Ctx.Request.Header)) 209 c.Ctx.Output.Header("Content-Type", "message/http") 210 c.Ctx.Output.Header("Content-Length", fmt.Sprint(len(hs))) 211 c.Ctx.Output.Header("Cache-Control", "no-cache, no-store, must-revalidate") 212 c.Ctx.WriteString(hs) 213 } 214 215 // HandlerFunc call function with the name 216 func (c *Controller) HandlerFunc(fnname string) bool { 217 if v, ok := c.methodMapping[fnname]; ok { 218 v() 219 return true 220 } 221 return false 222 } 223 224 // URLMapping register the internal Controller router. 225 func (c *Controller) URLMapping() {} 226 227 // Mapping the method to function 228 func (c *Controller) Mapping(method string, fn func()) { 229 c.methodMapping[method] = fn 230 } 231 232 // Render sends the response with rendered template bytes as text/html type. 233 func (c *Controller) Render() error { 234 if !c.EnableRender { 235 return nil 236 } 237 rb, err := c.RenderBytes() 238 if err != nil { 239 return err 240 } 241 242 if c.Ctx.ResponseWriter.Header().Get("Content-Type") == "" { 243 c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8") 244 } 245 246 return c.Ctx.Output.Body(rb) 247 } 248 249 // RenderString returns the rendered template string. Do not send out response. 250 func (c *Controller) RenderString() (string, error) { 251 b, e := c.RenderBytes() 252 return string(b), e 253 } 254 255 // RenderBytes returns the bytes of rendered template string. Do not send out response. 256 func (c *Controller) RenderBytes() ([]byte, error) { 257 buf, err := c.renderTemplate() 258 //if the controller has set layout, then first get the tplName's content set the content to the layout 259 if err == nil && c.Layout != "" { 260 c.Data["LayoutContent"] = template.HTML(buf.String()) 261 262 if c.LayoutSections != nil { 263 for sectionName, sectionTpl := range c.LayoutSections { 264 if sectionTpl == "" { 265 c.Data[sectionName] = "" 266 continue 267 } 268 buf.Reset() 269 err = ExecuteViewPathTemplate(&buf, sectionTpl, c.viewPath(), c.Data) 270 if err != nil { 271 return nil, err 272 } 273 c.Data[sectionName] = template.HTML(buf.String()) 274 } 275 } 276 277 buf.Reset() 278 ExecuteViewPathTemplate(&buf, c.Layout, c.viewPath(), c.Data) 279 } 280 return buf.Bytes(), err 281 } 282 283 func (c *Controller) renderTemplate() (bytes.Buffer, error) { 284 var buf bytes.Buffer 285 if c.TplName == "" { 286 c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt 287 } 288 if c.TplPrefix != "" { 289 c.TplName = c.TplPrefix + c.TplName 290 } 291 if BConfig.RunMode == DEV { 292 buildFiles := []string{c.TplName} 293 if c.Layout != "" { 294 buildFiles = append(buildFiles, c.Layout) 295 if c.LayoutSections != nil { 296 for _, sectionTpl := range c.LayoutSections { 297 if sectionTpl == "" { 298 continue 299 } 300 buildFiles = append(buildFiles, sectionTpl) 301 } 302 } 303 } 304 BuildTemplate(c.viewPath(), buildFiles...) 305 } 306 return buf, ExecuteViewPathTemplate(&buf, c.TplName, c.viewPath(), c.Data) 307 } 308 309 func (c *Controller) viewPath() string { 310 if c.ViewPath == "" { 311 return BConfig.WebConfig.ViewsPath 312 } 313 return c.ViewPath 314 } 315 316 // Redirect sends the redirection response to url with status code. 317 func (c *Controller) Redirect(url string, code int) { 318 LogAccess(c.Ctx, nil, code) 319 c.Ctx.Redirect(code, url) 320 } 321 322 // SetData set the data depending on the accepted 323 func (c *Controller) SetData(data interface{}) { 324 accept := c.Ctx.Input.Header("Accept") 325 switch accept { 326 case context.ApplicationYAML: 327 c.Data["yaml"] = data 328 case context.ApplicationXML, context.TextXML: 329 c.Data["xml"] = data 330 default: 331 c.Data["json"] = data 332 } 333 } 334 335 // Abort stops controller handler and show the error data if code is defined in ErrorMap or code string. 336 func (c *Controller) Abort(code string) { 337 status, err := strconv.Atoi(code) 338 if err != nil { 339 status = 200 340 } 341 c.CustomAbort(status, code) 342 } 343 344 // CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body. 345 func (c *Controller) CustomAbort(status int, body string) { 346 // first panic from ErrorMaps, it is user defined error functions. 347 if _, ok := ErrorMaps[body]; ok { 348 c.Ctx.Output.Status = status 349 panic(body) 350 } 351 // last panic user string 352 c.Ctx.ResponseWriter.WriteHeader(status) 353 c.Ctx.ResponseWriter.Write([]byte(body)) 354 panic(ErrAbort) 355 } 356 357 // StopRun makes panic of USERSTOPRUN error and go to recover function if defined. 358 func (c *Controller) StopRun() { 359 panic(ErrAbort) 360 } 361 362 // URLFor does another controller handler in this request function. 363 // it goes to this controller method if endpoint is not clear. 364 func (c *Controller) URLFor(endpoint string, values ...interface{}) string { 365 if len(endpoint) == 0 { 366 return "" 367 } 368 if endpoint[0] == '.' { 369 return URLFor(reflect.Indirect(reflect.ValueOf(c.AppController)).Type().Name()+endpoint, values...) 370 } 371 return URLFor(endpoint, values...) 372 } 373 374 // ServeJSON sends a json response with encoding charset. 375 func (c *Controller) ServeJSON(encoding ...bool) { 376 var ( 377 hasIndent = BConfig.RunMode != PROD 378 hasEncoding = len(encoding) > 0 && encoding[0] 379 ) 380 381 c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding) 382 } 383 384 // ServeJSONP sends a jsonp response. 385 func (c *Controller) ServeJSONP() { 386 hasIndent := BConfig.RunMode != PROD 387 c.Ctx.Output.JSONP(c.Data["jsonp"], hasIndent) 388 } 389 390 // ServeXML sends xml response. 391 func (c *Controller) ServeXML() { 392 hasIndent := BConfig.RunMode != PROD 393 c.Ctx.Output.XML(c.Data["xml"], hasIndent) 394 } 395 396 // ServeYAML sends yaml response. 397 func (c *Controller) ServeYAML() { 398 c.Ctx.Output.YAML(c.Data["yaml"]) 399 } 400 401 // ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header 402 func (c *Controller) ServeFormatted(encoding ...bool) { 403 hasIndent := BConfig.RunMode != PROD 404 hasEncoding := len(encoding) > 0 && encoding[0] 405 c.Ctx.Output.ServeFormatted(c.Data, hasIndent, hasEncoding) 406 } 407 408 // Input returns the input data map from POST or PUT request body and query string. 409 func (c *Controller) Input() url.Values { 410 if c.Ctx.Request.Form == nil { 411 c.Ctx.Request.ParseForm() 412 } 413 return c.Ctx.Request.Form 414 } 415 416 // ParseForm maps input data map to obj struct. 417 func (c *Controller) ParseForm(obj interface{}) error { 418 return ParseForm(c.Input(), obj) 419 } 420 421 // GetString returns the input value by key string or the default value while it's present and input is blank 422 func (c *Controller) GetString(key string, def ...string) string { 423 if v := c.Ctx.Input.Query(key); v != "" { 424 return v 425 } 426 if len(def) > 0 { 427 return def[0] 428 } 429 return "" 430 } 431 432 // GetStrings returns the input string slice by key string or the default value while it's present and input is blank 433 // it's designed for multi-value input field such as checkbox(input[type=checkbox]), multi-selection. 434 func (c *Controller) GetStrings(key string, def ...[]string) []string { 435 var defv []string 436 if len(def) > 0 { 437 defv = def[0] 438 } 439 440 if f := c.Input(); f == nil { 441 return defv 442 } else if vs := f[key]; len(vs) > 0 { 443 return vs 444 } 445 446 return defv 447 } 448 449 // GetInt returns input as an int or the default value while it's present and input is blank 450 func (c *Controller) GetInt(key string, def ...int) (int, error) { 451 strv := c.Ctx.Input.Query(key) 452 if len(strv) == 0 && len(def) > 0 { 453 return def[0], nil 454 } 455 return strconv.Atoi(strv) 456 } 457 458 // GetInt8 return input as an int8 or the default value while it's present and input is blank 459 func (c *Controller) GetInt8(key string, def ...int8) (int8, error) { 460 strv := c.Ctx.Input.Query(key) 461 if len(strv) == 0 && len(def) > 0 { 462 return def[0], nil 463 } 464 i64, err := strconv.ParseInt(strv, 10, 8) 465 return int8(i64), err 466 } 467 468 // GetUint8 return input as an uint8 or the default value while it's present and input is blank 469 func (c *Controller) GetUint8(key string, def ...uint8) (uint8, error) { 470 strv := c.Ctx.Input.Query(key) 471 if len(strv) == 0 && len(def) > 0 { 472 return def[0], nil 473 } 474 u64, err := strconv.ParseUint(strv, 10, 8) 475 return uint8(u64), err 476 } 477 478 // GetInt16 returns input as an int16 or the default value while it's present and input is blank 479 func (c *Controller) GetInt16(key string, def ...int16) (int16, error) { 480 strv := c.Ctx.Input.Query(key) 481 if len(strv) == 0 && len(def) > 0 { 482 return def[0], nil 483 } 484 i64, err := strconv.ParseInt(strv, 10, 16) 485 return int16(i64), err 486 } 487 488 // GetUint16 returns input as an uint16 or the default value while it's present and input is blank 489 func (c *Controller) GetUint16(key string, def ...uint16) (uint16, error) { 490 strv := c.Ctx.Input.Query(key) 491 if len(strv) == 0 && len(def) > 0 { 492 return def[0], nil 493 } 494 u64, err := strconv.ParseUint(strv, 10, 16) 495 return uint16(u64), err 496 } 497 498 // GetInt32 returns input as an int32 or the default value while it's present and input is blank 499 func (c *Controller) GetInt32(key string, def ...int32) (int32, error) { 500 strv := c.Ctx.Input.Query(key) 501 if len(strv) == 0 && len(def) > 0 { 502 return def[0], nil 503 } 504 i64, err := strconv.ParseInt(strv, 10, 32) 505 return int32(i64), err 506 } 507 508 // GetUint32 returns input as an uint32 or the default value while it's present and input is blank 509 func (c *Controller) GetUint32(key string, def ...uint32) (uint32, error) { 510 strv := c.Ctx.Input.Query(key) 511 if len(strv) == 0 && len(def) > 0 { 512 return def[0], nil 513 } 514 u64, err := strconv.ParseUint(strv, 10, 32) 515 return uint32(u64), err 516 } 517 518 // GetInt64 returns input value as int64 or the default value while it's present and input is blank. 519 func (c *Controller) GetInt64(key string, def ...int64) (int64, error) { 520 strv := c.Ctx.Input.Query(key) 521 if len(strv) == 0 && len(def) > 0 { 522 return def[0], nil 523 } 524 return strconv.ParseInt(strv, 10, 64) 525 } 526 527 // GetUint64 returns input value as uint64 or the default value while it's present and input is blank. 528 func (c *Controller) GetUint64(key string, def ...uint64) (uint64, error) { 529 strv := c.Ctx.Input.Query(key) 530 if len(strv) == 0 && len(def) > 0 { 531 return def[0], nil 532 } 533 return strconv.ParseUint(strv, 10, 64) 534 } 535 536 // GetBool returns input value as bool or the default value while it's present and input is blank. 537 func (c *Controller) GetBool(key string, def ...bool) (bool, error) { 538 strv := c.Ctx.Input.Query(key) 539 if len(strv) == 0 && len(def) > 0 { 540 return def[0], nil 541 } 542 return strconv.ParseBool(strv) 543 } 544 545 // GetFloat returns input value as float64 or the default value while it's present and input is blank. 546 func (c *Controller) GetFloat(key string, def ...float64) (float64, error) { 547 strv := c.Ctx.Input.Query(key) 548 if len(strv) == 0 && len(def) > 0 { 549 return def[0], nil 550 } 551 return strconv.ParseFloat(strv, 64) 552 } 553 554 // GetFile returns the file data in file upload field named as key. 555 // it returns the first one of multi-uploaded files. 556 func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader, error) { 557 return c.Ctx.Request.FormFile(key) 558 } 559 560 // GetFiles return multi-upload files 561 // files, err:=c.GetFiles("myfiles") 562 // if err != nil { 563 // http.Error(w, err.Error(), http.StatusNoContent) 564 // return 565 // } 566 // for i, _ := range files { 567 // //for each fileheader, get a handle to the actual file 568 // file, err := files[i].Open() 569 // defer file.Close() 570 // if err != nil { 571 // http.Error(w, err.Error(), http.StatusInternalServerError) 572 // return 573 // } 574 // //create destination file making sure the path is writeable. 575 // dst, err := os.Create("upload/" + files[i].Filename) 576 // defer dst.Close() 577 // if err != nil { 578 // http.Error(w, err.Error(), http.StatusInternalServerError) 579 // return 580 // } 581 // //copy the uploaded file to the destination file 582 // if _, err := io.Copy(dst, file); err != nil { 583 // http.Error(w, err.Error(), http.StatusInternalServerError) 584 // return 585 // } 586 // } 587 func (c *Controller) GetFiles(key string) ([]*multipart.FileHeader, error) { 588 if files, ok := c.Ctx.Request.MultipartForm.File[key]; ok { 589 return files, nil 590 } 591 return nil, http.ErrMissingFile 592 } 593 594 // SaveToFile saves uploaded file to new path. 595 // it only operates the first one of mutil-upload form file field. 596 func (c *Controller) SaveToFile(fromfile, tofile string) error { 597 file, _, err := c.Ctx.Request.FormFile(fromfile) 598 if err != nil { 599 return err 600 } 601 defer file.Close() 602 f, err := os.OpenFile(tofile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 603 if err != nil { 604 return err 605 } 606 defer f.Close() 607 io.Copy(f, file) 608 return nil 609 } 610 611 // StartSession starts session and load old session data info this controller. 612 func (c *Controller) StartSession() session.Store { 613 if c.CruSession == nil { 614 c.CruSession = c.Ctx.Input.CruSession 615 } 616 return c.CruSession 617 } 618 619 // SetSession puts value into session. 620 func (c *Controller) SetSession(name interface{}, value interface{}) { 621 if c.CruSession == nil { 622 c.StartSession() 623 } 624 c.CruSession.Set(name, value) 625 } 626 627 // GetSession gets value from session. 628 func (c *Controller) GetSession(name interface{}) interface{} { 629 if c.CruSession == nil { 630 c.StartSession() 631 } 632 return c.CruSession.Get(name) 633 } 634 635 // DelSession removes value from session. 636 func (c *Controller) DelSession(name interface{}) { 637 if c.CruSession == nil { 638 c.StartSession() 639 } 640 c.CruSession.Delete(name) 641 } 642 643 // SessionRegenerateID regenerates session id for this session. 644 // the session data have no changes. 645 func (c *Controller) SessionRegenerateID() { 646 if c.CruSession != nil { 647 c.CruSession.SessionRelease(c.Ctx.ResponseWriter) 648 } 649 c.CruSession = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request) 650 c.Ctx.Input.CruSession = c.CruSession 651 } 652 653 // DestroySession cleans session data and session cookie. 654 func (c *Controller) DestroySession() { 655 c.Ctx.Input.CruSession.Flush() 656 c.Ctx.Input.CruSession = nil 657 GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request) 658 } 659 660 // IsAjax returns this request is ajax or not. 661 func (c *Controller) IsAjax() bool { 662 return c.Ctx.Input.IsAjax() 663 } 664 665 // GetSecureCookie returns decoded cookie value from encoded browser cookie values. 666 func (c *Controller) GetSecureCookie(Secret, key string) (string, bool) { 667 return c.Ctx.GetSecureCookie(Secret, key) 668 } 669 670 // SetSecureCookie puts value into cookie after encoded the value. 671 func (c *Controller) SetSecureCookie(Secret, name, value string, others ...interface{}) { 672 c.Ctx.SetSecureCookie(Secret, name, value, others...) 673 } 674 675 // XSRFToken creates a CSRF token string and returns. 676 func (c *Controller) XSRFToken() string { 677 if c._xsrfToken == "" { 678 expire := int64(BConfig.WebConfig.XSRFExpire) 679 if c.XSRFExpire > 0 { 680 expire = int64(c.XSRFExpire) 681 } 682 c._xsrfToken = c.Ctx.XSRFToken(BConfig.WebConfig.XSRFKey, expire) 683 } 684 return c._xsrfToken 685 } 686 687 // CheckXSRFCookie checks xsrf token in this request is valid or not. 688 // the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" 689 // or in form field value named as "_xsrf". 690 func (c *Controller) CheckXSRFCookie() bool { 691 if !c.EnableXSRF { 692 return true 693 } 694 return c.Ctx.CheckXSRFCookie() 695 } 696 697 // XSRFFormHTML writes an input field contains xsrf token value. 698 func (c *Controller) XSRFFormHTML() string { 699 return `<input type="hidden" name="_xsrf" value="` + 700 c.XSRFToken() + `" />` 701 } 702 703 // GetControllerAndAction gets the executing controller name and action name. 704 func (c *Controller) GetControllerAndAction() (string, string) { 705 return c.controllerName, c.actionName 706 }