/src/fastweb/fastweb.go
Go | 1004 lines | 873 code | 124 blank | 7 comment | 201 complexity | bc8730bf888b79b24bb644ed9845558d MD5 | raw file
1// vim: set syntax=go autoindent: 2// Copyright 2010 Ivan Wong. All rights reserved. 3// Use of this source code is governed by a BSD-style 4// license that can be found in the LICENSE file. 5package fastweb 6 7import ( 8 "bufio" 9 "bytes" 10 "container/vector" 11 "go-fastcgi.googlecode.com/svn/trunk/src/fastcgi" 12 "fmt" 13 "url" 14 "io" 15 "io/ioutil" 16 "log" 17 "os" 18 "rand" 19 "reflect" 20 "regexp" 21 "strconv" 22 "strings" 23 "time" 24 "unsafe" 25) 26 27const ( 28 IntParam = 1 29 StrParam = 2 30) 31 32type ControllerInterface interface { 33 Init() 34 DefaultAction() string 35 SetEnv(env *env) 36 PreFilter() 37 Render() 38 SetContext(ctxt ControllerInterface) 39 StartSession() 40 CloseSession() 41} 42 43type Error interface { 44 os.Error 45 Type() string 46} 47 48type ErrorStruct struct { 49 typ string 50 message string 51} 52 53type methodInfo struct { 54 name string 55 method reflect.Value 56 nparams int 57 paramTypes []int 58} 59 60type controllerInfo struct { 61 name string 62 controller ControllerInterface 63 controllerType reflect.Type 64 controllerPtrType reflect.Type 65 methodMap map[string]*methodInfo 66} 67 68type Application struct { 69 controllerMap map[string]*controllerInfo 70 defaultController string 71} 72 73type env struct { 74 path string 75 controller string 76 lcontroller string 77 action string 78 laction string 79 params []string 80 request *fastcgi.Request 81 body string 82 form map[string][]string 83 upload map[string][](*Upload) 84 cookies map[string]string 85} 86 87type Upload struct { 88 File *os.File 89 Filename string 90} 91 92type cookie struct { 93 value string 94 expire *time.Time 95 path string 96 domain string 97 secure bool 98 httpOnly bool 99} 100 101type Controller struct { 102 Path string 103 Name string 104 LName string 105 Action string 106 LAction string 107 Params []string 108 PageTitle string 109 Layout string 110 ContentType string 111 Body string 112 Form map[string][]string 113 Upload map[string][]*Upload 114 Cookies map[string]string 115 Session *Session 116 setCookies map[string]*cookie 117 ctxt ControllerInterface 118 Request *fastcgi.Request 119 preRenered bool 120} 121 122func NewError(typ string, message string) *ErrorStruct { 123 return &ErrorStruct{typ, message} 124} 125 126func (e *ErrorStruct) String() string { return e.message } 127 128func (e *ErrorStruct) Type() string { return e.typ } 129 130func (c *Controller) Init() { 131 c.PageTitle = "" 132 c.Layout = "default" 133 c.ContentType = "text/html; charset=utf-8" 134} 135 136func (c *Controller) DefaultAction() string { return "Index" } 137 138func (c *Controller) SetEnv(env *env) { 139 c.Name = env.controller 140 c.LName = env.lcontroller 141 c.Action = env.action 142 c.LAction = env.laction 143 c.Path = env.path 144 c.Params = env.params 145 c.Request = env.request 146 c.Body = env.body 147 c.Form = env.form 148 c.Upload = env.upload 149 c.Cookies = env.cookies 150} 151 152func (c *Controller) PreFilter() {} 153 154type tmplInfo struct { 155 tmpl *Template 156 mtime int64 157} 158 159var tmplCache map[string]*tmplInfo = make(map[string]*tmplInfo) 160 161func loadTemplate(fname string) (*Template, os.Error) { 162 dir, e := os.Stat(fname) 163 if e != nil { 164 return nil, e 165 } 166 if !dir.IsRegular() { 167 return nil, NewError("Generic", "'"+fname+"' is not a regular file") 168 } 169 ti, _ := tmplCache[fname] 170 if ti == nil || dir.Mtime_ns > ti.mtime { 171 bytes, e := ioutil.ReadFile(fname) 172 if e != nil { 173 return nil, e 174 } 175 t, e := Parse(string(bytes), nil) 176 if e != nil { 177 return nil, e 178 } 179 ti = &tmplInfo{ 180 mtime: dir.Mtime_ns, 181 tmpl: t, 182 } 183 tmplCache[fname] = ti 184 } 185 return ti.tmpl, nil 186} 187 188func (c *Controller) SetContext(ctxt ControllerInterface) { 189 if c.ctxt == nil { 190 c.ctxt = ctxt 191 } 192} 193 194func (c *Controller) preRender() { 195 if !c.preRenered { 196 io.WriteString(c.Request.Stdout, "Content-Type: "+c.ContentType+"\r\n") 197 198 if c.setCookies != nil { 199 for k, ck := range c.setCookies { 200 s := "Set-Cookie: " + k + "=" + url.QueryEscape(ck.value) 201 if ck.expire != nil { 202 s += "; expire=" + ck.expire.Format(time.RFC1123) 203 } 204 if ck.path != "" { 205 s += "; path=" + ck.path 206 } 207 if ck.domain != "" { 208 s += "; path=" + ck.domain 209 } 210 if ck.secure { 211 s += "; secure" 212 } 213 if ck.httpOnly { 214 s += "; HttpOnly" 215 } 216 io.WriteString(c.Request.Stdout, s+"\r\n") 217 } 218 } 219 220 io.WriteString(c.Request.Stdout, "\r\n") 221 c.preRenered = true 222 } 223} 224 225func executeTemplate(fname string, t *Template, w io.Writer, data interface{}) { 226 e := t.Execute(w, data) 227 if e != nil { 228 log.Printf("error occurred during executing template %s: %s", fname, e) 229 } 230} 231 232func (c *Controller) RenderContent() string { 233 c.preRender() 234 235 fname := "views/" + c.LName + "/" + c.LAction + ".tpl" 236 t, e := loadTemplate(fname) 237 if e != nil { 238 // Dump c.ctxt contents 239 } 240 241 if t != nil { 242 executeTemplate(fname, t, c.Request.Stdout, c.ctxt) 243 } 244 245 return "" 246} 247 248func (c *Controller) renderTemplate(fname string) { 249 t, e := loadTemplate(fname) 250 if e == nil { 251 executeTemplate(fname, t, c.Request.Stdout, c.ctxt) 252 } else { 253 log.Printf("failed to load template %s: %s", fname, e) 254 } 255} 256 257func (c *Controller) RenderElement(name string) string { 258 c.renderTemplate("views/elements/" + name + ".tpl") 259 return "" 260} 261 262func (c *Controller) RenderControllerElement(name string) string { 263 c.renderTemplate("views/" + c.LName + "/elements/" + name + ".tpl") 264 return "" 265} 266 267func (c *Controller) Render() { 268 c.preRender() 269 270 if len(c.Layout) == 0 { 271 c.RenderContent() 272 return 273 } 274 275 fname := "views/layouts/" + c.Layout + ".tpl" 276 t, e := loadTemplate(fname) 277 if e != nil { 278 log.Printf("failed to load layout template %s: %s", fname, e) 279 c.RenderContent() 280 } else { 281 executeTemplate(fname, t, c.Request.Stdout, c.ctxt) 282 } 283} 284 285func (c *Controller) SetCookie(key string, value string) { 286 c.SetCookieFull(key, value, nil, "", "", false, false) 287} 288 289func (c *Controller) SetCookieFull(key string, value string, expire *time.Time, path string, domain string, secure bool, httpOnly bool) { 290 if c.setCookies == nil { 291 c.setCookies = make(map[string]*cookie) 292 } 293 294 c.setCookies[key] = &cookie{ 295 value: value, 296 expire: expire, 297 path: path, 298 domain: domain, 299 secure: secure, 300 httpOnly: httpOnly, 301 } 302} 303 304func (c *Controller) StartSession() { 305 if c.Session != nil { 306 return 307 } 308 309 c.Session = GetSession(c) 310} 311 312func (c *Controller) CloseSession() { 313 if c.Session == nil { 314 return 315 } 316 317 c.Session.Close() 318} 319 320type ErrorHandler struct { 321 Controller 322 typ string 323} 324 325func NewErrorHandler(e Error, r *fastcgi.Request) *ErrorHandler { 326 eh := &ErrorHandler{ 327 typ: e.Type(), 328 } 329 eh.Request = r 330 eh.Init() 331 eh.SetContext(eh) 332 return eh 333} 334 335func (eh *ErrorHandler) RenderContent() string { 336 eh.preRender() 337 338 fname := "views/errors/" + eh.typ + ".tpl" 339 t, e := loadTemplate(fname) 340 if e != nil { 341 var msg string 342 switch eh.typ { 343 case "PageNotFound": 344 msg = "Hmm, the page youâre looking for can't be found." 345 default: 346 msg = "We're sorry, but there was an error processing your request. Please try again later." 347 } 348 fmt.Fprintf(eh.Request.Stdout, "%s", msg) 349 } else { 350 t.Execute(eh.Request.Stdout, eh) 351 executeTemplate(fname, t, eh.Request.Stdout, eh) 352 } 353 354 return "" 355} 356 357func titleCase(s string) string { 358 if len(s) > 0 { 359 parts := strings.Split(s, "_") 360 for i, p := range parts { 361 l := len(p) 362 if l > 0 { 363 parts[i] = strings.ToUpper(string(p[0])) + p[1:len(p)] 364 } else { 365 parts[i] = "" 366 } 367 } 368 return strings.Join(parts, "") 369 } 370 return s 371} 372 373func deTitleCase(s string) string { 374 if len(s) > 0 { 375 var o string 376 for i, c := range s { 377 if c >= 'A' && c <= 'Z' { 378 if i > 0 { 379 o += "_" 380 } 381 o += strings.ToLower(string(c)) 382 } else { 383 o += string(c) 384 } 385 } 386 return o 387 } 388 return s 389} 390 391func parseKeyValueString(m map[string]*vector.StringVector, s string) os.Error { 392 if s == "" { 393 return nil 394 } 395 396 // copied from pkg/http/request.go 397 for _, kv := range strings.Split(s, "&") { 398 if kv == "" { 399 continue 400 } 401 kvPair := strings.SplitN(kv, "=", 2) 402 403 var key, value string 404 var e os.Error 405 key, e = url.QueryUnescape(kvPair[0]) 406 if e == nil && len(kvPair) > 1 { 407 value, e = url.QueryUnescape(kvPair[1]) 408 } 409 if e != nil { 410 return e 411 } 412 413 vec, ok := m[key] 414 if !ok { 415 vec = new(vector.StringVector) 416 m[key] = vec 417 } 418 vec.Push(value) 419 } 420 421 return nil 422} 423 424var boundaryRE, _ = regexp.Compile("boundary=\"?([^\";,]+)\"?") 425 426type multipartReader struct { 427 rd io.Reader 428 bd []byte 429 buf []byte 430 head int 431 tail int 432 eof bool 433 done bool 434} 435 436func newMultipartReader(rd io.Reader, bd string) *multipartReader { 437 return &multipartReader{ 438 rd: rd, 439 bd: []byte("\r\n--" + bd), 440 buf: make([]byte, 4096), 441 head: 0, 442 tail: 0, 443 } 444} 445 446func (md *multipartReader) finished() bool { return md.done } 447 448func (md *multipartReader) read(delim []byte) ([]byte, os.Error) { 449 if md.done { 450 return nil, os.EOF 451 } 452 453 if !md.eof && md.tail < len(md.buf) { 454 n, e := md.rd.Read(md.buf[md.tail:len(md.buf)]) 455 if e != nil { 456 if e != os.EOF { 457 return nil, e 458 } 459 md.eof = true 460 } 461 md.tail += n 462 } 463 464 if i := bytes.Index(md.buf[md.head:md.tail], delim); i >= 0 { 465 s := make([]byte, i) 466 copy(s, md.buf[md.head:md.head+i]) 467 md.head += i + len(delim) 468 return s, os.EOF 469 } 470 471 if md.eof { 472 md.done = true 473 return md.buf[md.head:md.tail], os.EOF 474 } 475 476 bf := md.tail - md.head 477 keep := len(delim) - 1 478 if keep > bf { 479 keep = bf 480 } 481 stop := md.tail - keep 482 n := stop - md.head 483 s := make([]byte, n) 484 if n > 0 { 485 copy(s, md.buf[md.head:stop]) 486 } 487 488 copy(md.buf[0:keep], md.buf[stop:md.tail]) 489 md.head = 0 490 md.tail = keep 491 492 return s, nil 493} 494 495func (md *multipartReader) readFirstLine() { md.readStringUntil(md.bd[2:len(md.bd)], false) } 496 497var crlf2 = []byte{'\r', '\n', '\r', '\n'} 498 499type byteConsumer func([]byte) os.Error 500 501func (md *multipartReader) readUntil(delim []byte, checkEnd bool, f byteConsumer) os.Error { 502 for { 503 b, e := md.read(delim) 504 if b != nil { 505 if e := f(b); e != nil { 506 return e 507 } 508 } 509 if e != nil { 510 if e == os.EOF { 511 if checkEnd && md.tail-md.head >= 2 && string(md.buf[md.head:md.head+2]) == "--" { 512 md.done = true 513 } 514 break 515 } 516 return e 517 } 518 } 519 520 return nil 521} 522 523func (md *multipartReader) readStringUntil(delim []byte, checkEnd bool) (string, os.Error) { 524 var s string 525 e := md.readUntil(delim, checkEnd, func(b []byte) os.Error { 526 s += string(b) 527 return nil 528 }) 529 return s, e 530} 531 532type hdrInfo struct { 533 key string 534 val string 535 attribs map[string]string 536} 537 538func parseHeader(line string) *hdrInfo { 539 var key, attrib string 540 var hdr *hdrInfo 541 var attribs map[string]string 542 var j int 543 phase := 0 544 line += ";" 545 for i, c := range line { 546 switch phase { 547 case 0: 548 if c == ':' { 549 key = strings.TrimSpace(line[0:i]) 550 phase++ 551 j = i + 1 552 } 553 case 1: 554 if c == ';' { 555 attribs = make(map[string]string) 556 hdr = &hdrInfo{ 557 key: key, 558 val: strings.TrimSpace(line[j:i]), 559 attribs: attribs, 560 } 561 phase++ 562 j = i + 1 563 } 564 case 2: 565 if c == '=' { 566 attrib = strings.TrimSpace(line[j:i]) 567 phase++ 568 j = i + 1 569 } 570 case 3: 571 if c == '"' { 572 phase++ 573 j = i + 1 574 } else if c == ';' { 575 attribs[attrib] = strings.TrimSpace(line[j:i]) 576 phase = 2 577 j = i + 1 578 } 579 case 4: 580 if c == '\\' { 581 phase++ 582 } else if c == '"' { 583 attribs[attrib] = line[j:i] 584 phase += 2 585 } 586 case 5: 587 phase-- 588 case 6: 589 if c == ';' { 590 phase = 2 591 j = i + 1 592 } 593 } 594 } 595 return hdr 596} 597 598func (md *multipartReader) readHeaders() (map[string]*hdrInfo, os.Error) { 599 s, _ := md.readStringUntil(crlf2, false) 600 lines := strings.Split(s[2:len(s)], "\r\n") 601 hdrs := make(map[string]*hdrInfo) 602 for _, line := range lines { 603 if hdr := parseHeader(line); hdr != nil { 604 hdrs[hdr.key] = hdr 605 } 606 } 607 return hdrs, nil 608} 609 610func (md *multipartReader) readBody() (string, os.Error) { 611 return md.readStringUntil(md.bd, true) 612} 613 614var pads = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 615 616func tempfile() (*os.File, os.Error) { 617 tmpdir := os.Getenv("TMPDIR") 618 if tmpdir == "" { 619 tmpdir = "/tmp" 620 } 621 622 for { 623 var s string 624 for i := 0; i < 10; i++ { 625 s += string(pads[rand.Int()%len(pads)]) 626 } 627 file, e := os.OpenFile(tmpdir+"/fastweb."+s, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) 628 if e == nil { 629 return file, e 630 } 631 pe, ok := e.(*os.PathError) 632 if !ok || pe.Error != os.EEXIST { 633 return nil, e 634 } 635 } 636 637 return nil, nil 638} 639 640func parseMultipartForm(m map[string]*vector.StringVector, u map[string]*vector.Vector, r *fastcgi.Request) os.Error { 641 ct := r.Params["CONTENT_TYPE"] 642 a := boundaryRE.FindStringSubmatchIndex(ct) 643 if len(a) < 4 { 644 return os.NewError("can't find boundary in content type") 645 } 646 b := ct[a[2]:a[3]] 647 md := newMultipartReader(r.Stdin, b) 648 md.readFirstLine() 649 for !md.finished() { 650 hdrs, e := md.readHeaders() 651 if e != nil { 652 return e 653 } 654 cd, ok := hdrs["Content-Disposition"] 655 if !ok { 656 return os.NewError("can't find Content-Disposition") 657 } 658 name, ok := cd.attribs["name"] 659 if !ok { 660 return os.NewError("can't find attrib 'name' in Content-Disposition") 661 } 662 filename, ok := cd.attribs["filename"] 663 if ok { 664 vec, ok := u[name] 665 if !ok { 666 vec = new(vector.Vector) 667 u[name] = vec 668 } 669 670 file, e := tempfile() 671 if e != nil { 672 return e 673 } 674 wr := bufio.NewWriter(file) 675 fname := file.Name() 676 md.readUntil(md.bd, true, func(b []byte) os.Error { 677 if _, e := wr.Write(b); e != nil { 678 return e 679 } 680 return nil 681 }) 682 wr.Flush() 683 // to flush (system) buffer, re-open immediately 684 file.Close() 685 file, _ = os.Open(fname) 686 687 vec.Push(&Upload{ 688 File: file, 689 Filename: filename, 690 }) 691 } else { 692 vec, ok := m[name] 693 if !ok { 694 vec = new(vector.StringVector) 695 m[name] = vec 696 } 697 s, e := md.readBody() 698 if e != nil { 699 return e 700 } 701 vec.Push(s) 702 } 703 } 704 return nil 705} 706 707func parseForm(r *fastcgi.Request) (string, map[string][]string, map[string][]*Upload, os.Error) { 708 m := make(map[string]*vector.StringVector) 709 u := make(map[string]*vector.Vector) 710 var body string 711 712 s := r.Params["QUERY_STRING"] 713 if s != "" { 714 e := parseKeyValueString(m, s) 715 if e != nil { 716 return body, nil, nil, e 717 } 718 } 719 720 if r.Params["REQUEST_METHOD"] == "POST" { 721 switch ct := r.Params["CONTENT_TYPE"]; true { 722 case strings.HasPrefix(ct, "application/x-www-form-urlencoded") && (len(ct) == 33 || ct[33] == ';'): 723 var b []byte 724 var e os.Error 725 if b, e = ioutil.ReadAll(r.Stdin); e != nil { 726 return body, nil, nil, e 727 } 728 body = string(b) 729 e = parseKeyValueString(m, body) 730 if e != nil { 731 return body, nil, nil, e 732 } 733 case strings.HasPrefix(ct, "multipart/form-data"): 734 e := parseMultipartForm(m, u, r) 735 if e != nil { 736 return body, nil, nil, e 737 } 738 default: 739 log.Printf("unknown content type '%s'", ct) 740 } 741 } 742 743 form := make(map[string][]string) 744 for k, vec := range m { 745 form[k] = vec.Copy() 746 } 747 748 upload := make(map[string][]*Upload) 749 for k, vec := range u { 750 d := vec.Copy() 751 v := make([]*Upload, len(d)) 752 for i, u := range d { 753 v[i] = u.(*Upload) 754 } 755 upload[k] = v 756 } 757 758 return body, form, upload, nil 759} 760 761func parseCookies(r *fastcgi.Request) (map[string]string, os.Error) { 762 cookies := make(map[string]string) 763 764 if s, ok := r.Params["HTTP_COOKIE"]; ok { 765 var key string 766 phase := 0 767 j := 0 768 s += ";" 769 for i, c := range s { 770 switch phase { 771 case 0: 772 if c == '=' { 773 key = strings.TrimSpace(s[j:i]) 774 j = i + 1 775 phase++ 776 } 777 case 1: 778 if c == ';' { 779 v, e := url.QueryUnescape(s[j:i]) 780 if e != nil { 781 return cookies, e 782 } 783 cookies[key] = v 784 phase = 0 785 j = i + 1 786 } 787 } 788 } 789 } 790 791 return cookies, nil 792} 793 794func (a *Application) getEnv(r *fastcgi.Request) *env { 795 var params []string 796 var lname string 797 var laction string 798 799 path, _ := r.Params["REQUEST_URI"] 800 p := strings.SplitN(path, "?", 2) 801 if len(p) > 1 { 802 path = p[0] 803 r.Params["QUERY_STRING"] = p[1] 804 } 805 806 pparts := strings.Split(path, "/") 807 n := len(pparts) 808 if n > 1 { 809 lname = pparts[1] 810 if n > 2 { 811 laction = pparts[2] 812 if n > 3 { 813 if pparts[n-1] == "" { 814 n-- 815 } 816 params = pparts[3:n] 817 } 818 } 819 } 820 821 name := titleCase(lname) 822 action := titleCase(laction) 823 824 body, form, upload, e := parseForm(r) 825 if e != nil { 826 log.Printf("failed to parse form: %s", e.String()) 827 } 828 829 cookies, e := parseCookies(r) 830 if e != nil { 831 log.Printf("failed to parse cookies: %s", e.String()) 832 } 833 834 return &env{ 835 path: path, 836 controller: name, 837 lcontroller: lname, 838 action: action, 839 laction: laction, 840 params: params, 841 request: r, 842 body: body, 843 form: form, 844 upload: upload, 845 cookies: cookies, 846 } 847} 848 849func (a *Application) route(r *fastcgi.Request) os.Error { 850 env := a.getEnv(r) 851 852 if env.controller == "" { 853 env.controller = a.defaultController 854 env.lcontroller = deTitleCase(env.controller) 855 } 856 857 cinfo, _ := a.controllerMap[env.controller] 858 if cinfo == nil { 859 return NewError("PageNotFound", "controller class '"+env.controller+"' not found") 860 } 861 862 vc := reflect.New(cinfo.controllerType) 863 vi := reflect.New(cinfo.controllerPtrType).Elem() 864 vi.Set(vc.Elem().Addr()) 865 c := vi.Interface().(ControllerInterface) 866 867 if env.action == "" { 868 env.action = c.DefaultAction() 869 env.laction = deTitleCase(env.action) 870 } 871 872 minfo, _ := cinfo.methodMap[env.action] 873 if minfo == nil { 874 return NewError("PageNotFound", "action '"+env.action+"' is not implemented in controller '"+env.controller+"'") 875 } 876 877 if minfo.nparams > len(env.params) { 878 return NewError("PageNotFound", "not enough parameter") 879 } 880 881 pv := make([]reflect.Value, minfo.nparams+1) 882 pv[0] = vc 883 884 for i := 0; i < minfo.nparams; i++ { 885 p := env.params[i] 886 switch minfo.paramTypes[i] { 887 case StrParam: 888 pv[i+1] = reflect.ValueOf(p) 889 case IntParam: 890 x, e2 := strconv.Atoi(p) 891 if e2 != nil { 892 return NewError("PageNotFound", fmt.Sprintf("parameter %d must be an integer, input: %s", i+1, p)) 893 } 894 pv[i+1] = reflect.ValueOf(x) 895 } 896 } 897 898 c.Init() 899 c.SetEnv(env) 900 901 c.PreFilter() 902 903 eval := minfo.method.Call(pv)[0] 904 if !eval.IsNil() { 905 elemval := eval.Elem() 906 return unsafe.Unreflect(elemval.Type(), unsafe.Pointer(elemval.UnsafeAddr())).(os.Error) 907 } 908 909 c.SetContext(c) 910 c.Render() 911 912 c.CloseSession() 913 914 return nil 915} 916 917func (a *Application) Handle(r *fastcgi.Request) bool { 918 e := a.route(r) 919 920 if e != nil { 921 var ee Error 922 if e, ok := e.(Error); ok { 923 ee = e 924 } else { 925 ee = NewError("Generic", e.String()) 926 } 927 log.Printf("%s", e.String()) 928 eh := NewErrorHandler(ee, r) 929 eh.Render() 930 } 931 932 return true 933} 934 935func NewApplication() *Application { 936 return &Application{ 937 controllerMap: make(map[string]*controllerInfo), 938 defaultController: "Default", 939 } 940} 941 942func (a *Application) RegisterController(c ControllerInterface) { 943 v := reflect.ValueOf(c) 944 pt := v.Type() 945 t := v.Elem().Type() 946 name := t.Name() 947 948 mmap := make(map[string]*methodInfo) 949 950 n := pt.NumMethod() 951 for i := 0; i < n; i++ { 952 m := pt.Method(i) 953 name := m.Name 954 switch name { 955 case "SetEnv", "Render", "DefaultAction", "Init", "PreFilter", "SetContext": 956 continue 957 } 958 mt := m.Type 959 if mt.NumOut() != 1 { 960 continue 961 } 962 mo := mt.Out(0) 963 switch mo.Kind() { 964 case reflect.Interface: 965 if mo.PkgPath() != "os" || mo.Name() != "Error" { 966 continue 967 } 968 default: 969 continue 970 } 971 nin := mt.NumIn() - 1 972 ptypes := make([]int, nin) 973 for j := 0; j < nin; j++ { 974 in := mt.In(j + 1) 975 switch in.Kind() { 976 case reflect.Int: 977 ptypes[j] = IntParam 978 case reflect.String: 979 ptypes[j] = StrParam 980 default: 981 continue 982 } 983 } 984 mmap[name] = &methodInfo{ 985 name: name, 986 method: m.Func, 987 nparams: nin, 988 paramTypes: ptypes, 989 } 990 } 991 992 a.controllerMap[name] = &controllerInfo{ 993 name: name, 994 controller: c, 995 controllerType: t, 996 controllerPtrType: pt, 997 methodMap: mmap, 998 } 999} 1000 1001func (a *Application) Run(addr string) os.Error { 1002 rand.Seed(time.Nanoseconds()) 1003 return fastcgi.RunStandalone(addr, a) 1004}