PageRenderTime 213ms CodeModel.GetById 46ms app.highlight 151ms RepoModel.GetById 1ms app.codeStats 1ms

/src/fastweb/fastweb.go

http://go-fastweb.googlecode.com/
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}