Browse Source

增加go-xweb

wangchuanjin 4 years ago
parent
commit
19a08e8ee2
45 changed files with 7649 additions and 0 deletions
  1. 7 0
      go-xweb/httpsession/IsRedisStore.go
  2. 2 0
      go-xweb/httpsession/README.md
  3. 52 0
      go-xweb/httpsession/generator.go
  4. 35 0
      go-xweb/httpsession/listener.go
  5. 120 0
      go-xweb/httpsession/manager.go
  6. 255 0
      go-xweb/httpsession/memorystore.go
  7. 141 0
      go-xweb/httpsession/redissessionstore.go
  8. 60 0
      go-xweb/httpsession/session.go
  9. 19 0
      go-xweb/httpsession/store.go
  10. 147 0
      go-xweb/httpsession/transfer.go
  11. 11 0
      go-xweb/log/README.md
  12. 32 0
      go-xweb/log/dbwriter.go
  13. 636 0
      go-xweb/log/logext.go
  14. 84 0
      go-xweb/uuid/dce.go
  15. 8 0
      go-xweb/uuid/doc.go
  16. 53 0
      go-xweb/uuid/hash.go
  17. 101 0
      go-xweb/uuid/node.go
  18. 132 0
      go-xweb/uuid/time.go
  19. 43 0
      go-xweb/uuid/util.go
  20. 163 0
      go-xweb/uuid/uuid.go
  21. 41 0
      go-xweb/uuid/version1.go
  22. 25 0
      go-xweb/uuid/version4.go
  23. 58 0
      go-xweb/xweb/README.md
  24. 46 0
      go-xweb/xweb/README_EN.md
  25. 977 0
      go-xweb/xweb/action.go
  26. 1018 0
      go-xweb/xweb/app.go
  27. 12 0
      go-xweb/xweb/conversion.go
  28. 16 0
      go-xweb/xweb/doc.go
  29. 42 0
      go-xweb/xweb/error.go
  30. 27 0
      go-xweb/xweb/fcgi.go
  31. 93 0
      go-xweb/xweb/filter.go
  32. 181 0
      go-xweb/xweb/helpers.go
  33. 121 0
      go-xweb/xweb/hooks.go
  34. 223 0
      go-xweb/xweb/memzipfile.go
  35. 120 0
      go-xweb/xweb/profile.go
  36. 182 0
      go-xweb/xweb/scgi.go
  37. 362 0
      go-xweb/xweb/server.go
  38. 185 0
      go-xweb/xweb/static.go
  39. 54 0
      go-xweb/xweb/status.go
  40. 467 0
      go-xweb/xweb/template.go
  41. 137 0
      go-xweb/xweb/validation/README.md
  42. 238 0
      go-xweb/xweb/validation/util.go
  43. 348 0
      go-xweb/xweb/validation/validation.go
  44. 526 0
      go-xweb/xweb/validation/validators.go
  45. 49 0
      go-xweb/xweb/xweb.go

+ 7 - 0
go-xweb/httpsession/IsRedisStore.go

@@ -0,0 +1,7 @@
+// IsRedisStore
+package httpsession
+
+var (
+	IsRedisSessionStore  = false
+	RedisSessionLockSize = 20
+)

+ 2 - 0
go-xweb/httpsession/README.md

@@ -0,0 +1,2 @@
+Httpsession is an implementation of HTTP session for Go.
+

+ 52 - 0
go-xweb/httpsession/generator.go

@@ -0,0 +1,52 @@
+package httpsession
+
+import (
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha1"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"net/http"
+	"time"
+)
+
+type IdGenerator interface {
+	Gen(req *http.Request) Id
+	IsValid(id Id) bool
+}
+
+type Sha1Generator struct {
+	hashKey string
+}
+
+func NewSha1Generator(hashKey string) *Sha1Generator {
+	return &Sha1Generator{hashKey}
+}
+
+var _ IdGenerator = NewSha1Generator("test")
+
+func GenRandKey(strength int) []byte {
+	k := make([]byte, strength)
+	if _, err := io.ReadFull(rand.Reader, k); err != nil {
+		return nil
+	}
+	return k
+}
+
+func (gen *Sha1Generator) Gen(req *http.Request) Id {
+	bs := GenRandKey(24)
+	if len(bs) == 0 {
+		return Id("")
+	}
+
+	sig := fmt.Sprintf("%s%d%s", req.RemoteAddr, time.Now().UnixNano(), string(bs))
+
+	h := hmac.New(sha1.New, []byte(gen.hashKey))
+	fmt.Fprintf(h, "%s", sig)
+	return Id(hex.EncodeToString(h.Sum(nil)))
+}
+
+func (gen *Sha1Generator) IsValid(id Id) bool {
+	return string(id) != ""
+}

+ 35 - 0
go-xweb/httpsession/listener.go

@@ -0,0 +1,35 @@
+package httpsession
+
+import "errors"
+
+type AfterCreatedListener interface {
+	OnAfterCreated(*Session)
+}
+
+type BeforeReleaseListener interface {
+	OnBeforeRelease(*Session)
+}
+
+func (manager *Manager) AddListener(listener interface{}) error {
+	switch listener.(type) {
+	case AfterCreatedListener:
+		manager.afterCreatedListeners[listener.(AfterCreatedListener)] = true
+	case BeforeReleaseListener:
+		manager.beforeReleaseListeners[listener.(BeforeReleaseListener)] = true
+	default:
+		return errors.New("Unknow listener type")
+	}
+	return nil
+}
+
+func (manager *Manager) RemoveListener(listener interface{}) error {
+	switch listener.(type) {
+	case AfterCreatedListener:
+		delete(manager.afterCreatedListeners, listener.(AfterCreatedListener))
+	case BeforeReleaseListener:
+		delete(manager.beforeReleaseListeners, listener.(BeforeReleaseListener))
+	default:
+		return errors.New("Unknow listener type")
+	}
+	return nil
+}

+ 120 - 0
go-xweb/httpsession/manager.go

@@ -0,0 +1,120 @@
+package httpsession
+
+import (
+	"log"
+	"net/http"
+	"sync"
+	"time"
+)
+
+const (
+	DefaultMaxAge = 30 * time.Minute
+)
+
+var Domain string = ""
+
+type Manager struct {
+	store                  Store
+	maxAge                 time.Duration
+	Path                   string
+	generator              IdGenerator
+	transfer               Transfer
+	beforeReleaseListeners map[BeforeReleaseListener]bool
+	afterCreatedListeners  map[AfterCreatedListener]bool
+	lock                   sync.Mutex
+}
+
+func Default() *Manager {
+	log.Println("IsRedisSessionStore", IsRedisSessionStore)
+	if IsRedisSessionStore {
+		store := NewRedisStore(DefaultMaxAge)
+		key := string(GenRandKey(16))
+		return NewManager(store,
+			NewSha1Generator(key),
+			NewCookieTransfer("SESSIONID", DefaultMaxAge, false, "/", Domain))
+
+	} else {
+		store := NewMemoryStore(DefaultMaxAge)
+		key := string(GenRandKey(16))
+		return NewManager(store,
+			NewSha1Generator(key),
+			NewCookieTransfer("SESSIONID", DefaultMaxAge, false, "/", Domain))
+
+	}
+}
+
+func NewManager(store Store, gen IdGenerator, transfer Transfer) *Manager {
+	return &Manager{
+		store:     store,
+		generator: gen,
+		transfer:  transfer,
+	}
+}
+
+func (manager *Manager) SetMaxAge(maxAge time.Duration) {
+	manager.maxAge = maxAge
+	manager.transfer.SetMaxAge(maxAge)
+	manager.store.SetMaxAge(maxAge)
+}
+
+func (manager *Manager) Session(req *http.Request, rw http.ResponseWriter) *Session {
+	manager.lock.Lock()
+	defer manager.lock.Unlock()
+
+	id, err := manager.transfer.Get(req)
+	if err != nil {
+		// TODO:
+		println("error:", err.Error())
+		return nil
+	}
+
+	if !manager.generator.IsValid(id) {
+		id = manager.generator.Gen(req)
+		manager.transfer.Set(req, rw, id)
+		manager.store.Add(id)
+
+		session := NewSession(id, manager.maxAge, manager)
+		session.Set("Lock", &sync.Mutex{})
+		// is exist?
+		manager.afterCreated(session)
+		return session
+	} else {
+		cookie, _ := req.Cookie(manager.transfer.(*CookieTransfer).Name)
+		cookie.Path = "/"
+		cookie.Secure = false
+		cookie.MaxAge = int(manager.transfer.(*CookieTransfer).MaxAge / time.Second)
+		http.SetCookie(rw, cookie)
+	}
+	session := NewSession(id, manager.maxAge, manager)
+	//	if session.Get("Lock") == nil {
+	//		session.Set("Lock", &sync.Mutex{})
+	//	}
+	return session
+}
+
+func (manager *Manager) Invalidate(rw http.ResponseWriter, session *Session) {
+	manager.beforeReleased(session)
+	manager.store.Clear(session.id)
+	manager.transfer.Clear(rw)
+}
+
+//add 2017-7-30
+func (manager *Manager) IsValidSession(id string) bool {
+	return manager.store.Exist(Id(id))
+}
+
+func (manager *Manager) afterCreated(session *Session) {
+	for listener, _ := range manager.afterCreatedListeners {
+		listener.OnAfterCreated(session)
+	}
+}
+
+func (manager *Manager) beforeReleased(session *Session) {
+	for listener, _ := range manager.beforeReleaseListeners {
+		listener.OnBeforeRelease(session)
+	}
+}
+
+func (manager *Manager) Run() error {
+	return manager.store.Run()
+}

+ 255 - 0
go-xweb/httpsession/memorystore.go

@@ -0,0 +1,255 @@
+package httpsession
+
+import (
+	"sync"
+	"time"
+)
+
+var _ Store = NewMemoryStore(30)
+
+type sessionNode struct {
+	lock   sync.RWMutex
+	kvs    map[string]interface{}
+	last   time.Time
+	maxAge time.Duration
+}
+
+func (node *sessionNode) Get(key string) interface{} {
+	node.lock.RLock()
+	v := node.kvs[key]
+	node.lock.RUnlock()
+	node.lock.Lock()
+	node.last = time.Now()
+	node.lock.Unlock()
+	return v
+}
+
+func (node *sessionNode) GetMultiple() map[string]interface{} {
+	m := make(map[string]interface{})
+	node.lock.RLock()
+	for k, v := range node.kvs {
+		m[k] = v
+	}
+	node.lock.RUnlock()
+	node.lock.Lock()
+	node.last = time.Now()
+	node.lock.Unlock()
+	return m
+}
+
+func (node *sessionNode) Set(key string, v interface{}) {
+	node.lock.Lock()
+	node.kvs[key] = v
+	node.last = time.Now()
+	node.lock.Unlock()
+}
+
+func (node *sessionNode) SetMultiple(m map[string]interface{}) {
+	node.lock.Lock()
+	for k, v := range m {
+		node.kvs[k] = v
+	}
+	node.last = time.Now()
+	node.lock.Unlock()
+}
+
+func (node *sessionNode) Del(key string) {
+	node.lock.Lock()
+	delete(node.kvs, key)
+	node.last = time.Now()
+	node.lock.Unlock()
+}
+
+//根据自定义字段,更新
+func (node *sessionNode) UpdateByCustomField(findkey string, findvalue interface{}, setkey string, setvalue interface{}) bool {
+	node.lock.Lock()
+	if v2, ok := node.kvs[findkey]; ok && v2 == findvalue {
+		//存在
+		if setkey != "" {
+			flag := setkey[:1]
+			switch flag {
+			case "+":
+				nkey := setkey[1:]
+				node.kvs[nkey] = node.kvs[nkey].(int) + setvalue.(int)
+			case "-":
+				nkey := setkey[1:]
+				node.kvs[nkey] = node.kvs[nkey].(int) - setvalue.(int)
+			default:
+				node.kvs[setkey] = setvalue
+			}
+			node.last = time.Now()
+		} else {
+			mapVal := setvalue.(*map[string]interface{})
+			for k, v := range *mapVal {
+				node.kvs[k] = v
+			}
+			node.last = time.Now()
+		}
+	}
+	node.lock.Unlock()
+	return true
+}
+
+type MemoryStore struct {
+	lock       sync.RWMutex
+	nodes      map[Id]*sessionNode
+	GcInterval time.Duration
+	maxAge     time.Duration
+}
+
+func NewMemoryStore(maxAge time.Duration) *MemoryStore {
+	return &MemoryStore{nodes: make(map[Id]*sessionNode),
+		maxAge: maxAge, GcInterval: 10 * time.Second}
+}
+
+func (store *MemoryStore) SetMaxAge(maxAge time.Duration) {
+	store.lock.Lock()
+	store.maxAge = maxAge
+	store.lock.Unlock()
+}
+
+func (store *MemoryStore) Get(id Id, key string) interface{} {
+	store.lock.RLock()
+	node, ok := store.nodes[id]
+	store.lock.RUnlock()
+	if !ok {
+		return nil
+	}
+
+	if store.maxAge > 0 && time.Now().Sub(node.last) > node.maxAge {
+		// lazy DELETE expire
+		store.lock.Lock()
+		delete(store.nodes, id)
+		store.lock.Unlock()
+		return nil
+	}
+
+	return node.Get(key)
+}
+
+func (store *MemoryStore) GetMultiple(id Id) map[string]interface{} {
+	store.lock.RLock()
+	node, ok := store.nodes[id]
+	store.lock.RUnlock()
+	if !ok {
+		return make(map[string]interface{})
+	}
+
+	if store.maxAge > 0 && time.Now().Sub(node.last) > node.maxAge {
+		// lazy DELETE expire
+		store.lock.Lock()
+		delete(store.nodes, id)
+		store.lock.Unlock()
+		return make(map[string]interface{})
+	}
+
+	return node.GetMultiple()
+}
+
+func (store *MemoryStore) Set(id Id, key string, value interface{}) {
+	store.lock.RLock()
+	node, ok := store.nodes[id]
+	store.lock.RUnlock()
+	if !ok {
+		store.lock.Lock()
+		node = store.newNode()
+		node.kvs[key] = value
+		store.nodes[id] = node
+		store.lock.Unlock()
+	}
+	node.Set(key, value)
+}
+
+func (store *MemoryStore) SetMultiple(id Id, m map[string]interface{}) {
+	store.lock.RLock()
+	node, ok := store.nodes[id]
+	store.lock.RUnlock()
+	if !ok {
+		store.lock.Lock()
+		node = store.newNode()
+		for k, v := range m {
+			node.kvs[k] = v
+		}
+		store.nodes[id] = node
+		store.lock.Unlock()
+	}
+	node.SetMultiple(m)
+}
+
+func (store *MemoryStore) newNode() *sessionNode {
+	return &sessionNode{
+		kvs:    make(map[string]interface{}),
+		last:   time.Now(),
+		maxAge: store.maxAge,
+	}
+}
+
+func (store *MemoryStore) Add(id Id) {
+	node := store.newNode()
+	store.lock.Lock()
+	store.nodes[id] = node
+	store.lock.Unlock()
+}
+
+func (store *MemoryStore) Del(id Id, key string) bool {
+	store.lock.RLock()
+	node, ok := store.nodes[id]
+	store.lock.RUnlock()
+	if ok {
+		node.Del(key)
+	}
+	return true
+}
+
+func (store *MemoryStore) Exist(id Id) bool {
+	store.lock.RLock()
+	defer store.lock.RUnlock()
+	_, ok := store.nodes[id]
+	return ok
+}
+
+func (store *MemoryStore) Clear(id Id) bool {
+	store.lock.Lock()
+	defer store.lock.Unlock()
+	delete(store.nodes, id)
+	return true
+}
+
+func (store *MemoryStore) Run() error {
+	time.AfterFunc(store.GcInterval, func() {
+		store.Run()
+		store.GC()
+	})
+	return nil
+}
+
+//根据指定字段更新Session
+func (store *MemoryStore) UpdateByCustomField(findkey string, findvalue interface{}, setkey string, setvalue interface{}) bool {
+	store.lock.RLock()
+	for _, v := range store.nodes {
+		v.UpdateByCustomField(findkey, findvalue, setkey, setvalue)
+	}
+	store.lock.RUnlock()
+	return true
+}
+
+//
+func (store *MemoryStore) GC() {
+	store.lock.Lock()
+	defer store.lock.Unlock()
+	if store.maxAge == 0 {
+		return
+	}
+	var i, j int
+	for k, v := range store.nodes {
+		if j > 20 || i > 5 {
+			break
+		}
+		if time.Now().Sub(v.last) > v.maxAge {
+			delete(store.nodes, k)
+			i = i + 1
+		}
+		j = j + 1
+	}
+
+}

+ 141 - 0
go-xweb/httpsession/redissessionstore.go

@@ -0,0 +1,141 @@
+//session存放在redis中
+//多节点部署时,可以session不受内存存放的影响
+package httpsession
+
+import (
+	"encoding/json"
+	"qfw/util/redis"
+	"sync"
+	"time"
+)
+
+var locks = make([]*sync.Mutex, RedisSessionLockSize)
+
+type redisStore struct {
+	last       time.Time
+	maxAge     time.Duration
+	GcInterval time.Duration
+}
+
+func NewRedisStore(maxAge time.Duration) *redisStore {
+	for i := 0; i < RedisSessionLockSize; i++ {
+		locks[i] = &sync.Mutex{}
+	}
+	return &redisStore{maxAge: maxAge, GcInterval: 10 * time.Second}
+}
+
+//设置超时
+func (store *redisStore) SetMaxAge(maxAge time.Duration) {
+	store.maxAge = maxAge
+}
+
+//取数据,使用redis的超时,来控制session超时
+func (store *redisStore) Get(id Id, key string) interface{} {
+	return store.GetMultiple(id)[key]
+}
+
+//同时获取多个值
+func (store *redisStore) GetMultiple(id Id) map[string]interface{} {
+	lock(id).Lock()
+	bs, err := redis.GetBytes("session", string(id))
+	m := make(map[string]interface{})
+	if err != nil || bs == nil {
+		lock(id).Unlock()
+		return m
+	}
+	redis.SetExpire("session", string(id), int(store.maxAge.Seconds()))
+	store.last = time.Now()
+	lock(id).Unlock()
+	json.Unmarshal(*bs, &m)
+	return m
+}
+
+//设置数据
+func (store *redisStore) Set(id Id, key string, value interface{}) {
+	store.SetMultiple(id, map[string]interface{}{key: value})
+}
+
+//同时设置多个值
+func (store *redisStore) SetMultiple(id Id, m map[string]interface{}) {
+	lock(id).Lock()
+	defer lock(id).Unlock()
+	var userdata map[string]interface{}
+	bs, err := redis.GetBytes("session", string(id))
+	if err != nil {
+		userdata = make(map[string]interface{})
+	} else {
+		json.Unmarshal(*bs, &userdata)
+	}
+	for k, v := range m {
+		userdata[k] = v
+	}
+	putdata, _ := json.Marshal(userdata)
+	redis.PutBytes("session", string(id), &putdata, int(store.maxAge.Seconds()))
+}
+
+func (store *redisStore) Add(id Id) {
+
+}
+
+func (store *redisStore) Del(id Id, key string) bool {
+	lock(id).Lock()
+	defer lock(id).Unlock()
+	bs, err := redis.GetBytes("session", string(id))
+	if err != nil {
+		return true
+	}
+	var userdata map[string]interface{}
+	json.Unmarshal(*bs, &userdata)
+	delete(userdata, key)
+	putdata, _ := json.Marshal(userdata)
+	redis.PutBytes("session", string(id), &putdata, int(store.maxAge.Seconds()))
+	return true
+}
+
+//根据自定义字段,更新
+func (store *redisStore) UpdateByCustomField(findkey string, findvalue interface{}, setkey string, setvalue interface{}) bool {
+	lock(Id(findkey)).Lock()
+	defer lock(Id(findkey)).Unlock()
+	bs, err := redis.GetBytes("session", findkey)
+	if err != nil {
+		return false
+	}
+	var data map[string]interface{}
+	json.Unmarshal(*bs, &data)
+	data[setkey] = setvalue
+	putdata, _ := json.Marshal(data)
+	redis.PutBytes("session", findkey, &putdata, int(store.maxAge.Seconds()))
+	return true
+}
+
+func (store *redisStore) Exist(id Id) bool {
+	lock(id).Lock()
+	defer lock(id).Unlock()
+	ret, err := redis.Exists("session", string(id))
+	return err == nil && ret
+}
+
+func (store *redisStore) Clear(id Id) bool {
+	lock(id).Lock()
+	defer lock(id).Unlock()
+	redis.Del("session", string(id))
+	return true
+}
+
+func (store *redisStore) Run() error {
+	return nil
+}
+
+//TODO 加缓冲
+func (store *redisStore) GC() {
+	//log.Println("Gc")
+}
+
+//
+func lock(id Id) *sync.Mutex {
+	n := 0
+	for _, v := range id {
+		n += int(v)
+	}
+	return locks[n%RedisSessionLockSize]
+}

+ 60 - 0
go-xweb/httpsession/session.go

@@ -0,0 +1,60 @@
+package httpsession
+
+import (
+	"net/http"
+	"time"
+)
+
+type Session struct {
+	id      Id
+	maxAge  time.Duration
+	manager *Manager
+}
+
+func (session *Session) Id() Id {
+	return session.id
+}
+
+func (session *Session) SetId(id Id) {
+	session.id = id
+}
+
+func (session *Session) Get(key string) interface{} {
+	return session.manager.store.Get(session.id, key)
+}
+
+func (session *Session) GetMultiple(keys ...string) map[string]interface{} {
+	return session.manager.store.GetMultiple(session.id)
+}
+
+func (session *Session) Set(key string, value interface{}) {
+	session.manager.store.Set(session.id, key, value)
+}
+
+func (session *Session) SetMultiple(m map[string]interface{}) {
+	session.manager.store.SetMultiple(session.id, m)
+}
+
+func (session *Session) Del(key string) bool {
+	return session.manager.store.Del(session.id, key)
+}
+
+func (session *Session) Invalidate(rw http.ResponseWriter) {
+	session.manager.Invalidate(rw, session)
+}
+
+func (session *Session) IsValid() bool {
+	return session.manager.generator.IsValid(session.id)
+}
+
+func (session *Session) SetMaxAge(maxAge time.Duration) {
+	session.maxAge = maxAge
+}
+
+func (session *Session) UpdateByCustomField(findkey string, findvalue interface{}, setkey string, setvalue interface{}) bool {
+	return session.manager.store.UpdateByCustomField(findkey, findvalue, setkey, setvalue)
+}
+
+func NewSession(id Id, maxAge time.Duration, manager *Manager) *Session {
+	return &Session{id: id, maxAge: manager.maxAge, manager: manager}
+}

+ 19 - 0
go-xweb/httpsession/store.go

@@ -0,0 +1,19 @@
+package httpsession
+
+import "time"
+
+type Id string
+
+type Store interface {
+	Get(id Id, key string) interface{}
+	GetMultiple(id Id) map[string]interface{}
+	Set(id Id, key string, value interface{})
+	SetMultiple(id Id, m map[string]interface{})
+	Del(id Id, key string) bool
+	Clear(id Id) bool
+	Add(id Id)
+	Exist(id Id) bool
+	SetMaxAge(maxAge time.Duration)
+	Run() error
+	UpdateByCustomField(findkey string, findvalue interface{}, setkey string, setvalue interface{}) bool
+}

+ 147 - 0
go-xweb/httpsession/transfer.go

@@ -0,0 +1,147 @@
+package httpsession
+
+import (
+	"net/http"
+	"net/url"
+	"sync"
+	"time"
+)
+
+// Transfer provide and set sessionid
+type Transfer interface {
+	SetMaxAge(maxAge time.Duration)
+	Get(req *http.Request) (Id, error)
+	Set(req *http.Request, rw http.ResponseWriter, id Id)
+	Clear(rw http.ResponseWriter)
+}
+
+// CookieRetriever provide sessionid from cookie
+type CookieTransfer struct {
+	Name     string
+	MaxAge   time.Duration
+	Lock     sync.Mutex
+	Secure   bool
+	RootPath string
+	Domain   string
+}
+
+func NewCookieTransfer(name string, maxAge time.Duration, secure bool, rootPath, domain string) *CookieTransfer {
+	return &CookieTransfer{
+		Name:     name,
+		MaxAge:   maxAge,
+		Secure:   secure,
+		RootPath: rootPath,
+		Domain:   domain,
+	}
+}
+
+func (transfer *CookieTransfer) SetMaxAge(maxAge time.Duration) {
+	transfer.MaxAge = maxAge
+}
+
+func (transfer *CookieTransfer) Get(req *http.Request) (Id, error) {
+	cookie, err := req.Cookie(transfer.Name)
+	if err != nil {
+		if err == http.ErrNoCookie {
+			return "", nil
+		}
+		return "", err
+	}
+	if cookie.Value == "" {
+		return Id(""), nil
+	}
+	id, _ := url.QueryUnescape(cookie.Value)
+	return Id(id), nil
+}
+
+func (transfer *CookieTransfer) Set(req *http.Request, rw http.ResponseWriter, id Id) {
+	sid := url.QueryEscape(string(id))
+	transfer.Lock.Lock()
+	defer transfer.Lock.Unlock()
+	cookie, _ := req.Cookie(transfer.Name)
+	if cookie == nil {
+		cookie = &http.Cookie{
+			Name:     transfer.Name,
+			Value:    sid,
+			Path:     transfer.RootPath,
+			Domain:   transfer.Domain,
+			HttpOnly: true,
+			Secure:   transfer.Secure,
+		}
+		if transfer.MaxAge > 0 {
+			cookie.MaxAge = int(transfer.MaxAge / time.Second)
+			//cookie.Expires = time.Now().Add(transfer.maxAge).UTC()
+		}
+
+		req.AddCookie(cookie)
+	} else {
+		cookie.Value = sid
+		if transfer.MaxAge > 0 {
+			cookie.MaxAge = int(transfer.MaxAge / time.Second)
+			//cookie.Expires = time.Now().Add(transfer.maxAge)
+		}
+	}
+	http.SetCookie(rw, cookie)
+}
+
+func (transfer *CookieTransfer) Clear(rw http.ResponseWriter) {
+	cookie := http.Cookie{
+		Name:     transfer.Name,
+		Path:     transfer.RootPath,
+		Domain:   transfer.Domain,
+		HttpOnly: true,
+		Secure:   transfer.Secure,
+		Expires:  time.Date(0, 1, 1, 0, 0, 0, 0, time.Local),
+		MaxAge:   -1,
+	}
+	http.SetCookie(rw, &cookie)
+}
+
+var _ Transfer = NewCookieTransfer("test", 0, false, "/", Domain)
+
+// CookieRetriever provide sessionid from url
+/*type UrlTransfer struct {
+}
+
+func NewUrlTransfer() *UrlTransfer {
+	return &UrlTransfer{}
+}
+
+func (transfer *UrlTransfer) Get(req *http.Request) (string, error) {
+	return "", nil
+}
+
+func (transfer *UrlTransfer) Set(rw http.ResponseWriter, id Id) {
+
+}
+
+var (
+	_ Transfer = NewUrlTransfer()
+)
+*/
+
+//for SWFUpload ...
+func NewCookieUrlTransfer(name string, maxAge time.Duration, secure bool, rootPath string) *CookieUrlTransfer {
+	return &CookieUrlTransfer{
+		CookieTransfer: CookieTransfer{
+			Name:     name,
+			MaxAge:   maxAge,
+			Secure:   secure,
+			RootPath: rootPath,
+		},
+	}
+}
+
+type CookieUrlTransfer struct {
+	CookieTransfer
+}
+
+func (transfer *CookieUrlTransfer) Get(req *http.Request) (Id, error) {
+	sessionId := req.URL.Query().Get(transfer.Name)
+	if sessionId != "" {
+		sessionId, _ = url.QueryUnescape(sessionId)
+		return Id(sessionId), nil
+	}
+
+	return transfer.CookieTransfer.Get(req)
+}

+ 11 - 0
go-xweb/log/README.md

@@ -0,0 +1,11 @@
+## log
+[![GoDoc](https://godoc.org/github.com/go-xweb/log?status.png)](https://godoc.org/github.com/go-xweb/log)
+
+Extension module of Golang log, 
+
+Most code came from [qiniu/log](https://github.com/qiniu/log)
+
+Improments:
+
+1. Add color support for unix console
+2. Implemented dbwriter to save log to database

+ 32 - 0
go-xweb/log/dbwriter.go

@@ -0,0 +1,32 @@
+package log
+
+import (
+	"database/sql"
+	"time"
+)
+
+type DBWriter struct {
+	db      *sql.DB
+	stmt    *sql.Stmt
+	content chan []byte
+}
+
+func NewDBWriter(db *sql.DB) (*DBWriter, error) {
+	_, err := db.Exec("CREATE TABLE IF NOT EXISTS log (id int, content text, created datetime)")
+	if err != nil {
+		return nil, err
+	}
+	stmt, err := db.Prepare("INSERT INTO log (content, created) values (?, ?)")
+	if err != nil {
+		return nil, err
+	}
+	return &DBWriter{db, stmt, make(chan []byte, 1000)}, nil
+}
+
+func (w *DBWriter) Write(p []byte) (n int, err error) {
+	_, err = w.stmt.Exec(string(p), time.Now())
+	if err == nil {
+		n = len(p)
+	}
+	return
+}

+ 636 - 0
go-xweb/log/logext.go

@@ -0,0 +1,636 @@
+package log
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+	"runtime"
+	"strings"
+	"sync"
+	"time"
+)
+
+// These flags define which text to prefix to each log entry generated by the Logger.
+const (
+	// Bits or'ed together to control what's printed. There is no control over the
+	// order they appear (the order listed here) or the format they present (as
+	// described in the comments).  A colon appears after these items:
+	//	2009/0123 01:23:23.123123 /a/b/c/d.go:23: message
+	Ldate         = 1 << iota     // the date: 2009/0123
+	Ltime                         // the time: 01:23:23
+	Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
+	Llongfile                     // full file name and line number: /a/b/c/d.go:23
+	Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
+	Lmodule                       // module name
+	Llevel                        // level: 0(Debug), 1(Info), 2(Warn), 3(Error), 4(Panic), 5(Fatal)
+	Llongcolor                    // color will start [info] end of line
+	Lshortcolor                   // color only include [info]
+	LstdFlags     = Ldate | Ltime // initial values for the standard logger
+	//Ldefault      = Llevel | LstdFlags | Lshortfile | Llongcolor
+) // [prefix][time][level][module][shortfile|longfile]
+
+func Ldefault() int {
+	if runtime.GOOS == "windows" {
+		return Llevel | LstdFlags | Lshortfile
+	}
+	return Llevel | LstdFlags | Lshortfile | Llongcolor
+}
+
+const (
+	Lall = iota
+)
+const (
+	Ldebug = iota
+	Linfo
+	Lwarn
+	Lerror
+	Lpanic
+	Lfatal
+	Lnone
+)
+
+const (
+	ForeBlack  = iota + 30 //30
+	ForeRed                //31
+	ForeGreen              //32
+	ForeYellow             //33
+	ForeBlue               //34
+	ForePurple             //35
+	ForeCyan               //36
+	ForeWhite              //37
+)
+
+const (
+	BackBlack  = iota + 40 //40
+	BackRed                //41
+	BackGreen              //42
+	BackYellow             //43
+	BackBlue               //44
+	BackPurple             //45
+	BackCyan               //46
+	BackWhite              //47
+)
+
+var levels = []string{
+	"[Debug]",
+	"[Info]",
+	"[Warn]",
+	"[Error]",
+	"[Panic]",
+	"[Fatal]",
+}
+
+// MUST called before all logs
+func SetLevels(lvs []string) {
+	levels = lvs
+}
+
+var colors = []int{
+	ForeCyan,
+	ForeGreen,
+	ForeYellow,
+	ForeRed,
+	ForePurple,
+	ForeBlue,
+}
+
+// MUST called before all logs
+func SetColors(cls []int) {
+	colors = cls
+}
+
+// A Logger represents an active logging object that generates lines of
+// output to an io.Writer.  Each logging operation makes a single call to
+// the Writer's Write method.  A Logger can be used simultaneously from
+// multiple goroutines; it guarantees to serialize access to the Writer.
+type Logger struct {
+	mu         sync.Mutex // ensures atomic writes; protects the following fields
+	prefix     string     // prefix to write at beginning of each line
+	flag       int        // properties
+	Level      int
+	out        io.Writer    // destination for output
+	buf        bytes.Buffer // for accumulating text to write
+	levelStats [6]int64
+}
+
+// New creates a new Logger.   The out variable sets the
+// destination to which log data will be written.
+// The prefix appears at the beginning of each generated log line.
+// The flag argument defines the logging properties.
+func New(out io.Writer, prefix string, flag int) *Logger {
+	return &Logger{out: out, prefix: prefix, Level: 1, flag: flag}
+}
+
+var Std = New(os.Stderr, "", Ldefault())
+
+// Cheap integer to fixed-width decimal ASCII.  Give a negative width to avoid zero-padding.
+// Knows the buffer has capacity.
+func itoa(buf *bytes.Buffer, i int, wid int) {
+	var u uint = uint(i)
+	if u == 0 && wid <= 1 {
+		buf.WriteByte('0')
+		return
+	}
+
+	// Assemble decimal in reverse order.
+	var b [32]byte
+	bp := len(b)
+	for ; u > 0 || wid > 0; u /= 10 {
+		bp--
+		wid--
+		b[bp] = byte(u%10) + '0'
+	}
+
+	// avoid slicing b to avoid an allocation.
+	for bp < len(b) {
+		buf.WriteByte(b[bp])
+		bp++
+	}
+}
+
+func moduleOf(file string) string {
+	pos := strings.LastIndex(file, "/")
+	if pos != -1 {
+		pos1 := strings.LastIndex(file[:pos], "/src/")
+		if pos1 != -1 {
+			return file[pos1+5 : pos]
+		}
+	}
+	return "UNKNOWN"
+}
+
+func (l *Logger) formatHeader(buf *bytes.Buffer, t time.Time,
+	file string, line int, lvl int, reqId string) {
+	if l.prefix != "" {
+		buf.WriteString(l.prefix)
+	}
+	if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
+		if l.flag&Ldate != 0 {
+			year, month, day := t.Date()
+			itoa(buf, year, 4)
+			buf.WriteByte('/')
+			itoa(buf, int(month), 2)
+			buf.WriteByte('/')
+			itoa(buf, day, 2)
+			buf.WriteByte(' ')
+		}
+		if l.flag&(Ltime|Lmicroseconds) != 0 {
+			hour, min, sec := t.Clock()
+			itoa(buf, hour, 2)
+			buf.WriteByte(':')
+			itoa(buf, min, 2)
+			buf.WriteByte(':')
+			itoa(buf, sec, 2)
+			if l.flag&Lmicroseconds != 0 {
+				buf.WriteByte('.')
+				itoa(buf, t.Nanosecond()/1e3, 6)
+			}
+			buf.WriteByte(' ')
+		}
+	}
+	if reqId != "" {
+		buf.WriteByte('[')
+		buf.WriteString(reqId)
+		buf.WriteByte(']')
+		buf.WriteByte(' ')
+	}
+
+	if l.flag&(Lshortcolor|Llongcolor) != 0 {
+		buf.WriteString(fmt.Sprintf("\033[1;%dm", colors[lvl]))
+	}
+	if l.flag&Llevel != 0 {
+		buf.WriteString(levels[lvl])
+		buf.WriteByte(' ')
+	}
+	if l.flag&Lshortcolor != 0 {
+		buf.WriteString("\033[0m")
+	}
+
+	if l.flag&Lmodule != 0 {
+		buf.WriteByte('[')
+		buf.WriteString(moduleOf(file))
+		buf.WriteByte(']')
+		buf.WriteByte(' ')
+	}
+	if l.flag&(Lshortfile|Llongfile) != 0 {
+		if l.flag&Lshortfile != 0 {
+			short := file
+			for i := len(file) - 1; i > 0; i-- {
+				if file[i] == '/' {
+					short = file[i+1:]
+					break
+				}
+			}
+			file = short
+		}
+		buf.WriteString(file)
+		buf.WriteByte(':')
+		itoa(buf, line, -1)
+		buf.WriteByte(' ')
+	}
+}
+
+// Output writes the output for a logging event.  The string s contains
+// the text to print after the prefix specified by the flags of the
+// Logger.  A newline is appended if the last character of s is not
+// already a newline.  Calldepth is used to recover the PC and is
+// provided for generality, although at the moment on all pre-defined
+// paths it will be 2.
+func (l *Logger) Output(reqId string, lvl int, calldepth int, s string) error {
+	if lvl < l.Level {
+		return nil
+	}
+	now := time.Now() // get this early.
+	var file string
+	var line int
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	if l.flag&(Lshortfile|Llongfile|Lmodule) != 0 {
+		// release lock while getting caller info - it's expensive.
+		l.mu.Unlock()
+		var ok bool
+		_, file, line, ok = runtime.Caller(calldepth)
+		if !ok {
+			file = "???"
+			line = 0
+		}
+		l.mu.Lock()
+	}
+	l.levelStats[lvl]++
+	l.buf.Reset()
+	l.formatHeader(&l.buf, now, file, line, lvl, reqId)
+	l.buf.WriteString(s)
+	if l.flag&Llongcolor != 0 {
+		l.buf.WriteString("\033[0m")
+	}
+	if len(s) > 0 && s[len(s)-1] != '\n' {
+		l.buf.WriteByte('\n')
+	}
+	_, err := l.out.Write(l.buf.Bytes())
+	return err
+}
+
+// -----------------------------------------
+
+// Printf calls l.Output to print to the logger.
+// Arguments are handled in the manner of fmt.Printf.
+func (l *Logger) Printf(format string, v ...interface{}) {
+	l.Output("", Linfo, 2, fmt.Sprintf(format, v...))
+}
+
+// Print calls l.Output to print to the logger.
+// Arguments are handled in the manner of fmt.Print.
+func (l *Logger) Print(v ...interface{}) {
+	l.Output("", Linfo, 2, fmt.Sprint(v...))
+}
+
+// Println calls l.Output to print to the logger.
+// Arguments are handled in the manner of fmt.Println.
+func (l *Logger) Println(v ...interface{}) {
+	l.Output("", Linfo, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+
+func (l *Logger) Debugf(format string, v ...interface{}) {
+	if Ldebug < l.Level {
+		return
+	}
+	l.Output("", Ldebug, 2, fmt.Sprintf(format, v...))
+}
+
+func (l *Logger) Debug(v ...interface{}) {
+	if Ldebug < l.Level {
+		return
+	}
+	l.Output("", Ldebug, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+func (l *Logger) Infof(format string, v ...interface{}) {
+	if Linfo < l.Level {
+		return
+	}
+	l.Output("", Linfo, 2, fmt.Sprintf(format, v...))
+}
+
+func (l *Logger) Info(v ...interface{}) {
+	if Linfo < l.Level {
+		return
+	}
+	l.Output("", Linfo, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+func (l *Logger) Warnf(format string, v ...interface{}) {
+	if Lwarn < l.Level {
+		return
+	}
+	l.Output("", Lwarn, 2, fmt.Sprintf(format, v...))
+}
+
+func (l *Logger) Warn(v ...interface{}) {
+	if Lwarn < l.Level {
+		return
+	}
+	l.Output("", Lwarn, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+
+func (l *Logger) Errorf(format string, v ...interface{}) {
+	if Lerror < l.Level {
+		return
+	}
+	l.Output("", Lerror, 2, fmt.Sprintf(format, v...))
+}
+
+func (l *Logger) Error(v ...interface{}) {
+	if Lerror < l.Level {
+		return
+	}
+	l.Output("", Lerror, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+
+func (l *Logger) Fatal(v ...interface{}) {
+	if Lfatal < l.Level {
+		return
+	}
+	l.Output("", Lfatal, 2, fmt.Sprintln(v...))
+	os.Exit(1)
+}
+
+// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
+func (l *Logger) Fatalf(format string, v ...interface{}) {
+	if Lfatal < l.Level {
+		return
+	}
+	l.Output("", Lfatal, 2, fmt.Sprintf(format, v...))
+	os.Exit(1)
+}
+
+// -----------------------------------------
+// Panic is equivalent to l.Print() followed by a call to panic().
+func (l *Logger) Panic(v ...interface{}) {
+	if Lpanic < l.Level {
+		return
+	}
+	s := fmt.Sprintln(v...)
+	l.Output("", Lpanic, 2, s)
+	panic(s)
+}
+
+// Panicf is equivalent to l.Printf() followed by a call to panic().
+func (l *Logger) Panicf(format string, v ...interface{}) {
+	if Lpanic < l.Level {
+		return
+	}
+	s := fmt.Sprintf(format, v...)
+	l.Output("", Lpanic, 2, s)
+	panic(s)
+}
+
+// -----------------------------------------
+func (l *Logger) Stack(v ...interface{}) {
+	s := fmt.Sprint(v...)
+	s += "\n"
+	buf := make([]byte, 1024*1024)
+	n := runtime.Stack(buf, true)
+	s += string(buf[:n])
+	s += "\n"
+	l.Output("", Lerror, 2, s)
+}
+
+// -----------------------------------------
+func (l *Logger) Stat() (stats []int64) {
+	l.mu.Lock()
+	v := l.levelStats
+	l.mu.Unlock()
+	return v[:]
+}
+
+// Flags returns the output flags for the logger.
+func (l *Logger) Flags() int {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	return l.flag
+}
+
+func RmColorFlags(flag int) int {
+	// for un std out, it should not show color since almost them don't support
+	if flag&Llongcolor != 0 {
+		flag = flag ^ Llongcolor
+	}
+	if flag&Lshortcolor != 0 {
+		flag = flag ^ Lshortcolor
+	}
+	return flag
+}
+
+// SetFlags sets the output flags for the logger.
+func (l *Logger) SetFlags(flag int) {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	l.flag = flag
+}
+
+// Prefix returns the output prefix for the logger.
+func (l *Logger) Prefix() string {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	return l.prefix
+}
+
+// SetPrefix sets the output prefix for the logger.
+func (l *Logger) SetPrefix(prefix string) {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	l.prefix = prefix
+}
+
+// SetOutputLevel sets the output level for the logger.
+func (l *Logger) SetOutputLevel(lvl int) {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	l.Level = lvl
+}
+
+func (l *Logger) OutputLevel() int {
+	return l.Level
+}
+
+func (l *Logger) SetOutput(w io.Writer) {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	l.out = w
+}
+
+// SetOutput sets the output destination for the standard logger.
+func SetOutput(w io.Writer) {
+	Std.SetOutput(w)
+}
+
+// Flags returns the output flags for the standard logger.
+func Flags() int {
+	return Std.Flags()
+}
+
+// SetFlags sets the output flags for the standard logger.
+func SetFlags(flag int) {
+	Std.SetFlags(flag)
+}
+
+// Prefix returns the output prefix for the standard logger.
+func Prefix() string {
+	return Std.Prefix()
+}
+
+// SetPrefix sets the output prefix for the standard logger.
+func SetPrefix(prefix string) {
+	Std.SetPrefix(prefix)
+}
+
+func SetOutputLevel(lvl int) {
+	Std.SetOutputLevel(lvl)
+}
+
+func OutputLevel() int {
+	return Std.OutputLevel()
+}
+
+// -----------------------------------------
+
+// Print calls Output to print to the standard logger.
+// Arguments are handled in the manner of fmt.Print.
+func Print(v ...interface{}) {
+	Std.Output("", Linfo, 2, fmt.Sprintln(v...))
+}
+
+// Printf calls Output to print to the standard logger.
+// Arguments are handled in the manner of fmt.Printf.
+func Printf(format string, v ...interface{}) {
+	Std.Output("", Linfo, 2, fmt.Sprintf(format, v...))
+}
+
+// Println calls Output to print to the standard logger.
+// Arguments are handled in the manner of fmt.Println.
+func Println(v ...interface{}) {
+	Std.Output("", Linfo, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+
+func Debugf(format string, v ...interface{}) {
+	if Ldebug < Std.Level {
+		return
+	}
+	Std.Output("", Ldebug, 2, fmt.Sprintf(format, v...))
+}
+
+func Debug(v ...interface{}) {
+	if Ldebug < Std.Level {
+		return
+	}
+	Std.Output("", Ldebug, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+
+func Infof(format string, v ...interface{}) {
+	if Linfo < Std.Level {
+		return
+	}
+	Std.Output("", Linfo, 2, fmt.Sprintf(format, v...))
+}
+
+func Info(v ...interface{}) {
+	if Linfo < Std.Level {
+		return
+	}
+	Std.Output("", Linfo, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+
+func Warnf(format string, v ...interface{}) {
+	if Lwarn < Std.Level {
+		return
+	}
+	Std.Output("", Lwarn, 2, fmt.Sprintf(format, v...))
+}
+
+func Warn(v ...interface{}) {
+	if Lwarn < Std.Level {
+		return
+	}
+	Std.Output("", Lwarn, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+
+func Errorf(format string, v ...interface{}) {
+	if Lerror < Std.Level {
+		return
+	}
+	Std.Output("", Lerror, 2, fmt.Sprintf(format, v...))
+}
+
+func Error(v ...interface{}) {
+	if Lerror < Std.Level {
+		return
+	}
+	Std.Output("", Lerror, 2, fmt.Sprintln(v...))
+}
+
+// -----------------------------------------
+
+// Fatal is equivalent to Print() followed by a call to os.Exit(1).
+func Fatal(v ...interface{}) {
+	if Lfatal < Std.Level {
+		return
+	}
+	Std.Output("", Lfatal, 2, fmt.Sprintln(v...))
+}
+
+// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
+func Fatalf(format string, v ...interface{}) {
+	if Lfatal < Std.Level {
+		return
+	}
+	Std.Output("", Lfatal, 2, fmt.Sprintf(format, v...))
+}
+
+// -----------------------------------------
+
+// Panic is equivalent to Print() followed by a call to panic().
+func Panic(v ...interface{}) {
+	if Lpanic < Std.Level {
+		return
+	}
+	Std.Output("", Lpanic, 2, fmt.Sprintln(v...))
+}
+
+// Panicf is equivalent to Printf() followed by a call to panic().
+func Panicf(format string, v ...interface{}) {
+	if Lpanic < Std.Level {
+		return
+	}
+	Std.Output("", Lpanic, 2, fmt.Sprintf(format, v...))
+}
+
+// -----------------------------------------
+
+func Stack(v ...interface{}) {
+	s := fmt.Sprint(v...)
+	s += "\n"
+	buf := make([]byte, 1024*1024)
+	n := runtime.Stack(buf, true)
+	s += string(buf[:n])
+	s += "\n"
+	Std.Output("", Lerror, 2, s)
+}
+
+// -----------------------------------------

+ 84 - 0
go-xweb/uuid/dce.go

@@ -0,0 +1,84 @@
+// Copyright 2011 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+	"encoding/binary"
+	"fmt"
+	"os"
+)
+
+// A Domain represents a Version 2 domain
+type Domain byte
+
+// Domain constants for DCE Security (Version 2) UUIDs.
+const (
+	Person = Domain(0)
+	Group  = Domain(1)
+	Org    = Domain(2)
+)
+
+// NewDCESecurity returns a DCE Security (Version 2) UUID.
+//
+// The domain should be one of Person, Group or Org.
+// On a POSIX system the id should be the users UID for the Person
+// domain and the users GID for the Group.  The meaning of id for
+// the domain Org or on non-POSIX systems is site defined.
+//
+// For a given domain/id pair the same token may be returned for up to
+// 7 minutes and 10 seconds.
+func NewDCESecurity(domain Domain, id uint32) UUID {
+	uuid := NewUUID()
+	if uuid != nil {
+		uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
+		uuid[9] = byte(domain)
+		binary.BigEndian.PutUint32(uuid[0:], id)
+	}
+	return uuid
+}
+
+// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
+// domain with the id returned by os.Getuid.
+//
+//  NewDCEPerson(Person, uint32(os.Getuid()))
+func NewDCEPerson() UUID {
+	return NewDCESecurity(Person, uint32(os.Getuid()))
+}
+
+// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
+// domain with the id returned by os.Getgid.
+//
+//  NewDCEGroup(Group, uint32(os.Getgid()))
+func NewDCEGroup() UUID {
+	return NewDCESecurity(Group, uint32(os.Getgid()))
+}
+
+// Domain returns the domain for a Version 2 UUID or false.
+func (uuid UUID) Domain() (Domain, bool) {
+	if v, _ := uuid.Version(); v != 2 {
+		return 0, false
+	}
+	return Domain(uuid[9]), true
+}
+
+// Id returns the id for a Version 2 UUID or false.
+func (uuid UUID) Id() (uint32, bool) {
+	if v, _ := uuid.Version(); v != 2 {
+		return 0, false
+	}
+	return binary.BigEndian.Uint32(uuid[0:4]), true
+}
+
+func (d Domain) String() string {
+	switch d {
+	case Person:
+		return "Person"
+	case Group:
+		return "Group"
+	case Org:
+		return "Org"
+	}
+	return fmt.Sprintf("Domain%d", int(d))
+}

+ 8 - 0
go-xweb/uuid/doc.go

@@ -0,0 +1,8 @@
+// Copyright 2011 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The uuid package generates and inspects UUIDs.
+//
+// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
+package uuid

+ 53 - 0
go-xweb/uuid/hash.go

@@ -0,0 +1,53 @@
+// Copyright 2011 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+	"crypto/md5"
+	"crypto/sha1"
+	"hash"
+)
+
+// Well known Name Space IDs and UUIDs
+var (
+	NameSpace_DNS  = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
+	NameSpace_URL  = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
+	NameSpace_OID  = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
+	NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
+	NIL            = Parse("00000000-0000-0000-0000-000000000000")
+)
+
+// NewHash returns a new UUID dervied from the hash of space concatenated with
+// data generated by h.  The hash should be at least 16 byte in length.  The
+// first 16 bytes of the hash are used to form the UUID.  The version of the
+// UUID will be the lower 4 bits of version.  NewHash is used to implement
+// NewMD5 and NewSHA1.
+func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
+	h.Reset()
+	h.Write(space)
+	h.Write([]byte(data))
+	s := h.Sum(nil)
+	uuid := make([]byte, 16)
+	copy(uuid, s)
+	uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
+	uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
+	return uuid
+}
+
+// NewMD5 returns a new MD5 (Version 3) UUID based on the
+// supplied name space and data.
+//
+//  NewHash(md5.New(), space, data, 3)
+func NewMD5(space UUID, data []byte) UUID {
+	return NewHash(md5.New(), space, data, 3)
+}
+
+// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
+// supplied name space and data.
+//
+//  NewHash(sha1.New(), space, data, 5)
+func NewSHA1(space UUID, data []byte) UUID {
+	return NewHash(sha1.New(), space, data, 5)
+}

+ 101 - 0
go-xweb/uuid/node.go

@@ -0,0 +1,101 @@
+// Copyright 2011 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import "net"
+
+var (
+	interfaces []net.Interface // cached list of interfaces
+	ifname     string          // name of interface being used
+	nodeID     []byte          // hardware for version 1 UUIDs
+)
+
+// NodeInterface returns the name of the interface from which the NodeID was
+// derived.  The interface "user" is returned if the NodeID was set by
+// SetNodeID.
+func NodeInterface() string {
+	return ifname
+}
+
+// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
+// If name is "" then the first usable interface found will be used or a random
+// Node ID will be generated.  If a named interface cannot be found then false
+// is returned.
+//
+// SetNodeInterface never fails when name is "".
+func SetNodeInterface(name string) bool {
+	if interfaces == nil {
+		var err error
+		interfaces, err = net.Interfaces()
+		if err != nil && name != "" {
+			return false
+		}
+	}
+
+	for _, ifs := range interfaces {
+		if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
+			if setNodeID(ifs.HardwareAddr) {
+				ifname = ifs.Name
+				return true
+			}
+		}
+	}
+
+	// We found no interfaces with a valid hardware address.  If name
+	// does not specify a specific interface generate a random Node ID
+	// (section 4.1.6)
+	if name == "" {
+		if nodeID == nil {
+			nodeID = make([]byte, 6)
+		}
+		randomBits(nodeID)
+		return true
+	}
+	return false
+}
+
+// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
+// if not already set.
+func NodeID() []byte {
+	if nodeID == nil {
+		SetNodeInterface("")
+	}
+	nid := make([]byte, 6)
+	copy(nid, nodeID)
+	return nid
+}
+
+// SetNodeID sets the Node ID to be used for Version 1 UUIDs.  The first 6 bytes
+// of id are used.  If id is less than 6 bytes then false is returned and the
+// Node ID is not set.
+func SetNodeID(id []byte) bool {
+	if setNodeID(id) {
+		ifname = "user"
+		return true
+	}
+	return false
+}
+
+func setNodeID(id []byte) bool {
+	if len(id) < 6 {
+		return false
+	}
+	if nodeID == nil {
+		nodeID = make([]byte, 6)
+	}
+	copy(nodeID, id)
+	return true
+}
+
+// NodeID returns the 6 byte node id encoded in uuid.  It returns nil if uuid is
+// not valid.  The NodeID is only well defined for version 1 and 2 UUIDs.
+func (uuid UUID) NodeID() []byte {
+	if len(uuid) != 16 {
+		return nil
+	}
+	node := make([]byte, 6)
+	copy(node, uuid[10:])
+	return node
+}

+ 132 - 0
go-xweb/uuid/time.go

@@ -0,0 +1,132 @@
+// Copyright 2014 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+	"encoding/binary"
+	"sync"
+	"time"
+)
+
+// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
+// 1582.
+type Time int64
+
+const (
+	lillian    = 2299160          // Julian day of 15 Oct 1582
+	unix       = 2440587          // Julian day of 1 Jan 1970
+	epoch      = unix - lillian   // Days between epochs
+	g1582      = epoch * 86400    // seconds between epochs
+	g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
+)
+
+var (
+	mu        sync.Mutex
+	lasttime  uint64 // last time we returned
+	clock_seq uint16 // clock sequence for this run
+
+	timeNow = time.Now // for testing
+)
+
+// UnixTime converts t the number of seconds and nanoseconds using the Unix
+// epoch of 1 Jan 1970.
+func (t Time) UnixTime() (sec, nsec int64) {
+	sec = int64(t - g1582ns100)
+	nsec = (sec % 10000000) * 100
+	sec /= 10000000
+	return sec, nsec
+}
+
+// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
+// adjusts the clock sequence as needed.  An error is returned if the current
+// time cannot be determined.
+func GetTime() (Time, error) {
+	defer mu.Unlock()
+	mu.Lock()
+	return getTime()
+}
+
+func getTime() (Time, error) {
+	t := timeNow()
+
+	// If we don't have a clock sequence already, set one.
+	if clock_seq == 0 {
+		setClockSequence(-1)
+	}
+	now := uint64(t.UnixNano()/100) + g1582ns100
+
+	// If time has gone backwards with this clock sequence then we
+	// increment the clock sequence
+	if now <= lasttime {
+		clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
+	}
+	lasttime = now
+	return Time(now), nil
+}
+
+// ClockSequence returns the current clock sequence, generating one if not
+// already set.  The clock sequence is only used for Version 1 UUIDs.
+//
+// The uuid package does not use global static storage for the clock sequence or
+// the last time a UUID was generated.  Unless SetClockSequence a new random
+// clock sequence is generated the first time a clock sequence is requested by
+// ClockSequence, GetTime, or NewUUID.  (section 4.2.1.1) sequence is generated
+// for
+func ClockSequence() int {
+	defer mu.Unlock()
+	mu.Lock()
+	return clockSequence()
+}
+
+func clockSequence() int {
+	if clock_seq == 0 {
+		setClockSequence(-1)
+	}
+	return int(clock_seq & 0x3fff)
+}
+
+// SetClockSeq sets the clock sequence to the lower 14 bits of seq.  Setting to
+// -1 causes a new sequence to be generated.
+func SetClockSequence(seq int) {
+	defer mu.Unlock()
+	mu.Lock()
+	setClockSequence(seq)
+}
+
+func setClockSequence(seq int) {
+	if seq == -1 {
+		var b [2]byte
+		randomBits(b[:]) // clock sequence
+		seq = int(b[0])<<8 | int(b[1])
+	}
+	old_seq := clock_seq
+	clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
+	if old_seq != clock_seq {
+		lasttime = 0
+	}
+}
+
+// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
+// uuid.  It returns false if uuid is not valid.  The time is only well defined
+// for version 1 and 2 UUIDs.
+func (uuid UUID) Time() (Time, bool) {
+	if len(uuid) != 16 {
+		return 0, false
+	}
+	time := int64(binary.BigEndian.Uint32(uuid[0:4]))
+	time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
+	time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
+	return Time(time), true
+}
+
+// ClockSequence returns the clock sequence encoded in uuid.  It returns false
+// if uuid is not valid.  The clock sequence is only well defined for version 1
+// and 2 UUIDs.
+func (uuid UUID) ClockSequence() (int, bool) {
+	if len(uuid) != 16 {
+		return 0, false
+	}
+	return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
+}

+ 43 - 0
go-xweb/uuid/util.go

@@ -0,0 +1,43 @@
+// Copyright 2011 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+	"io"
+)
+
+// randomBits completely fills slice b with random data.
+func randomBits(b []byte) {
+	if _, err := io.ReadFull(rander, b); err != nil {
+		panic(err.Error()) // rand should never fail
+	}
+}
+
+// xvalues returns the value of a byte as a hexadecimal digit or 255.
+var xvalues = []byte{
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
+	255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+}
+
+// xtob converts the the first two hex bytes of x into a byte.
+func xtob(x string) (byte, bool) {
+	b1 := xvalues[x[0]]
+	b2 := xvalues[x[1]]
+	return (b1 << 4) | b2, b1 != 255 && b2 != 255
+}

+ 163 - 0
go-xweb/uuid/uuid.go

@@ -0,0 +1,163 @@
+// Copyright 2011 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+	"bytes"
+	"crypto/rand"
+	"fmt"
+	"io"
+	"strings"
+)
+
+// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
+// 4122.
+type UUID []byte
+
+// A Version represents a UUIDs version.
+type Version byte
+
+// A Variant represents a UUIDs variant.
+type Variant byte
+
+// Constants returned by Variant.
+const (
+	Invalid   = Variant(iota) // Invalid UUID
+	RFC4122                   // The variant specified in RFC4122
+	Reserved                  // Reserved, NCS backward compatibility.
+	Microsoft                 // Reserved, Microsoft Corporation backward compatibility.
+	Future                    // Reserved for future definition.
+)
+
+var rander = rand.Reader // random function
+
+// New returns a new random (version 4) UUID as a string.  It is a convenience
+// function for NewRandom().String().
+func New() string {
+	return NewRandom().String()
+}
+
+// Parse decodes s into a UUID or returns nil.  Both the UUID form of
+// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
+// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
+func Parse(s string) UUID {
+	if len(s) == 36+9 {
+		if strings.ToLower(s[:9]) != "urn:uuid:" {
+			return nil
+		}
+		s = s[9:]
+	} else if len(s) != 36 {
+		return nil
+	}
+	if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
+		return nil
+	}
+	uuid := make([]byte, 16)
+	for i, x := range []int{
+		0, 2, 4, 6,
+		9, 11,
+		14, 16,
+		19, 21,
+		24, 26, 28, 30, 32, 34} {
+		if v, ok := xtob(s[x:]); !ok {
+			return nil
+		} else {
+			uuid[i] = v
+		}
+	}
+	return uuid
+}
+
+// Equal returns true if uuid1 and uuid2 are equal.
+func Equal(uuid1, uuid2 UUID) bool {
+	return bytes.Equal(uuid1, uuid2)
+}
+
+// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+// , or "" if uuid is invalid.
+func (uuid UUID) String() string {
+	if uuid == nil || len(uuid) != 16 {
+		return ""
+	}
+	b := []byte(uuid)
+	return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
+		b[:4], b[4:6], b[6:8], b[8:10], b[10:])
+}
+
+// URN returns the RFC 2141 URN form of uuid,
+// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,  or "" if uuid is invalid.
+func (uuid UUID) URN() string {
+	if uuid == nil || len(uuid) != 16 {
+		return ""
+	}
+	b := []byte(uuid)
+	return fmt.Sprintf("urn:uuid:%08x-%04x-%04x-%04x-%012x",
+		b[:4], b[4:6], b[6:8], b[8:10], b[10:])
+}
+
+// Variant returns the variant encoded in uuid.  It returns Invalid if
+// uuid is invalid.
+func (uuid UUID) Variant() Variant {
+	if len(uuid) != 16 {
+		return Invalid
+	}
+	switch {
+	case (uuid[8] & 0xc0) == 0x80:
+		return RFC4122
+	case (uuid[8] & 0xe0) == 0xc0:
+		return Microsoft
+	case (uuid[8] & 0xe0) == 0xe0:
+		return Future
+	default:
+		return Reserved
+	}
+	panic("unreachable")
+}
+
+// Version returns the verison of uuid.  It returns false if uuid is not
+// valid.
+func (uuid UUID) Version() (Version, bool) {
+	if len(uuid) != 16 {
+		return 0, false
+	}
+	return Version(uuid[6] >> 4), true
+}
+
+func (v Version) String() string {
+	if v > 15 {
+		return fmt.Sprintf("BAD_VERSION_%d", v)
+	}
+	return fmt.Sprintf("VERSION_%d", v)
+}
+
+func (v Variant) String() string {
+	switch v {
+	case RFC4122:
+		return "RFC4122"
+	case Reserved:
+		return "Reserved"
+	case Microsoft:
+		return "Microsoft"
+	case Future:
+		return "Future"
+	case Invalid:
+		return "Invalid"
+	}
+	return fmt.Sprintf("BadVariant%d", int(v))
+}
+
+// SetRand sets the random number generator to r, which implents io.Reader.
+// If r.Read returns an error when the package requests random data then
+// a panic will be issued.
+//
+// Calling SetRand with nil sets the random number generator to the default
+// generator.
+func SetRand(r io.Reader) {
+	if r == nil {
+		rander = rand.Reader
+		return
+	}
+	rander = r
+}

+ 41 - 0
go-xweb/uuid/version1.go

@@ -0,0 +1,41 @@
+// Copyright 2011 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+import (
+	"encoding/binary"
+)
+
+// NewUUID returns a Version 1 UUID based on the current NodeID and clock
+// sequence, and the current time.  If the NodeID has not been set by SetNodeID
+// or SetNodeInterface then it will be set automatically.  If the NodeID cannot
+// be set NewUUID returns nil.  If clock sequence has not been set by
+// SetClockSequence then it will be set automatically.  If GetTime fails to
+// return the current NewUUID returns nil.
+func NewUUID() UUID {
+	if nodeID == nil {
+		SetNodeInterface("")
+	}
+
+	now, err := GetTime()
+	if err != nil {
+		return nil
+	}
+
+	uuid := make([]byte, 16)
+
+	time_low := uint32(now & 0xffffffff)
+	time_mid := uint16((now >> 32) & 0xffff)
+	time_hi := uint16((now >> 48) & 0x0fff)
+	time_hi |= 0x1000 // Version 1
+
+	binary.BigEndian.PutUint32(uuid[0:], time_low)
+	binary.BigEndian.PutUint16(uuid[4:], time_mid)
+	binary.BigEndian.PutUint16(uuid[6:], time_hi)
+	binary.BigEndian.PutUint16(uuid[8:], clock_seq)
+	copy(uuid[10:], nodeID)
+
+	return uuid
+}

+ 25 - 0
go-xweb/uuid/version4.go

@@ -0,0 +1,25 @@
+// Copyright 2011 Google Inc.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package uuid
+
+// Random returns a Random (Version 4) UUID or panics.
+//
+// The strength of the UUIDs is based on the strength of the crypto/rand
+// package.
+//
+// A note about uniqueness derived from from the UUID Wikipedia entry:
+//
+//  Randomly generated UUIDs have 122 random bits.  One's annual risk of being
+//  hit by a meteorite is estimated to be one chance in 17 billion, that
+//  means the probability is about 0.00000000006 (6 × 10−11),
+//  equivalent to the odds of creating a few tens of trillions of UUIDs in a
+//  year and having one duplicate.
+func NewRandom() UUID {
+	uuid := make([]byte, 16)
+	randomBits([]byte(uuid))
+	uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
+	uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
+	return uuid
+}

+ 58 - 0
go-xweb/xweb/README.md

@@ -0,0 +1,58 @@
+# xweb
+
+xweb是一个强大的Go语言web框架。
+
+[English](https://github.com/go-xweb/xweb/blob/master/README_EN.md)
+
+[![Build Status](https://drone.io/github.com/go-xweb/xweb/status.png)](https://drone.io/github.com/go-xweb/xweb/latest)  [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xweb/xweb) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/go-xweb/xweb/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
+
+## 技术支持
+
+QQ群:369240307
+
+## 更新日志
+
+* **v0.2.1** : 自动Binding新增对jquery对象,map和array的支持。
+* **v0.2** : 新增 validation 子包,从 [https://github.com/astaxie/beego/tree/master/validation](http://https://github.com/astaxie/beego/tree/master/validation) 拷贝过来。
+* **v0.1.2** : 采用 [github.com/go-xweb/httpsession](http://github.com/go-xweb/httpsession) 作为session组件,API保持兼容;Action现在必须从*Action继承,这个改变与以前的版本不兼容,必须更改代码;新增两个模板函数{{session "key"}} 和 {{cookie "key"}};Action新增函数`MapForm`
+* **v0.1.1** : App新增AutoAction方法;Action新增AddTmplVar方法;Render方法的模版渲染方法中可以通过T混合传入函数和变量,更新了[快速入门](https://github.com/go-xweb/xweb/tree/master/docs/intro.md)。
+* **v0.1.0** : 初始版本
+
+## 特性
+
+* 在一个可执行程序中多Server(http,tls,scgi,fcgi),多App的支持
+* 简单好用的路由映射方式
+* 静态文件及版本支持,并支持自动加载,默认开启
+* 改进的模版支持,并支持自动加载,动态新增模板函数
+* session支持
+* validation支持
+
+## 安装
+
+在安装之前确认你已经安装了Go语言. Go语言安装请访问 [install instructions](http://golang.org/doc/install.html). 
+
+安装 xweb:
+
+    go get github.com/go-xweb/xweb
+
+## Examples
+
+请访问 [examples](https://github.com/go-xweb/xweb/tree/master/examples) folder
+
+## 案例
+
+* [xorm.io](http://xorm.io) - [github.com/go-xorm/website](http://github.com/go-xorm/website)
+* [Godaily.org](http://godaily.org) - [github.com/govc/godaily](http://github.com/govc/godaily)
+
+## 文档
+
+[快速入门](https://github.com/go-xweb/xweb/tree/master/docs/intro.md)
+
+源码文档请访问 [GoWalker](http://gowalker.org/github.com/go-xweb/xweb)
+
+## License
+BSD License
+[http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/)
+
+
+

+ 46 - 0
go-xweb/xweb/README_EN.md

@@ -0,0 +1,46 @@
+# xweb
+
+xweb is a web framework for Go which is based on web.go. It just like Struts for Java. 
+
+[中文](https://github.com/go-xweb/xweb/blob/master/README.md)
+
+[![Build Status](https://drone.io/github.com/go-xweb/xweb/status.png)](https://drone.io/github.com/go-xweb/xweb/latest)  [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xweb/xweb)
+
+## Changelog
+
+* **v0.1.1** : App added method AutoAction;Action added method AddTmplVar;Action's method Render's T param now supports variable, function or method。
+* **v0.1.0** : Inital release.
+
+## Features
+
+* multiple server and multiple application in one executable application
+* simple and helpful url route mapping
+
+## Installation
+
+Make sure you have the a working Go environment. See the [install instructions](http://golang.org/doc/install.html). 
+
+To install xweb, simply run:
+
+    go get github.com/lunny/xweb
+
+## Examples
+
+Please visit [examples](https://github.com/go-xweb/xweb/tree/master/examples) folder
+
+## Case
+
+* [xorm.io](http://xorm.io)
+* [Godaily.org](http://godaily.org)
+
+## Documentation
+
+API, Please visit [GoWalker](http://gowalker.org/github.com/go-xweb/xweb)
+
+
+## License
+BSD License
+[http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/)
+
+
+

+ 977 - 0
go-xweb/xweb/action.go

@@ -0,0 +1,977 @@
+package xweb
+
+import (
+	"bytes"
+	"compress/flate"
+	"compress/gzip"
+	"crypto/hmac"
+	"crypto/md5"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"encoding/xml"
+	"errors"
+	"fmt"
+	"html/template"
+	"io"
+	"io/ioutil"
+	"mime"
+	"mime/multipart"
+	"net/http"
+	"net/url"
+	"os"
+	"path"
+	"reflect"
+	"regexp"
+	"runtime"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/go-xweb/httpsession"
+	"github.com/go-xweb/log"
+	"github.com/go-xweb/uuid"
+)
+
+type ActionOption struct {
+	AutoMapForm bool
+	CheckXsrf   bool
+}
+
+// An Action object or it's substruct is created for every incoming HTTP request.
+// It provides information
+// about the request, including the http.Request object, the GET and POST params,
+// and acts as a Writer for the response.
+type Action struct {
+	Request *http.Request
+	App     *App
+	Option  *ActionOption
+	http.ResponseWriter
+	C            reflect.Value
+	session      *httpsession.Session
+	T            T
+	f            T
+	RootTemplate *template.Template
+	RequestBody  []byte
+	StatusCode   int
+}
+
+type Mapper struct {
+}
+
+type T map[string]interface{}
+
+func XsrfName() string {
+	return XSRF_TAG
+}
+
+//[SWH|+]:
+// Protocol returns request protocol name, such as HTTP/1.1 .
+func (c *Action) Protocol() string {
+	return c.Request.Proto
+}
+
+// Uri returns full request url with query string, fragment.
+func (c *Action) Uri() string {
+	return c.Request.RequestURI
+}
+
+// Url returns request url path (without query string, fragment).
+func (c *Action) Url() string {
+	return c.Request.URL.String()
+}
+
+// Site returns base site url as scheme://domain type.
+func (c *Action) Site() string {
+	schecm, _ := c.App.GetConfig("schecm").(string)
+	if schecm == "" {
+		schecm = c.Scheme()
+	}
+	return schecm + "://" + c.Domain()
+}
+
+// Scheme returns request scheme as "http" or "https".
+func (c *Action) Scheme() string {
+	if c.Request.URL.Scheme != "" {
+		return c.Request.URL.Scheme
+	} else if c.Request.TLS == nil {
+		return "http"
+	} else {
+		return "https"
+	}
+}
+
+// Domain returns host name.
+// Alias of Host method.
+func (c *Action) Domain() string {
+	return c.Host()
+}
+
+// Host returns host name.
+// if no host info in request, return localhost.
+func (c *Action) Host() string {
+	if c.Request.Host != "" {
+		hostParts := strings.Split(c.Request.Host, ":")
+		if len(hostParts) > 0 {
+			return hostParts[0]
+		}
+		return c.Request.Host
+	}
+	return "localhost"
+}
+
+// Is returns boolean of this request is on given method, such as Is("POST").
+func (c *Action) Is(method string) bool {
+	return c.Method() == method
+}
+
+// IsAjax returns boolean of this request is generated by ajax.
+func (c *Action) IsAjax() bool {
+	return c.Header("X-Requested-With") == "XMLHttpRequest"
+}
+
+// IsSecure returns boolean of this request is in https.
+func (c *Action) IsSecure() bool {
+	return c.Scheme() == "https"
+}
+
+// IsSecure returns boolean of this request is in webSocket.
+func (c *Action) IsWebsocket() bool {
+	return c.Header("Upgrade") == "websocket"
+}
+
+// IsSecure returns boolean of whether file uploads in this request or not..
+func (c *Action) IsUpload() bool {
+	return c.Request.MultipartForm != nil
+}
+
+// IP returns request client ip.
+// if in proxy, return first proxy id.
+// if error, return 127.0.0.1.
+func (c *Action) IP() string {
+	ips := c.Proxy()
+	if len(ips) > 0 && ips[0] != "" {
+		return ips[0]
+	}
+	ip := strings.Split(c.Request.RemoteAddr, ":")
+	if len(ip) > 0 {
+		if ip[0] != "[" {
+			return ip[0]
+		}
+	}
+	return "127.0.0.1"
+}
+
+// Proxy returns proxy client ips slice.
+func (c *Action) Proxy() []string {
+	if ips := c.Header("X-Forwarded-For"); ips != "" {
+		return strings.Split(ips, ",")
+	}
+	return []string{}
+}
+
+// Refer returns http referer header.
+func (c *Action) Refer() string {
+	return c.Header("Referer")
+}
+
+// SubDomains returns sub domain string.
+// if aa.bb.domain.com, returns aa.bb .
+func (c *Action) SubDomains() string {
+	parts := strings.Split(c.Host(), ".")
+	return strings.Join(parts[len(parts)-2:], ".")
+}
+
+// Port returns request client port.
+// when error or empty, return 80.
+func (c *Action) Port() int {
+	parts := strings.Split(c.Request.Host, ":")
+	if len(parts) == 2 {
+		port, _ := strconv.Atoi(parts[1])
+		return port
+	}
+	return 80
+}
+
+// UserAgent returns request client user agent string.
+func (c *Action) UserAgent() string {
+	return c.Header("User-Agent")
+}
+
+// Query returns input data item string by a given string.
+func (c *Action) Query(key string) string {
+	c.Request.ParseForm()
+	return c.Request.Form.Get(key)
+}
+
+// Header returns request header item string by a given string.
+func (c *Action) Header(key string) string {
+	return c.Request.Header.Get(key)
+}
+
+// Cookie returns request cookie item string by a given key.
+// if non-existed, return empty string.
+func (c *Action) Cookie(key string) string {
+	ck, err := c.Request.Cookie(key)
+	if err != nil {
+		return ""
+	}
+	return ck.Value
+}
+
+// Body returns the raw request body data as bytes.
+func (c *Action) Body() []byte {
+	if len(c.RequestBody) > 0 {
+		return c.RequestBody
+	}
+
+	requestbody, _ := ioutil.ReadAll(c.Request.Body)
+	c.Request.Body.Close()
+	bf := bytes.NewBuffer(requestbody)
+	c.Request.Body = ioutil.NopCloser(bf)
+	c.RequestBody = requestbody
+	return requestbody
+}
+
+func (c *Action) DisableHttpCache() {
+	c.SetHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT")
+	c.SetHeader("Last-Modified", webTime(time.Now().UTC()))
+	c.SetHeader("Cache-Control", "no-store, no-cache, must-revalidate")
+	c.SetHeader("Cache-Control", "post-check=0, pre-check=0")
+	c.SetHeader("Pragma", "no-cache")
+}
+
+func (c *Action) HttpCache(content []byte) bool {
+	h := md5.New()
+	h.Write(content)
+	Etag := hex.EncodeToString(h.Sum(nil))
+	//c.SetHeader("Connection", "keep-alive")
+	c.SetHeader("X-Cache", "HIT from COSCMS-Page-Cache")
+	//c.SetHeader("X-Cache", "HIT from COSCMS-Page-Cache 2013-12-02 17:16:01")
+	if inm := c.Header("If-None-Match"); inm != "" && inm == Etag {
+		h := c.ResponseWriter.Header()
+		delete(h, "Content-Type")
+		delete(h, "Content-Length")
+		c.ResponseWriter.WriteHeader(http.StatusNotModified)
+		return true
+	}
+	c.SetHeader("Etag", Etag)
+	c.SetHeader("Cache-Control", "public,max-age=1")
+	return false
+}
+
+// Body sets response body content.
+// if EnableGzip, compress content string.
+// it sends out response body directly.
+func (c *Action) SetBody(content []byte) error {
+	if c.App.AppConfig.EnableHttpCache && c.HttpCache(content) {
+		return nil
+	}
+	output_writer := c.ResponseWriter.(io.Writer)
+	if c.App.Server.Config.EnableGzip == true && c.Header("Accept-Encoding") != "" {
+		splitted := strings.SplitN(c.Header("Accept-Encoding"), ",", -1)
+		encodings := make([]string, len(splitted))
+
+		for i, val := range splitted {
+			encodings[i] = strings.TrimSpace(val)
+		}
+		for _, val := range encodings {
+			if val == "gzip" {
+				c.SetHeader("Content-Encoding", "gzip")
+				output_writer, _ = gzip.NewWriterLevel(c.ResponseWriter, gzip.BestSpeed)
+				break
+			} else if val == "deflate" {
+				c.SetHeader("Content-Encoding", "deflate")
+				output_writer, _ = flate.NewWriter(c.ResponseWriter, flate.BestSpeed)
+				break
+			}
+		}
+	} else {
+		c.SetHeader("Content-Length", strconv.Itoa(len(content)))
+	}
+	_, err := output_writer.Write(content)
+	switch output_writer.(type) {
+	case *gzip.Writer:
+		output_writer.(*gzip.Writer).Close()
+	case *flate.Writer:
+		output_writer.(*flate.Writer).Close()
+	}
+	return err
+}
+
+//[SWH|+];
+
+func (c *Action) XsrfValue() string {
+	var val string = ""
+	cookie, err := c.GetCookie(XSRF_TAG)
+	if err != nil {
+		val = uuid.NewRandom().String()
+		c.SetCookie(NewCookie(XSRF_TAG, val, int64(c.App.AppConfig.SessionTimeout)))
+	} else {
+		val = cookie.Value
+	}
+	return val
+}
+
+func (c *Action) XsrfFormHtml() template.HTML {
+	if c.App.AppConfig.CheckXsrf {
+		return template.HTML(fmt.Sprintf(`<input type="hidden" name="%v" value="%v" />`,
+			XSRF_TAG, c.XsrfValue()))
+	}
+	return template.HTML("")
+}
+
+// WriteString writes string data into the response object.
+func (c *Action) WriteBytes(bytes []byte) error {
+	//_, err := c.ResponseWriter.Write(bytes)
+	err := c.SetBody(bytes) //[SWH|+]
+	if err != nil {
+		c.App.Error("Error during write:", err)
+	}
+	return err
+}
+
+func (c *Action) Write(content string, values ...interface{}) error {
+	if len(values) > 0 {
+		content = fmt.Sprintf(content, values...)
+	}
+	//_, err := c.ResponseWriter.Write([]byte(content))
+	err := c.SetBody([]byte(content)) //[SWH|+]
+	if err != nil {
+		c.App.Error("Error during write:", err)
+	}
+	return err
+}
+
+// Abort is a helper method that sends an HTTP header and an optional
+// body. It is useful for returning 4xx or 5xx errors.
+// Once it has been called, any return value from the handler will
+// not be written to the response.
+func (c *Action) Abort(status int, body string) error {
+	c.StatusCode = status
+	return c.App.error(c.ResponseWriter, status, body)
+}
+
+// Redirect is a helper method for 3xx redirects.
+func (c *Action) Redirect(url string, status ...int) error {
+	if len(status) == 0 {
+		c.StatusCode = 302
+	} else {
+		c.StatusCode = status[0]
+	}
+	return c.App.Redirect(c.ResponseWriter, c.Request.URL.Path, url, status...)
+}
+
+// Notmodified writes a 304 HTTP response
+func (c *Action) NotModified() {
+	c.StatusCode = 304
+	c.ResponseWriter.WriteHeader(304)
+}
+
+// NotFound writes a 404 HTTP response
+func (c *Action) NotFound(message string) error {
+	c.StatusCode = 404
+	return c.Abort(404, message)
+}
+
+// ParseStruct mapping forms' name and values to struct's field
+// For example:
+//		<form>
+//			<input name="user.id"/>
+//			<input name="user.name"/>
+//			<input name="user.age"/>
+//		</form>
+//
+//		type User struct {
+//			Id int64
+//			Name string
+//			Age string
+//		}
+//
+//		var user User
+//		err := action.MapForm(&user)
+//
+func (c *Action) MapForm(st interface{}, names ...string) error {
+	v := reflect.ValueOf(st)
+	var name string
+	if len(names) == 0 {
+		name = UnTitle(v.Type().Elem().Name())
+	} else {
+		name = names[0]
+	}
+	return c.App.namedStructMap(v.Elem(), c.Request, name)
+}
+
+// ContentType sets the Content-Type header for an HTTP response.
+// For example, c.ContentType("json") sets the content-type to "application/json"
+// If the supplied value contains a slash (/) it is set as the Content-Type
+// verbatim. The return value is the content type as it was
+// set, or an empty string if none was found.
+func (c *Action) SetContentType(val string) string {
+	var ctype string
+	if strings.ContainsRune(val, '/') {
+		ctype = val
+	} else {
+		if !strings.HasPrefix(val, ".") {
+			val = "." + val
+		}
+		ctype = mime.TypeByExtension(val)
+	}
+	if ctype != "" {
+		c.SetHeader("Content-Type", ctype)
+	}
+	return ctype
+}
+
+// SetCookie adds a cookie header to the response.
+func (c *Action) SetCookie(cookie *http.Cookie) {
+	c.SetHeader("Set-Cookie", cookie.String())
+}
+
+func (c *Action) GetCookie(cookieName string) (*http.Cookie, error) {
+	return c.Request.Cookie(cookieName)
+}
+
+func getCookieSig(key string, val []byte, timestamp string) string {
+	hm := hmac.New(sha1.New, []byte(key))
+
+	hm.Write(val)
+	hm.Write([]byte(timestamp))
+
+	hex := fmt.Sprintf("%02x", hm.Sum(nil))
+	return hex
+}
+
+func (c *Action) SetSecureCookie(name string, val string, age int64) {
+	//base64 encode the val
+	if len(c.App.AppConfig.CookieSecret) == 0 {
+		c.App.Error("Secret Key for secure cookies has not been set. Please assign a cookie secret to web.Config.CookieSecret.")
+		return
+	}
+	var buf bytes.Buffer
+	encoder := base64.NewEncoder(base64.StdEncoding, &buf)
+	encoder.Write([]byte(val))
+	encoder.Close()
+	vs := buf.String()
+	vb := buf.Bytes()
+	timestamp := strconv.FormatInt(time.Now().Unix(), 10)
+	sig := getCookieSig(c.App.AppConfig.CookieSecret, vb, timestamp)
+	cookie := strings.Join([]string{vs, timestamp, sig}, "|")
+	c.SetCookie(NewCookie(name, cookie, age))
+}
+
+func (c *Action) GetSecureCookie(name string) (string, bool) {
+	for _, cookie := range c.Request.Cookies() {
+		if cookie.Name != name {
+			continue
+		}
+
+		parts := strings.SplitN(cookie.Value, "|", 3)
+
+		val := parts[0]
+		timestamp := parts[1]
+		sig := parts[2]
+
+		if getCookieSig(c.App.AppConfig.CookieSecret, []byte(val), timestamp) != sig {
+			return "", false
+		}
+
+		ts, _ := strconv.ParseInt(timestamp, 0, 64)
+
+		if time.Now().Unix()-31*86400 > ts {
+			return "", false
+		}
+
+		buf := bytes.NewBufferString(val)
+		encoder := base64.NewDecoder(base64.StdEncoding, buf)
+
+		res, _ := ioutil.ReadAll(encoder)
+		return string(res), true
+	}
+	return "", false
+}
+
+func (c *Action) Method() string {
+	return c.Request.Method
+}
+
+func (c *Action) Go(m string, anotherc ...interface{}) error {
+	var t reflect.Type
+	if len(anotherc) > 0 {
+		t = reflect.TypeOf(anotherc[0]).Elem()
+	} else {
+		t = reflect.TypeOf(c.C.Interface()).Elem()
+	}
+
+	root, ok := c.App.ActionsPath[t]
+	if !ok {
+		return NotFound()
+	}
+
+	uris := strings.Split(m, "?")
+
+	tag, ok := t.FieldByName(uris[0])
+	if !ok {
+		return NotFound()
+	}
+
+	tagStr := tag.Tag.Get("xweb")
+	var rPath string
+	if tagStr != "" {
+		p := tagStr
+		ts := strings.Split(tagStr, " ")
+		if len(ts) >= 2 {
+			p = ts[1]
+		}
+		rPath = path.Join(root, p, m[len(uris[0]):])
+	} else {
+		rPath = path.Join(root, m)
+	}
+	rPath = strings.Replace(rPath, "//", "/", -1)
+	return c.Redirect(rPath)
+}
+
+func (c *Action) Flush() {
+	flusher, _ := c.ResponseWriter.(http.Flusher)
+	flusher.Flush()
+}
+
+func (c *Action) BasePath() string {
+	return c.App.BasePath
+}
+
+func (c *Action) Namespace() string {
+	return c.App.ActionsPath[c.C.Type()]
+}
+
+func (c *Action) Debug(params ...interface{}) {
+	c.App.Debug(params...)
+}
+
+func (c *Action) Info(params ...interface{}) {
+	c.App.Info(params...)
+}
+
+func (c *Action) Warn(params ...interface{}) {
+	c.App.Warn(params...)
+}
+
+func (c *Action) Error(params ...interface{}) {
+	c.App.Error(params...)
+}
+
+func (c *Action) Fatal(params ...interface{}) {
+	c.App.Fatal(params...)
+}
+
+func (c *Action) Panic(params ...interface{}) {
+	c.App.Panic(params...)
+}
+
+func (c *Action) Debugf(format string, params ...interface{}) {
+	c.App.Debugf(format, params...)
+}
+
+func (c *Action) Infof(format string, params ...interface{}) {
+	c.App.Infof(format, params...)
+}
+
+func (c *Action) Warnf(format string, params ...interface{}) {
+	c.App.Warnf(format, params...)
+}
+
+func (c *Action) Errorf(format string, params ...interface{}) {
+	c.App.Errorf(format, params...)
+}
+
+func (c *Action) Fatalf(format string, params ...interface{}) {
+	c.App.Fatalf(format, params...)
+}
+
+func (c *Action) Panicf(format string, params ...interface{}) {
+	c.App.Panicf(format, params...)
+}
+
+// Include method provide to template for {{include "xx.tmpl"}}
+func (c *Action) Include(tmplName string) interface{} {
+	t := c.RootTemplate.New(tmplName)
+	t.Funcs(c.GetFuncs())
+	content, err := c.getTemplate(tmplName)
+	if err != nil {
+		c.Errorf("RenderTemplate %v read err: %s", tmplName, err)
+		return ""
+	}
+
+	constr := string(content)
+	//[SWH|+]call hook
+	if r, err := XHook.Call("BeforeRender", constr, c); err == nil {
+		constr = XHook.String(r[0])
+	}
+
+	tmpl, err := t.Parse(constr)
+
+	if err != nil {
+		c.Errorf("Parse %v err: %v", tmplName, err)
+		return ""
+	}
+	newbytes := bytes.NewBufferString("")
+	err = tmpl.Execute(newbytes, c.C.Elem().Interface())
+	if err != nil {
+		c.Errorf("Parse %v err: %v", tmplName, err)
+		return ""
+	}
+
+	tplcontent, err := ioutil.ReadAll(newbytes)
+	if err != nil {
+		c.Errorf("Parse %v err: %v", tmplName, err)
+		return ""
+	}
+	return template.HTML(string(tplcontent))
+}
+
+// render the template with vars map, you can have zero or one map
+func (c *Action) NamedRender(name, content string, params ...*T) error {
+	defer func() {
+		if r := recover(); r != nil {
+			log.Println(r)
+			for skip := 0; skip < 100; skip++ {
+				_, file, line, ok := runtime.Caller(skip)
+				if !ok {
+					break
+				}
+				go log.Printf("%v,%v\n", file, line)
+			}
+		}
+	}()
+	c.f["oinclude"] = c.Include
+	if c.App.AppConfig.SessionOn {
+		c.f["session"] = c.GetSession
+	}
+	c.f["cookie"] = c.Cookie
+	c.f["XsrfFormHtml"] = c.XsrfFormHtml
+	c.f["XsrfValue"] = c.XsrfValue
+	if len(params) > 0 {
+		c.AddTmplVars(params[0])
+	}
+	c.RootTemplate = template.New(name)
+	c.RootTemplate.Funcs(c.GetFuncs())
+
+	//[SWH|+]call hook
+	if r, err := XHook.Call("BeforeRender", content, c); err == nil {
+		content = XHook.String(r[0])
+	}
+	content = newInclude(c, content)
+	tmpl, err := c.RootTemplate.Parse(content)
+	if err == nil {
+		newbytes := bytes.NewBufferString("")
+		err = tmpl.Execute(newbytes, c.C.Elem().Interface())
+		if err == nil {
+			tplcontent, err := ioutil.ReadAll(newbytes)
+			if err == nil {
+				//[SWH|+]call hook
+				if r, err := XHook.Call("AfterRender", tplcontent, c); err == nil {
+					if ret := XHook.Value(r, 0); ret != nil {
+						tplcontent = ret.([]byte)
+					}
+				}
+				err = c.SetBody(tplcontent) //[SWH|+]
+				//_, err = c.ResponseWriter.Write(tplcontent)
+			}
+		}
+	}
+	return err
+}
+
+func (c *Action) getTemplate(tmpl string) ([]byte, error) {
+	if c.App.AppConfig.CacheTemplates {
+		return c.App.TemplateMgr.GetTemplate(tmpl)
+	}
+	path := c.App.getTemplatePath(tmpl)
+	if path == "" {
+		return nil, errors.New(fmt.Sprintf("No template file %v found", path))
+	}
+	content, err := ioutil.ReadFile(path)
+	if err == nil {
+		content = newIncludeIntmpl(c.App.AppConfig.TemplateDir, content)
+	}
+	return content, err
+}
+
+// render the template with vars map, you can have zero or one map
+func (c *Action) Render(tmpl string, params ...*T) error {
+	content, err := c.getTemplate(tmpl)
+	if err == nil {
+		err = c.NamedRender(tmpl, string(content), params...)
+	}
+	return err
+}
+
+//仅生成网页内容
+var regInclude = regexp.MustCompile(`\{\{\s*include\s*"(.*?\.html)"\}\}`)
+
+func (c *Action) NamedRender4Cache(name, content string, params ...*T) ([]byte, error) {
+	c.f["oinclude"] = c.Include
+	if c.App.AppConfig.SessionOn {
+		c.f["session"] = c.GetSession
+	}
+	c.f["cookie"] = c.Cookie
+	c.f["XsrfFormHtml"] = c.XsrfFormHtml
+	c.f["XsrfValue"] = c.XsrfValue
+	if len(params) > 0 {
+		c.AddTmplVars(params[0])
+	}
+
+	c.RootTemplate = template.New(name)
+	c.RootTemplate.Funcs(c.GetFuncs())
+
+	//[SWH|+]call hook
+	if r, err := XHook.Call("BeforeRender", content, c); err == nil {
+		content = XHook.String(r[0])
+	}
+	content = newInclude(c, content)
+	tmpl, err := c.RootTemplate.Parse(content)
+
+	if err == nil {
+		newbytes := bytes.NewBufferString("")
+		err = tmpl.Execute(newbytes, c.C.Elem().Interface())
+		if err == nil {
+			tplcontent, err := ioutil.ReadAll(newbytes)
+			if err == nil {
+				//[SWH|+]call hook
+				if r, err := XHook.Call("AfterRender", tplcontent, c); err == nil {
+					if ret := XHook.Value(r, 0); ret != nil {
+						tplcontent = ret.([]byte)
+					}
+				}
+				//err = c.SetBody(tplcontent) //[SWH|+]
+				//_, err = c.ResponseWriter.Write(tplcontent)
+				return tplcontent, nil
+			}
+		}
+	}
+	return nil, err
+}
+
+//生成可缓存的数据但并未写到流中
+func (c *Action) Render4Cache(tmpl string, params ...*T) ([]byte, error) {
+	content, err := c.getTemplate(tmpl)
+
+	if err == nil {
+		return c.NamedRender4Cache(tmpl, string(content), params...)
+	}
+	return nil, err
+}
+
+var FuncLock = &sync.Mutex{}
+
+func (c *Action) GetFuncs() template.FuncMap {
+	FuncLock.Lock()
+	funcs := template.FuncMap{}
+	for k, v := range c.App.FuncMaps {
+		funcs[k] = v
+	}
+	FuncLock.Unlock()
+	if c.f != nil {
+		for k, v := range c.f {
+			funcs[k] = v
+		}
+	}
+	return funcs
+}
+
+func (c *Action) SetConfig(name string, value interface{}) {
+	c.App.Config[name] = value
+}
+
+func (c *Action) GetConfig(name string) interface{} {
+	return c.App.Config[name]
+}
+
+func (c *Action) RenderString(content string, params ...*T) error {
+	h := md5.New()
+	h.Write([]byte(content))
+	name := h.Sum(nil)
+	return c.NamedRender(string(name), content, params...)
+}
+
+// SetHeader sets a response header. the current value
+// of that header will be overwritten .
+func (c *Action) SetHeader(key string, value string) {
+	c.ResponseWriter.Header().Set(key, value)
+}
+
+// add a name value for template
+func (c *Action) AddTmplVar(name string, varOrFunc interface{}) {
+	if varOrFunc == nil {
+		c.T[name] = varOrFunc
+		return
+	}
+
+	if reflect.ValueOf(varOrFunc).Type().Kind() == reflect.Func {
+		c.f[name] = varOrFunc
+	} else {
+		c.T[name] = varOrFunc
+	}
+}
+
+// add names and values for template
+func (c *Action) AddTmplVars(t *T) {
+	for name, value := range *t {
+		c.AddTmplVar(name, value)
+	}
+}
+
+func (c *Action) ServeJson(obj interface{}) {
+	content, err := json.MarshalIndent(obj, "", "  ")
+	if err != nil {
+		http.Error(c.ResponseWriter, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	c.SetHeader("Content-Length", strconv.Itoa(len(content)))
+	c.ResponseWriter.Header().Set("Content-Type", "application/json")
+	c.ResponseWriter.Write(content)
+}
+
+func (c *Action) ServeXml(obj interface{}) {
+	content, err := xml.Marshal(obj)
+	if err != nil {
+		http.Error(c.ResponseWriter, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	c.SetHeader("Content-Length", strconv.Itoa(len(content)))
+	c.ResponseWriter.Header().Set("Content-Type", "application/xml")
+	c.ResponseWriter.Write(content)
+}
+
+func (c *Action) ServeFile(fpath string) {
+	c.ResponseWriter.Header().Del("Content-Type")
+	http.ServeFile(c.ResponseWriter, c.Request, fpath)
+}
+
+func (c *Action) GetSlice(key string) []string {
+	return c.Request.Form[key]
+}
+
+func (c *Action) GetForm() url.Values {
+	return c.Request.Form
+}
+
+//增加_过滤脚本等
+func FilterXSS(str string) string {
+	str = strings.Replace(str, "<", "&#60;", -1)
+	str = strings.Replace(str, ">", "&#62;", -1)
+	str = strings.Replace(str, "%3C", "&#60;", -1)
+	str = strings.Replace(str, "%3E", "&#62;", -1)
+	str = strings.Replace(str, "expression", "expression", -1)
+	str = strings.Replace(str, "javascript", "javascript", -1)
+	return str
+}
+
+//增加_原GetString方法
+func (c *Action) GetStringComm(key string) string {
+	s := c.GetSlice(key)
+	if len(s) > 0 {
+		return s[0]
+	}
+	return ""
+}
+
+//修改_防Xss注入
+func (c *Action) GetString(key string) string {
+	return FilterXSS(c.GetStringComm(key))
+}
+
+func (c *Action) GetInteger(key string) (int, error) {
+	return strconv.Atoi(c.GetString(key))
+}
+
+func (c *Action) GetInt(key string) (int64, error) {
+	return strconv.ParseInt(c.GetString(key), 10, 64)
+}
+
+func (c *Action) GetBool(key string) (bool, error) {
+	return strconv.ParseBool(c.GetString(key))
+}
+
+func (c *Action) GetFloat(key string) (float64, error) {
+	return strconv.ParseFloat(c.GetString(key), 64)
+}
+
+func (c *Action) GetFile(key string) (multipart.File, *multipart.FileHeader, error) {
+	return c.Request.FormFile(key)
+}
+
+/** 2017-01-18 多文件上传支持 wanghuidong **/
+func (c *Action) GetFiles() ([]*multipart.FileHeader, error) {
+	c.Request.ParseMultipartForm(32 << 20)
+	mp := c.Request.MultipartForm
+	if mp == nil {
+		log.Println("not MultipartForm.")
+		return nil, nil
+	}
+	fileHeaderMap := mp.File
+	fileHeaders := make([]*multipart.FileHeader, 0)
+	for _, _fileHeaders := range fileHeaderMap {
+		for _, fileHeader := range _fileHeaders {
+			fileHeaders = append(fileHeaders, fileHeader)
+		}
+	}
+	return fileHeaders, nil
+}
+
+func (c *Action) GetLogger() *log.Logger {
+	return c.App.Logger
+}
+
+func (c *Action) SaveToFile(fromfile, tofile string) error {
+	log.Println("fromfile--" + fromfile + "---tofile:" + tofile)
+	file, _, err := c.Request.FormFile(fromfile)
+	log.Println("file: ", file)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+	f, err := os.OpenFile(tofile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	_, err = io.Copy(f, file)
+	return err
+}
+
+func (c *Action) Session() *httpsession.Session {
+	if c.session == nil {
+		c.session = c.App.SessionManager.Session(c.Request, c.ResponseWriter)
+	}
+	return c.session
+}
+
+func (c *Action) GetSession(key string) interface{} {
+	return c.Session().Get(key)
+}
+
+func (c *Action) SetSession(key string, value interface{}) {
+	c.Session().Set(key, value)
+}
+
+func (c *Action) DelSession(key string) {
+	c.Session().Del(key)
+}
+
+func newInclude(c *Action, content string) string {
+	for i := 0; i < 5; i++ {
+		newcontent := regInclude.ReplaceAllStringFunc(content, func(m string) string {
+			tpl := regInclude.FindStringSubmatch(m)[1]
+			c, _ := c.getTemplate(tpl)
+			return string(c)
+		})
+		if content == newcontent {
+			break
+		}
+		content = newcontent
+	}
+	return content
+}

+ 1018 - 0
go-xweb/xweb/app.go

@@ -0,0 +1,1018 @@
+package xweb
+
+import (
+	"errors"
+	"fmt"
+	"html/template"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/go-xweb/httpsession"
+	"github.com/go-xweb/log"
+)
+
+const (
+	XSRF_TAG string = "_xsrf"
+)
+
+type App struct {
+	BasePath        string
+	Name            string //[SWH|+]
+	Routes          []Route
+	RoutesEq        map[string]map[string]Route
+	filters         []Filter
+	Server          *Server
+	AppConfig       *AppConfig
+	Config          map[string]interface{}
+	Actions         map[string]interface{}
+	ActionsPath     map[reflect.Type]string
+	ActionsNamePath map[string]string
+	FuncMaps        template.FuncMap
+	Logger          *log.Logger
+	VarMaps         T
+	SessionManager  *httpsession.Manager //Session manager
+	RootTemplate    *template.Template
+	ErrorTemplate   *template.Template
+	StaticVerMgr    *StaticVerMgr
+	TemplateMgr     *TemplateMgr
+	ContentEncoding string
+}
+
+const (
+	Debug = iota + 1
+	Product
+)
+
+type AppConfig struct {
+	Mode              int
+	StaticDir         string
+	TemplateDir       string
+	SessionOn         bool
+	MaxUploadSize     int64
+	CookieSecret      string
+	StaticFileVersion bool
+	CacheTemplates    bool
+	ReloadTemplates   bool
+	CheckXsrf         bool
+	SessionTimeout    time.Duration
+	FormMapToStruct   bool //[SWH|+]
+	EnableHttpCache   bool //[SWH|+]
+}
+
+type Route struct {
+	Path           string          //path string
+	CompiledRegexp *regexp.Regexp  //path regexp
+	HttpMethods    map[string]bool //GET POST HEAD DELETE etc.
+	HandlerMethod  string          //struct method name
+	HandlerElement reflect.Type    //handler element
+}
+
+func NewApp(args ...string) *App {
+	path := args[0]
+	name := ""
+	if len(args) == 1 {
+		name = strings.Replace(path, "/", "_", -1)
+	} else {
+		name = args[1]
+	}
+	return &App{
+		BasePath: path,
+		Name:     name, //[SWH|+]
+		RoutesEq: make(map[string]map[string]Route),
+		AppConfig: &AppConfig{
+			Mode:              Product,
+			StaticDir:         "static",
+			TemplateDir:       "templates",
+			SessionOn:         true,
+			SessionTimeout:    3600,
+			MaxUploadSize:     10 * 1024 * 1024,
+			StaticFileVersion: true,
+			CacheTemplates:    true,
+			ReloadTemplates:   true,
+			CheckXsrf:         true,
+			FormMapToStruct:   true,
+		},
+		Config:          map[string]interface{}{},
+		Actions:         map[string]interface{}{},
+		ActionsPath:     map[reflect.Type]string{},
+		ActionsNamePath: map[string]string{},
+		FuncMaps:        defaultFuncs,
+		VarMaps:         T{},
+		filters:         make([]Filter, 0),
+		StaticVerMgr:    new(StaticVerMgr),
+		TemplateMgr:     new(TemplateMgr),
+	}
+}
+
+func (a *App) initApp() {
+	if a.AppConfig.StaticFileVersion {
+		a.StaticVerMgr.Init(a, a.AppConfig.StaticDir)
+	}
+	if a.AppConfig.CacheTemplates {
+		a.TemplateMgr.Init(a, a.AppConfig.TemplateDir, a.AppConfig.ReloadTemplates)
+	}
+	a.FuncMaps["StaticUrl"] = a.StaticUrl
+	a.FuncMaps["XsrfName"] = XsrfName
+	a.VarMaps["XwebVer"] = Version
+
+	if a.AppConfig.SessionOn {
+		if a.Server.SessionManager != nil {
+			a.SessionManager = a.Server.SessionManager
+		} else {
+			a.SessionManager = httpsession.Default()
+			if a.AppConfig.SessionTimeout > time.Second {
+				a.SessionManager.SetMaxAge(a.AppConfig.SessionTimeout)
+			}
+			a.SessionManager.Run()
+		}
+	}
+
+	if a.Logger == nil {
+		a.Logger = a.Server.Logger
+	}
+}
+
+func (a *App) SetStaticDir(dir string) {
+	a.AppConfig.StaticDir = dir
+}
+
+func (a *App) SetTemplateDir(path string) {
+	a.AppConfig.TemplateDir = path
+}
+
+func (a *App) getTemplatePath(name string) string {
+	templateFile := path.Join(a.AppConfig.TemplateDir, name)
+	if fileExists(templateFile) {
+		return templateFile
+	}
+	return ""
+}
+
+func (app *App) SetConfig(name string, val interface{}) {
+	app.Config[name] = val
+}
+
+func (app *App) GetConfig(name string) interface{} {
+	return app.Config[name]
+}
+
+func (app *App) AddAction(cs ...interface{}) {
+	for _, c := range cs {
+		app.AddRouter(app.BasePath, c)
+	}
+}
+
+func (app *App) AutoAction(cs ...interface{}) {
+	for _, c := range cs {
+		t := reflect.Indirect(reflect.ValueOf(c)).Type()
+		name := t.Name()
+		if strings.HasSuffix(name, "Action") {
+			path := strings.ToLower(name[:len(name)-6])
+			app.AddRouter(JoinPath(app.BasePath, path), c)
+		} else {
+			app.Warn("AutoAction needs a named ends with Action")
+		}
+	}
+}
+
+func (app *App) AddTmplVar(name string, varOrFun interface{}) {
+	if reflect.TypeOf(varOrFun).Kind() == reflect.Func {
+		app.FuncMaps[name] = varOrFun
+	} else {
+		app.VarMaps[name] = varOrFun
+	}
+}
+
+func (app *App) AddTmplVars(t *T) {
+	for name, value := range *t {
+		app.AddTmplVar(name, value)
+	}
+}
+
+func (app *App) AddFilter(filter Filter) {
+	app.filters = append(app.filters, filter)
+}
+
+func (app *App) Debug(params ...interface{}) {
+	args := append([]interface{}{"[" + app.Name + "]"}, params...)
+	app.Logger.Debug(args...)
+}
+
+func (app *App) Info(params ...interface{}) {
+	args := append([]interface{}{"[" + app.Name + "]"}, params...)
+	app.Logger.Info(args...)
+}
+
+func (app *App) Warn(params ...interface{}) {
+	args := append([]interface{}{"[" + app.Name + "]"}, params...)
+	app.Logger.Warn(args...)
+}
+
+func (app *App) Error(params ...interface{}) {
+	args := append([]interface{}{"[" + app.Name + "]"}, params...)
+	app.Logger.Error(args...)
+}
+
+func (app *App) Fatal(params ...interface{}) {
+	args := append([]interface{}{"[" + app.Name + "]"}, params...)
+	app.Logger.Fatal(args...)
+}
+
+func (app *App) Panic(params ...interface{}) {
+	args := append([]interface{}{"[" + app.Name + "]"}, params...)
+	app.Logger.Panic(args...)
+}
+
+func (app *App) Debugf(format string, params ...interface{}) {
+	app.Logger.Debugf("["+app.Name+"] "+format, params...)
+}
+
+func (app *App) Infof(format string, params ...interface{}) {
+	app.Logger.Infof("["+app.Name+"] "+format, params...)
+}
+
+func (app *App) Warnf(format string, params ...interface{}) {
+	app.Logger.Warnf("["+app.Name+"] "+format, params...)
+}
+
+func (app *App) Errorf(format string, params ...interface{}) {
+	app.Logger.Errorf("["+app.Name+"] "+format, params...)
+}
+
+func (app *App) Fatalf(format string, params ...interface{}) {
+	app.Logger.Fatalf("["+app.Name+"] "+format, params...)
+}
+
+func (app *App) Panicf(format string, params ...interface{}) {
+	app.Logger.Panicf("["+app.Name+"] "+format, params...)
+}
+
+func (app *App) filter(w http.ResponseWriter, req *http.Request) bool {
+	for _, filter := range app.filters {
+		if !filter.Do(w, req) {
+			return false
+		}
+	}
+	return true
+}
+
+func (a *App) addRoute(r string, methods map[string]bool, t reflect.Type, handler string) {
+	cr, err := regexp.Compile(r)
+	if err != nil {
+		a.Errorf("Error in route regex %q: %s", r, err)
+		return
+	}
+	a.Routes = append(a.Routes, Route{Path: r, CompiledRegexp: cr, HttpMethods: methods, HandlerMethod: handler, HandlerElement: t})
+}
+
+func (a *App) addEqRoute(r string, methods map[string]bool, t reflect.Type, handler string) {
+	if _, ok := a.RoutesEq[r]; !ok {
+		a.RoutesEq[r] = make(map[string]Route)
+	}
+	for v, _ := range methods {
+		a.RoutesEq[r][v] = Route{HandlerMethod: handler, HandlerElement: t}
+	}
+}
+
+var (
+	mapperType = reflect.TypeOf(Mapper{})
+)
+
+func (app *App) AddRouter(url string, c interface{}) {
+	t := reflect.TypeOf(c).Elem()
+	app.ActionsPath[t] = url
+	app.Actions[t.Name()] = c
+	app.ActionsNamePath[t.Name()] = url
+	for i := 0; i < t.NumField(); i++ {
+		if t.Field(i).Type != mapperType {
+			continue
+		}
+		name := t.Field(i).Name
+		a := strings.Title(name)
+		v := reflect.ValueOf(c).MethodByName(a)
+		if !v.IsValid() {
+			continue
+		}
+
+		tag := t.Field(i).Tag
+		tagStr := tag.Get("xweb")
+		methods := map[string]bool{"GET": true, "POST": true}
+		var p string
+		var isEq bool
+		if tagStr != "" {
+			tags := strings.Split(tagStr, " ")
+			path := tagStr
+			length := len(tags)
+			if length >= 2 {
+				for _, method := range strings.Split(tags[0], "|") {
+					methods[strings.ToUpper(method)] = true
+				}
+				path = tags[1]
+				if regexp.QuoteMeta(path) == path {
+					isEq = true
+				}
+			} else if length == 1 {
+				if tags[0][0] == '/' {
+					path = tags[0]
+					if regexp.QuoteMeta(path) == path {
+						isEq = true
+					}
+				} else {
+					for _, method := range strings.Split(tags[0], "|") {
+						methods[strings.ToUpper(method)] = true
+					}
+					path = "/" + name
+					isEq = true
+				}
+			} else {
+				path = "/" + name
+				isEq = true
+			}
+			p = strings.TrimRight(url, "/") + path
+		} else {
+			p = strings.TrimRight(url, "/") + "/" + name
+			isEq = true
+		}
+		if isEq {
+			app.addEqRoute(removeStick(p), methods, t, a)
+		} else {
+			app.addRoute(removeStick(p), methods, t, a)
+		}
+	}
+}
+
+// the main route handler in web.go
+func (a *App) routeHandler(req *http.Request, w http.ResponseWriter) {
+	requestPath := req.URL.Path
+	var statusCode = 0
+	defer func() {
+		if statusCode == 0 {
+			statusCode = 200
+		}
+		if statusCode >= 200 && statusCode < 400 {
+			a.Info(req.Method, statusCode, requestPath)
+		} else {
+			a.Error(req.Method, statusCode, requestPath)
+		}
+	}()
+
+	//ignore errors from ParseForm because it's usually harmless.
+	ct := req.Header.Get("Content-Type")
+	if strings.Contains(ct, "multipart/form-data") {
+		req.ParseMultipartForm(a.AppConfig.MaxUploadSize)
+	} else {
+		req.ParseForm()
+	}
+
+	//set some default headers
+	w.Header().Set("Server", "qfw")
+	tm := time.Now().UTC()
+	w.Header().Set("Date", webTime(tm))
+
+	// static files, needed op
+	if req.Method == "GET" || req.Method == "HEAD" {
+		success := a.TryServingFile(requestPath, req, w)
+		if success {
+			statusCode = 200
+			return
+		}
+		if requestPath == "/favicon.ico" {
+			statusCode = 404
+			a.error(w, 404, "Page not found")
+			return
+		}
+	}
+
+	//Set the default content-type
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
+	if !a.filter(w, req) {
+		statusCode = 302
+		return
+	}
+	requestPath = req.URL.Path //[SWH|+]support filter change req.URL.Path
+
+	reqPath := removeStick(requestPath)
+	allowMethod := Ternary(req.Method == "HEAD", "GET", req.Method).(string)
+	isFind := false
+	if routes, ok := a.RoutesEq[reqPath]; ok {
+		if route, ok := routes[allowMethod]; ok {
+			var isBreak bool = false
+			var args []reflect.Value
+			isBreak, statusCode = a.run(req, w, route, args)
+			if isBreak {
+				return
+			}
+			isFind = true
+		}
+	}
+	if !isFind {
+		for _, route := range a.Routes {
+			cr := route.CompiledRegexp
+
+			//if the methods don't match, skip this handler (except HEAD can be used in place of GET)
+			if _, ok := route.HttpMethods[allowMethod]; !ok {
+				continue
+			}
+
+			if !cr.MatchString(reqPath) {
+				continue
+			}
+
+			match := cr.FindStringSubmatch(reqPath)
+
+			if len(match[0]) != len(reqPath) {
+				continue
+			}
+
+			var args []reflect.Value
+			for _, arg := range match[1:] {
+				args = append(args, reflect.ValueOf(arg))
+			}
+			var isBreak bool = false
+			isBreak, statusCode = a.run(req, w, route, args)
+			if isBreak {
+				return
+			}
+		}
+	}
+	// try serving index.html or index.htm
+	if req.Method == "GET" || req.Method == "HEAD" {
+		if a.TryServingFile(path.Join(requestPath, "index.html"), req, w) {
+			statusCode = 200
+			return
+		} else if a.TryServingFile(path.Join(requestPath, "index.htm"), req, w) {
+			statusCode = 200
+			return
+		}
+	}
+
+	a.error(w, 404, "Page not found")
+	statusCode = 404
+}
+
+func (a *App) run(req *http.Request, w http.ResponseWriter, route Route, args []reflect.Value) (isBreak bool, statusCode int) {
+
+	vc := reflect.New(route.HandlerElement)
+	c := &Action{
+		Request:        req,
+		App:            a,
+		ResponseWriter: w,
+		T:              T{},
+		f:              T{},
+		Option: &ActionOption{
+			AutoMapForm: a.AppConfig.FormMapToStruct,
+			CheckXsrf:   a.AppConfig.CheckXsrf,
+		},
+	}
+
+	for k, v := range a.VarMaps {
+		c.T[k] = v
+	}
+
+	fieldA := vc.Elem().FieldByName("Action")
+	//fieldA := fieldByName(vc.Elem(), "Action")
+	if fieldA.IsValid() {
+		fieldA.Set(reflect.ValueOf(c))
+	}
+
+	fieldC := vc.Elem().FieldByName("C")
+	//fieldC := fieldByName(vc.Elem(), "C")
+	if fieldC.IsValid() {
+		fieldC.Set(reflect.ValueOf(vc))
+		//fieldC.Set(vc)
+	}
+
+	initM := vc.MethodByName("Init")
+	if initM.IsValid() {
+		params := []reflect.Value{}
+		initM.Call(params)
+	}
+
+	if c.Option.AutoMapForm {
+		a.StructMap(vc.Elem(), req)
+	}
+
+	if c.Option.CheckXsrf && req.Method == "POST" {
+		res, err := req.Cookie(XSRF_TAG)
+		formVals := req.Form[XSRF_TAG]
+		var formVal string
+		if len(formVals) > 0 {
+			formVal = formVals[0]
+		}
+		if err != nil || res.Value == "" || res.Value != formVal {
+			a.error(w, 500, "xsrf token error.")
+			a.Error("xsrf token error.")
+			statusCode = 500
+			isBreak = true
+			return
+		}
+	}
+
+	//[SWH|+]------------------------------------------Before-Hook
+	structName := reflect.ValueOf(route.HandlerElement.Name())
+	actionName := reflect.ValueOf(route.HandlerMethod)
+	initM = vc.MethodByName("Before")
+	if initM.IsValid() {
+		structAction := []reflect.Value{structName, actionName}
+		if ok := initM.Call(structAction); !ok[0].Bool() {
+			isBreak = true
+			return
+		}
+	}
+
+	ret, err := a.SafelyCall(vc, route.HandlerMethod, args)
+	if err != nil {
+		//there was an error or panic while calling the handler
+		if a.AppConfig.Mode == Debug {
+			a.error(w, 500, fmt.Sprintf("<pre>handler error: %v</pre>", err))
+		} else if a.AppConfig.Mode == Product {
+			a.error(w, 500, "Server Error")
+		}
+		statusCode = 500
+		isBreak = true
+		return
+	}
+	statusCode = fieldA.Interface().(*Action).StatusCode
+
+	//[SWH|+]------------------------------------------After-Hook
+	initM = vc.MethodByName("After")
+	if initM.IsValid() {
+		structAction := []reflect.Value{structName, actionName}
+		for _, v := range ret {
+			structAction = append(structAction, v)
+		}
+		if len(structAction) != initM.Type().NumIn() {
+			a.Error("Error : %v.After(): The number of params is not adapted.", structName)
+			isBreak = true
+			return
+		}
+		if ok := initM.Call(structAction); !ok[0].Bool() {
+			isBreak = true
+			return
+		}
+	}
+
+	if len(ret) == 0 {
+		isBreak = true
+		return
+	}
+
+	sval := ret[0]
+
+	var content []byte
+	if sval.Interface() == nil || sval.Kind() == reflect.Bool {
+		isBreak = true
+		return
+	} else if sval.Kind() == reflect.String {
+		content = []byte(sval.String())
+	} else if sval.Kind() == reflect.Slice && sval.Type().Elem().Kind() == reflect.Uint8 {
+		content = sval.Interface().([]byte)
+	} else if err, ok := sval.Interface().(error); ok {
+		if err != nil {
+			a.Error("Error:", err)
+			a.error(w, 500, "Server Error")
+			statusCode = 500
+		}
+		isBreak = true
+		return
+	} else {
+		a.Warn("unkonw returned result type %v, ignored %v", sval,
+			sval.Interface().(error))
+
+		isBreak = true
+		return
+	}
+
+	w.Header().Set("Content-Length", strconv.Itoa(len(content)))
+
+	_, err = w.Write(content)
+	if err != nil {
+		a.Error("Error during write: %v", err)
+		statusCode = 500
+		isBreak = true
+		return
+	}
+	return
+}
+
+func (a *App) error(w http.ResponseWriter, status int, content string) error {
+	w.WriteHeader(status)
+	if errorTmpl == "" {
+		errTmplFile := a.AppConfig.TemplateDir + "/_error.html"
+		if file, err := os.Stat(errTmplFile); err == nil && !file.IsDir() {
+			if b, e := ioutil.ReadFile(errTmplFile); e == nil {
+				errorTmpl = string(b)
+			}
+		}
+		if errorTmpl == "" {
+			errorTmpl = defaultErrorTmpl
+		}
+	}
+	res := fmt.Sprintf(errorTmpl, status, statusText[status],
+		status, statusText[status], content, Version)
+	_, err := w.Write([]byte(res))
+	return err
+}
+
+func (a *App) StaticUrl(url string) string {
+	var basePath string
+	if a.AppConfig.StaticDir == RootApp().AppConfig.StaticDir {
+		basePath = RootApp().BasePath
+	} else {
+		basePath = a.BasePath
+	}
+	if !a.AppConfig.StaticFileVersion {
+		return path.Join(basePath, url)
+	}
+	ver := a.StaticVerMgr.GetVersion(url)
+	if ver == "" {
+		return path.Join(basePath, url)
+	}
+	return path.Join(basePath, url+"?v="+ver)
+}
+
+// safelyCall invokes `function` in recover block
+func (a *App) SafelyCall(vc reflect.Value, method string, args []reflect.Value) (resp []reflect.Value, err error) {
+	defer func() {
+		if e := recover(); e != nil {
+			if !a.Server.Config.RecoverPanic {
+				// go back to panic
+				panic(e)
+			} else {
+				resp = nil
+				var content string
+				content = fmt.Sprintf("Handler crashed with error: %v", e)
+				for i := 1; ; i += 1 {
+					_, file, line, ok := runtime.Caller(i)
+					if !ok {
+						break
+					} else {
+						content += "\n"
+					}
+					content += fmt.Sprintf("%v %v", file, line)
+				}
+				a.Error(content)
+				err = errors.New(content)
+				return
+			}
+		}
+	}()
+	function := vc.MethodByName(method)
+	return function.Call(args), err
+}
+
+// Init content-length header.
+func (a *App) InitHeadContent(w http.ResponseWriter, contentLength int64) {
+	if a.ContentEncoding == "gzip" {
+		w.Header().Set("Content-Encoding", "gzip")
+	} else if a.ContentEncoding == "deflate" {
+		w.Header().Set("Content-Encoding", "deflate")
+	} else {
+		w.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10))
+	}
+}
+
+// tryServingFile attempts to serve a static file, and returns
+// whether or not the operation is successful.
+func (a *App) TryServingFile(name string, req *http.Request, w http.ResponseWriter) bool {
+	newPath := name
+	if strings.HasPrefix(name, a.BasePath) {
+		newPath = name[len(a.BasePath):]
+	}
+	staticFile := filepath.Join(a.AppConfig.StaticDir, newPath)
+	finfo, err := os.Stat(staticFile)
+	if err != nil {
+		return false
+	}
+	if !finfo.IsDir() {
+		isStaticFileToCompress := false
+		if a.Server.Config.EnableGzip && a.Server.Config.StaticExtensionsToGzip != nil && len(a.Server.Config.StaticExtensionsToGzip) > 0 {
+			for _, statExtension := range a.Server.Config.StaticExtensionsToGzip {
+				if strings.HasSuffix(strings.ToLower(staticFile), strings.ToLower(statExtension)) {
+					isStaticFileToCompress = true
+					break
+				}
+			}
+		}
+		if isStaticFileToCompress {
+			a.ContentEncoding = GetAcceptEncodingZip(req)
+			memzipfile, err := OpenMemZipFile(staticFile, a.ContentEncoding)
+			if err != nil {
+				return false
+			}
+			a.InitHeadContent(w, finfo.Size())
+			http.ServeContent(w, req, staticFile, finfo.ModTime(), memzipfile)
+		} else {
+			http.ServeFile(w, req, staticFile)
+		}
+		return true
+	}
+	return false
+}
+
+var (
+	sc *Action = &Action{}
+)
+
+// StructMap function mapping params to controller's properties
+func (a *App) StructMap(vc reflect.Value, r *http.Request) error {
+	return a.namedStructMap(vc, r, "")
+}
+
+// user[name][test]
+func SplitJson(s string) ([]string, error) {
+	res := make([]string, 0)
+	var begin, end int
+	var isleft bool
+	for i, r := range s {
+		switch r {
+		case '[':
+			isleft = true
+			if i > 0 && s[i-1] != ']' {
+				if begin == end {
+					return nil, errors.New("unknow character")
+				}
+				res = append(res, s[begin:end+1])
+			}
+			begin = i + 1
+			end = begin
+		case ']':
+			if !isleft {
+				return nil, errors.New("unknow character")
+			}
+			isleft = false
+			if begin != end {
+				//return nil, errors.New("unknow character")
+
+				res = append(res, s[begin:end+1])
+				begin = i + 1
+				end = begin
+			}
+		default:
+			end = i
+		}
+		if i == len(s)-1 && begin != end {
+			res = append(res, s[begin:end+1])
+		}
+	}
+	return res, nil
+}
+
+func (a *App) namedStructMap(vc reflect.Value, r *http.Request, topName string) error {
+	for k, t := range r.Form {
+		if k == XSRF_TAG || k == "" {
+			continue
+		}
+
+		if topName != "" {
+			if !strings.HasPrefix(k, topName) {
+				continue
+			}
+			k = k[len(topName)+1:]
+		}
+
+		v := t[0]
+		names := strings.Split(k, ".")
+		var err error
+		if len(names) == 1 {
+			names, err = SplitJson(k)
+			if err != nil {
+				a.Warn("Unrecognize form key", k, err)
+				continue
+			}
+		}
+
+		var value reflect.Value = vc
+		for i, name := range names {
+			name = strings.Title(name)
+			if i != len(names)-1 {
+				if value.Kind() != reflect.Struct {
+					a.Warnf("arg error, value kind is %v", value.Kind())
+					break
+				}
+
+				//fmt.Println(name)
+				value = value.FieldByName(name)
+				if !value.IsValid() {
+					a.Warnf("(%v value is not valid %v)", name, value)
+					break
+				}
+				if !value.CanSet() {
+					a.Warnf("can not set %v -> %v", name, value.Interface())
+					break
+				}
+
+				if value.Kind() == reflect.Ptr {
+					if value.IsNil() {
+						value.Set(reflect.New(value.Type().Elem()))
+					}
+					value = value.Elem()
+				}
+			} else {
+				if value.Kind() != reflect.Struct {
+					a.Warnf("arg error, value %v kind is %v", name, value.Kind())
+					break
+				}
+				tv := value.FieldByName(name)
+				if !tv.IsValid() {
+					break
+				}
+				if !tv.CanSet() {
+					a.Warnf("can not set %v to %v", k, tv)
+					break
+				}
+
+				if tv.Kind() == reflect.Ptr {
+					tv.Set(reflect.New(tv.Type().Elem()))
+					tv = tv.Elem()
+				}
+
+				var l interface{}
+				switch k := tv.Kind(); k {
+				case reflect.String:
+					l = v
+					tv.Set(reflect.ValueOf(l))
+				case reflect.Bool:
+					l = (v != "false" && v != "0")
+					tv.Set(reflect.ValueOf(l))
+				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
+					x, err := strconv.Atoi(v)
+					if err != nil {
+						a.Warnf("arg %v as int: %v", v, err)
+						break
+					}
+					l = x
+					//tv.set
+					tv.Set(reflect.ValueOf(l))
+				case reflect.Int64:
+					x, err := strconv.ParseInt(v, 10, 64)
+					if err != nil {
+						a.Warnf("arg %v as int64: %v", v, err)
+						break
+					}
+					l = x
+					tv.Set(reflect.ValueOf(l))
+				case reflect.Float32, reflect.Float64:
+					x, err := strconv.ParseFloat(v, 64)
+					if err != nil {
+						a.Warnf("arg %v as float64: %v", v, err)
+						break
+					}
+					l = x
+					tv.Set(reflect.ValueOf(l))
+				case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+					x, err := strconv.ParseUint(v, 10, 64)
+					if err != nil {
+						a.Warnf("arg %v as uint: %v", v, err)
+						break
+					}
+					l = x
+					tv.Set(reflect.ValueOf(l))
+				case reflect.Struct:
+					if tvf, ok := tv.Interface().(FromConversion); ok {
+						err := tvf.FromString(v)
+						if err != nil {
+							a.Warnf("struct %v invoke FromString faild", tvf)
+						}
+					} else if tv.Type().String() == "time.Time" {
+						x, err := time.Parse("2006-01-02 15:04:05.000 -0700", v)
+						if err != nil {
+							x, err = time.Parse("2006-01-02 15:04:05", v)
+							if err != nil {
+								x, err = time.Parse("2006-01-02", v)
+								if err != nil {
+									a.Warnf("unsupported time format %v, %v", v, err)
+									break
+								}
+							}
+						}
+						l = x
+						tv.Set(reflect.ValueOf(l))
+					} else {
+						a.Warn("can not set an struct which is not implement Fromconversion interface")
+					}
+				case reflect.Ptr:
+					a.Warn("can not set an ptr of ptr")
+				case reflect.Slice, reflect.Array:
+					tt := tv.Type().Elem()
+					tk := tt.Kind()
+					if tk == reflect.String {
+						tv.Set(reflect.ValueOf(t))
+						break
+					}
+
+					if tv.IsNil() {
+						tv.Set(reflect.MakeSlice(tv.Type(), len(t), len(t)))
+					}
+
+					for i, s := range t {
+						var err error
+						switch tk {
+						case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int8, reflect.Int64:
+							var v int64
+							v, err = strconv.ParseInt(s, 10, tt.Bits())
+							if err == nil {
+								tv.Index(i).SetInt(v)
+							}
+						case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+							var v uint64
+							v, err = strconv.ParseUint(s, 10, tt.Bits())
+							if err == nil {
+								tv.Index(i).SetUint(v)
+							}
+						case reflect.Float32, reflect.Float64:
+							var v float64
+							v, err = strconv.ParseFloat(s, tt.Bits())
+							if err == nil {
+								tv.Index(i).SetFloat(v)
+							}
+						case reflect.Bool:
+							var v bool
+							v, err = strconv.ParseBool(s)
+							if err == nil {
+								tv.Index(i).SetBool(v)
+							}
+						case reflect.Complex64, reflect.Complex128:
+							// TODO:
+							err = fmt.Errorf("unsupported slice element type %v", tk.String())
+						default:
+							err = fmt.Errorf("unsupported slice element type %v", tk.String())
+						}
+						if err != nil {
+							a.Warnf("slice error: %v, %v", name, err)
+							break
+						}
+					}
+				default:
+					a.Warnf("unknow mapping method", name)
+					break
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func (app *App) Redirect(w http.ResponseWriter, requestPath, url string, status ...int) error {
+	err := redirect(w, url, status...)
+	if err != nil {
+		app.Errorf("redirect error: %s", err)
+		return err
+	}
+	return nil
+}
+
+func (app *App) Action(name string) interface{} {
+	if v, ok := app.Actions[name]; ok {
+		return v
+	}
+	return nil
+}
+
+/*
+example:
+{
+	"AdminAction":{
+		"Index":["GET","POST"],
+		"Add":	["GET","POST"],
+		"Edit":	["GET","POST"]
+	}
+}
+*/
+func (app *App) Nodes() (r map[string]map[string][]string) {
+	r = make(map[string]map[string][]string)
+	for _, val := range app.Routes {
+		name := val.HandlerElement.Name()
+		if _, ok := r[name]; !ok {
+			r[name] = make(map[string][]string)
+		}
+		if _, ok := r[name][val.HandlerMethod]; !ok {
+			r[name][val.HandlerMethod] = make([]string, 0)
+		}
+		for k, _ := range val.HttpMethods {
+			r[name][val.HandlerMethod] = append(r[name][val.HandlerMethod], k) //FUNC1:[POST,GET]
+		}
+	}
+	for _, vals := range app.RoutesEq {
+		for k, v := range vals {
+			name := v.HandlerElement.Name()
+			if _, ok := r[name]; !ok {
+				r[name] = make(map[string][]string)
+			}
+			if _, ok := r[name][v.HandlerMethod]; !ok {
+				r[name][v.HandlerMethod] = make([]string, 0)
+			}
+			r[name][v.HandlerMethod] = append(r[name][v.HandlerMethod], k) //FUNC1:[POST,GET]
+		}
+	}
+	return
+}

+ 12 - 0
go-xweb/xweb/conversion.go

@@ -0,0 +1,12 @@
+package xweb
+
+// a struct implements this interface can be convert from request param to a struct
+type FromConversion interface {
+	FromString(content string) error
+}
+
+// a struct implements this interface can be convert from struct to template variable
+// Not Implemented
+type ToConversion interface {
+	ToString() string
+}

+ 16 - 0
go-xweb/xweb/doc.go

@@ -0,0 +1,16 @@
+// Copyright 2013 - 2014 The xweb Authors. All rights reserved.
+// Use of this source code is governed by a BSD
+// license that can be found in the LICENSE file.
+
+/*
+Package xweb is a simple and powerful web framework for Go.
+
+Installation
+
+Make sure you have installed Go 1.1+ and then:
+
+    go get github.com/go-xweb/xweb
+
+More usage, please visit https://github.com/go-xweb/xweb/
+*/
+package xweb

+ 42 - 0
go-xweb/xweb/error.go

@@ -0,0 +1,42 @@
+package xweb
+
+import (
+	"fmt"
+	"net/http"
+)
+
+type AbortError struct {
+	Code    int
+	Content string
+}
+
+func (a *AbortError) Error() string {
+	return fmt.Sprintf("%v %v", a.Code, a.Content)
+}
+
+func Abort(code int, content ...string) error {
+	if len(content) >= 1 {
+		return &AbortError{code, content[0]}
+	}
+	return &AbortError{code, statusText[code]}
+}
+
+func NotFound(content ...string) error {
+	return Abort(http.StatusNotFound, content...)
+}
+
+func NotSupported(content ...string) error {
+	return Abort(http.StatusMethodNotAllowed, content...)
+}
+
+func InternalServerError(content ...string) error {
+	return Abort(http.StatusInternalServerError, content...)
+}
+
+func Forbidden(content ...string) error {
+	return Abort(http.StatusForbidden, content...)
+}
+
+func Unauthorized(content ...string) error {
+	return Abort(http.StatusUnauthorized, content...)
+}

+ 27 - 0
go-xweb/xweb/fcgi.go

@@ -0,0 +1,27 @@
+package xweb
+
+import (
+	"net"
+	"net/http/fcgi"
+)
+
+func (s *Server) listenAndServeFcgi(addr string) error {
+	var l net.Listener
+	var err error
+
+	//if the path begins with a "/", assume it's a unix address
+	if addr[0] == '/' {
+		l, err = net.Listen("unix", addr)
+	} else {
+		l, err = net.Listen("tcp", addr)
+	}
+
+	//save the listener so it can be closed
+	s.l = l
+
+	if err != nil {
+		s.Logger.Println("FCGI listen error", err.Error())
+		return err
+	}
+	return fcgi.Serve(s.l, s)
+}

+ 93 - 0
go-xweb/xweb/filter.go

@@ -0,0 +1,93 @@
+package xweb
+
+import (
+	"net/http"
+	"net/url"
+	"regexp"
+)
+
+type Filter interface {
+	Do(http.ResponseWriter, *http.Request) bool
+}
+
+type LoginFilter struct {
+	App           *App
+	SessionName   string
+	AnonymousUrls []*regexp.Regexp
+	AskLoginUrls  []*regexp.Regexp
+	Redirect      string
+	OriUrlName    string
+}
+
+func NewLoginFilter(app *App, name string, redirect string) *LoginFilter {
+	filter := &LoginFilter{App: app, SessionName: name,
+		AnonymousUrls: make([]*regexp.Regexp, 0),
+		AskLoginUrls:  make([]*regexp.Regexp, 0),
+		Redirect:      redirect,
+	}
+	filter.AddAnonymousUrls("/favicon.ico", redirect)
+	return filter
+}
+
+func (s *LoginFilter) AddAnonymousUrls(urls ...string) {
+	for _, r := range urls {
+		cr, err := regexp.Compile(r)
+		if err == nil {
+			s.AnonymousUrls = append(s.AnonymousUrls, cr)
+		}
+	}
+}
+
+func (s *LoginFilter) AddAskLoginUrls(urls ...string) {
+	for _, r := range urls {
+		cr, err := regexp.Compile(r)
+		if err == nil {
+			s.AskLoginUrls = append(s.AskLoginUrls, cr)
+		}
+	}
+}
+
+func (s *LoginFilter) Do(w http.ResponseWriter, req *http.Request) bool {
+	requestPath := removeStick(req.URL.Path)
+
+	session := s.App.SessionManager.Session(req, w)
+	id := session.Get(s.SessionName)
+	has := (id != nil && id != "")
+
+	var redirect = s.Redirect
+	if s.OriUrlName != "" {
+		redirect = redirect + "?" + s.OriUrlName + "=" + url.QueryEscape(req.URL.String())
+	}
+	for _, cr := range s.AskLoginUrls {
+		if !cr.MatchString(requestPath) {
+			continue
+		}
+		match := cr.FindStringSubmatch(requestPath)
+		if len(match[0]) != len(requestPath) {
+			continue
+		}
+		if !has {
+			s.App.Redirect(w, requestPath, redirect)
+		}
+		return has
+	}
+	if len(s.AnonymousUrls) == 0 {
+		return true
+	}
+
+	for _, cr := range s.AnonymousUrls {
+		if !cr.MatchString(requestPath) {
+			continue
+		}
+		match := cr.FindStringSubmatch(requestPath)
+		if len(match[0]) != len(requestPath) {
+			continue
+		}
+		return true
+	}
+
+	if !has {
+		s.App.Redirect(w, requestPath, redirect)
+	}
+	return has
+}

+ 181 - 0
go-xweb/xweb/helpers.go

@@ -0,0 +1,181 @@
+package xweb
+
+import (
+	"bytes"
+	"net/http"
+	"net/url"
+	"os"
+	"path"
+	"reflect"
+	"regexp"
+	"strings"
+	"sync"
+	"time"
+)
+
+// the func is the same as condition ? true : false
+func Ternary(express bool, trueVal interface{}, falseVal interface{}) interface{} {
+	if express {
+		return trueVal
+	}
+	return falseVal
+}
+
+// internal utility methods
+func webTime(t time.Time) string {
+	ftime := t.Format(time.RFC1123)
+	if strings.HasSuffix(ftime, "UTC") {
+		ftime = ftime[0:len(ftime)-3] + "GMT"
+	}
+	return ftime
+}
+
+func JoinPath(paths ...string) string {
+	if len(paths) < 1 {
+		return ""
+	}
+	res := ""
+	for _, p := range paths {
+		res = path.Join(res, p)
+	}
+	return res
+}
+
+func PageSize(total, limit int) int {
+	if total <= 0 {
+		return 1
+	} else {
+		x := total % limit
+		if x > 0 {
+			return total/limit + 1
+		} else {
+			return total / limit
+		}
+	}
+}
+
+func SimpleParse(data string) map[string]string {
+	configs := make(map[string]string)
+	lines := strings.Split(string(data), "\n")
+	for _, line := range lines {
+		line = strings.TrimRight(line, "\r")
+		vs := strings.Split(line, "=")
+		if len(vs) == 2 {
+			configs[strings.TrimSpace(vs[0])] = strings.TrimSpace(vs[1])
+		}
+	}
+	return configs
+}
+
+func dirExists(dir string) bool {
+	d, e := os.Stat(dir)
+	switch {
+	case e != nil:
+		return false
+	case !d.IsDir():
+		return false
+	}
+
+	return true
+}
+
+func fileExists(dir string) bool {
+	info, err := os.Stat(dir)
+	if err != nil {
+		return false
+	}
+
+	return !info.IsDir()
+}
+
+// Urlencode is a helper method that converts a map into URL-encoded form data.
+// It is a useful when constructing HTTP POST requests.
+func Urlencode(data map[string]string) string {
+	var buf bytes.Buffer
+	for k, v := range data {
+		buf.WriteString(url.QueryEscape(k))
+		buf.WriteByte('=')
+		buf.WriteString(url.QueryEscape(v))
+		buf.WriteByte('&')
+	}
+	s := buf.String()
+	return s[0 : len(s)-1]
+}
+
+func UnTitle(s string) string {
+	if len(s) < 2 {
+		return strings.ToLower(s)
+	}
+	return strings.ToLower(string(s[0])) + s[1:]
+}
+
+var slugRegex = regexp.MustCompile(`(?i:[^a-z0-9\-_])`)
+
+// Slug is a helper function that returns the URL slug for string s.
+// It's used to return clean, URL-friendly strings that can be
+// used in routing.
+func Slug(s string, sep string) string {
+	if s == "" {
+		return ""
+	}
+	slug := slugRegex.ReplaceAllString(s, sep)
+	if slug == "" {
+		return ""
+	}
+	quoted := regexp.QuoteMeta(sep)
+	sepRegex := regexp.MustCompile("(" + quoted + "){2,}")
+	slug = sepRegex.ReplaceAllString(slug, sep)
+	sepEnds := regexp.MustCompile("^" + quoted + "|" + quoted + "$")
+	slug = sepEnds.ReplaceAllString(slug, "")
+	return strings.ToLower(slug)
+}
+
+// NewCookie is a helper method that returns a new http.Cookie object.
+// Duration is specified in seconds. If the duration is zero, the cookie is permanent.
+// This can be used in conjunction with ctx.SetCookie.
+func NewCookie(name string, value string, age int64) *http.Cookie {
+	var utctime time.Time
+	if age == 0 {
+		// 2^31 - 1 seconds (roughly 2038)
+		utctime = time.Unix(2147483647, 0)
+	} else {
+		utctime = time.Unix(time.Now().Unix()+age, 0)
+	}
+	return &http.Cookie{Name: name, Value: value, Expires: utctime}
+}
+
+func removeStick(uri string) string {
+	uri = strings.TrimRight(uri, "/")
+	if uri == "" {
+		uri = "/"
+	}
+	return uri
+}
+
+var (
+	fieldCache      = make(map[reflect.Type]map[string]int)
+	fieldCacheMutex sync.RWMutex
+)
+
+// this method cache fields' index to field name
+func fieldByName(v reflect.Value, name string) reflect.Value {
+	t := v.Type()
+	fieldCacheMutex.RLock()
+	cache, ok := fieldCache[t]
+	fieldCacheMutex.RUnlock()
+	if !ok {
+		cache = make(map[string]int)
+		for i := 0; i < v.NumField(); i++ {
+			cache[t.Field(i).Name] = i
+		}
+		fieldCacheMutex.Lock()
+		fieldCache[t] = cache
+		fieldCacheMutex.Unlock()
+	}
+
+	if i, ok := cache[name]; ok {
+		return v.Field(i)
+	}
+
+	return reflect.Zero(t)
+}

+ 121 - 0
go-xweb/xweb/hooks.go

@@ -0,0 +1,121 @@
+/************************
+[钩子引擎 (version 0.3)]
+@author:S.W.H
+@E-mail:swh@admpub.com
+@update:2014-01-18
+************************/
+package xweb
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"sync"
+)
+
+var (
+	ErrParamsNotAdapted             = errors.New("The number of params is not adapted.")
+	XHook               *HookEngine = NewHookEngine(10)
+)
+
+type Hook []reflect.Value
+type HookEngine struct {
+	Hooks map[string]Hook
+	Index map[string]uint
+	lock  *sync.RWMutex
+}
+
+func (f *HookEngine) Bind(name string, fns ...interface{}) (err error) {
+	f.lock.Lock()
+	defer func() {
+		f.lock.Unlock()
+		if e := recover(); e != nil {
+			err = errors.New(name + " is not callable.")
+		}
+	}()
+	if _, ok := f.Hooks[name]; !ok {
+		f.Hooks[name] = make(Hook, 0)
+	}
+	if _, ok := f.Index[name]; !ok {
+		f.Index[name] = 0
+	}
+	hln := uint(len(f.Hooks[name]))
+	fln := f.Index[name] + 1 + uint(len(fns))
+	if hln < fln {
+		for _, fn := range fns {
+			v := reflect.ValueOf(fn)
+			f.Hooks[name] = append(f.Hooks[name], v)
+			f.Index[name]++
+		}
+	} else {
+		for _, fn := range fns {
+			v := reflect.ValueOf(fn)
+			f.Hooks[name][f.Index[name]] = v
+			f.Index[name]++
+		}
+	}
+	return
+}
+
+func (f *HookEngine) Call(name string, params ...interface{}) (result []reflect.Value, err error) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+	if _, ok := f.Hooks[name]; !ok {
+		err = errors.New(name + " does not exist.")
+		return
+	}
+	ln := len(params)
+	in := make([]reflect.Value, ln)
+	for k, param := range params {
+		in[k] = reflect.ValueOf(param)
+	}
+	for _, v := range f.Hooks[name] {
+		if v.IsValid() == false {
+			continue
+		}
+		if ln != v.Type().NumIn() {
+			continue
+			err = ErrParamsNotAdapted
+			return
+		}
+		result = v.Call(in)
+		for _k, _v := range result {
+			in[_k] = _v
+		}
+	}
+	if len(result) == 0 {
+		err = errors.New(name + " have nothing to do.")
+	}
+	return
+}
+
+func (f *HookEngine) Value(c []reflect.Value, index int) (r interface{}) {
+	if len(c) >= index && c[index].CanInterface() {
+		r = c[index].Interface()
+	}
+	return
+}
+
+func (f *HookEngine) String(c reflect.Value) string {
+	return fmt.Sprintf("%s", c)
+}
+
+func NewHookEngine(size int) *HookEngine {
+	h := &HookEngine{Hooks: make(map[string]Hook, size), Index: make(map[string]uint, size), lock: new(sync.RWMutex)}
+
+	//func(mux *http.ServeMux) *http.ServeMux
+	h.Hooks["MuxHandle"] = make(Hook, 0)
+
+	//func(result *bool, serv *Server, w http.ResponseWriter, req *http.Request) *bool
+	h.Hooks["BeforeProcess"] = make(Hook, 0)
+
+	//func(result *bool, serv *Server, w http.ResponseWriter, req *http.Request) *bool
+	h.Hooks["AfterProcess"] = make(Hook, 0)
+
+	//func(content string, action *Action) string
+	h.Hooks["BeforeRender"] = make(Hook, 0)
+
+	//func(content []byte, action *Action) []byte
+	h.Hooks["AfterRender"] = make(Hook, 0)
+	return h
+}

+ 223 - 0
go-xweb/xweb/memzipfile.go

@@ -0,0 +1,223 @@
+package xweb
+
+import (
+	"bytes"
+	"compress/flate"
+	"compress/gzip"
+	"errors"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"strings"
+	"sync"
+	"time"
+)
+
+var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo)
+var lock sync.RWMutex
+
+// OpenMemZipFile returns MemFile object with a compressed static file.
+// it's used for serve static file if gzip enable.
+func OpenMemZipFile(path string, zip string) (*MemFile, error) {
+	osfile, e := os.Open(path)
+	if e != nil {
+		return nil, e
+	}
+	defer osfile.Close()
+
+	osfileinfo, e := osfile.Stat()
+	if e != nil {
+		return nil, e
+	}
+
+	modtime := osfileinfo.ModTime()
+	fileSize := osfileinfo.Size()
+	lock.RLock()
+	cfi, ok := gmfim[zip+":"+path]
+	lock.RUnlock()
+	if ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize {
+	} else {
+		var content []byte
+		if zip == "gzip" {
+			//将文件内容压缩到zipbuf中
+			var zipbuf bytes.Buffer
+			gzipwriter, e := gzip.NewWriterLevel(&zipbuf, gzip.BestCompression)
+			if e != nil {
+				return nil, e
+			}
+			_, e = io.Copy(gzipwriter, osfile)
+			gzipwriter.Close()
+			if e != nil {
+				return nil, e
+			}
+			//读zipbuf到content
+			content, e = ioutil.ReadAll(&zipbuf)
+			if e != nil {
+				return nil, e
+			}
+		} else if zip == "deflate" {
+			//将文件内容压缩到zipbuf中
+			var zipbuf bytes.Buffer
+			deflatewriter, e := flate.NewWriter(&zipbuf, flate.BestCompression)
+			if e != nil {
+				return nil, e
+			}
+			_, e = io.Copy(deflatewriter, osfile)
+			deflatewriter.Close()
+			if e != nil {
+				return nil, e
+			}
+			//将zipbuf读入到content
+			content, e = ioutil.ReadAll(&zipbuf)
+			if e != nil {
+				return nil, e
+			}
+		} else {
+			content, e = ioutil.ReadAll(osfile)
+			if e != nil {
+				return nil, e
+			}
+		}
+
+		cfi = &MemFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize}
+		lock.Lock()
+		defer lock.Unlock()
+		gmfim[zip+":"+path] = cfi
+	}
+	return &MemFile{fi: cfi, offset: 0}, nil
+}
+
+// MemFileInfo contains a compressed file bytes and file information.
+// it implements os.FileInfo interface.
+type MemFileInfo struct {
+	os.FileInfo
+	modTime     time.Time
+	content     []byte
+	contentSize int64
+	fileSize    int64
+}
+
+// Name returns the compressed filename.
+func (fi *MemFileInfo) Name() string {
+	return fi.Name()
+}
+
+// Size returns the raw file content size, not compressed size.
+func (fi *MemFileInfo) Size() int64 {
+	return fi.contentSize
+}
+
+// Mode returns file mode.
+func (fi *MemFileInfo) Mode() os.FileMode {
+	return fi.Mode()
+}
+
+// ModTime returns the last modified time of raw file.
+func (fi *MemFileInfo) ModTime() time.Time {
+	return fi.modTime
+}
+
+// IsDir returns the compressing file is a directory or not.
+func (fi *MemFileInfo) IsDir() bool {
+	return fi.IsDir()
+}
+
+// return nil. implement the os.FileInfo interface method.
+func (fi *MemFileInfo) Sys() interface{} {
+	return nil
+}
+
+// MemFile contains MemFileInfo and bytes offset when reading.
+// it implements io.Reader,io.ReadCloser and io.Seeker.
+type MemFile struct {
+	fi     *MemFileInfo
+	offset int64
+}
+
+// Close memfile.
+func (f *MemFile) Close() error {
+	return nil
+}
+
+// Get os.FileInfo of memfile.
+func (f *MemFile) Stat() (os.FileInfo, error) {
+	return f.fi, nil
+}
+
+// read os.FileInfo of files in directory of memfile.
+// it returns empty slice.
+func (f *MemFile) Readdir(count int) ([]os.FileInfo, error) {
+	infos := []os.FileInfo{}
+
+	return infos, nil
+}
+
+// Read bytes from the compressed file bytes.
+func (f *MemFile) Read(p []byte) (n int, err error) {
+	if len(f.fi.content)-int(f.offset) >= len(p) {
+		n = len(p)
+	} else {
+		n = len(f.fi.content) - int(f.offset)
+		err = io.EOF
+	}
+	copy(p, f.fi.content[f.offset:f.offset+int64(n)])
+	f.offset += int64(n)
+	return
+}
+
+var errWhence = errors.New("Seek: invalid whence")
+var errOffset = errors.New("Seek: invalid offset")
+
+// Read bytes from the compressed file bytes by seeker.
+func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) {
+	switch whence {
+	default:
+		return 0, errWhence
+	case os.SEEK_SET:
+	case os.SEEK_CUR:
+		offset += f.offset
+	case os.SEEK_END:
+		offset += int64(len(f.fi.content))
+	}
+	if offset < 0 || int(offset) > len(f.fi.content) {
+		return 0, errOffset
+	}
+	f.offset = offset
+	return f.offset, nil
+}
+
+// GetAcceptEncodingZip returns accept encoding format in http header.
+// zip is first, then deflate if both accepted.
+// If no accepted, return empty string.
+func GetAcceptEncodingZip(r *http.Request) string {
+	ss := r.Header.Get("Accept-Encoding")
+	ss = strings.ToLower(ss)
+	if strings.Contains(ss, "gzip") {
+		return "gzip"
+	} else if strings.Contains(ss, "deflate") {
+		return "deflate"
+	} else {
+		return ""
+	}
+	return ""
+}
+
+// CloseZWriter closes the io.Writer after compressing static file.
+func CloseZWriter(zwriter io.Writer) {
+	if zwriter == nil {
+		return
+	}
+
+	switch zwriter.(type) {
+	case *gzip.Writer:
+		zwriter.(*gzip.Writer).Close()
+	case *flate.Writer:
+		zwriter.(*flate.Writer).Close()
+		//其他情况不close, 保持和默认(非压缩)行为一致
+		/*
+			case io.WriteCloser:
+				zwriter.(io.WriteCloser).Close()
+		*/
+	}
+}

+ 120 - 0
go-xweb/xweb/profile.go

@@ -0,0 +1,120 @@
+package xweb
+import (
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"runtime"
+	"runtime/debug"
+	runtimePprof "runtime/pprof"
+	"strconv"
+	"time"
+)
+
+var startTime = time.Now()
+var pid int
+
+func init() {
+	pid = os.Getpid()
+}
+
+// start cpu profile monitor
+func StartCPUProfile() {
+	f, err := os.Create("cpu-" + strconv.Itoa(pid) + ".pprof")
+	if err != nil {
+		log.Fatal(err)
+	}
+	runtimePprof.StartCPUProfile(f)
+}
+
+// stop cpu profile monitor
+func StopCPUProfile() {
+	runtimePprof.StopCPUProfile()
+}
+
+// print gc information to io.Writer
+func PrintGCSummary(w io.Writer) {
+	memStats := &runtime.MemStats{}
+	runtime.ReadMemStats(memStats)
+	gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)}
+	debug.ReadGCStats(gcstats)
+
+	printGC(memStats, gcstats, w)
+}
+
+func printGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) {
+
+	if gcstats.NumGC > 0 {
+		lastPause := gcstats.Pause[0]
+		elapsed := time.Now().Sub(startTime)
+		overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100
+		allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds()
+
+		fmt.Fprintf(w, "NumGC:%d Pause:%s Pause(Avg):%s Overhead:%3.2f%% Alloc:%s Sys:%s Alloc(Rate):%s/s Histogram:%s %s %s \n",
+			gcstats.NumGC,
+			FriendlyTime(lastPause),
+			FriendlyTime(AvgTime(gcstats.Pause)),
+			overhead,
+			FriendlyBytes(memStats.Alloc),
+			FriendlyBytes(memStats.Sys),
+			FriendlyBytes(uint64(allocatedRate)),
+			FriendlyTime(gcstats.PauseQuantiles[94]),
+			FriendlyTime(gcstats.PauseQuantiles[98]),
+			FriendlyTime(gcstats.PauseQuantiles[99]))
+	} else {
+		// while GC has disabled
+		elapsed := time.Now().Sub(startTime)
+		allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds()
+
+		fmt.Fprintf(w, "Alloc:%s Sys:%s Alloc(Rate):%s/s\n",
+			FriendlyBytes(memStats.Alloc),
+			FriendlyBytes(memStats.Sys),
+			FriendlyBytes(uint64(allocatedRate)))
+	}
+}
+
+func AvgTime(items []time.Duration) time.Duration {
+	var sum time.Duration
+	for _, item := range items {
+		sum += item
+	}
+	return time.Duration(int64(sum) / int64(len(items)))
+}
+
+// format bytes number friendly
+func FriendlyBytes(bytes uint64) string {
+	units := [...]string{"YB", "ZB", "EB", "PB", "TB", "GB", "MB", "KB", "B"}
+	total := len(units)
+	for total--; total > 0 && bytes > 1024; total-- {
+		bytes /= 1024
+	}
+	return fmt.Sprintf("%d%s", bytes, units[total])
+}
+
+// short string format
+func FriendlyTime(d time.Duration) string {
+
+	u := uint64(d)
+	if u < uint64(time.Second) {
+		switch {
+		case u == 0:
+			return "0"
+		case u < uint64(time.Microsecond):
+			return fmt.Sprintf("%.2fns", float64(u))
+		case u < uint64(time.Millisecond):
+			return fmt.Sprintf("%.2fus", float64(u)/1000)
+		default:
+			return fmt.Sprintf("%.2fms", float64(u)/1000/1000)
+		}
+	} else {
+		switch {
+		case u < uint64(time.Minute):
+			return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000)
+		case u < uint64(time.Hour):
+			return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60)
+		default:
+			return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60)
+		}
+	}
+
+}

+ 182 - 0
go-xweb/xweb/scgi.go

@@ -0,0 +1,182 @@
+package xweb
+
+import (
+	"bufio"
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"net/http/cgi"
+	"strconv"
+	"strings"
+)
+
+type scgiBody struct {
+	reader io.Reader
+	conn   io.ReadWriteCloser
+	closed bool
+}
+
+func (b *scgiBody) Read(p []byte) (n int, err error) {
+	if b.closed {
+		return 0, errors.New("SCGI read after close")
+	}
+	return b.reader.Read(p)
+}
+
+func (b *scgiBody) Close() error {
+	b.closed = true
+	return b.conn.Close()
+}
+
+type scgiConn struct {
+	fd           io.ReadWriteCloser
+	req          *http.Request
+	headers      http.Header
+	wroteHeaders bool
+}
+
+func (conn *scgiConn) WriteHeader(status int) {
+	if !conn.wroteHeaders {
+		conn.wroteHeaders = true
+
+		var buf bytes.Buffer
+		text := statusText[status]
+
+		fmt.Fprintf(&buf, "HTTP/1.1 %d %s\r\n", status, text)
+
+		for k, v := range conn.headers {
+			for _, i := range v {
+				buf.WriteString(k + ": " + i + "\r\n")
+			}
+		}
+
+		buf.WriteString("\r\n")
+		conn.fd.Write(buf.Bytes())
+	}
+}
+
+func (conn *scgiConn) Header() http.Header {
+	return conn.headers
+}
+
+func (conn *scgiConn) Write(data []byte) (n int, err error) {
+	if !conn.wroteHeaders {
+		conn.WriteHeader(200)
+	}
+
+	if conn.req.Method == "HEAD" {
+		return 0, errors.New("Body Not Allowed")
+	}
+
+	return conn.fd.Write(data)
+}
+
+func (conn *scgiConn) Close() { conn.fd.Close() }
+
+func (conn *scgiConn) finishRequest() error {
+	var buf bytes.Buffer
+	if !conn.wroteHeaders {
+		conn.wroteHeaders = true
+
+		for k, v := range conn.headers {
+			for _, i := range v {
+				buf.WriteString(k + ": " + i + "\r\n")
+			}
+		}
+
+		buf.WriteString("\r\n")
+		conn.fd.Write(buf.Bytes())
+	}
+	return nil
+}
+
+func (s *Server) readScgiRequest(fd io.ReadWriteCloser) (*http.Request, error) {
+	reader := bufio.NewReader(fd)
+	line, err := reader.ReadString(':')
+	if err != nil {
+		s.Logger.Println("Error during SCGI read: ", err.Error())
+	}
+	length, _ := strconv.Atoi(line[0 : len(line)-1])
+	if length > 16384 {
+		s.Logger.Println("Error: max header size is 16k")
+	}
+	headerData := make([]byte, length)
+	_, err = reader.Read(headerData)
+	if err != nil {
+		return nil, err
+	}
+
+	b, err := reader.ReadByte()
+	if err != nil {
+		return nil, err
+	}
+	// discard the trailing comma
+	if b != ',' {
+		return nil, errors.New("SCGI protocol error: missing comma")
+	}
+	headerList := bytes.Split(headerData, []byte{0})
+	headers := map[string]string{}
+	for i := 0; i < len(headerList)-1; i += 2 {
+		headers[string(headerList[i])] = string(headerList[i+1])
+	}
+	httpReq, err := cgi.RequestFromMap(headers)
+	if err != nil {
+		return nil, err
+	}
+	if httpReq.ContentLength > 0 {
+		httpReq.Body = &scgiBody{
+			reader: io.LimitReader(reader, httpReq.ContentLength),
+			conn:   fd,
+		}
+	} else {
+		httpReq.Body = &scgiBody{reader: reader, conn: fd}
+	}
+	return httpReq, nil
+}
+
+func (s *Server) handleScgiRequest(fd io.ReadWriteCloser) {
+	req, err := s.readScgiRequest(fd)
+	if err != nil {
+		s.Logger.Println("SCGI error: %q", err.Error())
+	}
+	sc := scgiConn{fd, req, make(map[string][]string), false}
+	for _, app := range s.Apps {
+		app.routeHandler(req, &sc)
+	}
+	sc.finishRequest()
+	fd.Close()
+}
+
+func (s *Server) listenAndServeScgi(addr string) error {
+
+	var l net.Listener
+	var err error
+
+	//if the path begins with a "/", assume it's a unix address
+	if strings.HasPrefix(addr, "/") {
+		l, err = net.Listen("unix", addr)
+	} else {
+		l, err = net.Listen("tcp", addr)
+	}
+
+	//save the listener so it can be closed
+	s.l = l
+
+	if err != nil {
+		s.Logger.Println("SCGI listen error", err.Error())
+		return err
+	}
+
+	for {
+		fd, err := l.Accept()
+		if err != nil {
+			s.Logger.Println("SCGI accept error", err.Error())
+			return err
+		}
+		go s.handleScgiRequest(fd)
+	}
+	return nil
+}

+ 362 - 0
go-xweb/xweb/server.go

@@ -0,0 +1,362 @@
+package xweb
+
+import (
+	"crypto/tls"
+	"fmt"
+	"net"
+	"net/http"
+	"net/http/pprof"
+	"os"
+	"qfw/util/endless"
+	"runtime"
+	runtimePprof "runtime/pprof"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/go-xweb/httpsession"
+	"github.com/go-xweb/log"
+)
+
+// ServerConfig is configuration for server objects.
+type ServerConfig struct {
+	Addr                   string
+	Port                   int
+	RecoverPanic           bool
+	Profiler               bool
+	EnableGzip             bool
+	StaticExtensionsToGzip []string
+	Url                    string
+	UrlPrefix              string
+	UrlSuffix              string
+	StaticHtmlDir          string
+	SessionTimeout         time.Duration
+}
+
+var ServerNumber uint = 0
+
+// Server represents a xweb server.
+type Server struct {
+	Config         *ServerConfig
+	Apps           map[string]*App
+	AppsNamePath   map[string]string
+	Name           string
+	SessionManager *httpsession.Manager
+	RootApp        *App
+	Logger         *log.Logger
+	Env            map[string]interface{}
+	//save the listener so it can be closed
+	l net.Listener
+}
+
+func NewServer(args ...string) *Server {
+	name := ""
+	if len(args) == 1 {
+		name = args[0]
+	} else {
+		name = fmt.Sprintf("Server%d", ServerNumber)
+		ServerNumber++
+	}
+	s := &Server{
+		Config:       Config,
+		Env:          map[string]interface{}{},
+		Apps:         map[string]*App{},
+		AppsNamePath: map[string]string{},
+		Name:         name,
+	}
+	Servers[s.Name] = s
+
+	s.SetLogger(log.New(os.Stdout, "", log.Ldefault()))
+
+	app := NewApp("/", "root")
+	s.AddApp(app)
+	return s
+}
+
+func (s *Server) AddApp(a *App) {
+	a.BasePath = strings.TrimRight(a.BasePath, "/") + "/"
+	s.Apps[a.BasePath] = a
+
+	if a.Name != "" {
+		s.AppsNamePath[a.Name] = a.BasePath
+	}
+
+	a.Server = s
+	a.Logger = s.Logger
+	if a.BasePath == "/" {
+		s.RootApp = a
+	}
+}
+
+func (s *Server) AddAction(cs ...interface{}) {
+	s.RootApp.AddAction(cs...)
+}
+
+func (s *Server) AutoAction(c ...interface{}) {
+	s.RootApp.AutoAction(c...)
+}
+
+func (s *Server) AddRouter(url string, c interface{}) {
+	s.RootApp.AddRouter(url, c)
+}
+
+func (s *Server) AddTmplVar(name string, varOrFun interface{}) {
+	s.RootApp.AddTmplVar(name, varOrFun)
+}
+
+func (s *Server) AddTmplVars(t *T) {
+	s.RootApp.AddTmplVars(t)
+}
+
+func (s *Server) AddFilter(filter Filter) {
+	s.RootApp.AddFilter(filter)
+}
+
+func (s *Server) AddConfig(name string, value interface{}) {
+	s.RootApp.SetConfig(name, value)
+}
+
+func (s *Server) SetConfig(name string, value interface{}) {
+	s.RootApp.SetConfig(name, value)
+}
+
+func (s *Server) GetConfig(name string) interface{} {
+	return s.RootApp.GetConfig(name)
+}
+
+func (s *Server) error(w http.ResponseWriter, status int, content string) error {
+	return s.RootApp.error(w, status, content)
+}
+
+func (s *Server) initServer() {
+	if s.Config == nil {
+		s.Config = &ServerConfig{}
+		s.Config.Profiler = true
+	}
+
+	for _, app := range s.Apps {
+		app.initApp()
+	}
+}
+
+// ServeHTTP is the interface method for Go's http server package
+func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) {
+	s.Process(c, req)
+}
+
+// Process invokes the routing system for server s
+// non-root app's route will override root app's if there is same path
+func (s *Server) Process(w http.ResponseWriter, req *http.Request) {
+	var result bool = true
+	_, _ = XHook.Call("BeforeProcess", &result, s, w, req)
+	if !result {
+		return
+	}
+	if s.Config.UrlSuffix != "" && strings.HasSuffix(req.URL.Path, s.Config.UrlSuffix) {
+		req.URL.Path = strings.TrimSuffix(req.URL.Path, s.Config.UrlSuffix)
+	}
+	if s.Config.UrlPrefix != "" && strings.HasPrefix(req.URL.Path, "/"+s.Config.UrlPrefix) {
+		req.URL.Path = strings.TrimPrefix(req.URL.Path, "/"+s.Config.UrlPrefix)
+	}
+	if req.URL.Path[0] != '/' {
+		req.URL.Path = "/" + req.URL.Path
+	}
+	for _, app := range s.Apps {
+		if app != s.RootApp && strings.HasPrefix(req.URL.Path, app.BasePath) {
+			app.routeHandler(req, w)
+			return
+		}
+	}
+	s.RootApp.routeHandler(req, w)
+	_, _ = XHook.Call("AfterProcess", &result, s, w, req)
+}
+
+// Run starts the web application and serves HTTP requests for s
+func (s *Server) RunBase(addr string, mux *http.ServeMux) {
+	addrs := strings.Split(addr, ":")
+	s.Config.Addr = addrs[0]
+	s.Config.Port, _ = strconv.Atoi(addrs[1])
+
+	s.initServer()
+
+	//mux := http.NewServeMux()
+	if s.Config.Profiler {
+		mux.Handle("/debug/pprof", http.HandlerFunc(pprof.Index))
+		mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
+		mux.Handle("/debug/pprof/block", pprof.Handler("block"))
+		mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
+		mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
+
+		mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
+		mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
+		mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
+
+		mux.Handle("/debug/pprof/startcpuprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			StartCPUProfile()
+		}))
+		mux.Handle("/debug/pprof/stopcpuprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			StopCPUProfile()
+		}))
+		mux.Handle("/debug/pprof/memprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			runtime.GC()
+			runtimePprof.WriteHeapProfile(rw)
+		}))
+		mux.Handle("/debug/pprof/gc", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			PrintGCSummary(rw)
+		}))
+
+	}
+
+	if c, err := XHook.Call("MuxHandle", mux); err == nil {
+		if ret := XHook.Value(c, 0); ret != nil {
+			mux = ret.(*http.ServeMux)
+		}
+	}
+	mux.Handle("/", s)
+
+	s.Logger.Infof("http server is listening %s", addr)
+	err := endless.ListenAndServe(addr, mux, func() {})
+	if err != nil {
+		s.Logger.Error("ListenAndServe:", err)
+	}
+	/*l, err := net.Listen("tcp", addr)
+	if err != nil {
+		s.Logger.Error("ListenAndServe:", err)
+	}
+	s.l = l
+	err = http.Serve(s.l, mux)
+	s.l.Close()*/
+}
+
+func (s *Server) Run(addr string) {
+	addrs := strings.Split(addr, ":")
+	s.Config.Addr = addrs[0]
+	s.Config.Port, _ = strconv.Atoi(addrs[1])
+
+	s.initServer()
+
+	mux := http.NewServeMux()
+	if s.Config.Profiler {
+		mux.Handle("/debug/pprof", http.HandlerFunc(pprof.Index))
+		mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
+		mux.Handle("/debug/pprof/block", pprof.Handler("block"))
+		mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
+		mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
+
+		mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
+		mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
+		mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
+
+		mux.Handle("/debug/pprof/startcpuprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			StartCPUProfile()
+		}))
+		mux.Handle("/debug/pprof/stopcpuprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			StopCPUProfile()
+		}))
+		mux.Handle("/debug/pprof/memprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			runtime.GC()
+			runtimePprof.WriteHeapProfile(rw)
+		}))
+		mux.Handle("/debug/pprof/gc", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+			PrintGCSummary(rw)
+		}))
+
+	}
+
+	if c, err := XHook.Call("MuxHandle", mux); err == nil {
+		if ret := XHook.Value(c, 0); ret != nil {
+			mux = ret.(*http.ServeMux)
+		}
+	}
+	mux.Handle("/", s)
+
+	s.Logger.Infof("http server is listening %s", addr)
+	err := endless.ListenAndServe(addr, mux, func() {})
+	if err != nil {
+		s.Logger.Error("ListenAndServe:", func() {})
+	}
+	/*l, err := net.Listen("tcp", addr)
+	if err != nil {
+		s.Logger.Error("ListenAndServe:", err)
+	}
+	s.l = l
+	err = http.Serve(s.l, mux)
+	s.l.Close()*/
+}
+
+// RunFcgi starts the web application and serves FastCGI requests for s.
+func (s *Server) RunFcgi(addr string) {
+	s.initServer()
+	s.Logger.Infof("fcgi server is listening %s", addr)
+	s.listenAndServeFcgi(addr)
+}
+
+// RunScgi starts the web application and serves SCGI requests for s.
+func (s *Server) RunScgi(addr string) {
+	s.initServer()
+	s.Logger.Infof("scgi server is listening %s", addr)
+	s.listenAndServeScgi(addr)
+}
+
+// RunTLS starts the web application and serves HTTPS requests for s.
+func (s *Server) RunTLS(addr string, config *tls.Config) error {
+	s.initServer()
+	mux := http.NewServeMux()
+	mux.Handle("/", s)
+	l, err := tls.Listen("tcp", addr, config)
+	if err != nil {
+		s.Logger.Errorf("Listen: %v", err)
+		return err
+	}
+
+	s.l = l
+
+	s.Logger.Infof("https server is listening %s", addr)
+
+	return http.Serve(s.l, mux)
+}
+
+// Close stops server s.
+func (s *Server) Close() {
+	if s.l != nil {
+		s.l.Close()
+	}
+}
+
+// SetLogger sets the logger for server s
+func (s *Server) SetLogger(logger *log.Logger) {
+	s.Logger = logger
+	s.Logger.SetPrefix("[" + s.Name + "] ")
+	if s.RootApp != nil {
+		s.RootApp.Logger = s.Logger
+	}
+}
+
+func (s *Server) InitSession() {
+	if s.SessionManager == nil {
+		s.SessionManager = httpsession.Default()
+	}
+	if s.Config.SessionTimeout > time.Second {
+		s.SessionManager.SetMaxAge(s.Config.SessionTimeout)
+	}
+	s.SessionManager.Run()
+	if s.RootApp != nil {
+		s.RootApp.SessionManager = s.SessionManager
+	}
+}
+
+func (s *Server) SetTemplateDir(path string) {
+	s.RootApp.SetTemplateDir(path)
+}
+
+func (s *Server) SetStaticDir(path string) {
+	s.RootApp.SetStaticDir(path)
+}
+
+func (s *Server) App(name string) *App {
+	path, ok := s.AppsNamePath[name]
+	if ok {
+		return s.Apps[path]
+	}
+	return nil
+}

+ 185 - 0
go-xweb/xweb/static.go

@@ -0,0 +1,185 @@
+package xweb
+
+import (
+	"crypto/md5"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"github.com/howeyc/fsnotify"
+)
+
+type StaticVerMgr struct {
+	Caches  map[string]string
+	mutex   *sync.Mutex
+	Path    string
+	Ignores map[string]bool
+	app     *App
+}
+
+func (self *StaticVerMgr) Moniter(staticPath string) error {
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		return err
+	}
+
+	done := make(chan bool)
+	go func() {
+		for {
+			select {
+			case ev := <-watcher.Event:
+				if ev == nil {
+					break
+				}
+				if _, ok := self.Ignores[filepath.Base(ev.Name)]; ok {
+					break
+				}
+				d, err := os.Stat(ev.Name)
+				if err != nil {
+					break
+				}
+
+				if ev.IsCreate() {
+					if d.IsDir() {
+						watcher.Watch(ev.Name)
+					} else {
+						url := ev.Name[len(self.Path)+1:]
+						self.CacheItem(url)
+					}
+				} else if ev.IsDelete() {
+					if d.IsDir() {
+						watcher.RemoveWatch(ev.Name)
+					} else {
+						pa := ev.Name[len(self.Path)+1:]
+						self.CacheDelete(pa)
+					}
+				} else if ev.IsModify() {
+					if d.IsDir() {
+					} else {
+						url := ev.Name[len(staticPath)+1:]
+						self.CacheItem(url)
+					}
+				} else if ev.IsRename() {
+					if d.IsDir() {
+						watcher.RemoveWatch(ev.Name)
+					} else {
+						url := ev.Name[len(staticPath)+1:]
+						self.CacheDelete(url)
+					}
+				}
+			case err := <-watcher.Error:
+				self.app.Errorf("error: %v", err)
+			}
+		}
+	}()
+
+	err = filepath.Walk(staticPath, func(f string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			return watcher.Watch(f)
+		}
+		return nil
+	})
+
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	<-done
+
+	watcher.Close()
+	return nil
+}
+
+func (self *StaticVerMgr) Init(app *App, staticPath string) error {
+	self.Path = staticPath
+	self.Caches = make(map[string]string)
+	self.mutex = &sync.Mutex{}
+	self.Ignores = map[string]bool{".DS_Store": true}
+	self.app = app
+
+	if dirExists(staticPath) {
+		self.CacheAll(staticPath)
+
+		go self.Moniter(staticPath)
+	}
+
+	return nil
+}
+
+func (self *StaticVerMgr) getFileVer(url string) string {
+	//content, err := ioutil.ReadFile(path.Join(self.Path, url))
+	fPath := filepath.Join(self.Path, url)
+	self.app.Debug("loaded static ", fPath)
+	f, err := os.Open(fPath)
+	if err != nil {
+		return ""
+	}
+	defer f.Close()
+
+	fInfo, err := f.Stat()
+	if err != nil {
+		return ""
+	}
+
+	content := make([]byte, int(fInfo.Size()))
+	_, err = f.Read(content)
+	if err == nil {
+		h := md5.New()
+		io.WriteString(h, string(content))
+		return fmt.Sprintf("%x", h.Sum(nil))[0:4]
+	}
+	return ""
+}
+
+func (self *StaticVerMgr) CacheAll(staticPath string) error {
+	self.mutex.Lock()
+	defer self.mutex.Unlock()
+	//fmt.Print("Getting static file version number, please wait... ")
+	err := filepath.Walk(staticPath, func(f string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			return nil
+		}
+		rp := f[len(staticPath)+1:]
+		if _, ok := self.Ignores[filepath.Base(rp)]; !ok {
+			self.Caches[rp] = self.getFileVer(rp)
+		}
+		return nil
+	})
+	//fmt.Println("Complete.")
+	return err
+}
+
+func (self *StaticVerMgr) GetVersion(url string) string {
+	self.mutex.Lock()
+	defer self.mutex.Unlock()
+	if ver, ok := self.Caches[url]; ok {
+		return ver
+	}
+
+	ver := self.getFileVer(url)
+	if ver != "" {
+		self.Caches[url] = ver
+	}
+	return ver
+}
+
+func (self *StaticVerMgr) CacheDelete(url string) {
+	self.mutex.Lock()
+	defer self.mutex.Unlock()
+	delete(self.Caches, url)
+	self.app.Infof("static file %s is deleted.\n", url)
+}
+
+func (self *StaticVerMgr) CacheItem(url string) {
+	fmt.Println(url)
+	ver := self.getFileVer(url)
+	if ver != "" {
+		self.mutex.Lock()
+		defer self.mutex.Unlock()
+		self.Caches[url] = ver
+		self.app.Infof("static file %s is created.", url)
+	}
+}

+ 54 - 0
go-xweb/xweb/status.go

@@ -0,0 +1,54 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xweb
+
+import "net/http"
+
+var statusText = map[int]string{
+	http.StatusContinue:           "Continue",
+	http.StatusSwitchingProtocols: "Switching Protocols",
+
+	http.StatusOK:                   "OK",
+	http.StatusCreated:              "Created",
+	http.StatusAccepted:             "Accepted",
+	http.StatusNonAuthoritativeInfo: "Non-Authoritative Information",
+	http.StatusNoContent:            "No Content",
+	http.StatusResetContent:         "Reset Content",
+	http.StatusPartialContent:       "Partial Content",
+
+	http.StatusMultipleChoices:   "Multiple Choices",
+	http.StatusMovedPermanently:  "Moved Permanently",
+	http.StatusFound:             "Found",
+	http.StatusSeeOther:          "See Other",
+	http.StatusNotModified:       "Not Modified",
+	http.StatusUseProxy:          "Use Proxy",
+	http.StatusTemporaryRedirect: "Temporary Redirect",
+
+	http.StatusBadRequest:                   "Bad Request",
+	http.StatusUnauthorized:                 "Unauthorized",
+	http.StatusPaymentRequired:              "Payment Required",
+	http.StatusForbidden:                    "Forbidden",
+	http.StatusNotFound:                     "Not Found",
+	http.StatusMethodNotAllowed:             "Method Not Allowed",
+	http.StatusNotAcceptable:                "Not Acceptable",
+	http.StatusProxyAuthRequired:            "Proxy Authentication Required",
+	http.StatusRequestTimeout:               "Request Timeout",
+	http.StatusConflict:                     "Conflict",
+	http.StatusGone:                         "Gone",
+	http.StatusLengthRequired:               "Length Required",
+	http.StatusPreconditionFailed:           "Precondition Failed",
+	http.StatusRequestEntityTooLarge:        "Request Entity Too Large",
+	http.StatusRequestURITooLong:            "Request URI Too Long",
+	http.StatusUnsupportedMediaType:         "Unsupported Media Type",
+	http.StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
+	http.StatusExpectationFailed:            "Expectation Failed",
+
+	http.StatusInternalServerError:     "Internal Server Error",
+	http.StatusNotImplemented:          "Not Implemented",
+	http.StatusBadGateway:              "Bad Gateway",
+	http.StatusServiceUnavailable:      "Service Unavailable",
+	http.StatusGatewayTimeout:          "Gateway Timeout",
+	http.StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
+}

+ 467 - 0
go-xweb/xweb/template.go

@@ -0,0 +1,467 @@
+package xweb
+
+import (
+	"fmt"
+	"html/template"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"qfw/util"
+	"reflect"
+	"regexp"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/howeyc/fsnotify"
+)
+
+func IsNil(a interface{}) bool {
+	if a == nil {
+		return true
+	}
+	aa := reflect.ValueOf(a)
+	return !aa.IsValid() || (aa.Type().Kind() == reflect.Ptr && aa.IsNil())
+}
+
+//替换函数
+func Replace(s interface{}, os, ns string, n int) string {
+	return strings.Replace(fmt.Sprintf("%s", s), os, ns, n)
+}
+
+//正则
+func Regexp(s interface{}, os, ns string) string {
+	return regexp.MustCompile(os).ReplaceAllString(fmt.Sprintf("%s", s), ns)
+}
+
+//long转日期
+func LongToDate(s interface{}, n int) string {
+	if n == 0 {
+		return util.FormatDateWithObj(&s, util.Date_Short_Layout)
+	} else {
+		return util.FormatDateWithObj(&s, util.Date_Full_Layout)
+	}
+}
+
+//文本加密
+func ConEncode(con interface{}) string {
+	if con == nil {
+		return ""
+	}
+	log.Println(con)
+	se := util.SimpleEncrypt{Key: "#topnet@con$temp#"}
+	return se.EncodeString(fmt.Sprint(con))
+}
+
+//文本加密
+func ConDecode(con interface{}) string {
+	if con == nil {
+		return ""
+	}
+	se := util.SimpleEncrypt{Key: "#topnet@con$temp#"}
+	return se.DecodeString(fmt.Sprint(con))
+}
+
+func Add(left interface{}, right interface{}) interface{} {
+	var rleft, rright int64
+	var fleft, fright float64
+	var isInt bool = true
+	switch left.(type) {
+	case int:
+		rleft = int64(left.(int))
+	case int8:
+		rleft = int64(left.(int8))
+	case int16:
+		rleft = int64(left.(int16))
+	case int32:
+		rleft = int64(left.(int32))
+	case int64:
+		rleft = left.(int64)
+	case float32:
+		fleft = float64(left.(float32))
+		isInt = false
+	case float64:
+		fleft = left.(float64)
+		isInt = false
+	}
+
+	switch right.(type) {
+	case int:
+		rright = int64(right.(int))
+	case int8:
+		rright = int64(right.(int8))
+	case int16:
+		rright = int64(right.(int16))
+	case int32:
+		rright = int64(right.(int32))
+	case int64:
+		rright = right.(int64)
+	case float32:
+		fright = float64(left.(float32))
+		isInt = false
+	case float64:
+		fleft = left.(float64)
+		isInt = false
+	}
+
+	var intSum int64 = rleft + rright
+
+	if isInt {
+		return intSum
+	} else {
+		return fleft + fright + float64(intSum)
+	}
+}
+
+func Subtract(left interface{}, right interface{}) interface{} {
+	var rleft, rright int64
+	var fleft, fright float64
+	var isInt bool = true
+	switch left.(type) {
+	case int:
+		rleft = int64(left.(int))
+	case int8:
+		rleft = int64(left.(int8))
+	case int16:
+		rleft = int64(left.(int16))
+	case int32:
+		rleft = int64(left.(int32))
+	case int64:
+		rleft = left.(int64)
+	case float32:
+		fleft = float64(left.(float32))
+		isInt = false
+	case float64:
+		fleft = left.(float64)
+		isInt = false
+	}
+
+	switch right.(type) {
+	case int:
+		rright = int64(right.(int))
+	case int8:
+		rright = int64(right.(int8))
+	case int16:
+		rright = int64(right.(int16))
+	case int32:
+		rright = int64(right.(int32))
+	case int64:
+		rright = right.(int64)
+	case float32:
+		fright = float64(left.(float32))
+		isInt = false
+	case float64:
+		fleft = left.(float64)
+		isInt = false
+	}
+
+	if isInt {
+		return rleft - rright
+	} else {
+		return fleft + float64(rleft) - (fright + float64(rright))
+	}
+}
+
+func Now() time.Time {
+	return time.Now()
+}
+
+func FormatDate(t time.Time, format string) string {
+	return t.Format(format)
+}
+
+func Eq(left interface{}, right interface{}) bool {
+	leftIsNil := (left == nil)
+	rightIsNil := (right == nil)
+	if leftIsNil || rightIsNil {
+		if leftIsNil && rightIsNil {
+			return true
+		}
+		return false
+	}
+	return fmt.Sprintf("%v", left) == fmt.Sprintf("%v", right)
+}
+
+func Html(raw string) template.HTML {
+	return template.HTML(raw)
+}
+
+func Js(raw string) template.JS {
+	return template.JS(raw)
+}
+
+//Usage:UrlFor("main:root:/user/login") or UrlFor("root:/user/login") or UrlFor("/user/login") or UrlFor()
+func UrlFor(args ...string) string {
+	s := [3]string{"main", "root", ""}
+	var u []string
+	size := len(args)
+	if size > 0 {
+		u = strings.Split(args[0], ":")
+	} else {
+		u = []string{""}
+	}
+	var appUrl string = ""
+	switch len(u) {
+	case 1:
+		s[2] = u[0]
+	case 2:
+		s[1] = u[0]
+		s[2] = u[1]
+	default:
+		s[0] = u[0]
+		s[1] = u[1]
+		s[2] = u[2]
+	}
+	var url, prefix, suffix string
+	if server, ok := Servers[s[0]]; ok {
+		url += server.Config.Url
+		prefix = server.Config.UrlPrefix
+		suffix = server.Config.UrlSuffix
+		if appPath, ok := server.AppsNamePath[s[1]]; ok {
+			appUrl = appPath
+		}
+	}
+	url = strings.TrimRight(url, "/") + "/"
+	if size == 0 {
+		return url
+	}
+	if appUrl != "/" {
+		appUrl = strings.TrimLeft(appUrl, "/")
+		if length := len(appUrl); length > 0 && appUrl[length-1] != '/' {
+			appUrl = appUrl + "/"
+		}
+	} else {
+		appUrl = ""
+	}
+	url += prefix + appUrl
+	if s[2] == "" {
+		return url
+	}
+	url += strings.TrimLeft(s[2], "/") + suffix
+	return url
+}
+
+var (
+	defaultFuncs template.FuncMap = template.FuncMap{
+		"Now":        Now,
+		"Eq":         Eq,
+		"FormatDate": FormatDate,
+		"Html":       Html,
+		"Add":        Add,
+		"Subtract":   Subtract,
+		"IsNil":      IsNil,
+		"UrlFor":     UrlFor,
+		"Js":         Js,
+		"Replace":    Replace,
+		"Regexp":     Regexp,
+		"LongToDate": LongToDate,
+		"ConDecode":  ConDecode, //文本解密
+		"ConEncode":  ConEncode, //文本加密
+	}
+)
+
+type TemplateMgr struct {
+	Caches       map[string][]byte
+	mutex        *sync.Mutex
+	RootDir      string
+	Ignores      map[string]bool
+	IsReload     bool
+	app          *App
+	Preprocessor func([]byte) []byte
+}
+
+func (self *TemplateMgr) Moniter(rootDir string) error {
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		return err
+	}
+
+	done := make(chan bool)
+	go func() {
+		for {
+			select {
+			case ev := <-watcher.Event:
+				if ev == nil {
+					break
+				}
+				if _, ok := self.Ignores[filepath.Base(ev.Name)]; ok {
+					break
+				}
+				d, err := os.Stat(ev.Name)
+				if err != nil {
+					break
+				}
+
+				if ev.IsCreate() {
+					if d.IsDir() {
+						watcher.Watch(ev.Name)
+					} else {
+						tmpl := ev.Name[len(self.RootDir)+1:]
+						content, err := ioutil.ReadFile(ev.Name)
+						if err != nil {
+							self.app.Errorf("loaded template %v failed: %v", tmpl, err)
+							break
+						}
+						self.app.Infof("loaded template file %v success", tmpl)
+						self.CacheTemplate(tmpl, content)
+					}
+				} else if ev.IsDelete() {
+					if d.IsDir() {
+						watcher.RemoveWatch(ev.Name)
+					} else {
+						tmpl := ev.Name[len(self.RootDir)+1:]
+						self.CacheDelete(tmpl)
+					}
+				} else if ev.IsModify() {
+					if d.IsDir() {
+					} else {
+						tmpl := ev.Name[len(self.RootDir)+1:]
+						content, err := ioutil.ReadFile(ev.Name)
+						if err != nil {
+							self.app.Errorf("reloaded template %v failed: %v", tmpl, err)
+							break
+						}
+						content = newIncludeIntmpl(rootDir, content)
+						self.CacheTemplate(tmpl, content)
+						self.app.Infof("reloaded template %v success", tmpl)
+					}
+				} else if ev.IsRename() {
+					if d.IsDir() {
+						watcher.RemoveWatch(ev.Name)
+					} else {
+						tmpl := ev.Name[len(self.RootDir)+1:]
+						self.CacheDelete(tmpl)
+					}
+				}
+			case err := <-watcher.Error:
+				self.app.Error("error:", err)
+			}
+		}
+	}()
+
+	err = filepath.Walk(self.RootDir, func(f string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			return watcher.Watch(f)
+		}
+		return nil
+	})
+
+	if err != nil {
+		self.app.Error(err.Error())
+		return err
+	}
+
+	<-done
+
+	watcher.Close()
+	return nil
+}
+
+func newIncludeIntmpl(rootDir string, content []byte) []byte {
+	for i := 0; i < 4; i++ {
+		b := false
+		newcontent := regInclude.ReplaceAllFunc(content, func(m []byte) []byte {
+			b = true
+			tpl := regInclude.FindSubmatch(m)[1]
+			fpath := filepath.Join(rootDir, string(tpl))
+			c, err := ioutil.ReadFile(fpath)
+			if err != nil {
+				return []byte{}
+			}
+			//c, _ := c.getTemplate(tpl)
+			return c
+		})
+		if !b {
+			break
+		}
+		content = newcontent
+	}
+	return content
+}
+
+func (self *TemplateMgr) CacheAll(rootDir string) error {
+	self.mutex.Lock()
+	defer self.mutex.Unlock()
+	//fmt.Print("Reading the contents of the template files, please wait... ")
+	err := filepath.Walk(rootDir, func(f string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			return nil
+		}
+		tmpl := f[len(rootDir)+1:]
+		tmpl = strings.Replace(tmpl, "\\", "/", -1) //[SWH|+]fix windows env
+		if _, ok := self.Ignores[filepath.Base(tmpl)]; !ok {
+			fpath := filepath.Join(self.RootDir, tmpl)
+			content, err := ioutil.ReadFile(fpath)
+			if err != nil {
+				self.app.Debugf("load template %s error: %v", fpath, err)
+				return err
+			}
+			content = newIncludeIntmpl(rootDir, content)
+			self.app.Debug("loaded template", fpath)
+			self.Caches[tmpl] = content
+		}
+		return nil
+	})
+	//fmt.Println("Complete.")
+	return err
+}
+
+func (self *TemplateMgr) Init(app *App, rootDir string, reload bool) error {
+	self.RootDir = rootDir
+	self.Caches = make(map[string][]byte)
+	self.Ignores = make(map[string]bool)
+	self.mutex = &sync.Mutex{}
+	self.app = app
+	if dirExists(rootDir) {
+		self.CacheAll(rootDir)
+
+		if reload {
+			go self.Moniter(rootDir)
+		}
+	}
+
+	if len(self.Ignores) == 0 {
+		self.Ignores["*.tmp"] = false
+	}
+
+	return nil
+}
+
+func (self *TemplateMgr) GetTemplate(tmpl string) ([]byte, error) {
+	self.mutex.Lock()
+	defer self.mutex.Unlock()
+	if content, ok := self.Caches[tmpl]; ok {
+		self.app.Debugf("load template %v from cache", tmpl)
+		return content, nil
+	}
+
+	content, err := ioutil.ReadFile(filepath.Join(self.RootDir, tmpl))
+	if err == nil {
+		content = newIncludeIntmpl(self.RootDir, content)
+		self.app.Debugf("load template %v from the file:", tmpl)
+		self.Caches[tmpl] = content
+	}
+	return content, err
+}
+
+func (self *TemplateMgr) CacheTemplate(tmpl string, content []byte) {
+	if self.Preprocessor != nil {
+		content = self.Preprocessor(content)
+	}
+	self.mutex.Lock()
+	defer self.mutex.Unlock()
+	tmpl = strings.Replace(tmpl, "\\", "/", -1)
+	self.app.Debugf("update template %v on cache", tmpl)
+	self.Caches[tmpl] = content
+	return
+}
+
+func (self *TemplateMgr) CacheDelete(tmpl string) {
+	self.mutex.Lock()
+	defer self.mutex.Unlock()
+	tmpl = strings.Replace(tmpl, "\\", "/", -1)
+	self.app.Debugf("delete template %v from cache", tmpl)
+	delete(self.Caches, tmpl)
+	return
+}

+ 137 - 0
go-xweb/xweb/validation/README.md

@@ -0,0 +1,137 @@
+validation
+==============
+
+validation is a form validation for a data validation and error collecting using Go.
+
+## Installation and tests
+
+Install:
+
+	go get github.com/go-xweb/xweb/validation
+
+Test:
+
+	go test github.com/go-xweb/xweb/validation
+
+## Example
+
+Direct Use:
+
+	import (
+		"github.com/go-xweb/xweb/validation"
+		"log"
+	)
+
+	type User struct {
+		Name string
+		Age int
+	}
+
+	func main() {
+		u := User{"man", 40}
+		valid := validation.Validation{}
+		valid.Required(u.Name, "name")
+		valid.MaxSize(u.Name, 15, "nameMax")
+		valid.Range(u.Age, 0, 140, "age")
+		if valid.HasErrors() {
+			// validation does not pass
+			// print invalid message
+			for _, err := range valid.Errors {
+				log.Println(err.Key, err.Message)
+			}
+		}
+		// or use like this
+		if v := valid.Max(u.Age, 140); !v.Ok {
+			log.Println(v.Error.Key, v.Error.Message)
+		}
+	}
+
+Struct Tag Use:
+
+	import (
+		"github.com/go-xweb/xweb/validation"
+	)
+
+	// validation function follow with "valid" tag
+	// functions divide with ";"
+	// parameters in parentheses "()" and divide with ","
+	// Match function's pattern string must in "//"
+	type User struct {
+		Id   int
+		Name string `valid:"Required;Match(/^(test)?\\w*@;com$/)"`
+		Age  int    `valid:"Required;Range(1, 140)"`
+	}
+	type Profile struct {
+		Id   int
+		Email string `valid:"Required;Match(/^\\w+@coscms\\.com$/)"`
+		Addr  string `valid:"Required"`
+	}
+	type NotValid struct {
+		A string
+		B string
+	}
+	type Group struct {
+		Id   int
+		User
+		*Profile
+		NotValid `valid:"-"` //valid标签设为“-”,意味着跳过此项不查询其成员
+	}
+
+	func main() {
+		valid := Validation{}
+		u := User{Name: "test", Age: 40}
+		b, err := valid.Valid(u) //检查所有字段
+		//b, err := valid.Valid(u, "Name", "Age") //检查指定字段:Name和Age
+		if err != nil {
+			// handle error
+		}
+		if !b {
+			// validation does not pass
+			// blabla...
+		}
+
+		valid.Clear()
+
+		u := Group{
+			User:           User{Name: "test", Age: 40},
+			Profile:        &Profile{Email:"test@coscms.com",Addr:"address"},
+			NotValid:       NotValid{},
+		}
+		b, err := valid.Valid(u) //检查所有字段
+		//b, err := valid.Valid(u, "User.Name", "Profile.Email") //检查指定字段
+		if err != nil {
+			// handle error
+		}
+		if !b {
+			// validation does not pass
+			// blabla...
+		}
+	}
+
+Struct Tag Functions:
+
+	-
+	Required
+	Min(min int)
+	Max(max int)
+	Range(min, max int)
+	MinSize(min int)
+	MaxSize(max int)
+	Length(length int)
+	Alpha
+	Numeric
+	AlphaNumeric
+	Match(pattern string)
+	AlphaDash
+	Email
+	IP
+	Base64
+	Mobile
+	Tel
+	Phone
+	ZipCode
+
+
+## LICENSE
+
+BSD License http://creativecommons.org/licenses/BSD/

+ 238 - 0
go-xweb/xweb/validation/util.go

@@ -0,0 +1,238 @@
+package validation
+
+import (
+	"fmt"
+	"reflect"
+	"regexp"
+	"strconv"
+	"strings"
+
+	"github.com/coscms/tagfast"
+)
+
+const (
+	VALIDTAG = "valid"
+)
+
+var (
+	// key: function name
+	// value: the number of parameters
+	funcs = make(Funcs)
+
+	// doesn't belong to validation functions
+	unFuncs = map[string]bool{
+		"Clear":     true,
+		"HasErrors": true,
+		"ErrorMap":  true,
+		"Error":     true,
+		"apply":     true,
+		"Check":     true,
+		"Valid":     true,
+		"NoMatch":   true,
+	}
+)
+
+func init() {
+	v := &Validation{}
+	t := reflect.TypeOf(v)
+	for i := 0; i < t.NumMethod(); i++ {
+		m := t.Method(i)
+		if !unFuncs[m.Name] {
+			funcs[m.Name] = m.Func
+		}
+	}
+}
+
+type ValidFunc struct {
+	Name   string
+	Params []interface{}
+}
+
+type Funcs map[string]reflect.Value
+
+func (f Funcs) Call(name string, params ...interface{}) (result []reflect.Value, err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+	if _, ok := f[name]; !ok {
+		err = fmt.Errorf("%s does not exist", name)
+		return
+	}
+	if len(params) != f[name].Type().NumIn() {
+		err = fmt.Errorf("The number of params is not adapted")
+		return
+	}
+	in := make([]reflect.Value, len(params))
+	for k, param := range params {
+		in[k] = reflect.ValueOf(param)
+	}
+	result = f[name].Call(in)
+	return
+}
+
+func isStruct(t reflect.Type) bool {
+	return t.Kind() == reflect.Struct
+}
+
+func isStructPtr(t reflect.Type) bool {
+	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
+}
+
+func getValidFuncs(f reflect.StructField, t reflect.Type, fName string) (vfs []ValidFunc, err error) {
+	tag, tagf := tagfast.Tago(t, f, VALIDTAG)
+	if len(tag) == 0 {
+		return
+	}
+	cached := tagf.GetParsed(VALIDTAG)
+	if cached == nil {
+		//fmt.Printf("%s :[Tag]: %s\n\n", fName, tag)
+		if vfs, tag, err = getRegFuncs(tag, fName); err != nil {
+			fmt.Printf("%+v\n", err)
+			return
+		}
+		fs := strings.Split(tag, ";")
+		for _, vfunc := range fs {
+			var vf ValidFunc
+			if len(vfunc) == 0 {
+				continue
+			}
+			vf, err = parseFunc(vfunc, fName)
+			if err != nil {
+				return
+			}
+			vfs = append(vfs, vf)
+		}
+		tagf.SetParsed(VALIDTAG, vfs)
+	} else {
+		vfs = cached.([]ValidFunc)
+	} // */_ = tagf
+	return
+}
+
+// Get Match function
+// May be get NoMatch function in the future
+func getRegFuncs(tag, key string) (vfs []ValidFunc, str string, err error) {
+	tag = strings.TrimSpace(tag)
+	index := strings.Index(tag, "Match(/")
+	if index == -1 {
+		str = tag
+		return
+	}
+	end := strings.LastIndex(tag, "/)")
+	if end < index {
+		err = fmt.Errorf("invalid Match function")
+		return
+	}
+	reg, err := regexp.Compile(tag[index+len("Match(/") : end])
+	if err != nil {
+		return
+	}
+	vfs = []ValidFunc{ValidFunc{"Match", []interface{}{reg, key + "|Match"}}}
+	str = strings.TrimSpace(tag[:index]) + strings.TrimSpace(tag[end+len("/)"):])
+	return
+}
+
+func parseFunc(vfunc, key string) (v ValidFunc, err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	vfunc = strings.TrimSpace(vfunc)
+	start := strings.Index(vfunc, "(")
+	var num int
+
+	// doesn't need parameter valid function
+	if start == -1 {
+		if num, err = numIn(vfunc); err != nil {
+			return
+		}
+		if num != 0 {
+			err = fmt.Errorf("%s require %d parameters", vfunc, num)
+			return
+		}
+		v = ValidFunc{vfunc, []interface{}{key + "|" + vfunc}}
+		return
+	}
+
+	end := strings.Index(vfunc, ")")
+	if end == -1 {
+		err = fmt.Errorf("invalid valid function")
+		return
+	}
+
+	name := strings.TrimSpace(vfunc[:start])
+	if num, err = numIn(name); err != nil {
+		return
+	}
+
+	params := strings.Split(vfunc[start+1:end], ",")
+	// the num of param must be equal
+	if num != len(params) {
+		err = fmt.Errorf("%s require %d parameters", name, num)
+		return
+	}
+
+	tParams, err := trim(name, key+"|"+name, params)
+	if err != nil {
+		return
+	}
+	v = ValidFunc{name, tParams}
+	return
+}
+
+func numIn(name string) (num int, err error) {
+	fn, ok := funcs[name]
+	if !ok {
+		err = fmt.Errorf("doesn't exsits %s valid function", name)
+		return
+	}
+	// sub *Validation obj and key
+	num = fn.Type().NumIn() - 3
+	return
+}
+
+func trim(name, key string, s []string) (ts []interface{}, err error) {
+	ts = make([]interface{}, len(s), len(s)+1)
+	fn, ok := funcs[name]
+	if !ok {
+		err = fmt.Errorf("doesn't exsits %s valid function", name)
+		return
+	}
+	for i := 0; i < len(s); i++ {
+		var param interface{}
+		// skip *Validation and obj params
+		if param, err = magic(fn.Type().In(i+2), strings.TrimSpace(s[i])); err != nil {
+			return
+		}
+		ts[i] = param
+	}
+	ts = append(ts, key)
+	return
+}
+
+// modify the parameters's type to adapt the function input parameters' type
+func magic(t reflect.Type, s string) (i interface{}, err error) {
+	switch t.Kind() {
+	case reflect.Int:
+		i, err = strconv.Atoi(s)
+	case reflect.String:
+		i = s
+	case reflect.Ptr:
+		if t.Elem().String() != "regexp.Regexp" {
+			err = fmt.Errorf("does not support %s", t.Elem().String())
+			return
+		}
+		i, err = regexp.Compile(s)
+	default:
+		err = fmt.Errorf("does not support %s", t.Kind().String())
+	}
+	return
+}
+
+func mergeParam(v *Validation, obj interface{}, params []interface{}) []interface{} {
+	return append([]interface{}{v, obj}, params...)
+}

+ 348 - 0
go-xweb/xweb/validation/validation.go

@@ -0,0 +1,348 @@
+package validation
+
+import (
+	"fmt"
+	"reflect"
+	"regexp"
+	"strings"
+
+	"github.com/coscms/tagfast"
+)
+
+type ValidFormer interface {
+	Valid(*Validation)
+}
+
+type ValidationError struct {
+	Message, Key, Name, Field, Tmpl string
+	Value                           interface{}
+	LimitValue                      interface{}
+}
+
+// Returns the Message.
+func (e *ValidationError) String() string {
+	if e == nil {
+		return ""
+	}
+	return e.Message
+}
+
+// A ValidationResult is returned from every validation method.
+// It provides an indication of success, and a pointer to the Error (if any).
+type ValidationResult struct {
+	Error *ValidationError
+	Ok    bool
+}
+
+func (r *ValidationResult) Key(key string) *ValidationResult {
+	if r.Error != nil {
+		r.Error.Key = key
+	}
+	return r
+}
+
+func (r *ValidationResult) Message(message string, args ...interface{}) *ValidationResult {
+	if r.Error != nil {
+		if len(args) == 0 {
+			r.Error.Message = message
+		} else {
+			r.Error.Message = fmt.Sprintf(message, args...)
+		}
+	}
+	return r
+}
+
+// A Validation context manages data validation and error messages.
+type Validation struct {
+	Errors    []*ValidationError
+	ErrorsMap map[string]*ValidationError
+}
+
+func (v *Validation) Clear() {
+	v.Errors = []*ValidationError{}
+}
+
+func (v *Validation) HasErrors() bool {
+	return len(v.Errors) > 0
+}
+
+// Return the errors mapped by key.
+// If there are multiple validation errors associated with a single key, the
+// first one "wins".  (Typically the first validation will be the more basic).
+func (v *Validation) ErrorMap() map[string]*ValidationError {
+	return v.ErrorsMap
+}
+
+// Add an error to the validation context.
+func (v *Validation) Error(message string, args ...interface{}) *ValidationResult {
+	result := (&ValidationResult{
+		Ok:    false,
+		Error: &ValidationError{},
+	}).Message(message, args...)
+	v.Errors = append(v.Errors, result.Error)
+	return result
+}
+
+// Test that the argument is non-nil and non-empty (if string or list)
+func (v *Validation) Required(obj interface{}, key string) *ValidationResult {
+	return v.apply(Required{key}, obj)
+}
+
+// Test that the obj is greater than min if obj's type is int
+func (v *Validation) Min(obj interface{}, min int, key string) *ValidationResult {
+	return v.apply(Min{min, key}, obj)
+}
+
+// Test that the obj is less than max if obj's type is int
+func (v *Validation) Max(obj interface{}, max int, key string) *ValidationResult {
+	return v.apply(Max{max, key}, obj)
+}
+
+// Test that the obj is between mni and max if obj's type is int
+func (v *Validation) Range(obj interface{}, min, max int, key string) *ValidationResult {
+	return v.apply(Range{Min{Min: min}, Max{Max: max}, key}, obj)
+}
+
+func (v *Validation) MinSize(obj interface{}, min int, key string) *ValidationResult {
+	return v.apply(MinSize{min, key}, obj)
+}
+
+func (v *Validation) MaxSize(obj interface{}, max int, key string) *ValidationResult {
+	return v.apply(MaxSize{max, key}, obj)
+}
+
+func (v *Validation) Length(obj interface{}, n int, key string) *ValidationResult {
+	return v.apply(Length{n, key}, obj)
+}
+
+func (v *Validation) Alpha(obj interface{}, key string) *ValidationResult {
+	return v.apply(Alpha{key}, obj)
+}
+
+func (v *Validation) Numeric(obj interface{}, key string) *ValidationResult {
+	return v.apply(Numeric{key}, obj)
+}
+
+func (v *Validation) AlphaNumeric(obj interface{}, key string) *ValidationResult {
+	return v.apply(AlphaNumeric{key}, obj)
+}
+
+func (v *Validation) Match(obj interface{}, regex *regexp.Regexp, key string) *ValidationResult {
+	return v.apply(Match{regex, key}, obj)
+}
+
+func (v *Validation) NoMatch(obj interface{}, regex *regexp.Regexp, key string) *ValidationResult {
+	return v.apply(NoMatch{Match{Regexp: regex}, key}, obj)
+}
+
+func (v *Validation) AlphaDash(obj interface{}, key string) *ValidationResult {
+	return v.apply(AlphaDash{NoMatch{Match: Match{Regexp: alphaDashPattern}}, key}, obj)
+}
+
+func (v *Validation) Email(obj interface{}, key string) *ValidationResult {
+	return v.apply(Email{Match{Regexp: emailPattern}, key}, obj)
+}
+
+func (v *Validation) IP(obj interface{}, key string) *ValidationResult {
+	return v.apply(IP{Match{Regexp: ipPattern}, key}, obj)
+}
+
+func (v *Validation) Base64(obj interface{}, key string) *ValidationResult {
+	return v.apply(Base64{Match{Regexp: base64Pattern}, key}, obj)
+}
+
+func (v *Validation) Mobile(obj interface{}, key string) *ValidationResult {
+	return v.apply(Mobile{Match{Regexp: mobilePattern}, key}, obj)
+}
+
+func (v *Validation) Tel(obj interface{}, key string) *ValidationResult {
+	return v.apply(Tel{Match{Regexp: telPattern}, key}, obj)
+}
+
+func (v *Validation) Phone(obj interface{}, key string) *ValidationResult {
+	return v.apply(Phone{Mobile{Match: Match{Regexp: mobilePattern}},
+		Tel{Match: Match{Regexp: telPattern}}, key}, obj)
+}
+
+func (v *Validation) ZipCode(obj interface{}, key string) *ValidationResult {
+	return v.apply(ZipCode{Match{Regexp: zipCodePattern}, key}, obj)
+}
+
+func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult {
+	if chk.IsSatisfied(obj) {
+		return &ValidationResult{Ok: true}
+	}
+
+	// Add the error to the validation context.
+	key := chk.GetKey()
+	Field := key
+	Name := ""
+
+	parts := strings.Split(key, "|")
+	if len(parts) == 2 {
+		Field = parts[0]
+		Name = parts[1]
+	}
+
+	err := &ValidationError{
+		Message:    chk.DefaultMessage(),
+		Key:        key,
+		Name:       Name,
+		Field:      Field,
+		Value:      obj,
+		Tmpl:       MessageTmpls[Name],
+		LimitValue: chk.GetLimitValue(),
+	}
+	v.setError(err)
+
+	// Also return it in the result.
+	return &ValidationResult{
+		Ok:    false,
+		Error: err,
+	}
+}
+
+func (v *Validation) setError(err *ValidationError) {
+	v.Errors = append(v.Errors, err)
+	if v.ErrorsMap == nil {
+		v.ErrorsMap = make(map[string]*ValidationError)
+	}
+	if _, ok := v.ErrorsMap[err.Field]; !ok {
+		v.ErrorsMap[err.Field] = err
+	}
+}
+
+func (v *Validation) SetError(fieldName string, errMsg string) *ValidationError {
+	err := &ValidationError{Key: fieldName, Field: fieldName, Tmpl: errMsg, Message: errMsg}
+	v.setError(err)
+	return err
+}
+
+// Apply a group of validators to a field, in order, and return the
+// ValidationResult from the first one that fails, or the last one that
+// succeeds.
+func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResult {
+	var result *ValidationResult
+	for _, check := range checks {
+		result = v.apply(check, obj)
+		if !result.Ok {
+			return result
+		}
+	}
+	return result
+}
+
+// the obj parameter must be a struct or a struct pointer
+func (v *Validation) Valid(obj interface{}, args ...string) (b bool, err error) {
+	err = v.validExec(obj, "", args...)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	if !v.HasErrors() {
+		if form, ok := obj.(ValidFormer); ok {
+			form.Valid(v)
+		}
+	}
+
+	return !v.HasErrors(), nil
+}
+
+func (v *Validation) validExec(obj interface{}, baseName string, args ...string) (err error) {
+	objT := reflect.TypeOf(obj)
+	objV := reflect.ValueOf(obj)
+	switch {
+	case isStruct(objT):
+	case isStructPtr(objT):
+		objT = objT.Elem()
+		objV = objV.Elem()
+	default:
+		err = fmt.Errorf("%v must be a struct or a struct pointer", obj)
+		return
+	}
+	var chkFields map[string][]string = make(map[string][]string)
+	var pNum int = len(args)
+	//fmt.Println(objT.Name(), ":[Struct NumIn]", pNum)
+	if pNum > 0 {
+		//aa.b.c,ab.b.c
+		for _, v := range args {
+			arr := strings.SplitN(v, ".", 2)
+			if _, ok := chkFields[arr[0]]; !ok {
+				chkFields[arr[0]] = make([]string, 0)
+			}
+			if len(arr) > 1 {
+				chkFields[arr[0]] = append(chkFields[arr[0]], arr[1])
+			}
+		}
+	}
+	args = make([]string, 0)
+	if len(chkFields) > 0 { //检测指定字段
+		for field, args := range chkFields {
+			f, ok := objT.FieldByName(field)
+			if !ok {
+				err = fmt.Errorf("No name for the '%s' field", field)
+				return
+			}
+			tag := tagfast.Tag(objT, f, VALIDTAG)
+			if tag == "-" {
+				continue
+			}
+			var vfs []ValidFunc
+
+			var fName string
+			if baseName == "" {
+				fName = f.Name
+			} else {
+				fName = strings.Join([]string{baseName, f.Name}, ".")
+			}
+			fv := objV.FieldByName(field)
+			if isStruct(f.Type) || isStructPtr(f.Type) {
+				if fv.CanInterface() {
+					err = v.validExec(fv.Interface(), fName, args...)
+				}
+				continue
+			}
+			if vfs, err = getValidFuncs(f, objT, fName); err != nil {
+				return
+			}
+			for _, vf := range vfs {
+				if _, err = funcs.Call(vf.Name,
+					mergeParam(v, fv.Interface(), vf.Params)...); err != nil {
+					return
+				}
+			}
+		}
+	} else { //检测全部字段
+		for i := 0; i < objT.NumField(); i++ {
+			tag := tagfast.Tag(objT, objT.Field(i), VALIDTAG)
+			if tag == "-" {
+				continue
+			}
+			var vfs []ValidFunc
+
+			var fName string
+			if baseName == "" {
+				fName = objT.Field(i).Name
+			} else {
+				fName = strings.Join([]string{baseName, objT.Field(i).Name}, ".")
+			}
+			//fmt.Println(fName, ":[Type]:", objT.Field(i).Type.Kind())
+			if isStruct(objT.Field(i).Type) || isStructPtr(objT.Field(i).Type) {
+				if objV.Field(i).CanInterface() {
+					err = v.validExec(objV.Field(i).Interface(), fName)
+				}
+				continue
+			}
+			if vfs, err = getValidFuncs(objT.Field(i), objT, fName); err != nil {
+				return
+			}
+			for _, vf := range vfs {
+				if _, err = funcs.Call(vf.Name,
+					mergeParam(v, objV.Field(i).Interface(), vf.Params)...); err != nil {
+					return
+				}
+			}
+		}
+	}
+	return
+}

+ 526 - 0
go-xweb/xweb/validation/validators.go

@@ -0,0 +1,526 @@
+package validation
+
+import (
+	"fmt"
+	"reflect"
+	"regexp"
+	"time"
+	"unicode/utf8"
+)
+
+var MessageTmpls = map[string]string{
+	"Required":     "Can not be empty",
+	"Min":          "Minimum is %d",
+	"Max":          "Maximum is %d",
+	"Range":        "Range is %d to %d",
+	"MinSize":      "Minimum size is %d",
+	"MaxSize":      "Maximum size is %d",
+	"Length":       "Required length is %d",
+	"Alpha":        "Must be valid alpha characters",
+	"Numeric":      "Must be valid numeric characters",
+	"AlphaNumeric": "Must be valid alpha or numeric characters",
+	"Match":        "Must match %s",
+	"NoMatch":      "Must not match %s",
+	"AlphaDash":    "Must be valid alpha or numeric or dash(-_) characters",
+	"Email":        "Must be a valid email address",
+	"IP":           "Must be a valid ip address",
+	"Base64":       "Must be valid base64 characters",
+	"Mobile":       "Must be valid mobile number",
+	"Tel":          "Must be valid telephone number",
+	"Phone":        "Must be valid telephone or mobile phone number",
+	"ZipCode":      "Must be valid zipcode",
+}
+
+type Validator interface {
+	IsSatisfied(interface{}) bool
+	DefaultMessage() string
+	GetKey() string
+	GetLimitValue() interface{}
+}
+
+type Required struct {
+	Key string
+}
+
+func (r Required) IsSatisfied(obj interface{}) bool {
+	if obj == nil {
+		return false
+	}
+
+	if str, ok := obj.(string); ok {
+		return len(str) > 0
+	}
+	if b, ok := obj.(bool); ok {
+		return b
+	}
+	if i, ok := obj.(int); ok {
+		return i != 0
+	}
+	if t, ok := obj.(time.Time); ok {
+		return !t.IsZero()
+	}
+	v := reflect.ValueOf(obj)
+	if v.Kind() == reflect.Slice {
+		return v.Len() > 0
+	}
+	return true
+}
+
+func (r Required) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["Required"])
+}
+
+func (r Required) GetKey() string {
+	return r.Key
+}
+
+func (r Required) GetLimitValue() interface{} {
+	return nil
+}
+
+type Min struct {
+	Min int
+	Key string
+}
+
+func (m Min) IsSatisfied(obj interface{}) bool {
+	num, ok := obj.(int)
+	if ok {
+		return num >= m.Min
+	}
+	return false
+}
+
+func (m Min) DefaultMessage() string {
+	return fmt.Sprintf(MessageTmpls["Min"], m.Min)
+}
+
+func (m Min) GetKey() string {
+	return m.Key
+}
+
+func (m Min) GetLimitValue() interface{} {
+	return m.Min
+}
+
+type Max struct {
+	Max int
+	Key string
+}
+
+func (m Max) IsSatisfied(obj interface{}) bool {
+	num, ok := obj.(int)
+	if ok {
+		return num <= m.Max
+	}
+	return false
+}
+
+func (m Max) DefaultMessage() string {
+	return fmt.Sprintf(MessageTmpls["Max"], m.Max)
+}
+
+func (m Max) GetKey() string {
+	return m.Key
+}
+
+func (m Max) GetLimitValue() interface{} {
+	return m.Max
+}
+
+// Requires an integer to be within Min, Max inclusive.
+type Range struct {
+	Min
+	Max
+	Key string
+}
+
+func (r Range) IsSatisfied(obj interface{}) bool {
+	return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj)
+}
+
+func (r Range) DefaultMessage() string {
+	return fmt.Sprintf(MessageTmpls["Range"], r.Min.Min, r.Max.Max)
+}
+
+func (r Range) GetKey() string {
+	return r.Key
+}
+
+func (r Range) GetLimitValue() interface{} {
+	return []int{r.Min.Min, r.Max.Max}
+}
+
+// Requires an array or string to be at least a given length.
+type MinSize struct {
+	Min int
+	Key string
+}
+
+func (m MinSize) IsSatisfied(obj interface{}) bool {
+	if str, ok := obj.(string); ok {
+		return utf8.RuneCountInString(str) >= m.Min
+	}
+	v := reflect.ValueOf(obj)
+	if v.Kind() == reflect.Slice {
+		return v.Len() >= m.Min
+	}
+	return false
+}
+
+func (m MinSize) DefaultMessage() string {
+	return fmt.Sprintf(MessageTmpls["MinSize"], m.Min)
+}
+
+func (m MinSize) GetKey() string {
+	return m.Key
+}
+
+func (m MinSize) GetLimitValue() interface{} {
+	return m.Min
+}
+
+// Requires an array or string to be at most a given length.
+type MaxSize struct {
+	Max int
+	Key string
+}
+
+func (m MaxSize) IsSatisfied(obj interface{}) bool {
+	if str, ok := obj.(string); ok {
+		return utf8.RuneCountInString(str) <= m.Max
+	}
+	v := reflect.ValueOf(obj)
+	if v.Kind() == reflect.Slice {
+		return v.Len() <= m.Max
+	}
+	return false
+}
+
+func (m MaxSize) DefaultMessage() string {
+	return fmt.Sprintf(MessageTmpls["MaxSize"], m.Max)
+}
+
+func (m MaxSize) GetKey() string {
+	return m.Key
+}
+
+func (m MaxSize) GetLimitValue() interface{} {
+	return m.Max
+}
+
+// Requires an array or string to be exactly a given length.
+type Length struct {
+	N   int
+	Key string
+}
+
+func (l Length) IsSatisfied(obj interface{}) bool {
+	if str, ok := obj.(string); ok {
+		return utf8.RuneCountInString(str) == l.N
+	}
+	v := reflect.ValueOf(obj)
+	if v.Kind() == reflect.Slice {
+		return v.Len() == l.N
+	}
+	return false
+}
+
+func (l Length) DefaultMessage() string {
+	return fmt.Sprintf(MessageTmpls["Length"], l.N)
+}
+
+func (l Length) GetKey() string {
+	return l.Key
+}
+
+func (l Length) GetLimitValue() interface{} {
+	return l.N
+}
+
+type Alpha struct {
+	Key string
+}
+
+func (a Alpha) IsSatisfied(obj interface{}) bool {
+	if str, ok := obj.(string); ok {
+		for _, v := range str {
+			if ('Z' < v || v < 'A') && ('z' < v || v < 'a') {
+				return false
+			}
+		}
+		return true
+	}
+	return false
+}
+
+func (a Alpha) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["Alpha"])
+}
+
+func (a Alpha) GetKey() string {
+	return a.Key
+}
+
+func (a Alpha) GetLimitValue() interface{} {
+	return nil
+}
+
+type Numeric struct {
+	Key string
+}
+
+func (n Numeric) IsSatisfied(obj interface{}) bool {
+	if str, ok := obj.(string); ok {
+		for _, v := range str {
+			if '9' < v || v < '0' {
+				return false
+			}
+		}
+		return true
+	}
+	return false
+}
+
+func (n Numeric) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["Numeric"])
+}
+
+func (n Numeric) GetKey() string {
+	return n.Key
+}
+
+func (n Numeric) GetLimitValue() interface{} {
+	return nil
+}
+
+type AlphaNumeric struct {
+	Key string
+}
+
+func (a AlphaNumeric) IsSatisfied(obj interface{}) bool {
+	if str, ok := obj.(string); ok {
+		for _, v := range str {
+			if ('Z' < v || v < 'A') && ('z' < v || v < 'a') && ('9' < v || v < '0') {
+				return false
+			}
+		}
+		return true
+	}
+	return false
+}
+
+func (a AlphaNumeric) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["AlphaNumeric"])
+}
+
+func (a AlphaNumeric) GetKey() string {
+	return a.Key
+}
+
+func (a AlphaNumeric) GetLimitValue() interface{} {
+	return nil
+}
+
+// Requires a string to match a given regex.
+type Match struct {
+	Regexp *regexp.Regexp
+	Key    string
+}
+
+func (m Match) IsSatisfied(obj interface{}) bool {
+	return m.Regexp.MatchString(fmt.Sprintf("%v", obj))
+}
+
+func (m Match) DefaultMessage() string {
+	return fmt.Sprintf(MessageTmpls["Match"], m.Regexp.String())
+}
+
+func (m Match) GetKey() string {
+	return m.Key
+}
+
+func (m Match) GetLimitValue() interface{} {
+	return m.Regexp.String()
+}
+
+// Requires a string to not match a given regex.
+type NoMatch struct {
+	Match
+	Key string
+}
+
+func (n NoMatch) IsSatisfied(obj interface{}) bool {
+	return !n.Match.IsSatisfied(obj)
+}
+
+func (n NoMatch) DefaultMessage() string {
+	return fmt.Sprintf(MessageTmpls["NoMatch"], n.Regexp.String())
+}
+
+func (n NoMatch) GetKey() string {
+	return n.Key
+}
+
+func (n NoMatch) GetLimitValue() interface{} {
+	return n.Regexp.String()
+}
+
+var alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
+
+type AlphaDash struct {
+	NoMatch
+	Key string
+}
+
+func (a AlphaDash) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["AlphaDash"])
+}
+
+func (a AlphaDash) GetKey() string {
+	return a.Key
+}
+
+func (a AlphaDash) GetLimitValue() interface{} {
+	return nil
+}
+
+var emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
+
+type Email struct {
+	Match
+	Key string
+}
+
+func (e Email) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["Email"])
+}
+
+func (e Email) GetKey() string {
+	return e.Key
+}
+
+func (e Email) GetLimitValue() interface{} {
+	return nil
+}
+
+var ipPattern = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$")
+
+type IP struct {
+	Match
+	Key string
+}
+
+func (i IP) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["IP"])
+}
+
+func (i IP) GetKey() string {
+	return i.Key
+}
+
+func (i IP) GetLimitValue() interface{} {
+	return nil
+}
+
+var base64Pattern = regexp.MustCompile("^(?:[A-Za-z0-99+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")
+
+type Base64 struct {
+	Match
+	Key string
+}
+
+func (b Base64) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["Base64"])
+}
+
+func (b Base64) GetKey() string {
+	return b.Key
+}
+
+func (b Base64) GetLimitValue() interface{} {
+	return nil
+}
+
+// just for chinese mobile phone number
+var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|(47)|[8][012356789]))\\d{8}$")
+
+type Mobile struct {
+	Match
+	Key string
+}
+
+func (m Mobile) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["Mobile"])
+}
+
+func (m Mobile) GetKey() string {
+	return m.Key
+}
+
+func (m Mobile) GetLimitValue() interface{} {
+	return nil
+}
+
+// just for chinese telephone number
+var telPattern = regexp.MustCompile("^(0\\d{2,3}(\\-)?)?\\d{7,8}$")
+
+type Tel struct {
+	Match
+	Key string
+}
+
+func (t Tel) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["Tel"])
+}
+
+func (t Tel) GetKey() string {
+	return t.Key
+}
+
+func (t Tel) GetLimitValue() interface{} {
+	return nil
+}
+
+// just for chinese telephone or mobile phone number
+type Phone struct {
+	Mobile
+	Tel
+	Key string
+}
+
+func (p Phone) IsSatisfied(obj interface{}) bool {
+	return p.Mobile.IsSatisfied(obj) || p.Tel.IsSatisfied(obj)
+}
+
+func (p Phone) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["Phone"])
+}
+
+func (p Phone) GetKey() string {
+	return p.Key
+}
+
+func (p Phone) GetLimitValue() interface{} {
+	return nil
+}
+
+// just for chinese zipcode
+var zipCodePattern = regexp.MustCompile("^[1-9]\\d{5}$")
+
+type ZipCode struct {
+	Match
+	Key string
+}
+
+func (z ZipCode) DefaultMessage() string {
+	return fmt.Sprint(MessageTmpls["ZipCode"])
+}
+
+func (z ZipCode) GetKey() string {
+	return z.Key
+}
+
+func (z ZipCode) GetLimitValue() interface{} {
+	return nil
+}

File diff suppressed because it is too large
+ 49 - 0
go-xweb/xweb/xweb.go


Some files were not shown because too many files changed in this diff