├── .gitignore ├── examples ├── .gopmfile ├── templatexample │ ├── templates │ │ ├── users │ │ │ └── list.html │ │ ├── footer.html │ │ ├── header.html │ │ └── home.html │ └── tmpl.go ├── simple │ ├── static │ │ ├── bootstrap │ │ │ ├── img │ │ │ │ ├── glyphicons-halflings.png │ │ │ │ └── glyphicons-halflings-white.png │ │ │ ├── css │ │ │ │ └── bootstrap-responsive.min.css │ │ │ └── js │ │ │ │ └── bootstrap.min.js │ │ └── js │ │ │ └── jquery-bootstrap-pagination.js │ ├── templates │ │ ├── error.html │ │ ├── footer.html │ │ ├── list.html │ │ ├── add.html │ │ ├── edit.html │ │ ├── header.html │ │ └── login.html │ └── simple.go ├── autoaction.go ├── hello.go ├── multiapp.go ├── multiserver.go ├── https │ ├── main.go │ ├── key.pem │ └── cert.pem ├── actionmethod.go ├── logger.go ├── func.go ├── hook.go ├── arrayform.go ├── mapform.go ├── mapform2.go ├── upload.go ├── cookie.go ├── autovar.go └── ajax.go ├── .gitmodules ├── .gopmfile ├── conversion.go ├── doc.go ├── fcgi.go ├── template_test.go ├── error.go ├── LICENSE ├── README_EN.md ├── README.md ├── filter.go ├── validation ├── util_test.go ├── README.md ├── util.go ├── validation.go ├── validators.go └── validation_test.go ├── status.go ├── hooks.go ├── profile.go ├── scgi.go ├── static.go ├── helpers.go ├── memzipfile.go ├── server.go ├── template.go ├── xweb.go └── action.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.log 3 | .vendor 4 | -------------------------------------------------------------------------------- /examples/.gopmfile: -------------------------------------------------------------------------------- 1 | [deps] 2 | github.com/lunny/xweb=d:\projects\xweb -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/docs-zh-CN"] 2 | path = docs/docs-zh-CN 3 | url = https://github.com/go-xweb/docs-zh-CN.git 4 | -------------------------------------------------------------------------------- /examples/templatexample/templates/users/list.html: -------------------------------------------------------------------------------- 1 | {{template "hearder.html"}} 2 |
3 |
4 | {{template "footer.html"}} -------------------------------------------------------------------------------- /examples/simple/static/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-xweb/xweb/HEAD/examples/simple/static/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /examples/simple/templates/error.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{.T.error}} 4 |
5 |
返回 6 |
7 |
-------------------------------------------------------------------------------- /examples/simple/static/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-xweb/xweb/HEAD/examples/simple/static/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /.gopmfile: -------------------------------------------------------------------------------- 1 | [deps] 2 | github.com/go-xweb/httpsession = 3 | github.com/go-xweb/log = 4 | github.com/go-xweb/uuid = 5 | github.com/howeyc/fsnotify = 6 | 7 | [res] 8 | include = 9 | 10 | -------------------------------------------------------------------------------- /examples/templatexample/templates/footer.html: -------------------------------------------------------------------------------- 1 |
---------------以下为footer.html模版-----------------
2 |
this is footer
3 |
{{.T.footer}}
4 |
invoke method {{hello3}}
5 | -------------------------------------------------------------------------------- /examples/templatexample/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{.T.title}} 4 | 5 |
this is header.html, now invoke function:{{hello1}}
6 |
---------------以上为header.html模版-----------------
-------------------------------------------------------------------------------- /examples/simple/templates/footer.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/templatexample/templates/home.html: -------------------------------------------------------------------------------- 1 | {{include "header.html"}} 2 |
---------------以下为home.html模版-----------------
3 |
4 | Hello, templates! {{.T.body}}, invoke method{{hello2}} 5 |
6 |
---------------以上为home.html模版-----------------
7 | {{include "footer.html"}} -------------------------------------------------------------------------------- /conversion.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | // a struct implements this interface can be convert from request param to a struct 4 | type FromConversion interface { 5 | FromString(content string) error 6 | } 7 | 8 | // a struct implements this interface can be convert from struct to template variable 9 | // Not Implemented 10 | type ToConversion interface { 11 | ToString() string 12 | } 13 | -------------------------------------------------------------------------------- /examples/autoaction.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | type MainAction struct { 8 | *xweb.Action 9 | 10 | hello xweb.Mapper `xweb:"/(.*)"` 11 | } 12 | 13 | func (c *MainAction) Hello(world string) { 14 | c.Write("hello %v", world) 15 | } 16 | 17 | func main() { 18 | xweb.AutoAction(&MainAction{}) 19 | xweb.Run("0.0.0.0:9999") 20 | //visit http://localhost:9999/main/world 21 | } 22 | -------------------------------------------------------------------------------- /examples/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | type MainAction struct { 8 | *xweb.Action 9 | 10 | hello xweb.Mapper `xweb:"/(.*)"` 11 | } 12 | 13 | func (c *MainAction) Hello(world string) { 14 | c.Write("hello %v", world) 15 | } 16 | 17 | func main() { 18 | xweb.RootApp().AppConfig.SessionOn = false 19 | xweb.AddRouter("/", &MainAction{}) 20 | xweb.Run("0.0.0.0:9999") 21 | } 22 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 - 2014 The xweb Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package xweb is a simple and powerful web framework for Go. 7 | 8 | Installation 9 | 10 | Make sure you have installed Go 1.1+ and then: 11 | 12 | go get github.com/go-xweb/xweb 13 | 14 | More usage, please visit https://github.com/go-xweb/xweb/ 15 | */ 16 | package xweb 17 | -------------------------------------------------------------------------------- /examples/multiapp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | type MainAction struct { 8 | *xweb.Action 9 | 10 | hello xweb.Mapper `xweb:"/(.*)"` 11 | } 12 | 13 | func (c *MainAction) Hello(world string) { 14 | c.Write("hello %v", world) 15 | } 16 | 17 | func main() { 18 | app1 := xweb.NewApp("/") 19 | app1.AddAction(&MainAction{}) 20 | xweb.AddApp(app1) 21 | 22 | app2 := xweb.NewApp("/user/") 23 | app2.AddAction(&MainAction{}) 24 | xweb.AddApp(app2) 25 | 26 | xweb.Run("0.0.0.0:9999") 27 | } 28 | -------------------------------------------------------------------------------- /examples/multiserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | type MainAction struct { 8 | *xweb.Action 9 | 10 | hello xweb.Mapper `xweb:"/(.*)"` 11 | } 12 | 13 | func (c *MainAction) Hello(world string) { 14 | c.Write("hello %v", world) 15 | } 16 | 17 | func main() { 18 | mc := &MainAction{} 19 | 20 | server1 := xweb.NewServer() 21 | server1.AddRouter("/", mc) 22 | go server1.Run("0.0.0.0:9999") 23 | 24 | server2 := xweb.NewServer() 25 | server2.AddRouter("/", mc) 26 | go server2.Run("0.0.0.0:8999") 27 | 28 | <-make(chan int) 29 | } 30 | -------------------------------------------------------------------------------- /fcgi.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "net" 5 | "net/http/fcgi" 6 | ) 7 | 8 | func (s *Server) listenAndServeFcgi(addr string) error { 9 | var l net.Listener 10 | var err error 11 | 12 | //if the path begins with a "/", assume it's a unix address 13 | if addr[0] == '/' { 14 | l, err = net.Listen("unix", addr) 15 | } else { 16 | l, err = net.Listen("tcp", addr) 17 | } 18 | 19 | //save the listener so it can be closed 20 | s.l = l 21 | 22 | if err != nil { 23 | s.Logger.Println("FCGI listen error", err.Error()) 24 | return err 25 | } 26 | return fcgi.Serve(s.l, s) 27 | } 28 | -------------------------------------------------------------------------------- /examples/https/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-xweb/xweb" 7 | ) 8 | 9 | type MainAction struct { 10 | *xweb.Action 11 | 12 | hello xweb.Mapper `xweb:"/(.*)"` 13 | } 14 | 15 | func (c *MainAction) Hello(world string) { 16 | c.Write("hello %v", world) 17 | } 18 | 19 | func main() { 20 | xweb.RootApp().AppConfig.SessionOn = false 21 | xweb.AddRouter("/", &MainAction{}) 22 | 23 | config, err := xweb.SimpleTLSConfig("cert.pem", "key.pem") 24 | if err != nil { 25 | fmt.Println(err) 26 | return 27 | } 28 | 29 | xweb.RunTLS("0.0.0.0:9999", config) 30 | } 31 | -------------------------------------------------------------------------------- /examples/actionmethod.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-xweb/xweb" 7 | ) 8 | 9 | type MainAction struct { 10 | *xweb.Action 11 | 12 | hello xweb.Mapper `xweb:"/(.*)"` 13 | } 14 | 15 | var content string = ` 16 | base path is {{.Basepath}} 17 | ` 18 | 19 | func (c *MainAction) Basepath() string { 20 | return c.App.BasePath 21 | } 22 | 23 | func (c *MainAction) Hello(world string) { 24 | err := c.RenderString(content) 25 | if err != nil { 26 | fmt.Println(err) 27 | } 28 | } 29 | 30 | func main() { 31 | xweb.AddAction(&MainAction{}) 32 | xweb.Run("0.0.0.0:9999") 33 | } 34 | -------------------------------------------------------------------------------- /examples/logger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/go-xweb/log" 7 | "github.com/go-xweb/xweb" 8 | ) 9 | 10 | type MainAction struct { 11 | *xweb.Action 12 | 13 | hello xweb.Mapper `xweb:"/(.*)"` 14 | } 15 | 16 | func (c *MainAction) Hello(world string) { 17 | c.Write("hello %v", world) 18 | } 19 | 20 | func main() { 21 | f, err := os.Create("server.log") 22 | if err != nil { 23 | println(err.Error()) 24 | return 25 | } 26 | logger := log.New(f, "", log.Ldate|log.Ltime) 27 | 28 | xweb.AddAction(&MainAction{}) 29 | xweb.SetLogger(logger) 30 | xweb.Run("0.0.0.0:9999") 31 | } 32 | -------------------------------------------------------------------------------- /examples/simple/templates/list.html: -------------------------------------------------------------------------------- 1 | 2 | {{include "header.html"}} 3 | 4 | 5 | 6 | {{range .T.Users}} 7 | 15 | {{end}}
用户名密码操作
{{.Name}}{{.Passwd}} 8 | {{if Eq .Name "admin"}} 9 | 不可删除 10 | {{else}} 11 | 修改 12 | 删除 13 | {{end}} 14 |
16 | 17 | {{include "footer.html"}} 18 | -------------------------------------------------------------------------------- /template_test.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import "testing" 4 | 5 | func TestIsNil(t *testing.T) { 6 | if !IsNil(nil) { 7 | t.Error("nil") 8 | } 9 | 10 | if IsNil(1) { 11 | t.Error("1") 12 | } 13 | 14 | if IsNil("tttt") { 15 | t.Error("tttt") 16 | } 17 | 18 | type A struct { 19 | } 20 | 21 | var a A 22 | 23 | if IsNil(a) { 24 | t.Error("a0") 25 | } 26 | 27 | if IsNil(&a) { 28 | t.Error("a") 29 | } 30 | 31 | if IsNil(new(A)) { 32 | t.Error("a2") 33 | } 34 | 35 | var b *A 36 | if !IsNil(b) { 37 | t.Error("b") 38 | } 39 | 40 | var c interface{} 41 | if !IsNil(c) { 42 | t.Error("c") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/func.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-xweb/xweb" 7 | ) 8 | 9 | type MainAction struct { 10 | *xweb.Action 11 | 12 | hello xweb.Mapper `xweb:"/(.*)"` 13 | } 14 | 15 | func (c *MainAction) Hello(world string) error { 16 | return c.RenderString(fmt.Sprintf("hello {{if isWorld}}%v{{else}}go{{end}}", world)) 17 | } 18 | 19 | func (c *MainAction) IsWorld() bool { 20 | return true 21 | } 22 | 23 | func (c *MainAction) Init() { 24 | fmt.Println("init mainaction") 25 | c.AddTmplVar("isWorld", c.IsWorld) 26 | } 27 | 28 | func main() { 29 | xweb.AddAction(&MainAction{}) 30 | xweb.Run("0.0.0.0:9999") 31 | } 32 | -------------------------------------------------------------------------------- /examples/simple/templates/add.html: -------------------------------------------------------------------------------- 1 | {{include "header.html"}} 2 |
3 | {{XsrfFormHtml}} 4 |
5 | 6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 | 24 | {{include "footer.html"}} -------------------------------------------------------------------------------- /examples/hook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/go-xweb/xweb" 8 | ) 9 | 10 | type MainAction struct { 11 | *xweb.Action 12 | 13 | start time.Time 14 | 15 | hello xweb.Mapper `xweb:"/(.*)"` 16 | } 17 | 18 | func (c *MainAction) Hello(world string) { 19 | c.Write("hello %v", world) 20 | } 21 | 22 | func (c *MainAction) Before(structName, actionName string) bool { 23 | c.start = time.Now() 24 | fmt.Println("before", c.start) 25 | return true 26 | } 27 | 28 | func (c *MainAction) After(structName, actionName string, actionResult interface{}) bool { 29 | fmt.Println("after", time.Now().Sub(c.start)) 30 | return true 31 | } 32 | 33 | func main() { 34 | xweb.AddRouter("/", &MainAction{}) 35 | xweb.Run("0.0.0.0:9999") 36 | } 37 | -------------------------------------------------------------------------------- /examples/templatexample/tmpl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | type MainAction struct { 8 | *xweb.Action 9 | 10 | home xweb.Mapper `xweb:"/"` 11 | } 12 | 13 | func hello1() string { 14 | return "this hello is in header" 15 | } 16 | 17 | func hello2() string { 18 | return "this hello is in body" 19 | } 20 | 21 | func hello3() string { 22 | return "this hello is in footer" 23 | } 24 | 25 | func (this *MainAction) Home() error { 26 | return this.Render("home.html", &xweb.T{ 27 | "title": "模版测试例子", 28 | "body": "模版具体内容", 29 | "footer": "版权所有", 30 | "hello1": hello1, 31 | "hello2": hello2, 32 | "hello3": hello3, 33 | }) 34 | } 35 | 36 | func main() { 37 | xweb.AddAction(&MainAction{}) 38 | xweb.Run("0.0.0.0:8888") 39 | } 40 | -------------------------------------------------------------------------------- /examples/simple/templates/edit.html: -------------------------------------------------------------------------------- 1 | {{include "header.html"}} 2 | 3 |
4 | {{XsrfFormHtml}} 5 |
6 | 7 |
8 | 9 | 10 |
11 |
12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 |
24 | 25 | {{include "footer.html"}} -------------------------------------------------------------------------------- /examples/simple/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | 18 |
19 | 29 | -------------------------------------------------------------------------------- /examples/https/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDHPp33HsQUaLKDigIhVQT1voIZ3da8mIiLLnUzyj45543Jee8b 3 | N6N2E/MTew4EPkOJYtkFv+amjm7yswGOo4GSIf+PP4jiwntBkMT5vSAc9+BDCHUg 4 | fNxqjX22QEiRADzms8aKnWEjbk9FdOzNrnSNJeZZEqsDQSj6aYeRR63kEQIDAQAB 5 | AoGAQ9zuDOervY/Tjb4J77R3lgQnaAwJQf9qMo3GWbd+7lYSExe2+zw+Ls+osW/u 6 | XD+g3UCPzseIFh7ZZ0zVMPI8BSIM3K23hsPjZOqsSFTOxIdLq0yALZBFfM8/Kiu6 7 | QqdbWFNX/SkmEFCZplpIz7lw4u2agUA2rihp7hkEcqeq1AECQQDs5xo4KuTiVVp/ 8 | LFkDxNqH86cqeN/i7z045lDuVCar6oomd03rm1sytTARzwUmaz9E0Q7jqZPzWML0 9 | cUOnUQ4xAkEA105fqvOas+C3IS37bWwgNuaL8mfB5F4ywhHuloIvRV3nCaufIIV5 10 | Ec+haxuma9Eas32qlGNTYIdon5sofXVb4QJBANK6X6BGx4Js2ir1j9jCaoE0QyaM 11 | jtqWZKcQeD0Hrb6OyoSc6zsA3oaklTXCKJqcG5NjQxNP7MMx2XkGp19VwoECQCTS 12 | Ono5/xMUMz1xZ7Zm73t0Iirqo7Yyheu6tVr4GK18Sa7VsvkU2oe5QpnWuLdno3Fe 13 | 5HVMJ04y2imxl1MdZwECQBOOJ0YquSSpv5kQxJmd6AaE66sptouIMH+AL/XukYxP 14 | H5drH2NPMna8M8LX2agONAzqX3KSxsN86ocPYc3R9lA= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /examples/arrayform.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | var page = ` 8 | 9 | Multipart Test 10 | 11 |
12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 | ` 23 | 24 | type MainAction struct { 25 | *xweb.Action 26 | 27 | upload xweb.Mapper `xweb:"/"` 28 | 29 | Inputs []string 30 | } 31 | 32 | func (c *MainAction) Upload() { 33 | if c.Method() == "GET" { 34 | c.Write(page) 35 | } else if c.Method() == "POST" { 36 | for i, input := range c.Inputs { 37 | c.Write("

input %v: %v

", i, input) 38 | } 39 | } 40 | } 41 | 42 | func main() { 43 | xweb.AddAction(&MainAction{}) 44 | xweb.Run("0.0.0.0:9999") 45 | } 46 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type AbortError struct { 9 | Code int 10 | Content string 11 | } 12 | 13 | func (a *AbortError) Error() string { 14 | return fmt.Sprintf("%v %v", a.Code, a.Content) 15 | } 16 | 17 | func Abort(code int, content ...string) error { 18 | if len(content) >= 1 { 19 | return &AbortError{code, content[0]} 20 | } 21 | return &AbortError{code, statusText[code]} 22 | } 23 | 24 | func NotFound(content ...string) error { 25 | return Abort(http.StatusNotFound, content...) 26 | } 27 | 28 | func NotSupported(content ...string) error { 29 | return Abort(http.StatusMethodNotAllowed, content...) 30 | } 31 | 32 | func InternalServerError(content ...string) error { 33 | return Abort(http.StatusInternalServerError, content...) 34 | } 35 | 36 | func Forbidden(content ...string) error { 37 | return Abort(http.StatusForbidden, content...) 38 | } 39 | 40 | func Unauthorized(content ...string) error { 41 | return Abort(http.StatusUnauthorized, content...) 42 | } 43 | -------------------------------------------------------------------------------- /examples/https/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICyjCCAjOgAwIBAgIJANHaGqH7d/6xMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNV 3 | BAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdoYWkxEDAO 4 | BgNVBAoMB0dvZGFpbHkxEjAQBgNVBAMMCWxvY2FsaG9zdDEjMCEGCSqGSIb3DQEJ 5 | ARYUeGlhb2x1bndlbkBnbWFpbC5jb20wHhcNMTQwNDA1MDcyNTQxWhcNMTQwNTA1 6 | MDcyNTQxWjB+MQswCQYDVQQGEwJDTjERMA8GA1UECAwIU2hhbmdoYWkxETAPBgNV 7 | BAcMCFNoYW5naGFpMRAwDgYDVQQKDAdHb2RhaWx5MRIwEAYDVQQDDAlsb2NhbGhv 8 | c3QxIzAhBgkqhkiG9w0BCQEWFHhpYW9sdW53ZW5AZ21haWwuY29tMIGfMA0GCSqG 9 | SIb3DQEBAQUAA4GNADCBiQKBgQDHPp33HsQUaLKDigIhVQT1voIZ3da8mIiLLnUz 10 | yj45543Jee8bN6N2E/MTew4EPkOJYtkFv+amjm7yswGOo4GSIf+PP4jiwntBkMT5 11 | vSAc9+BDCHUgfNxqjX22QEiRADzms8aKnWEjbk9FdOzNrnSNJeZZEqsDQSj6aYeR 12 | R63kEQIDAQABo1AwTjAdBgNVHQ4EFgQUddhMguW9igPna8kXKvUIuKFwld8wHwYD 13 | VR0jBBgwFoAUddhMguW9igPna8kXKvUIuKFwld8wDAYDVR0TBAUwAwEB/zANBgkq 14 | hkiG9w0BAQUFAAOBgQCOfRf9krV+f58gCzFCBifJSxMVwzmXGYdGrQPInduPLwRl 15 | uXU4G4i7z5wvnpHJAgQEi8SEK1QdlWBlJas289/vQB0Q7cw4+G+dENygf8V4WokI 16 | 91nGqg7gUUcg2kYdx/Pwlqy4wU8W6lYzNxDKQFtDgPzJ5RqkGdQ41Zk2rZ8mDw== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 - 2014 xweb authors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /examples/mapform.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | var page = ` 8 | 9 | Multipart Test 10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | ` 20 | 21 | type MainAction struct { 22 | *xweb.Action 23 | 24 | parse xweb.Mapper `xweb:"/"` 25 | } 26 | 27 | type User struct { 28 | Id int64 29 | Name string 30 | Age float64 31 | } 32 | 33 | func (c *MainAction) Init() { 34 | c.Option.AutoMapForm = false 35 | c.Option.CheckXsrf = false 36 | } 37 | 38 | func (c *MainAction) Parse() error { 39 | if c.Method() == "GET" { 40 | return c.Write(page) 41 | } else if c.Method() == "POST" { 42 | var user User 43 | err := c.MapForm(&user, "") 44 | if err != nil { 45 | return err 46 | } 47 | return c.Write("%v", user) 48 | } 49 | return nil 50 | } 51 | 52 | func main() { 53 | xweb.AddAction(&MainAction{}) 54 | xweb.Run("0.0.0.0:9999") 55 | } 56 | -------------------------------------------------------------------------------- /examples/mapform2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | var page = ` 8 | 9 | Multipart Test 10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | ` 20 | 21 | type MainAction struct { 22 | *xweb.Action 23 | 24 | parse xweb.Mapper `xweb:"/"` 25 | } 26 | 27 | type User struct { 28 | Id int64 29 | Name string 30 | Age float64 31 | } 32 | 33 | func (c *MainAction) Init() { 34 | c.Option.AutoMapForm = false 35 | c.Option.CheckXsrf = false 36 | } 37 | 38 | func (c *MainAction) Parse() error { 39 | if c.Method() == "GET" { 40 | return c.Write(page) 41 | } else if c.Method() == "POST" { 42 | var user User 43 | err := c.MapForm(&user) 44 | if err != nil { 45 | return err 46 | } 47 | return c.Write("%v", user) 48 | } 49 | return nil 50 | } 51 | 52 | func main() { 53 | xweb.AddAction(&MainAction{}) 54 | xweb.Run("0.0.0.0:9999") 55 | } 56 | -------------------------------------------------------------------------------- /examples/upload.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-xweb/xweb" 5 | ) 6 | 7 | var page = ` 8 | 9 | Multipart Test 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | ` 27 | 28 | type MainAction struct { 29 | *xweb.Action 30 | 31 | upload xweb.Mapper `xweb:"/"` 32 | 33 | Id int 34 | Input1 string 35 | Input2 string 36 | } 37 | 38 | func (c *MainAction) Upload() { 39 | if c.Method() == "GET" { 40 | c.Write(page) 41 | } else if c.Method() == "POST" { 42 | savefile := "./a" 43 | c.SaveToFile("file", savefile) 44 | 45 | c.Write("

input1: %v

", c.Input1) 46 | c.Write("

input2: %v

", c.Input2) 47 | c.Write("

file: %v

", savefile) 48 | } 49 | } 50 | 51 | func main() { 52 | xweb.AddAction(&MainAction{}) 53 | xweb.Run("0.0.0.0:9999") 54 | } 55 | -------------------------------------------------------------------------------- /examples/cookie.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html" 6 | 7 | "github.com/go-xweb/xweb" 8 | ) 9 | 10 | var cookieName = "cookie" 11 | 12 | var notice = ` 13 |
%v
14 | ` 15 | var form = ` 16 |
17 | {{XsrfFormHtml}} 18 |
19 | 20 | 21 |
22 | 23 | 24 | 25 |
26 | ` 27 | 28 | type CookieAction struct { 29 | *xweb.Action 30 | 31 | index xweb.Mapper `xweb:"/"` 32 | update xweb.Mapper 33 | } 34 | 35 | func (this *CookieAction) Index() string { 36 | cookie, _ := this.GetCookie(cookieName) 37 | var top string 38 | if cookie == nil { 39 | top = fmt.Sprintf(notice, "The cookie has not been set") 40 | } else { 41 | var val = html.EscapeString(cookie.Value) 42 | top = fmt.Sprintf(notice, "The value of the cookie is '"+val+"'.") 43 | } 44 | return top + form 45 | } 46 | 47 | func (this *CookieAction) Update() { 48 | if this.GetString("submit") == "Delete" { 49 | this.SetCookie(xweb.NewCookie(cookieName, "", -1)) 50 | } else { 51 | this.SetCookie(xweb.NewCookie(cookieName, this.GetString("cookie"), 0)) 52 | } 53 | this.Redirect("/", 301) 54 | } 55 | 56 | func main() { 57 | xweb.AddAction(&CookieAction{}) 58 | xweb.Run("0.0.0.0:9999") 59 | } 60 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # xweb 2 | 3 | xweb is a web framework for Go which is based on web.go. It just like Struts for Java. 4 | 5 | [中文](https://github.com/go-xweb/xweb/blob/master/README.md) 6 | 7 | [![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) 8 | 9 | ## Changelog 10 | 11 | * **v0.1.1** : App added method AutoAction;Action added method AddTmplVar;Action's method Render's T param now supports variable, function or method。 12 | * **v0.1.0** : Inital release. 13 | 14 | ## Features 15 | 16 | * multiple server and multiple application in one executable application 17 | * simple and helpful url route mapping 18 | 19 | ## Installation 20 | 21 | Make sure you have the a working Go environment. See the [install instructions](http://golang.org/doc/install.html). 22 | 23 | To install xweb, simply run: 24 | 25 | go get github.com/lunny/xweb 26 | 27 | ## Examples 28 | 29 | Please visit [examples](https://github.com/go-xweb/xweb/tree/master/examples) folder 30 | 31 | ## Case 32 | 33 | * [xorm.io](http://xorm.io) 34 | * [Godaily.org](http://godaily.org) 35 | 36 | ## Documentation 37 | 38 | API, Please visit [GoWalker](http://gowalker.org/github.com/go-xweb/xweb) 39 | 40 | 41 | ## License 42 | BSD License 43 | [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/) 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /examples/autovar.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | 10 | "github.com/go-xweb/xweb" 11 | ) 12 | 13 | type User struct { 14 | Id int64 15 | PtrId *int64 16 | Name string 17 | PtrName *string 18 | Age float32 19 | PtrAge *float32 20 | Child *User 21 | } 22 | 23 | type MainAction struct { 24 | *xweb.Action 25 | 26 | get xweb.Mapper `xweb:"/"` 27 | 28 | User User 29 | User2 *User 30 | Id *int64 31 | Key *string 32 | Keys []string 33 | Keys2 []int 34 | } 35 | 36 | func (c *MainAction) Get() { 37 | right := (*c.Key == "Value" && *c.Id == 123 && c.User.Id == 2 && 38 | *c.User2.PtrId == 3 && c.User2.Id == 4 && c.User2.Child.Id == 66) 39 | if right { 40 | c.Write(fmt.Sprintf("right!!!\nc.User:%v\nc.User2:%v\nc.User2.PtrId:%v\nc.User2.Child:%v\nc.Id:%v\nc.Key:%v", 41 | c.User, c.User2, *c.User2.PtrId, c.User2.Child, *c.Id, *c.Key)) 42 | } else { 43 | c.Write("not right") 44 | } 45 | fmt.Println("c.Keys:", c.Keys) 46 | fmt.Println("c.Keys2:", c.Keys2) 47 | } 48 | 49 | func main() { 50 | xweb.AddAction(&MainAction{}) 51 | xweb.RootApp().AppConfig.CheckXsrf = false 52 | go xweb.Run("0.0.0.0:9999") 53 | 54 | values := url.Values{"key": {"Value"}, "id": {"123"}, 55 | "user.id": {"2"}, "user2.ptrId": {"3"}, 56 | "user2.id": {"4"}, "user2.child.id": {"66"}, 57 | "keys": {"1", "2", "3"}, 58 | "keys2": {"1", "2", "3"}, 59 | } 60 | resp, err := http.PostForm("http://127.0.0.1:9999/", values) 61 | if err != nil { 62 | fmt.Println(err) 63 | return 64 | } 65 | 66 | bytes, err := ioutil.ReadAll(resp.Body.(io.Reader)) 67 | if err != nil { 68 | fmt.Println(err) 69 | return 70 | } 71 | 72 | fmt.Println(string(bytes)) 73 | 74 | var s chan int 75 | <-s 76 | } 77 | -------------------------------------------------------------------------------- /examples/simple/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 39 | 40 | 41 |
42 | 49 |
50 |
51 | {{include "footer.html"}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xweb 2 | 3 | xweb是一个强大的Go语言web框架。 4 | 5 | [English](https://github.com/go-xweb/xweb/blob/master/README_EN.md) 6 | 7 | [![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") 8 | 9 | ## 技术支持 10 | 11 | QQ群:369240307 12 | 13 | ## 更新日志 14 | 15 | * **v0.2.1** : 自动Binding新增对jquery对象,map和array的支持。 16 | * **v0.2** : 新增 validation 子包,从 [https://github.com/astaxie/beego/tree/master/validation](http://https://github.com/astaxie/beego/tree/master/validation) 拷贝过来。 17 | * **v0.1.2** : 采用 [github.com/go-xweb/httpsession](http://github.com/go-xweb/httpsession) 作为session组件,API保持兼容;Action现在必须从*Action继承,这个改变与以前的版本不兼容,必须更改代码;新增两个模板函数{{session "key"}} 和 {{cookie "key"}};Action新增函数`MapForm` 18 | * **v0.1.1** : App新增AutoAction方法;Action新增AddTmplVar方法;Render方法的模版渲染方法中可以通过T混合传入函数和变量,更新了[快速入门](https://github.com/go-xweb/xweb/tree/master/docs/intro.md)。 19 | * **v0.1.0** : 初始版本 20 | 21 | ## 特性 22 | 23 | * 在一个可执行程序中多Server(http,tls,scgi,fcgi),多App的支持 24 | * 简单好用的路由映射方式 25 | * 静态文件及版本支持,并支持自动加载,默认开启 26 | * 改进的模版支持,并支持自动加载,动态新增模板函数 27 | * session支持 28 | * validation支持 29 | 30 | ## 安装 31 | 32 | 在安装之前确认你已经安装了Go语言. Go语言安装请访问 [install instructions](http://golang.org/doc/install.html). 33 | 34 | 安装 xweb: 35 | 36 | go get github.com/go-xweb/xweb 37 | 38 | ## Examples 39 | 40 | 请访问 [examples](https://github.com/go-xweb/xweb/tree/master/examples) folder 41 | 42 | ## 案例 43 | 44 | * [xorm.io](http://xorm.io) - [github.com/go-xorm/website](http://github.com/go-xorm/website) 45 | * [Godaily.org](http://godaily.org) - [github.com/govc/godaily](http://github.com/govc/godaily) 46 | 47 | ## 文档 48 | 49 | [快速入门](https://github.com/go-xweb/xweb/tree/master/docs/intro.md) 50 | 51 | 源码文档请访问 [GoWalker](http://gowalker.org/github.com/go-xweb/xweb) 52 | 53 | ## License 54 | BSD License 55 | [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/) 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "regexp" 7 | ) 8 | 9 | type Filter interface { 10 | Do(http.ResponseWriter, *http.Request) bool 11 | } 12 | 13 | type LoginFilter struct { 14 | App *App 15 | SessionName string 16 | AnonymousUrls []*regexp.Regexp 17 | AskLoginUrls []*regexp.Regexp 18 | Redirect string 19 | OriUrlName string 20 | } 21 | 22 | func NewLoginFilter(app *App, name string, redirect string) *LoginFilter { 23 | filter := &LoginFilter{App: app, SessionName: name, 24 | AnonymousUrls: make([]*regexp.Regexp, 0), 25 | AskLoginUrls: make([]*regexp.Regexp, 0), 26 | Redirect: redirect, 27 | } 28 | filter.AddAnonymousUrls("/favicon.ico", redirect) 29 | return filter 30 | } 31 | 32 | func (s *LoginFilter) AddAnonymousUrls(urls ...string) { 33 | for _, r := range urls { 34 | cr, err := regexp.Compile(r) 35 | if err == nil { 36 | s.AnonymousUrls = append(s.AnonymousUrls, cr) 37 | } 38 | } 39 | } 40 | 41 | func (s *LoginFilter) AddAskLoginUrls(urls ...string) { 42 | for _, r := range urls { 43 | cr, err := regexp.Compile(r) 44 | if err == nil { 45 | s.AskLoginUrls = append(s.AskLoginUrls, cr) 46 | } 47 | } 48 | } 49 | 50 | func (s *LoginFilter) Do(w http.ResponseWriter, req *http.Request) bool { 51 | requestPath := removeStick(req.URL.Path) 52 | 53 | session := s.App.SessionManager.Session(req, w) 54 | id := session.Get(s.SessionName) 55 | has := (id != nil && id != "") 56 | 57 | var redirect = s.Redirect 58 | if s.OriUrlName != "" { 59 | redirect = redirect + "?" + s.OriUrlName + "=" + url.QueryEscape(req.URL.String()) 60 | } 61 | 62 | for _, cr := range s.AskLoginUrls { 63 | if !cr.MatchString(requestPath) { 64 | continue 65 | } 66 | match := cr.FindStringSubmatch(requestPath) 67 | if len(match[0]) != len(requestPath) { 68 | continue 69 | } 70 | if !has { 71 | s.App.Redirect(w, requestPath, redirect) 72 | } 73 | return has 74 | } 75 | 76 | if len(s.AnonymousUrls) == 0 { 77 | return true 78 | } 79 | 80 | for _, cr := range s.AnonymousUrls { 81 | if !cr.MatchString(requestPath) { 82 | continue 83 | } 84 | match := cr.FindStringSubmatch(requestPath) 85 | if len(match[0]) != len(requestPath) { 86 | continue 87 | } 88 | return true 89 | } 90 | 91 | if !has { 92 | s.App.Redirect(w, requestPath, redirect) 93 | } 94 | return has 95 | } 96 | -------------------------------------------------------------------------------- /validation/util_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type user struct { 9 | Id int 10 | Tag string `valid:"Maxx(aa)"` 11 | Name string `valid:"Required;"` 12 | Age int `valid:"Required;Range(1, 140)"` 13 | match string `valid:"Required; Match(/^(test)?\\w*@(/test/);com$/);Max(2)"` 14 | } 15 | 16 | func TestGetValidFuncs(t *testing.T) { 17 | u := user{Name: "test", Age: 1} 18 | tf := reflect.TypeOf(u) 19 | var vfs []ValidFunc 20 | var err error 21 | 22 | f, _ := tf.FieldByName("Id") 23 | if vfs, err = getValidFuncs(f,tf,f.Name); err != nil { 24 | t.Fatal(err) 25 | } 26 | if len(vfs) != 0 { 27 | t.Fatal("should get none ValidFunc") 28 | } 29 | 30 | f, _ = tf.FieldByName("Tag") 31 | if vfs, err = getValidFuncs(f, tf,f.Name); err.Error() != "doesn't exsits Maxx valid function" { 32 | t.Fatal(err) 33 | } 34 | 35 | f, _ = tf.FieldByName("Name") 36 | if vfs, err = getValidFuncs(f, tf,f.Name); err != nil { 37 | t.Fatal(err) 38 | } 39 | if len(vfs) != 1 { 40 | t.Fatal("should get 1 ValidFunc") 41 | } 42 | if vfs[0].Name != "Required" && len(vfs[0].Params) != 0 { 43 | t.Error("Required funcs should be got") 44 | } 45 | 46 | f, _ = tf.FieldByName("Age") 47 | if vfs, err = getValidFuncs(f, tf,f.Name); err != nil { 48 | t.Fatal(err) 49 | } 50 | if len(vfs) != 2 { 51 | t.Fatal("should get 2 ValidFunc") 52 | } 53 | if vfs[0].Name != "Required" && len(vfs[0].Params) != 0 { 54 | t.Error("Required funcs should be got") 55 | } 56 | if vfs[1].Name != "Range" && len(vfs[1].Params) != 2 { 57 | t.Error("Range funcs should be got") 58 | } 59 | 60 | f, _ = tf.FieldByName("match") 61 | if vfs, err = getValidFuncs(f,tf,f.Name); err != nil { 62 | t.Fatal(err) 63 | } 64 | if len(vfs) != 3 { 65 | t.Fatal("should get 3 ValidFunc but now is", len(vfs)) 66 | } 67 | } 68 | 69 | func TestCall(t *testing.T) { 70 | u := user{Name: "test", Age: 180} 71 | tf := reflect.TypeOf(u) 72 | var vfs []ValidFunc 73 | var err error 74 | f, _ := tf.FieldByName("Age") 75 | if vfs, err = getValidFuncs(f,tf,f.Name); err != nil { 76 | t.Fatal(err) 77 | } 78 | valid := &Validation{} 79 | vfs[1].Params = append([]interface{}{valid, u.Age}, vfs[1].Params...) 80 | if _, err = funcs.Call(vfs[1].Name, vfs[1].Params...); err != nil { 81 | t.Fatal(err) 82 | } 83 | if len(valid.Errors) != 1 { 84 | t.Error("age out of range should be has an error") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/ajax.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/go-xweb/xweb" 8 | ) 9 | 10 | var tmpl = ` 11 | 12 | 13 | 14 | 56 | 57 | 58 |
59 | 60 | 61 | 62 |
63 | 64 | 65 | ` 66 | 67 | type User struct { 68 | Username string 69 | Password string 70 | } 71 | 72 | type MainAction struct { 73 | *xweb.Action 74 | 75 | home xweb.Mapper `xweb:"/"` 76 | login xweb.Mapper 77 | User User 78 | } 79 | 80 | func (c *MainAction) Home() error { 81 | return c.Write(tmpl) 82 | } 83 | 84 | func (c *MainAction) Login() error { 85 | fmt.Println("user:", c.User) 86 | forms := c.GetForm() 87 | for k, v := range forms { 88 | fmt.Println("--", k, "-- is", reflect.TypeOf(k)) 89 | fmt.Println("--", v, "-- is", reflect.TypeOf(v)) 90 | } 91 | return nil 92 | } 93 | 94 | func main() { 95 | xweb.RootApp().AppConfig.CheckXsrf = false 96 | xweb.RootApp().AppConfig.SessionOn = false 97 | xweb.AddRouter("/", &MainAction{}) 98 | xweb.Run("0.0.0.0:9999") 99 | } 100 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xweb 6 | 7 | import "net/http" 8 | 9 | var statusText = map[int]string{ 10 | http.StatusContinue: "Continue", 11 | http.StatusSwitchingProtocols: "Switching Protocols", 12 | 13 | http.StatusOK: "OK", 14 | http.StatusCreated: "Created", 15 | http.StatusAccepted: "Accepted", 16 | http.StatusNonAuthoritativeInfo: "Non-Authoritative Information", 17 | http.StatusNoContent: "No Content", 18 | http.StatusResetContent: "Reset Content", 19 | http.StatusPartialContent: "Partial Content", 20 | 21 | http.StatusMultipleChoices: "Multiple Choices", 22 | http.StatusMovedPermanently: "Moved Permanently", 23 | http.StatusFound: "Found", 24 | http.StatusSeeOther: "See Other", 25 | http.StatusNotModified: "Not Modified", 26 | http.StatusUseProxy: "Use Proxy", 27 | http.StatusTemporaryRedirect: "Temporary Redirect", 28 | 29 | http.StatusBadRequest: "Bad Request", 30 | http.StatusUnauthorized: "Unauthorized", 31 | http.StatusPaymentRequired: "Payment Required", 32 | http.StatusForbidden: "Forbidden", 33 | http.StatusNotFound: "Not Found", 34 | http.StatusMethodNotAllowed: "Method Not Allowed", 35 | http.StatusNotAcceptable: "Not Acceptable", 36 | http.StatusProxyAuthRequired: "Proxy Authentication Required", 37 | http.StatusRequestTimeout: "Request Timeout", 38 | http.StatusConflict: "Conflict", 39 | http.StatusGone: "Gone", 40 | http.StatusLengthRequired: "Length Required", 41 | http.StatusPreconditionFailed: "Precondition Failed", 42 | http.StatusRequestEntityTooLarge: "Request Entity Too Large", 43 | http.StatusRequestURITooLong: "Request URI Too Long", 44 | http.StatusUnsupportedMediaType: "Unsupported Media Type", 45 | http.StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable", 46 | http.StatusExpectationFailed: "Expectation Failed", 47 | 48 | http.StatusInternalServerError: "Internal Server Error", 49 | http.StatusNotImplemented: "Not Implemented", 50 | http.StatusBadGateway: "Bad Gateway", 51 | http.StatusServiceUnavailable: "Service Unavailable", 52 | http.StatusGatewayTimeout: "Gateway Timeout", 53 | http.StatusHTTPVersionNotSupported: "HTTP Version Not Supported", 54 | } 55 | -------------------------------------------------------------------------------- /validation/README.md: -------------------------------------------------------------------------------- 1 | validation 2 | ============== 3 | 4 | validation is a form validation for a data validation and error collecting using Go. 5 | 6 | ## Installation and tests 7 | 8 | Install: 9 | 10 | go get github.com/go-xweb/xweb/validation 11 | 12 | Test: 13 | 14 | go test github.com/go-xweb/xweb/validation 15 | 16 | ## Example 17 | 18 | Direct Use: 19 | 20 | import ( 21 | "github.com/go-xweb/xweb/validation" 22 | "log" 23 | ) 24 | 25 | type User struct { 26 | Name string 27 | Age int 28 | } 29 | 30 | func main() { 31 | u := User{"man", 40} 32 | valid := validation.Validation{} 33 | valid.Required(u.Name, "name") 34 | valid.MaxSize(u.Name, 15, "nameMax") 35 | valid.Range(u.Age, 0, 140, "age") 36 | if valid.HasErrors() { 37 | // validation does not pass 38 | // print invalid message 39 | for _, err := range valid.Errors { 40 | log.Println(err.Key, err.Message) 41 | } 42 | } 43 | // or use like this 44 | if v := valid.Max(u.Age, 140); !v.Ok { 45 | log.Println(v.Error.Key, v.Error.Message) 46 | } 47 | } 48 | 49 | Struct Tag Use: 50 | 51 | import ( 52 | "github.com/go-xweb/xweb/validation" 53 | ) 54 | 55 | // validation function follow with "valid" tag 56 | // functions divide with ";" 57 | // parameters in parentheses "()" and divide with "," 58 | // Match function's pattern string must in "//" 59 | type User struct { 60 | Id int 61 | Name string `valid:"Required;Match(/^(test)?\\w*@;com$/)"` 62 | Age int `valid:"Required;Range(1, 140)"` 63 | } 64 | type Profile struct { 65 | Id int 66 | Email string `valid:"Required;Match(/^\\w+@coscms\\.com$/)"` 67 | Addr string `valid:"Required"` 68 | } 69 | type NotValid struct { 70 | A string 71 | B string 72 | } 73 | type Group struct { 74 | Id int 75 | User 76 | *Profile 77 | NotValid `valid:"-"` //valid标签设为“-”,意味着跳过此项不查询其成员 78 | } 79 | 80 | func main() { 81 | valid := Validation{} 82 | u := User{Name: "test", Age: 40} 83 | b, err := valid.Valid(u) //检查所有字段 84 | //b, err := valid.Valid(u, "Name", "Age") //检查指定字段:Name和Age 85 | if err != nil { 86 | // handle error 87 | } 88 | if !b { 89 | // validation does not pass 90 | // blabla... 91 | } 92 | 93 | valid.Clear() 94 | 95 | u := Group{ 96 | User: User{Name: "test", Age: 40}, 97 | Profile: &Profile{Email:"test@coscms.com",Addr:"address"}, 98 | NotValid: NotValid{}, 99 | } 100 | b, err := valid.Valid(u) //检查所有字段 101 | //b, err := valid.Valid(u, "User.Name", "Profile.Email") //检查指定字段 102 | if err != nil { 103 | // handle error 104 | } 105 | if !b { 106 | // validation does not pass 107 | // blabla... 108 | } 109 | } 110 | 111 | Struct Tag Functions: 112 | 113 | - 114 | Required 115 | Min(min int) 116 | Max(max int) 117 | Range(min, max int) 118 | MinSize(min int) 119 | MaxSize(max int) 120 | Length(length int) 121 | Alpha 122 | Numeric 123 | AlphaNumeric 124 | Match(pattern string) 125 | AlphaDash 126 | Email 127 | IP 128 | Base64 129 | Mobile 130 | Tel 131 | Phone 132 | ZipCode 133 | 134 | 135 | ## LICENSE 136 | 137 | BSD License http://creativecommons.org/licenses/BSD/ 138 | -------------------------------------------------------------------------------- /hooks.go: -------------------------------------------------------------------------------- 1 | /************************ 2 | [钩子引擎 (version 0.3)] 3 | @author:S.W.H 4 | @E-mail:swh@admpub.com 5 | @update:2014-01-18 6 | ************************/ 7 | package xweb 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "reflect" 13 | "sync" 14 | ) 15 | 16 | var ( 17 | ErrParamsNotAdapted = errors.New("The number of params is not adapted.") 18 | XHook *HookEngine = NewHookEngine(10) 19 | ) 20 | 21 | type Hook []reflect.Value 22 | type HookEngine struct { 23 | Hooks map[string]Hook 24 | Index map[string]uint 25 | lock *sync.RWMutex 26 | } 27 | 28 | func (f *HookEngine) Bind(name string, fns ...interface{}) (err error) { 29 | f.lock.Lock() 30 | defer func() { 31 | f.lock.Unlock() 32 | if e := recover(); e != nil { 33 | err = errors.New(name + " is not callable.") 34 | } 35 | }() 36 | if _, ok := f.Hooks[name]; !ok { 37 | f.Hooks[name] = make(Hook, 0) 38 | } 39 | if _, ok := f.Index[name]; !ok { 40 | f.Index[name] = 0 41 | } 42 | hln := uint(len(f.Hooks[name])) 43 | fln := f.Index[name] + 1 + uint(len(fns)) 44 | if hln < fln { 45 | for _, fn := range fns { 46 | v := reflect.ValueOf(fn) 47 | f.Hooks[name] = append(f.Hooks[name], v) 48 | f.Index[name]++ 49 | } 50 | } else { 51 | for _, fn := range fns { 52 | v := reflect.ValueOf(fn) 53 | f.Hooks[name][f.Index[name]] = v 54 | f.Index[name]++ 55 | } 56 | } 57 | return 58 | } 59 | 60 | func (f *HookEngine) Call(name string, params ...interface{}) (result []reflect.Value, err error) { 61 | f.lock.Lock() 62 | defer f.lock.Unlock() 63 | if _, ok := f.Hooks[name]; !ok { 64 | err = errors.New(name + " does not exist.") 65 | return 66 | } 67 | ln := len(params) 68 | in := make([]reflect.Value, ln) 69 | for k, param := range params { 70 | in[k] = reflect.ValueOf(param) 71 | } 72 | for _, v := range f.Hooks[name] { 73 | if v.IsValid() == false { 74 | continue 75 | } 76 | if ln != v.Type().NumIn() { 77 | continue 78 | err = ErrParamsNotAdapted 79 | return 80 | } 81 | result = v.Call(in) 82 | for _k, _v := range result { 83 | in[_k] = _v 84 | } 85 | } 86 | if len(result) == 0 { 87 | err = errors.New(name + " have nothing to do.") 88 | } 89 | return 90 | } 91 | 92 | func (f *HookEngine) Value(c []reflect.Value, index int) (r interface{}) { 93 | if len(c) >= index && c[index].CanInterface() { 94 | r = c[index].Interface() 95 | } 96 | return 97 | } 98 | 99 | func (f *HookEngine) String(c reflect.Value) string { 100 | return fmt.Sprintf("%s", c) 101 | } 102 | 103 | func NewHookEngine(size int) *HookEngine { 104 | h := &HookEngine{Hooks: make(map[string]Hook, size), Index: make(map[string]uint, size), lock: new(sync.RWMutex)} 105 | 106 | //func(mux *http.ServeMux) *http.ServeMux 107 | h.Hooks["MuxHandle"] = make(Hook, 0) 108 | 109 | //func(result *bool, serv *Server, w http.ResponseWriter, req *http.Request) *bool 110 | h.Hooks["BeforeProcess"] = make(Hook, 0) 111 | 112 | //func(result *bool, serv *Server, w http.ResponseWriter, req *http.Request) *bool 113 | h.Hooks["AfterProcess"] = make(Hook, 0) 114 | 115 | //func(content string, action *Action) string 116 | h.Hooks["BeforeRender"] = make(Hook, 0) 117 | 118 | //func(content []byte, action *Action) []byte 119 | h.Hooks["AfterRender"] = make(Hook, 0) 120 | return h 121 | } 122 | -------------------------------------------------------------------------------- /profile.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | import ( 3 | "fmt" 4 | "io" 5 | "log" 6 | "os" 7 | "runtime" 8 | "runtime/debug" 9 | runtimePprof "runtime/pprof" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | var startTime = time.Now() 15 | var pid int 16 | 17 | func init() { 18 | pid = os.Getpid() 19 | } 20 | 21 | // start cpu profile monitor 22 | func StartCPUProfile() { 23 | f, err := os.Create("cpu-" + strconv.Itoa(pid) + ".pprof") 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | runtimePprof.StartCPUProfile(f) 28 | } 29 | 30 | // stop cpu profile monitor 31 | func StopCPUProfile() { 32 | runtimePprof.StopCPUProfile() 33 | } 34 | 35 | // print gc information to io.Writer 36 | func PrintGCSummary(w io.Writer) { 37 | memStats := &runtime.MemStats{} 38 | runtime.ReadMemStats(memStats) 39 | gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)} 40 | debug.ReadGCStats(gcstats) 41 | 42 | printGC(memStats, gcstats, w) 43 | } 44 | 45 | func printGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) { 46 | 47 | if gcstats.NumGC > 0 { 48 | lastPause := gcstats.Pause[0] 49 | elapsed := time.Now().Sub(startTime) 50 | overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100 51 | allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() 52 | 53 | 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", 54 | gcstats.NumGC, 55 | FriendlyTime(lastPause), 56 | FriendlyTime(AvgTime(gcstats.Pause)), 57 | overhead, 58 | FriendlyBytes(memStats.Alloc), 59 | FriendlyBytes(memStats.Sys), 60 | FriendlyBytes(uint64(allocatedRate)), 61 | FriendlyTime(gcstats.PauseQuantiles[94]), 62 | FriendlyTime(gcstats.PauseQuantiles[98]), 63 | FriendlyTime(gcstats.PauseQuantiles[99])) 64 | } else { 65 | // while GC has disabled 66 | elapsed := time.Now().Sub(startTime) 67 | allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() 68 | 69 | fmt.Fprintf(w, "Alloc:%s Sys:%s Alloc(Rate):%s/s\n", 70 | FriendlyBytes(memStats.Alloc), 71 | FriendlyBytes(memStats.Sys), 72 | FriendlyBytes(uint64(allocatedRate))) 73 | } 74 | } 75 | 76 | func AvgTime(items []time.Duration) time.Duration { 77 | var sum time.Duration 78 | for _, item := range items { 79 | sum += item 80 | } 81 | return time.Duration(int64(sum) / int64(len(items))) 82 | } 83 | 84 | // format bytes number friendly 85 | func FriendlyBytes(bytes uint64) string { 86 | units := [...]string{"YB", "ZB", "EB", "PB", "TB", "GB", "MB", "KB", "B"} 87 | total := len(units) 88 | for total--; total > 0 && bytes > 1024; total-- { 89 | bytes /= 1024 90 | } 91 | return fmt.Sprintf("%d%s", bytes, units[total]) 92 | } 93 | 94 | // short string format 95 | func FriendlyTime(d time.Duration) string { 96 | 97 | u := uint64(d) 98 | if u < uint64(time.Second) { 99 | switch { 100 | case u == 0: 101 | return "0" 102 | case u < uint64(time.Microsecond): 103 | return fmt.Sprintf("%.2fns", float64(u)) 104 | case u < uint64(time.Millisecond): 105 | return fmt.Sprintf("%.2fus", float64(u)/1000) 106 | default: 107 | return fmt.Sprintf("%.2fms", float64(u)/1000/1000) 108 | } 109 | } else { 110 | switch { 111 | case u < uint64(time.Minute): 112 | return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000) 113 | case u < uint64(time.Hour): 114 | return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60) 115 | default: 116 | return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60) 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /examples/simple/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/go-xorm/xorm" 9 | "github.com/go-xweb/xweb" 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | type MainAction struct { 14 | *xweb.Action 15 | 16 | root xweb.Mapper `xweb:"GET /"` 17 | list xweb.Mapper `xweb:"GET /list"` 18 | login xweb.Mapper 19 | logout xweb.Mapper 20 | add xweb.Mapper `xweb:"GET|POST /add"` 21 | del xweb.Mapper `xweb:"GET /delete"` 22 | edit xweb.Mapper `xweb:"/edit"` 23 | 24 | Id int64 25 | User User 26 | } 27 | 28 | type User struct { 29 | Id int64 30 | Name string 31 | Passwd string 32 | } 33 | 34 | var ( 35 | engine *xorm.Engine 36 | ) 37 | 38 | var su *User = &User{} 39 | 40 | func init() { 41 | var err error 42 | engine, err = xorm.NewEngine("sqlite3", "./data.db") 43 | if err != nil { 44 | fmt.Println(err) 45 | return 46 | } 47 | engine.ShowSQL = true 48 | err = engine.CreateTables(su) 49 | if err != nil { 50 | fmt.Println(err) 51 | } else { 52 | cnt, err := engine.Count(su) 53 | if err != nil { 54 | fmt.Println(err) 55 | } else if cnt == 0 { 56 | user := User{1, "admin", "123"} 57 | _, err := engine.Insert(&user) 58 | if err != nil { 59 | fmt.Println(err) 60 | } else { 61 | fmt.Println("Init db successfully!") 62 | } 63 | } 64 | } 65 | } 66 | 67 | func (c *MainAction) Root() { 68 | c.Go("login") 69 | } 70 | 71 | func (c *MainAction) IsLogin() bool { 72 | s := c.GetSession("userId") 73 | return s != nil 74 | } 75 | 76 | func (c *MainAction) Logout() { 77 | c.DelSession("userId") 78 | c.Go("root") 79 | } 80 | 81 | func (c *MainAction) List() error { 82 | users := make([]User, 0) 83 | err := engine.Find(&users) 84 | if err == nil { 85 | err = c.Render("list.html", &xweb.T{ 86 | "Users": &users, 87 | }) 88 | } 89 | return err 90 | } 91 | 92 | func (c *MainAction) Login() { 93 | if c.Method() == "GET" { 94 | c.Render("login.html") 95 | } else if c.Method() == "POST" { 96 | if c.User.Name == "" || c.User.Passwd == "" { 97 | c.Go("login") 98 | return 99 | } 100 | has, err := engine.Get(&c.User) 101 | if err == nil && has { 102 | if has { 103 | c.SetSession("userId", c.User.Id) 104 | c.Go("list") 105 | } else { 106 | c.Write("No user %v or password is error", c.User.Name) 107 | } 108 | } else { 109 | c.Write("Login error: %v", err) 110 | } 111 | } 112 | } 113 | 114 | func (c *MainAction) Add() { 115 | if c.Method() == "GET" { 116 | c.Render("add.html") 117 | } else if c.Method() == "POST" { 118 | _, err := engine.Insert(&c.User) 119 | if err == nil { 120 | c.Go("list") 121 | } else { 122 | c.Write("add user error: %v", err) 123 | } 124 | } 125 | } 126 | 127 | func (c *MainAction) Del() { 128 | _, err := engine.Id(c.Id).Delete(su) 129 | if err != nil { 130 | c.Write("删除失败:%v", err) 131 | } else { 132 | c.Go("list") 133 | } 134 | } 135 | 136 | func (c *MainAction) Edit() { 137 | if c.Method() == "GET" { 138 | if c.Id > 0 { 139 | has, err := engine.Id(c.Id).Get(&c.User) 140 | if err == nil { 141 | if has { 142 | c.Render("edit.html") 143 | } else { 144 | c.NotFound("no exist") 145 | } 146 | } else { 147 | c.Write("error: %v", err) 148 | } 149 | } else { 150 | c.Write("error: no user id") 151 | } 152 | } else if c.Method() == "POST" { 153 | if c.User.Id > 0 { 154 | _, err := engine.Id(c.User.Id).Update(&c.User) 155 | if err == nil { 156 | c.Go("list") 157 | } else { 158 | c.Write("save user error: %v", err) 159 | } 160 | } else { 161 | c.Write("error: no user id") 162 | } 163 | } 164 | } 165 | 166 | func main() { 167 | xweb.AddAction(&MainAction{}) 168 | 169 | app := xweb.MainServer().RootApp 170 | filter := xweb.NewLoginFilter(app, "userId", "/login") 171 | filter.AddAnonymousUrls("/", "/login", "/logout") 172 | app.AddFilter(filter) 173 | //app.AppConfig.StaticFileVersion = false 174 | 175 | f, err := os.Create("simple.log") 176 | if err != nil { 177 | println(err.Error()) 178 | return 179 | } 180 | logger := log.New(f, "", log.Ldate|log.Ltime) 181 | xweb.SetLogger(logger) 182 | xweb.Run("0.0.0.0:8080") 183 | } 184 | -------------------------------------------------------------------------------- /scgi.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | "net/http" 11 | "net/http/cgi" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | type scgiBody struct { 17 | reader io.Reader 18 | conn io.ReadWriteCloser 19 | closed bool 20 | } 21 | 22 | func (b *scgiBody) Read(p []byte) (n int, err error) { 23 | if b.closed { 24 | return 0, errors.New("SCGI read after close") 25 | } 26 | return b.reader.Read(p) 27 | } 28 | 29 | func (b *scgiBody) Close() error { 30 | b.closed = true 31 | return b.conn.Close() 32 | } 33 | 34 | type scgiConn struct { 35 | fd io.ReadWriteCloser 36 | req *http.Request 37 | headers http.Header 38 | wroteHeaders bool 39 | } 40 | 41 | func (conn *scgiConn) WriteHeader(status int) { 42 | if !conn.wroteHeaders { 43 | conn.wroteHeaders = true 44 | 45 | var buf bytes.Buffer 46 | text := statusText[status] 47 | 48 | fmt.Fprintf(&buf, "HTTP/1.1 %d %s\r\n", status, text) 49 | 50 | for k, v := range conn.headers { 51 | for _, i := range v { 52 | buf.WriteString(k + ": " + i + "\r\n") 53 | } 54 | } 55 | 56 | buf.WriteString("\r\n") 57 | conn.fd.Write(buf.Bytes()) 58 | } 59 | } 60 | 61 | func (conn *scgiConn) Header() http.Header { 62 | return conn.headers 63 | } 64 | 65 | func (conn *scgiConn) Write(data []byte) (n int, err error) { 66 | if !conn.wroteHeaders { 67 | conn.WriteHeader(200) 68 | } 69 | 70 | if conn.req.Method == "HEAD" { 71 | return 0, errors.New("Body Not Allowed") 72 | } 73 | 74 | return conn.fd.Write(data) 75 | } 76 | 77 | func (conn *scgiConn) Close() { conn.fd.Close() } 78 | 79 | func (conn *scgiConn) finishRequest() error { 80 | var buf bytes.Buffer 81 | if !conn.wroteHeaders { 82 | conn.wroteHeaders = true 83 | 84 | for k, v := range conn.headers { 85 | for _, i := range v { 86 | buf.WriteString(k + ": " + i + "\r\n") 87 | } 88 | } 89 | 90 | buf.WriteString("\r\n") 91 | conn.fd.Write(buf.Bytes()) 92 | } 93 | return nil 94 | } 95 | 96 | func (s *Server) readScgiRequest(fd io.ReadWriteCloser) (*http.Request, error) { 97 | reader := bufio.NewReader(fd) 98 | line, err := reader.ReadString(':') 99 | if err != nil { 100 | s.Logger.Println("Error during SCGI read: ", err.Error()) 101 | } 102 | length, _ := strconv.Atoi(line[0 : len(line)-1]) 103 | if length > 16384 { 104 | s.Logger.Println("Error: max header size is 16k") 105 | } 106 | headerData := make([]byte, length) 107 | _, err = reader.Read(headerData) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | b, err := reader.ReadByte() 113 | if err != nil { 114 | return nil, err 115 | } 116 | // discard the trailing comma 117 | if b != ',' { 118 | return nil, errors.New("SCGI protocol error: missing comma") 119 | } 120 | headerList := bytes.Split(headerData, []byte{0}) 121 | headers := map[string]string{} 122 | for i := 0; i < len(headerList)-1; i += 2 { 123 | headers[string(headerList[i])] = string(headerList[i+1]) 124 | } 125 | httpReq, err := cgi.RequestFromMap(headers) 126 | if err != nil { 127 | return nil, err 128 | } 129 | if httpReq.ContentLength > 0 { 130 | httpReq.Body = &scgiBody{ 131 | reader: io.LimitReader(reader, httpReq.ContentLength), 132 | conn: fd, 133 | } 134 | } else { 135 | httpReq.Body = &scgiBody{reader: reader, conn: fd} 136 | } 137 | return httpReq, nil 138 | } 139 | 140 | func (s *Server) handleScgiRequest(fd io.ReadWriteCloser) { 141 | req, err := s.readScgiRequest(fd) 142 | if err != nil { 143 | s.Logger.Println("SCGI error: %q", err.Error()) 144 | } 145 | sc := scgiConn{fd, req, make(map[string][]string), false} 146 | for _, app := range s.Apps { 147 | app.routeHandler(req, &sc) 148 | } 149 | sc.finishRequest() 150 | fd.Close() 151 | } 152 | 153 | func (s *Server) listenAndServeScgi(addr string) error { 154 | 155 | var l net.Listener 156 | var err error 157 | 158 | //if the path begins with a "/", assume it's a unix address 159 | if strings.HasPrefix(addr, "/") { 160 | l, err = net.Listen("unix", addr) 161 | } else { 162 | l, err = net.Listen("tcp", addr) 163 | } 164 | 165 | //save the listener so it can be closed 166 | s.l = l 167 | 168 | if err != nil { 169 | s.Logger.Println("SCGI listen error", err.Error()) 170 | return err 171 | } 172 | 173 | for { 174 | fd, err := l.Accept() 175 | if err != nil { 176 | s.Logger.Println("SCGI accept error", err.Error()) 177 | return err 178 | } 179 | go s.handleScgiRequest(fd) 180 | } 181 | return nil 182 | } 183 | -------------------------------------------------------------------------------- /static.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | 11 | "github.com/howeyc/fsnotify" 12 | ) 13 | 14 | type StaticVerMgr struct { 15 | Caches map[string]string 16 | mutex *sync.Mutex 17 | Path string 18 | Ignores map[string]bool 19 | app *App 20 | } 21 | 22 | func (self *StaticVerMgr) Moniter(staticPath string) error { 23 | watcher, err := fsnotify.NewWatcher() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | done := make(chan bool) 29 | go func() { 30 | for { 31 | select { 32 | case ev := <-watcher.Event: 33 | if ev == nil { 34 | break 35 | } 36 | if _, ok := self.Ignores[filepath.Base(ev.Name)]; ok { 37 | break 38 | } 39 | d, err := os.Stat(ev.Name) 40 | if err != nil { 41 | break 42 | } 43 | 44 | if ev.IsCreate() { 45 | if d.IsDir() { 46 | watcher.Watch(ev.Name) 47 | } else { 48 | url := ev.Name[len(self.Path)+1:] 49 | self.CacheItem(url) 50 | } 51 | } else if ev.IsDelete() { 52 | if d.IsDir() { 53 | watcher.RemoveWatch(ev.Name) 54 | } else { 55 | pa := ev.Name[len(self.Path)+1:] 56 | self.CacheDelete(pa) 57 | } 58 | } else if ev.IsModify() { 59 | if d.IsDir() { 60 | } else { 61 | url := ev.Name[len(staticPath)+1:] 62 | self.CacheItem(url) 63 | } 64 | } else if ev.IsRename() { 65 | if d.IsDir() { 66 | watcher.RemoveWatch(ev.Name) 67 | } else { 68 | url := ev.Name[len(staticPath)+1:] 69 | self.CacheDelete(url) 70 | } 71 | } 72 | case err := <-watcher.Error: 73 | self.app.Errorf("error: %v", err) 74 | } 75 | } 76 | }() 77 | 78 | err = filepath.Walk(staticPath, func(f string, info os.FileInfo, err error) error { 79 | if info.IsDir() { 80 | return watcher.Watch(f) 81 | } 82 | return nil 83 | }) 84 | 85 | if err != nil { 86 | fmt.Println(err) 87 | return err 88 | } 89 | 90 | <-done 91 | 92 | watcher.Close() 93 | return nil 94 | } 95 | 96 | func (self *StaticVerMgr) Init(app *App, staticPath string) error { 97 | self.Path = staticPath 98 | self.Caches = make(map[string]string) 99 | self.mutex = &sync.Mutex{} 100 | self.Ignores = map[string]bool{".DS_Store": true} 101 | self.app = app 102 | 103 | if dirExists(staticPath) { 104 | self.CacheAll(staticPath) 105 | 106 | go self.Moniter(staticPath) 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func (self *StaticVerMgr) getFileVer(url string) string { 113 | //content, err := ioutil.ReadFile(path.Join(self.Path, url)) 114 | fPath := filepath.Join(self.Path, url) 115 | self.app.Debug("loaded static ", fPath) 116 | f, err := os.Open(fPath) 117 | if err != nil { 118 | return "" 119 | } 120 | defer f.Close() 121 | 122 | fInfo, err := f.Stat() 123 | if err != nil { 124 | return "" 125 | } 126 | 127 | content := make([]byte, int(fInfo.Size())) 128 | _, err = f.Read(content) 129 | if err == nil { 130 | h := md5.New() 131 | io.WriteString(h, string(content)) 132 | return fmt.Sprintf("%x", h.Sum(nil))[0:4] 133 | } 134 | return "" 135 | } 136 | 137 | func (self *StaticVerMgr) CacheAll(staticPath string) error { 138 | self.mutex.Lock() 139 | defer self.mutex.Unlock() 140 | //fmt.Print("Getting static file version number, please wait... ") 141 | err := filepath.Walk(staticPath, func(f string, info os.FileInfo, err error) error { 142 | if info.IsDir() { 143 | return nil 144 | } 145 | rp := f[len(staticPath)+1:] 146 | if _, ok := self.Ignores[filepath.Base(rp)]; !ok { 147 | self.Caches[rp] = self.getFileVer(rp) 148 | } 149 | return nil 150 | }) 151 | //fmt.Println("Complete.") 152 | return err 153 | } 154 | 155 | func (self *StaticVerMgr) GetVersion(url string) string { 156 | self.mutex.Lock() 157 | defer self.mutex.Unlock() 158 | if ver, ok := self.Caches[url]; ok { 159 | return ver 160 | } 161 | 162 | ver := self.getFileVer(url) 163 | if ver != "" { 164 | self.Caches[url] = ver 165 | } 166 | return ver 167 | } 168 | 169 | func (self *StaticVerMgr) CacheDelete(url string) { 170 | self.mutex.Lock() 171 | defer self.mutex.Unlock() 172 | delete(self.Caches, url) 173 | self.app.Infof("static file %s is deleted.\n", url) 174 | } 175 | 176 | func (self *StaticVerMgr) CacheItem(url string) { 177 | fmt.Println(url) 178 | ver := self.getFileVer(url) 179 | if ver != "" { 180 | self.mutex.Lock() 181 | defer self.mutex.Unlock() 182 | self.Caches[url] = ver 183 | self.app.Infof("static file %s is created.", url) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "net/url" 7 | "os" 8 | "path" 9 | "reflect" 10 | "regexp" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // the func is the same as condition ? true : false 17 | func Ternary(express bool, trueVal interface{}, falseVal interface{}) interface{} { 18 | if express { 19 | return trueVal 20 | } 21 | return falseVal 22 | } 23 | 24 | // internal utility methods 25 | func webTime(t time.Time) string { 26 | ftime := t.Format(time.RFC1123) 27 | if strings.HasSuffix(ftime, "UTC") { 28 | ftime = ftime[0:len(ftime)-3] + "GMT" 29 | } 30 | return ftime 31 | } 32 | 33 | func JoinPath(paths ...string) string { 34 | if len(paths) < 1 { 35 | return "" 36 | } 37 | res := "" 38 | for _, p := range paths { 39 | res = path.Join(res, p) 40 | } 41 | return res 42 | } 43 | 44 | func PageSize(total, limit int) int { 45 | if total <= 0 { 46 | return 1 47 | } else { 48 | x := total % limit 49 | if x > 0 { 50 | return total/limit + 1 51 | } else { 52 | return total / limit 53 | } 54 | } 55 | } 56 | 57 | func SimpleParse(data string) map[string]string { 58 | configs := make(map[string]string) 59 | lines := strings.Split(string(data), "\n") 60 | for _, line := range lines { 61 | line = strings.TrimRight(line, "\r") 62 | vs := strings.Split(line, "=") 63 | if len(vs) == 2 { 64 | configs[strings.TrimSpace(vs[0])] = strings.TrimSpace(vs[1]) 65 | } 66 | } 67 | return configs 68 | } 69 | 70 | func dirExists(dir string) bool { 71 | d, e := os.Stat(dir) 72 | switch { 73 | case e != nil: 74 | return false 75 | case !d.IsDir(): 76 | return false 77 | } 78 | 79 | return true 80 | } 81 | 82 | func fileExists(dir string) bool { 83 | info, err := os.Stat(dir) 84 | if err != nil { 85 | return false 86 | } 87 | 88 | return !info.IsDir() 89 | } 90 | 91 | // Urlencode is a helper method that converts a map into URL-encoded form data. 92 | // It is a useful when constructing HTTP POST requests. 93 | func Urlencode(data map[string]string) string { 94 | var buf bytes.Buffer 95 | for k, v := range data { 96 | buf.WriteString(url.QueryEscape(k)) 97 | buf.WriteByte('=') 98 | buf.WriteString(url.QueryEscape(v)) 99 | buf.WriteByte('&') 100 | } 101 | s := buf.String() 102 | return s[0 : len(s)-1] 103 | } 104 | 105 | func UnTitle(s string) string { 106 | if len(s) < 2 { 107 | return strings.ToLower(s) 108 | } 109 | return strings.ToLower(string(s[0])) + s[1:] 110 | } 111 | 112 | var slugRegex = regexp.MustCompile(`(?i:[^a-z0-9\-_])`) 113 | 114 | // Slug is a helper function that returns the URL slug for string s. 115 | // It's used to return clean, URL-friendly strings that can be 116 | // used in routing. 117 | func Slug(s string, sep string) string { 118 | if s == "" { 119 | return "" 120 | } 121 | slug := slugRegex.ReplaceAllString(s, sep) 122 | if slug == "" { 123 | return "" 124 | } 125 | quoted := regexp.QuoteMeta(sep) 126 | sepRegex := regexp.MustCompile("(" + quoted + "){2,}") 127 | slug = sepRegex.ReplaceAllString(slug, sep) 128 | sepEnds := regexp.MustCompile("^" + quoted + "|" + quoted + "$") 129 | slug = sepEnds.ReplaceAllString(slug, "") 130 | return strings.ToLower(slug) 131 | } 132 | 133 | // NewCookie is a helper method that returns a new http.Cookie object. 134 | // Duration is specified in seconds. If the duration is zero, the cookie is permanent. 135 | // This can be used in conjunction with ctx.SetCookie. 136 | func NewCookie(name string, value string, age int64) *http.Cookie { 137 | var utctime time.Time 138 | if age == 0 { 139 | // 2^31 - 1 seconds (roughly 2038) 140 | utctime = time.Unix(2147483647, 0) 141 | } else { 142 | utctime = time.Unix(time.Now().Unix()+age, 0) 143 | } 144 | return &http.Cookie{Name: name, Value: value, Expires: utctime} 145 | } 146 | 147 | func removeStick(uri string) string { 148 | uri = strings.TrimRight(uri, "/") 149 | if uri == "" { 150 | uri = "/" 151 | } 152 | return uri 153 | } 154 | 155 | var ( 156 | fieldCache = make(map[reflect.Type]map[string]int) 157 | fieldCacheMutex sync.RWMutex 158 | ) 159 | 160 | // this method cache fields' index to field name 161 | func fieldByName(v reflect.Value, name string) reflect.Value { 162 | t := v.Type() 163 | fieldCacheMutex.RLock() 164 | cache, ok := fieldCache[t] 165 | fieldCacheMutex.RUnlock() 166 | if !ok { 167 | cache = make(map[string]int) 168 | for i := 0; i < v.NumField(); i++ { 169 | cache[t.Field(i).Name] = i 170 | } 171 | fieldCacheMutex.Lock() 172 | fieldCache[t] = cache 173 | fieldCacheMutex.Unlock() 174 | } 175 | 176 | if i, ok := cache[name]; ok { 177 | return v.Field(i) 178 | } 179 | 180 | return reflect.Zero(t) 181 | } 182 | -------------------------------------------------------------------------------- /memzipfile.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "compress/gzip" 7 | "errors" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "os" 12 | "strings" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo) 18 | var lock sync.RWMutex 19 | 20 | // OpenMemZipFile returns MemFile object with a compressed static file. 21 | // it's used for serve static file if gzip enable. 22 | func OpenMemZipFile(path string, zip string) (*MemFile, error) { 23 | osfile, e := os.Open(path) 24 | if e != nil { 25 | return nil, e 26 | } 27 | defer osfile.Close() 28 | 29 | osfileinfo, e := osfile.Stat() 30 | if e != nil { 31 | return nil, e 32 | } 33 | 34 | modtime := osfileinfo.ModTime() 35 | fileSize := osfileinfo.Size() 36 | lock.RLock() 37 | cfi, ok := gmfim[zip+":"+path] 38 | lock.RUnlock() 39 | if ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize { 40 | } else { 41 | var content []byte 42 | if zip == "gzip" { 43 | //将文件内容压缩到zipbuf中 44 | var zipbuf bytes.Buffer 45 | gzipwriter, e := gzip.NewWriterLevel(&zipbuf, gzip.BestCompression) 46 | if e != nil { 47 | return nil, e 48 | } 49 | _, e = io.Copy(gzipwriter, osfile) 50 | gzipwriter.Close() 51 | if e != nil { 52 | return nil, e 53 | } 54 | //读zipbuf到content 55 | content, e = ioutil.ReadAll(&zipbuf) 56 | if e != nil { 57 | return nil, e 58 | } 59 | } else if zip == "deflate" { 60 | //将文件内容压缩到zipbuf中 61 | var zipbuf bytes.Buffer 62 | deflatewriter, e := flate.NewWriter(&zipbuf, flate.BestCompression) 63 | if e != nil { 64 | return nil, e 65 | } 66 | _, e = io.Copy(deflatewriter, osfile) 67 | deflatewriter.Close() 68 | if e != nil { 69 | return nil, e 70 | } 71 | //将zipbuf读入到content 72 | content, e = ioutil.ReadAll(&zipbuf) 73 | if e != nil { 74 | return nil, e 75 | } 76 | } else { 77 | content, e = ioutil.ReadAll(osfile) 78 | if e != nil { 79 | return nil, e 80 | } 81 | } 82 | 83 | cfi = &MemFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize} 84 | lock.Lock() 85 | defer lock.Unlock() 86 | gmfim[zip+":"+path] = cfi 87 | } 88 | return &MemFile{fi: cfi, offset: 0}, nil 89 | } 90 | 91 | // MemFileInfo contains a compressed file bytes and file information. 92 | // it implements os.FileInfo interface. 93 | type MemFileInfo struct { 94 | os.FileInfo 95 | modTime time.Time 96 | content []byte 97 | contentSize int64 98 | fileSize int64 99 | } 100 | 101 | // Name returns the compressed filename. 102 | func (fi *MemFileInfo) Name() string { 103 | return fi.Name() 104 | } 105 | 106 | // Size returns the raw file content size, not compressed size. 107 | func (fi *MemFileInfo) Size() int64 { 108 | return fi.contentSize 109 | } 110 | 111 | // Mode returns file mode. 112 | func (fi *MemFileInfo) Mode() os.FileMode { 113 | return fi.Mode() 114 | } 115 | 116 | // ModTime returns the last modified time of raw file. 117 | func (fi *MemFileInfo) ModTime() time.Time { 118 | return fi.modTime 119 | } 120 | 121 | // IsDir returns the compressing file is a directory or not. 122 | func (fi *MemFileInfo) IsDir() bool { 123 | return fi.IsDir() 124 | } 125 | 126 | // return nil. implement the os.FileInfo interface method. 127 | func (fi *MemFileInfo) Sys() interface{} { 128 | return nil 129 | } 130 | 131 | // MemFile contains MemFileInfo and bytes offset when reading. 132 | // it implements io.Reader,io.ReadCloser and io.Seeker. 133 | type MemFile struct { 134 | fi *MemFileInfo 135 | offset int64 136 | } 137 | 138 | // Close memfile. 139 | func (f *MemFile) Close() error { 140 | return nil 141 | } 142 | 143 | // Get os.FileInfo of memfile. 144 | func (f *MemFile) Stat() (os.FileInfo, error) { 145 | return f.fi, nil 146 | } 147 | 148 | // read os.FileInfo of files in directory of memfile. 149 | // it returns empty slice. 150 | func (f *MemFile) Readdir(count int) ([]os.FileInfo, error) { 151 | infos := []os.FileInfo{} 152 | 153 | return infos, nil 154 | } 155 | 156 | // Read bytes from the compressed file bytes. 157 | func (f *MemFile) Read(p []byte) (n int, err error) { 158 | if len(f.fi.content)-int(f.offset) >= len(p) { 159 | n = len(p) 160 | } else { 161 | n = len(f.fi.content) - int(f.offset) 162 | err = io.EOF 163 | } 164 | copy(p, f.fi.content[f.offset:f.offset+int64(n)]) 165 | f.offset += int64(n) 166 | return 167 | } 168 | 169 | var errWhence = errors.New("Seek: invalid whence") 170 | var errOffset = errors.New("Seek: invalid offset") 171 | 172 | // Read bytes from the compressed file bytes by seeker. 173 | func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) { 174 | switch whence { 175 | default: 176 | return 0, errWhence 177 | case os.SEEK_SET: 178 | case os.SEEK_CUR: 179 | offset += f.offset 180 | case os.SEEK_END: 181 | offset += int64(len(f.fi.content)) 182 | } 183 | if offset < 0 || int(offset) > len(f.fi.content) { 184 | return 0, errOffset 185 | } 186 | f.offset = offset 187 | return f.offset, nil 188 | } 189 | 190 | // GetAcceptEncodingZip returns accept encoding format in http header. 191 | // zip is first, then deflate if both accepted. 192 | // If no accepted, return empty string. 193 | func GetAcceptEncodingZip(r *http.Request) string { 194 | ss := r.Header.Get("Accept-Encoding") 195 | ss = strings.ToLower(ss) 196 | if strings.Contains(ss, "gzip") { 197 | return "gzip" 198 | } else if strings.Contains(ss, "deflate") { 199 | return "deflate" 200 | } else { 201 | return "" 202 | } 203 | return "" 204 | } 205 | 206 | // CloseZWriter closes the io.Writer after compressing static file. 207 | func CloseZWriter(zwriter io.Writer) { 208 | if zwriter == nil { 209 | return 210 | } 211 | 212 | switch zwriter.(type) { 213 | case *gzip.Writer: 214 | zwriter.(*gzip.Writer).Close() 215 | case *flate.Writer: 216 | zwriter.(*flate.Writer).Close() 217 | //其他情况不close, 保持和默认(非压缩)行为一致 218 | /* 219 | case io.WriteCloser: 220 | zwriter.(io.WriteCloser).Close() 221 | */ 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /validation/util.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/coscms/tagfast" 11 | ) 12 | 13 | const ( 14 | VALIDTAG = "valid" 15 | ) 16 | 17 | var ( 18 | // key: function name 19 | // value: the number of parameters 20 | funcs = make(Funcs) 21 | 22 | // doesn't belong to validation functions 23 | unFuncs = map[string]bool{ 24 | "Clear": true, 25 | "HasErrors": true, 26 | "ErrorMap": true, 27 | "Error": true, 28 | "apply": true, 29 | "Check": true, 30 | "Valid": true, 31 | "NoMatch": true, 32 | } 33 | ) 34 | 35 | func init() { 36 | v := &Validation{} 37 | t := reflect.TypeOf(v) 38 | for i := 0; i < t.NumMethod(); i++ { 39 | m := t.Method(i) 40 | if !unFuncs[m.Name] { 41 | funcs[m.Name] = m.Func 42 | } 43 | } 44 | } 45 | 46 | type ValidFunc struct { 47 | Name string 48 | Params []interface{} 49 | } 50 | 51 | type Funcs map[string]reflect.Value 52 | 53 | func (f Funcs) Call(name string, params ...interface{}) (result []reflect.Value, err error) { 54 | defer func() { 55 | if r := recover(); r != nil { 56 | err = fmt.Errorf("%v", r) 57 | } 58 | }() 59 | if _, ok := f[name]; !ok { 60 | err = fmt.Errorf("%s does not exist", name) 61 | return 62 | } 63 | if len(params) != f[name].Type().NumIn() { 64 | err = fmt.Errorf("The number of params is not adapted") 65 | return 66 | } 67 | in := make([]reflect.Value, len(params)) 68 | for k, param := range params { 69 | in[k] = reflect.ValueOf(param) 70 | } 71 | result = f[name].Call(in) 72 | return 73 | } 74 | 75 | func isStruct(t reflect.Type) bool { 76 | return t.Kind() == reflect.Struct 77 | } 78 | 79 | func isStructPtr(t reflect.Type) bool { 80 | return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct 81 | } 82 | 83 | func getValidFuncs(f reflect.StructField, t reflect.Type, fName string) (vfs []ValidFunc, err error) { 84 | tag, tagf := tagfast.Tago(t, f, VALIDTAG) 85 | if len(tag) == 0 { 86 | return 87 | } 88 | cached := tagf.GetParsed(VALIDTAG) 89 | if cached == nil { 90 | //fmt.Printf("%s :[Tag]: %s\n\n", fName, tag) 91 | if vfs, tag, err = getRegFuncs(tag, fName); err != nil { 92 | fmt.Printf("%+v\n", err) 93 | return 94 | } 95 | fs := strings.Split(tag, ";") 96 | for _, vfunc := range fs { 97 | var vf ValidFunc 98 | if len(vfunc) == 0 { 99 | continue 100 | } 101 | vf, err = parseFunc(vfunc, fName) 102 | if err != nil { 103 | return 104 | } 105 | vfs = append(vfs, vf) 106 | } 107 | tagf.SetParsed(VALIDTAG, vfs) 108 | } else { 109 | vfs = cached.([]ValidFunc) 110 | } // */_ = tagf 111 | return 112 | } 113 | 114 | // Get Match function 115 | // May be get NoMatch function in the future 116 | func getRegFuncs(tag, key string) (vfs []ValidFunc, str string, err error) { 117 | tag = strings.TrimSpace(tag) 118 | index := strings.Index(tag, "Match(/") 119 | if index == -1 { 120 | str = tag 121 | return 122 | } 123 | end := strings.LastIndex(tag, "/)") 124 | if end < index { 125 | err = fmt.Errorf("invalid Match function") 126 | return 127 | } 128 | reg, err := regexp.Compile(tag[index+len("Match(/") : end]) 129 | if err != nil { 130 | return 131 | } 132 | vfs = []ValidFunc{ValidFunc{"Match", []interface{}{reg, key + "|Match"}}} 133 | str = strings.TrimSpace(tag[:index]) + strings.TrimSpace(tag[end+len("/)"):]) 134 | return 135 | } 136 | 137 | func parseFunc(vfunc, key string) (v ValidFunc, err error) { 138 | defer func() { 139 | if r := recover(); r != nil { 140 | err = fmt.Errorf("%v", r) 141 | } 142 | }() 143 | 144 | vfunc = strings.TrimSpace(vfunc) 145 | start := strings.Index(vfunc, "(") 146 | var num int 147 | 148 | // doesn't need parameter valid function 149 | if start == -1 { 150 | if num, err = numIn(vfunc); err != nil { 151 | return 152 | } 153 | if num != 0 { 154 | err = fmt.Errorf("%s require %d parameters", vfunc, num) 155 | return 156 | } 157 | v = ValidFunc{vfunc, []interface{}{key + "|" + vfunc}} 158 | return 159 | } 160 | 161 | end := strings.Index(vfunc, ")") 162 | if end == -1 { 163 | err = fmt.Errorf("invalid valid function") 164 | return 165 | } 166 | 167 | name := strings.TrimSpace(vfunc[:start]) 168 | if num, err = numIn(name); err != nil { 169 | return 170 | } 171 | 172 | params := strings.Split(vfunc[start+1:end], ",") 173 | // the num of param must be equal 174 | if num != len(params) { 175 | err = fmt.Errorf("%s require %d parameters", name, num) 176 | return 177 | } 178 | 179 | tParams, err := trim(name, key+"|"+name, params) 180 | if err != nil { 181 | return 182 | } 183 | v = ValidFunc{name, tParams} 184 | return 185 | } 186 | 187 | func numIn(name string) (num int, err error) { 188 | fn, ok := funcs[name] 189 | if !ok { 190 | err = fmt.Errorf("doesn't exsits %s valid function", name) 191 | return 192 | } 193 | // sub *Validation obj and key 194 | num = fn.Type().NumIn() - 3 195 | return 196 | } 197 | 198 | func trim(name, key string, s []string) (ts []interface{}, err error) { 199 | ts = make([]interface{}, len(s), len(s)+1) 200 | fn, ok := funcs[name] 201 | if !ok { 202 | err = fmt.Errorf("doesn't exsits %s valid function", name) 203 | return 204 | } 205 | for i := 0; i < len(s); i++ { 206 | var param interface{} 207 | // skip *Validation and obj params 208 | if param, err = magic(fn.Type().In(i+2), strings.TrimSpace(s[i])); err != nil { 209 | return 210 | } 211 | ts[i] = param 212 | } 213 | ts = append(ts, key) 214 | return 215 | } 216 | 217 | // modify the parameters's type to adapt the function input parameters' type 218 | func magic(t reflect.Type, s string) (i interface{}, err error) { 219 | switch t.Kind() { 220 | case reflect.Int: 221 | i, err = strconv.Atoi(s) 222 | case reflect.String: 223 | i = s 224 | case reflect.Ptr: 225 | if t.Elem().String() != "regexp.Regexp" { 226 | err = fmt.Errorf("does not support %s", t.Elem().String()) 227 | return 228 | } 229 | i, err = regexp.Compile(s) 230 | default: 231 | err = fmt.Errorf("does not support %s", t.Kind().String()) 232 | } 233 | return 234 | } 235 | 236 | func mergeParam(v *Validation, obj interface{}, params []interface{}) []interface{} { 237 | return append([]interface{}{v, obj}, params...) 238 | } 239 | -------------------------------------------------------------------------------- /examples/simple/static/js/jquery-bootstrap-pagination.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 3 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 4 | 5 | (function($) { 6 | /* 7 | $(".my-pagination").pagination(); 8 | 9 | $(".my-pagination").pagination({total_pages: 3, current_page: 1}); 10 | 11 | $(".my-pagination").pagination({ 12 | total_pages: 3, 13 | current_page: 1, 14 | callback: function(event, page) { 15 | alert("Clicked: " + page); 16 | } 17 | }); 18 | */ 19 | 20 | var PaginationView; 21 | $.fn.pagination = function(options) { 22 | return this.each(function() { 23 | return new PaginationView($(this), options).render(); 24 | }); 25 | }; 26 | return PaginationView = (function() { 27 | 28 | function PaginationView(el, options) { 29 | var defaults; 30 | this.el = el; 31 | this.clicked = __bind(this.clicked, this); 32 | 33 | this.render = __bind(this.render, this); 34 | 35 | this.pages = __bind(this.pages, this); 36 | 37 | this.buildLi = __bind(this.buildLi, this); 38 | 39 | this.buildLinks = __bind(this.buildLinks, this); 40 | 41 | defaults = { 42 | current_page: 1, 43 | total_pages: 1, 44 | next: ">", 45 | prev: "<", 46 | first: false, 47 | last: false, 48 | display_max: 8, 49 | ignore_single_page: true 50 | }; 51 | this.settings = $.extend(defaults, options); 52 | $("a", this.el).live("click", this.clicked); 53 | this.el.data("paginationView", this); 54 | } 55 | 56 | PaginationView.prototype.buildLinks = function() { 57 | var current_page, links, page, _i, _len, _ref; 58 | current_page = this.settings.current_page; 59 | links = []; 60 | if (this.settings.first) { 61 | links.push(this.buildLi(1, this.settings.first)); 62 | } 63 | if (this.settings.prev) { 64 | links.push(this.buildLi(current_page - 1, this.settings.prev)); 65 | } 66 | _ref = this.pages(); 67 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 68 | page = _ref[_i]; 69 | links.push(this.buildLi(page, page)); 70 | } 71 | if (this.settings.next) { 72 | links.push(this.buildLi(current_page + 1, this.settings.next)); 73 | } 74 | if (this.settings.last) { 75 | links.push(this.buildLi(this.settings.total_pages, this.settings.last)); 76 | } 77 | return links; 78 | }; 79 | 80 | PaginationView.prototype.buildLi = function(page, text) { 81 | if (text == null) { 82 | text = page; 83 | } 84 | return "
  • " + text + "
  • "; 85 | }; 86 | 87 | PaginationView.prototype.pages = function() { 88 | var buf, current_page, max, page, pages, total_pages, _i, _j, _k, _l, _m, _ref, _ref1, _ref2, _ref3; 89 | total_pages = this.settings.total_pages; 90 | current_page = this.settings.current_page; 91 | pages = []; 92 | max = this.settings.display_max; 93 | if (total_pages > 10) { 94 | pages.push(1); 95 | if (current_page > max - 1) { 96 | pages.push(".."); 97 | } 98 | if (current_page === total_pages) { 99 | for (page = _i = _ref = total_pages - max; _ref <= total_pages ? _i <= total_pages : _i >= total_pages; page = _ref <= total_pages ? ++_i : --_i) { 100 | pages.push(page); 101 | } 102 | } 103 | if (total_pages - current_page < max - 1) { 104 | for (page = _j = _ref1 = total_pages - max; _ref1 <= total_pages ? _j <= total_pages : _j >= total_pages; page = _ref1 <= total_pages ? ++_j : --_j) { 105 | pages.push(page); 106 | } 107 | } else if (current_page > max - 1) { 108 | buf = max / 2; 109 | for (page = _k = _ref2 = current_page - buf, _ref3 = current_page + buf; _ref2 <= _ref3 ? _k <= _ref3 : _k >= _ref3; page = _ref2 <= _ref3 ? ++_k : --_k) { 110 | pages.push(page); 111 | } 112 | } else if (current_page <= max - 1) { 113 | for (page = _l = 2; 2 <= max ? _l <= max : _l >= max; page = 2 <= max ? ++_l : --_l) { 114 | pages.push(page); 115 | } 116 | } 117 | pages = $.grep(pages, function(v, k) { 118 | return $.inArray(v, pages) === k; 119 | }); 120 | if (__indexOf.call(pages, total_pages) < 0) { 121 | if (!((total_pages - current_page) < max - 1)) { 122 | pages.push(".."); 123 | } 124 | pages.push(total_pages); 125 | } 126 | } else { 127 | for (page = _m = 1; 1 <= total_pages ? _m <= total_pages : _m >= total_pages; page = 1 <= total_pages ? ++_m : --_m) { 128 | pages.push(page); 129 | } 130 | } 131 | return pages; 132 | }; 133 | 134 | PaginationView.prototype.render = function() { 135 | var html, link, _i, _len, _ref; 136 | this.el.html(""); 137 | if (this.settings.total_pages === 1 && this.settings.ignore_single_page) { 138 | return; 139 | } 140 | html = [""); 149 | this.el.html(html.join("\n")); 150 | $("[data-page=" + this.settings.current_page + "]", this.el).closest("li").addClass("active"); 151 | $("[data-page='..']", this.el).closest("li").addClass("disabled"); 152 | $("[data-page='0']", this.el).closest("li").addClass("disabled"); 153 | $("[data-page='" + (this.settings.total_pages + 1) + "']", this.el).closest("li").addClass("disabled"); 154 | if (this.settings.current_page === 1 && this.settings.first) { 155 | $("li:first", this.el).removeClass("active").addClass("disabled"); 156 | } 157 | if (this.settings.current_page === this.settings.total_pages && this.settings.last) { 158 | return $("li:last", this.el).removeClass("active").addClass("disabled"); 159 | } 160 | }; 161 | 162 | PaginationView.prototype.clicked = function(event) { 163 | var page; 164 | page = parseInt($(event.target).attr("data-page")); 165 | if (this.settings.callback != null) { 166 | this.settings.callback(event, page); 167 | } 168 | this.settings.current_page = page; 169 | return this.render(); 170 | }; 171 | 172 | return PaginationView; 173 | 174 | })(); 175 | })(jQuery); 176 | 177 | }).call(this); 178 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "net/http/pprof" 9 | "os" 10 | "runtime" 11 | runtimePprof "runtime/pprof" 12 | "strconv" 13 | "strings" 14 | "time" 15 | 16 | "github.com/go-xweb/httpsession" 17 | "github.com/go-xweb/log" 18 | ) 19 | 20 | // ServerConfig is configuration for server objects. 21 | type ServerConfig struct { 22 | Addr string 23 | Port int 24 | RecoverPanic bool 25 | Profiler bool 26 | EnableGzip bool 27 | StaticExtensionsToGzip []string 28 | Url string 29 | UrlPrefix string 30 | UrlSuffix string 31 | StaticHtmlDir string 32 | SessionTimeout time.Duration 33 | } 34 | 35 | var ServerNumber uint = 0 36 | 37 | // Server represents a xweb server. 38 | type Server struct { 39 | Config *ServerConfig 40 | Apps map[string]*App 41 | AppsNamePath map[string]string 42 | Name string 43 | SessionManager *httpsession.Manager 44 | RootApp *App 45 | Logger *log.Logger 46 | Env map[string]interface{} 47 | //save the listener so it can be closed 48 | l net.Listener 49 | } 50 | 51 | func NewServer(args ...string) *Server { 52 | name := "" 53 | if len(args) == 1 { 54 | name = args[0] 55 | } else { 56 | name = fmt.Sprintf("Server%d", ServerNumber) 57 | ServerNumber++ 58 | } 59 | s := &Server{ 60 | Config: Config, 61 | Env: map[string]interface{}{}, 62 | Apps: map[string]*App{}, 63 | AppsNamePath: map[string]string{}, 64 | Name: name, 65 | } 66 | Servers[s.Name] = s 67 | 68 | s.SetLogger(log.New(os.Stdout, "", log.Ldefault())) 69 | 70 | app := NewApp("/", "root") 71 | s.AddApp(app) 72 | return s 73 | } 74 | 75 | func (s *Server) AddApp(a *App) { 76 | a.BasePath = strings.TrimRight(a.BasePath, "/") + "/" 77 | s.Apps[a.BasePath] = a 78 | 79 | if a.Name != "" { 80 | s.AppsNamePath[a.Name] = a.BasePath 81 | } 82 | 83 | a.Server = s 84 | a.Logger = s.Logger 85 | if a.BasePath == "/" { 86 | s.RootApp = a 87 | } 88 | } 89 | 90 | func (s *Server) AddAction(cs ...interface{}) { 91 | s.RootApp.AddAction(cs...) 92 | } 93 | 94 | func (s *Server) AutoAction(c ...interface{}) { 95 | s.RootApp.AutoAction(c...) 96 | } 97 | 98 | func (s *Server) AddRouter(url string, c interface{}) { 99 | s.RootApp.AddRouter(url, c) 100 | } 101 | 102 | func (s *Server) AddTmplVar(name string, varOrFun interface{}) { 103 | s.RootApp.AddTmplVar(name, varOrFun) 104 | } 105 | 106 | func (s *Server) AddTmplVars(t *T) { 107 | s.RootApp.AddTmplVars(t) 108 | } 109 | 110 | func (s *Server) AddFilter(filter Filter) { 111 | s.RootApp.AddFilter(filter) 112 | } 113 | 114 | func (s *Server) AddConfig(name string, value interface{}) { 115 | s.RootApp.SetConfig(name, value) 116 | } 117 | 118 | func (s *Server) SetConfig(name string, value interface{}) { 119 | s.RootApp.SetConfig(name, value) 120 | } 121 | 122 | func (s *Server) GetConfig(name string) interface{} { 123 | return s.RootApp.GetConfig(name) 124 | } 125 | 126 | func (s *Server) error(w http.ResponseWriter, status int, content string) error { 127 | return s.RootApp.error(w, status, content) 128 | } 129 | 130 | func (s *Server) initServer() { 131 | if s.Config == nil { 132 | s.Config = &ServerConfig{} 133 | s.Config.Profiler = true 134 | } 135 | 136 | for _, app := range s.Apps { 137 | app.initApp() 138 | } 139 | } 140 | 141 | // ServeHTTP is the interface method for Go's http server package 142 | func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) { 143 | s.Process(c, req) 144 | } 145 | 146 | // Process invokes the routing system for server s 147 | // non-root app's route will override root app's if there is same path 148 | func (s *Server) Process(w http.ResponseWriter, req *http.Request) { 149 | var result bool = true 150 | _, _ = XHook.Call("BeforeProcess", &result, s, w, req) 151 | if !result { 152 | return 153 | } 154 | if s.Config.UrlSuffix != "" && strings.HasSuffix(req.URL.Path, s.Config.UrlSuffix) { 155 | req.URL.Path = strings.TrimSuffix(req.URL.Path, s.Config.UrlSuffix) 156 | } 157 | if s.Config.UrlPrefix != "" && strings.HasPrefix(req.URL.Path, "/"+s.Config.UrlPrefix) { 158 | req.URL.Path = strings.TrimPrefix(req.URL.Path, "/"+s.Config.UrlPrefix) 159 | } 160 | if req.URL.Path[0] != '/' { 161 | req.URL.Path = "/" + req.URL.Path 162 | } 163 | for _, app := range s.Apps { 164 | if app != s.RootApp && strings.HasPrefix(req.URL.Path, app.BasePath) { 165 | app.routeHandler(req, w) 166 | return 167 | } 168 | } 169 | s.RootApp.routeHandler(req, w) 170 | _, _ = XHook.Call("AfterProcess", &result, s, w, req) 171 | } 172 | 173 | // Run starts the web application and serves HTTP requests for s 174 | func (s *Server) Run(addr string) { 175 | addrs := strings.Split(addr, ":") 176 | s.Config.Addr = addrs[0] 177 | s.Config.Port, _ = strconv.Atoi(addrs[1]) 178 | 179 | s.initServer() 180 | 181 | mux := http.NewServeMux() 182 | if s.Config.Profiler { 183 | mux.Handle("/debug/pprof", http.HandlerFunc(pprof.Index)) 184 | mux.Handle("/debug/pprof/heap", pprof.Handler("heap")) 185 | mux.Handle("/debug/pprof/block", pprof.Handler("block")) 186 | mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) 187 | mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) 188 | 189 | mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) 190 | mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) 191 | mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) 192 | 193 | mux.Handle("/debug/pprof/startcpuprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 194 | StartCPUProfile() 195 | })) 196 | mux.Handle("/debug/pprof/stopcpuprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 197 | StopCPUProfile() 198 | })) 199 | mux.Handle("/debug/pprof/memprof", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 200 | runtime.GC() 201 | runtimePprof.WriteHeapProfile(rw) 202 | })) 203 | mux.Handle("/debug/pprof/gc", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 204 | PrintGCSummary(rw) 205 | })) 206 | 207 | } 208 | 209 | if c, err := XHook.Call("MuxHandle", mux); err == nil { 210 | if ret := XHook.Value(c, 0); ret != nil { 211 | mux = ret.(*http.ServeMux) 212 | } 213 | } 214 | mux.Handle("/", s) 215 | 216 | s.Logger.Infof("http server is listening %s", addr) 217 | 218 | l, err := net.Listen("tcp", addr) 219 | if err != nil { 220 | s.Logger.Error("ListenAndServe:", err) 221 | } 222 | s.l = l 223 | err = http.Serve(s.l, mux) 224 | s.l.Close() 225 | } 226 | 227 | // RunFcgi starts the web application and serves FastCGI requests for s. 228 | func (s *Server) RunFcgi(addr string) { 229 | s.initServer() 230 | s.Logger.Infof("fcgi server is listening %s", addr) 231 | s.listenAndServeFcgi(addr) 232 | } 233 | 234 | // RunScgi starts the web application and serves SCGI requests for s. 235 | func (s *Server) RunScgi(addr string) { 236 | s.initServer() 237 | s.Logger.Infof("scgi server is listening %s", addr) 238 | s.listenAndServeScgi(addr) 239 | } 240 | 241 | // RunTLS starts the web application and serves HTTPS requests for s. 242 | func (s *Server) RunTLS(addr string, config *tls.Config) error { 243 | s.initServer() 244 | mux := http.NewServeMux() 245 | mux.Handle("/", s) 246 | l, err := tls.Listen("tcp", addr, config) 247 | if err != nil { 248 | s.Logger.Errorf("Listen: %v", err) 249 | return err 250 | } 251 | 252 | s.l = l 253 | 254 | s.Logger.Infof("https server is listening %s", addr) 255 | 256 | return http.Serve(s.l, mux) 257 | } 258 | 259 | // Close stops server s. 260 | func (s *Server) Close() { 261 | if s.l != nil { 262 | s.l.Close() 263 | } 264 | } 265 | 266 | // SetLogger sets the logger for server s 267 | func (s *Server) SetLogger(logger *log.Logger) { 268 | s.Logger = logger 269 | s.Logger.SetPrefix("[" + s.Name + "] ") 270 | if s.RootApp != nil { 271 | s.RootApp.Logger = s.Logger 272 | } 273 | } 274 | 275 | func (s *Server) InitSession() { 276 | if s.SessionManager == nil { 277 | s.SessionManager = httpsession.Default() 278 | } 279 | if s.Config.SessionTimeout > time.Second { 280 | s.SessionManager.SetMaxAge(s.Config.SessionTimeout) 281 | } 282 | s.SessionManager.Run() 283 | if s.RootApp != nil { 284 | s.RootApp.SessionManager = s.SessionManager 285 | } 286 | } 287 | 288 | func (s *Server) SetTemplateDir(path string) { 289 | s.RootApp.SetTemplateDir(path) 290 | } 291 | 292 | func (s *Server) SetStaticDir(path string) { 293 | s.RootApp.SetStaticDir(path) 294 | } 295 | 296 | func (s *Server) App(name string) *App { 297 | path, ok := s.AppsNamePath[name] 298 | if ok { 299 | return s.Apps[path] 300 | } 301 | return nil 302 | } 303 | -------------------------------------------------------------------------------- /template.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "reflect" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/howeyc/fsnotify" 15 | ) 16 | 17 | func IsNil(a interface{}) bool { 18 | if a == nil { 19 | return true 20 | } 21 | aa := reflect.ValueOf(a) 22 | return !aa.IsValid() || (aa.Type().Kind() == reflect.Ptr && aa.IsNil()) 23 | } 24 | 25 | func Add(left interface{}, right interface{}) interface{} { 26 | var rleft, rright int64 27 | var fleft, fright float64 28 | var isInt bool = true 29 | switch left.(type) { 30 | case int: 31 | rleft = int64(left.(int)) 32 | case int8: 33 | rleft = int64(left.(int8)) 34 | case int16: 35 | rleft = int64(left.(int16)) 36 | case int32: 37 | rleft = int64(left.(int32)) 38 | case int64: 39 | rleft = left.(int64) 40 | case float32: 41 | fleft = float64(left.(float32)) 42 | isInt = false 43 | case float64: 44 | fleft = left.(float64) 45 | isInt = false 46 | } 47 | 48 | switch right.(type) { 49 | case int: 50 | rright = int64(right.(int)) 51 | case int8: 52 | rright = int64(right.(int8)) 53 | case int16: 54 | rright = int64(right.(int16)) 55 | case int32: 56 | rright = int64(right.(int32)) 57 | case int64: 58 | rright = right.(int64) 59 | case float32: 60 | fright = float64(left.(float32)) 61 | isInt = false 62 | case float64: 63 | fleft = left.(float64) 64 | isInt = false 65 | } 66 | 67 | var intSum int64 = rleft + rright 68 | 69 | if isInt { 70 | return intSum 71 | } else { 72 | return fleft + fright + float64(intSum) 73 | } 74 | } 75 | 76 | func Subtract(left interface{}, right interface{}) interface{} { 77 | var rleft, rright int64 78 | var fleft, fright float64 79 | var isInt bool = true 80 | switch left.(type) { 81 | case int: 82 | rleft = int64(left.(int)) 83 | case int8: 84 | rleft = int64(left.(int8)) 85 | case int16: 86 | rleft = int64(left.(int16)) 87 | case int32: 88 | rleft = int64(left.(int32)) 89 | case int64: 90 | rleft = left.(int64) 91 | case float32: 92 | fleft = float64(left.(float32)) 93 | isInt = false 94 | case float64: 95 | fleft = left.(float64) 96 | isInt = false 97 | } 98 | 99 | switch right.(type) { 100 | case int: 101 | rright = int64(right.(int)) 102 | case int8: 103 | rright = int64(right.(int8)) 104 | case int16: 105 | rright = int64(right.(int16)) 106 | case int32: 107 | rright = int64(right.(int32)) 108 | case int64: 109 | rright = right.(int64) 110 | case float32: 111 | fright = float64(left.(float32)) 112 | isInt = false 113 | case float64: 114 | fleft = left.(float64) 115 | isInt = false 116 | } 117 | 118 | if isInt { 119 | return rleft - rright 120 | } else { 121 | return fleft + float64(rleft) - (fright + float64(rright)) 122 | } 123 | } 124 | 125 | func Now() time.Time { 126 | return time.Now() 127 | } 128 | 129 | func FormatDate(t time.Time, format string) string { 130 | return t.Format(format) 131 | } 132 | 133 | func Eq(left interface{}, right interface{}) bool { 134 | leftIsNil := (left == nil) 135 | rightIsNil := (right == nil) 136 | if leftIsNil || rightIsNil { 137 | if leftIsNil && rightIsNil { 138 | return true 139 | } 140 | return false 141 | } 142 | return fmt.Sprintf("%v", left) == fmt.Sprintf("%v", right) 143 | } 144 | 145 | func Html(raw string) template.HTML { 146 | return template.HTML(raw) 147 | } 148 | 149 | func Js(raw string) template.JS { 150 | return template.JS(raw) 151 | } 152 | 153 | //Usage:UrlFor("main:root:/user/login") or UrlFor("root:/user/login") or UrlFor("/user/login") or UrlFor() 154 | func UrlFor(args ...string) string { 155 | s := [3]string{"main", "root", ""} 156 | var u []string 157 | size := len(args) 158 | if size > 0 { 159 | u = strings.Split(args[0], ":") 160 | } else { 161 | u = []string{""} 162 | } 163 | var appUrl string = "" 164 | switch len(u) { 165 | case 1: 166 | s[2] = u[0] 167 | case 2: 168 | s[1] = u[0] 169 | s[2] = u[1] 170 | default: 171 | s[0] = u[0] 172 | s[1] = u[1] 173 | s[2] = u[2] 174 | } 175 | var url, prefix, suffix string 176 | if server, ok := Servers[s[0]]; ok { 177 | url += server.Config.Url 178 | prefix = server.Config.UrlPrefix 179 | suffix = server.Config.UrlSuffix 180 | if appPath, ok := server.AppsNamePath[s[1]]; ok { 181 | appUrl = appPath 182 | } 183 | } 184 | url = strings.TrimRight(url, "/") + "/" 185 | if size == 0 { 186 | return url 187 | } 188 | if appUrl != "/" { 189 | appUrl = strings.TrimLeft(appUrl, "/") 190 | if length := len(appUrl); length > 0 && appUrl[length-1] != '/' { 191 | appUrl = appUrl + "/" 192 | } 193 | } else { 194 | appUrl = "" 195 | } 196 | url += prefix + appUrl 197 | if s[2] == "" { 198 | return url 199 | } 200 | url += strings.TrimLeft(s[2], "/") + suffix 201 | return url 202 | } 203 | 204 | var ( 205 | defaultFuncs template.FuncMap = template.FuncMap{ 206 | "Now": Now, 207 | "Eq": Eq, 208 | "FormatDate": FormatDate, 209 | "Html": Html, 210 | "Add": Add, 211 | "Subtract": Subtract, 212 | "IsNil": IsNil, 213 | "UrlFor": UrlFor, 214 | "Js": Js, 215 | } 216 | ) 217 | 218 | type TemplateMgr struct { 219 | Caches map[string][]byte 220 | mutex *sync.Mutex 221 | RootDir string 222 | Ignores map[string]bool 223 | IsReload bool 224 | app *App 225 | Preprocessor func([]byte) []byte 226 | } 227 | 228 | func (self *TemplateMgr) Moniter(rootDir string) error { 229 | watcher, err := fsnotify.NewWatcher() 230 | if err != nil { 231 | return err 232 | } 233 | 234 | done := make(chan bool) 235 | go func() { 236 | for { 237 | select { 238 | case ev := <-watcher.Event: 239 | if ev == nil { 240 | break 241 | } 242 | if _, ok := self.Ignores[filepath.Base(ev.Name)]; ok { 243 | break 244 | } 245 | d, err := os.Stat(ev.Name) 246 | if err != nil { 247 | break 248 | } 249 | 250 | if ev.IsCreate() { 251 | if d.IsDir() { 252 | watcher.Watch(ev.Name) 253 | } else { 254 | tmpl := ev.Name[len(self.RootDir)+1:] 255 | content, err := ioutil.ReadFile(ev.Name) 256 | if err != nil { 257 | self.app.Errorf("loaded template %v failed: %v", tmpl, err) 258 | break 259 | } 260 | self.app.Infof("loaded template file %v success", tmpl) 261 | self.CacheTemplate(tmpl, content) 262 | } 263 | } else if ev.IsDelete() { 264 | if d.IsDir() { 265 | watcher.RemoveWatch(ev.Name) 266 | } else { 267 | tmpl := ev.Name[len(self.RootDir)+1:] 268 | self.CacheDelete(tmpl) 269 | } 270 | } else if ev.IsModify() { 271 | if d.IsDir() { 272 | } else { 273 | tmpl := ev.Name[len(self.RootDir)+1:] 274 | content, err := ioutil.ReadFile(ev.Name) 275 | if err != nil { 276 | self.app.Errorf("reloaded template %v failed: %v", tmpl, err) 277 | break 278 | } 279 | 280 | self.CacheTemplate(tmpl, content) 281 | self.app.Infof("reloaded template %v success", tmpl) 282 | } 283 | } else if ev.IsRename() { 284 | if d.IsDir() { 285 | watcher.RemoveWatch(ev.Name) 286 | } else { 287 | tmpl := ev.Name[len(self.RootDir)+1:] 288 | self.CacheDelete(tmpl) 289 | } 290 | } 291 | case err := <-watcher.Error: 292 | self.app.Error("error:", err) 293 | } 294 | } 295 | }() 296 | 297 | err = filepath.Walk(self.RootDir, func(f string, info os.FileInfo, err error) error { 298 | if info.IsDir() { 299 | return watcher.Watch(f) 300 | } 301 | return nil 302 | }) 303 | 304 | if err != nil { 305 | self.app.Error(err.Error()) 306 | return err 307 | } 308 | 309 | <-done 310 | 311 | watcher.Close() 312 | return nil 313 | } 314 | 315 | func (self *TemplateMgr) CacheAll(rootDir string) error { 316 | self.mutex.Lock() 317 | defer self.mutex.Unlock() 318 | //fmt.Print("Reading the contents of the template files, please wait... ") 319 | err := filepath.Walk(rootDir, func(f string, info os.FileInfo, err error) error { 320 | if info.IsDir() { 321 | return nil 322 | } 323 | tmpl := f[len(rootDir)+1:] 324 | tmpl = strings.Replace(tmpl, "\\", "/", -1) //[SWH|+]fix windows env 325 | if _, ok := self.Ignores[filepath.Base(tmpl)]; !ok { 326 | fpath := filepath.Join(self.RootDir, tmpl) 327 | content, err := ioutil.ReadFile(fpath) 328 | if err != nil { 329 | self.app.Debugf("load template %s error: %v", fpath, err) 330 | return err 331 | } 332 | self.app.Debug("loaded template", fpath) 333 | self.Caches[tmpl] = content 334 | } 335 | return nil 336 | }) 337 | //fmt.Println("Complete.") 338 | return err 339 | } 340 | 341 | func (self *TemplateMgr) Init(app *App, rootDir string, reload bool) error { 342 | self.RootDir = rootDir 343 | self.Caches = make(map[string][]byte) 344 | self.Ignores = make(map[string]bool) 345 | self.mutex = &sync.Mutex{} 346 | self.app = app 347 | if dirExists(rootDir) { 348 | self.CacheAll(rootDir) 349 | 350 | if reload { 351 | go self.Moniter(rootDir) 352 | } 353 | } 354 | 355 | if len(self.Ignores) == 0 { 356 | self.Ignores["*.tmp"] = false 357 | } 358 | 359 | return nil 360 | } 361 | 362 | func (self *TemplateMgr) GetTemplate(tmpl string) ([]byte, error) { 363 | self.mutex.Lock() 364 | defer self.mutex.Unlock() 365 | if content, ok := self.Caches[tmpl]; ok { 366 | self.app.Debugf("load template %v from cache", tmpl) 367 | return content, nil 368 | } 369 | 370 | content, err := ioutil.ReadFile(filepath.Join(self.RootDir, tmpl)) 371 | if err == nil { 372 | self.app.Debugf("load template %v from the file:", tmpl) 373 | self.Caches[tmpl] = content 374 | } 375 | return content, err 376 | } 377 | 378 | func (self *TemplateMgr) CacheTemplate(tmpl string, content []byte) { 379 | if self.Preprocessor != nil { 380 | content = self.Preprocessor(content) 381 | } 382 | self.mutex.Lock() 383 | defer self.mutex.Unlock() 384 | tmpl = strings.Replace(tmpl, "\\", "/", -1) 385 | self.app.Debugf("update template %v on cache", tmpl) 386 | self.Caches[tmpl] = content 387 | return 388 | } 389 | 390 | func (self *TemplateMgr) CacheDelete(tmpl string) { 391 | self.mutex.Lock() 392 | defer self.mutex.Unlock() 393 | tmpl = strings.Replace(tmpl, "\\", "/", -1) 394 | self.app.Debugf("delete template %v from cache", tmpl) 395 | delete(self.Caches, tmpl) 396 | return 397 | } 398 | -------------------------------------------------------------------------------- /validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/coscms/tagfast" 10 | ) 11 | 12 | type ValidFormer interface { 13 | Valid(*Validation) 14 | } 15 | 16 | type ValidationError struct { 17 | Message, Key, Name, Field, Tmpl string 18 | Value interface{} 19 | LimitValue interface{} 20 | } 21 | 22 | // Returns the Message. 23 | func (e *ValidationError) String() string { 24 | if e == nil { 25 | return "" 26 | } 27 | return e.Message 28 | } 29 | 30 | // A ValidationResult is returned from every validation method. 31 | // It provides an indication of success, and a pointer to the Error (if any). 32 | type ValidationResult struct { 33 | Error *ValidationError 34 | Ok bool 35 | } 36 | 37 | func (r *ValidationResult) Key(key string) *ValidationResult { 38 | if r.Error != nil { 39 | r.Error.Key = key 40 | } 41 | return r 42 | } 43 | 44 | func (r *ValidationResult) Message(message string, args ...interface{}) *ValidationResult { 45 | if r.Error != nil { 46 | if len(args) == 0 { 47 | r.Error.Message = message 48 | } else { 49 | r.Error.Message = fmt.Sprintf(message, args...) 50 | } 51 | } 52 | return r 53 | } 54 | 55 | // A Validation context manages data validation and error messages. 56 | type Validation struct { 57 | Errors []*ValidationError 58 | ErrorsMap map[string]*ValidationError 59 | } 60 | 61 | func (v *Validation) Clear() { 62 | v.Errors = []*ValidationError{} 63 | } 64 | 65 | func (v *Validation) HasErrors() bool { 66 | return len(v.Errors) > 0 67 | } 68 | 69 | // Return the errors mapped by key. 70 | // If there are multiple validation errors associated with a single key, the 71 | // first one "wins". (Typically the first validation will be the more basic). 72 | func (v *Validation) ErrorMap() map[string]*ValidationError { 73 | return v.ErrorsMap 74 | } 75 | 76 | // Add an error to the validation context. 77 | func (v *Validation) Error(message string, args ...interface{}) *ValidationResult { 78 | result := (&ValidationResult{ 79 | Ok: false, 80 | Error: &ValidationError{}, 81 | }).Message(message, args...) 82 | v.Errors = append(v.Errors, result.Error) 83 | return result 84 | } 85 | 86 | // Test that the argument is non-nil and non-empty (if string or list) 87 | func (v *Validation) Required(obj interface{}, key string) *ValidationResult { 88 | return v.apply(Required{key}, obj) 89 | } 90 | 91 | // Test that the obj is greater than min if obj's type is int 92 | func (v *Validation) Min(obj interface{}, min int, key string) *ValidationResult { 93 | return v.apply(Min{min, key}, obj) 94 | } 95 | 96 | // Test that the obj is less than max if obj's type is int 97 | func (v *Validation) Max(obj interface{}, max int, key string) *ValidationResult { 98 | return v.apply(Max{max, key}, obj) 99 | } 100 | 101 | // Test that the obj is between mni and max if obj's type is int 102 | func (v *Validation) Range(obj interface{}, min, max int, key string) *ValidationResult { 103 | return v.apply(Range{Min{Min: min}, Max{Max: max}, key}, obj) 104 | } 105 | 106 | func (v *Validation) MinSize(obj interface{}, min int, key string) *ValidationResult { 107 | return v.apply(MinSize{min, key}, obj) 108 | } 109 | 110 | func (v *Validation) MaxSize(obj interface{}, max int, key string) *ValidationResult { 111 | return v.apply(MaxSize{max, key}, obj) 112 | } 113 | 114 | func (v *Validation) Length(obj interface{}, n int, key string) *ValidationResult { 115 | return v.apply(Length{n, key}, obj) 116 | } 117 | 118 | func (v *Validation) Alpha(obj interface{}, key string) *ValidationResult { 119 | return v.apply(Alpha{key}, obj) 120 | } 121 | 122 | func (v *Validation) Numeric(obj interface{}, key string) *ValidationResult { 123 | return v.apply(Numeric{key}, obj) 124 | } 125 | 126 | func (v *Validation) AlphaNumeric(obj interface{}, key string) *ValidationResult { 127 | return v.apply(AlphaNumeric{key}, obj) 128 | } 129 | 130 | func (v *Validation) Match(obj interface{}, regex *regexp.Regexp, key string) *ValidationResult { 131 | return v.apply(Match{regex, key}, obj) 132 | } 133 | 134 | func (v *Validation) NoMatch(obj interface{}, regex *regexp.Regexp, key string) *ValidationResult { 135 | return v.apply(NoMatch{Match{Regexp: regex}, key}, obj) 136 | } 137 | 138 | func (v *Validation) AlphaDash(obj interface{}, key string) *ValidationResult { 139 | return v.apply(AlphaDash{NoMatch{Match: Match{Regexp: alphaDashPattern}}, key}, obj) 140 | } 141 | 142 | func (v *Validation) Email(obj interface{}, key string) *ValidationResult { 143 | return v.apply(Email{Match{Regexp: emailPattern}, key}, obj) 144 | } 145 | 146 | func (v *Validation) IP(obj interface{}, key string) *ValidationResult { 147 | return v.apply(IP{Match{Regexp: ipPattern}, key}, obj) 148 | } 149 | 150 | func (v *Validation) Base64(obj interface{}, key string) *ValidationResult { 151 | return v.apply(Base64{Match{Regexp: base64Pattern}, key}, obj) 152 | } 153 | 154 | func (v *Validation) Mobile(obj interface{}, key string) *ValidationResult { 155 | return v.apply(Mobile{Match{Regexp: mobilePattern}, key}, obj) 156 | } 157 | 158 | func (v *Validation) Tel(obj interface{}, key string) *ValidationResult { 159 | return v.apply(Tel{Match{Regexp: telPattern}, key}, obj) 160 | } 161 | 162 | func (v *Validation) Phone(obj interface{}, key string) *ValidationResult { 163 | return v.apply(Phone{Mobile{Match: Match{Regexp: mobilePattern}}, 164 | Tel{Match: Match{Regexp: telPattern}}, key}, obj) 165 | } 166 | 167 | func (v *Validation) ZipCode(obj interface{}, key string) *ValidationResult { 168 | return v.apply(ZipCode{Match{Regexp: zipCodePattern}, key}, obj) 169 | } 170 | 171 | func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult { 172 | if chk.IsSatisfied(obj) { 173 | return &ValidationResult{Ok: true} 174 | } 175 | 176 | // Add the error to the validation context. 177 | key := chk.GetKey() 178 | Field := key 179 | Name := "" 180 | 181 | parts := strings.Split(key, "|") 182 | if len(parts) == 2 { 183 | Field = parts[0] 184 | Name = parts[1] 185 | } 186 | 187 | err := &ValidationError{ 188 | Message: chk.DefaultMessage(), 189 | Key: key, 190 | Name: Name, 191 | Field: Field, 192 | Value: obj, 193 | Tmpl: MessageTmpls[Name], 194 | LimitValue: chk.GetLimitValue(), 195 | } 196 | v.setError(err) 197 | 198 | // Also return it in the result. 199 | return &ValidationResult{ 200 | Ok: false, 201 | Error: err, 202 | } 203 | } 204 | 205 | func (v *Validation) setError(err *ValidationError) { 206 | v.Errors = append(v.Errors, err) 207 | if v.ErrorsMap == nil { 208 | v.ErrorsMap = make(map[string]*ValidationError) 209 | } 210 | if _, ok := v.ErrorsMap[err.Field]; !ok { 211 | v.ErrorsMap[err.Field] = err 212 | } 213 | } 214 | 215 | func (v *Validation) SetError(fieldName string, errMsg string) *ValidationError { 216 | err := &ValidationError{Key: fieldName, Field: fieldName, Tmpl: errMsg, Message: errMsg} 217 | v.setError(err) 218 | return err 219 | } 220 | 221 | // Apply a group of validators to a field, in order, and return the 222 | // ValidationResult from the first one that fails, or the last one that 223 | // succeeds. 224 | func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResult { 225 | var result *ValidationResult 226 | for _, check := range checks { 227 | result = v.apply(check, obj) 228 | if !result.Ok { 229 | return result 230 | } 231 | } 232 | return result 233 | } 234 | 235 | // the obj parameter must be a struct or a struct pointer 236 | func (v *Validation) Valid(obj interface{}, args ...string) (b bool, err error) { 237 | err = v.validExec(obj, "", args...) 238 | if err != nil { 239 | fmt.Println(err) 240 | return 241 | } 242 | if !v.HasErrors() { 243 | if form, ok := obj.(ValidFormer); ok { 244 | form.Valid(v) 245 | } 246 | } 247 | 248 | return !v.HasErrors(), nil 249 | } 250 | 251 | func (v *Validation) validExec(obj interface{}, baseName string, args ...string) (err error) { 252 | objT := reflect.TypeOf(obj) 253 | objV := reflect.ValueOf(obj) 254 | switch { 255 | case isStruct(objT): 256 | case isStructPtr(objT): 257 | objT = objT.Elem() 258 | objV = objV.Elem() 259 | default: 260 | err = fmt.Errorf("%v must be a struct or a struct pointer", obj) 261 | return 262 | } 263 | var chkFields map[string][]string = make(map[string][]string) 264 | var pNum int = len(args) 265 | //fmt.Println(objT.Name(), ":[Struct NumIn]", pNum) 266 | if pNum > 0 { 267 | //aa.b.c,ab.b.c 268 | for _, v := range args { 269 | arr := strings.SplitN(v, ".", 2) 270 | if _, ok := chkFields[arr[0]]; !ok { 271 | chkFields[arr[0]] = make([]string, 0) 272 | } 273 | if len(arr) > 1 { 274 | chkFields[arr[0]] = append(chkFields[arr[0]], arr[1]) 275 | } 276 | } 277 | } 278 | args = make([]string, 0) 279 | if len(chkFields) > 0 { //检测指定字段 280 | for field, args := range chkFields { 281 | f, ok := objT.FieldByName(field) 282 | if !ok { 283 | err = fmt.Errorf("No name for the '%s' field", field) 284 | return 285 | } 286 | tag := tagfast.Tag(objT, f, VALIDTAG) 287 | if tag == "-" { 288 | continue 289 | } 290 | var vfs []ValidFunc 291 | 292 | var fName string 293 | if baseName == "" { 294 | fName = f.Name 295 | } else { 296 | fName = strings.Join([]string{baseName, f.Name}, ".") 297 | } 298 | fv := objV.FieldByName(field) 299 | if isStruct(f.Type) || isStructPtr(f.Type) { 300 | if fv.CanInterface() { 301 | err = v.validExec(fv.Interface(), fName, args...) 302 | } 303 | continue 304 | } 305 | if vfs, err = getValidFuncs(f, objT, fName); err != nil { 306 | return 307 | } 308 | for _, vf := range vfs { 309 | if _, err = funcs.Call(vf.Name, 310 | mergeParam(v, fv.Interface(), vf.Params)...); err != nil { 311 | return 312 | } 313 | } 314 | } 315 | } else { //检测全部字段 316 | for i := 0; i < objT.NumField(); i++ { 317 | tag := tagfast.Tag(objT, objT.Field(i), VALIDTAG) 318 | if tag == "-" { 319 | continue 320 | } 321 | var vfs []ValidFunc 322 | 323 | var fName string 324 | if baseName == "" { 325 | fName = objT.Field(i).Name 326 | } else { 327 | fName = strings.Join([]string{baseName, objT.Field(i).Name}, ".") 328 | } 329 | //fmt.Println(fName, ":[Type]:", objT.Field(i).Type.Kind()) 330 | if isStruct(objT.Field(i).Type) || isStructPtr(objT.Field(i).Type) { 331 | if objV.Field(i).CanInterface() { 332 | err = v.validExec(objV.Field(i).Interface(), fName) 333 | } 334 | continue 335 | } 336 | if vfs, err = getValidFuncs(objT.Field(i), objT, fName); err != nil { 337 | return 338 | } 339 | for _, vf := range vfs { 340 | if _, err = funcs.Call(vf.Name, 341 | mergeParam(v, objV.Field(i).Interface(), vf.Params)...); err != nil { 342 | return 343 | } 344 | } 345 | } 346 | } 347 | return 348 | } 349 | -------------------------------------------------------------------------------- /xweb.go: -------------------------------------------------------------------------------- 1 | // Package web is a lightweight web framework for Go. It's ideal for 2 | // writing simple, performant backend web services. 3 | package xweb 4 | 5 | import ( 6 | "crypto/tls" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/go-xweb/log" 14 | ) 15 | 16 | const ( 17 | Version = "0.2.1" 18 | ) 19 | 20 | func redirect(w http.ResponseWriter, url string, status ...int) error { 21 | s := 302 22 | if len(status) > 0 { 23 | s = status[0] 24 | } 25 | w.Header().Set("Location", url) 26 | w.WriteHeader(s) 27 | _, err := w.Write([]byte("Redirecting to: " + url)) 28 | return err 29 | } 30 | 31 | func Download(w http.ResponseWriter, fpath string) error { 32 | f, err := os.Open(fpath) 33 | if err != nil { 34 | return err 35 | } 36 | defer f.Close() 37 | 38 | fName := filepath.Base(fpath) 39 | w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%v\"", fName)) 40 | _, err = io.Copy(w, f) 41 | return err 42 | } 43 | 44 | const ( 45 | defaultErrorTmpl = ` 46 | 47 | 48 | %d - %s 49 | 57 | 58 |
    59 |
    60 |

    %d - %s

    61 |
    62 |
    %s
    63 |
    64 | 65 | (xweb v%s) 66 |
    67 |
    68 | 69 | ` 70 | ) 71 | 72 | var errorTmpl string = "" 73 | 74 | func Error(w http.ResponseWriter, status int, content string) error { 75 | return mainServer.error(w, status, content) 76 | } 77 | 78 | // Process invokes the main server's routing system. 79 | func Process(c http.ResponseWriter, req *http.Request) { 80 | mainServer.Process(c, req) 81 | } 82 | 83 | // Run starts the web application and serves HTTP requests for the main server. 84 | func Run(addr string) { 85 | mainServer.Run(addr) 86 | } 87 | 88 | func SimpleTLSConfig(certFile, keyFile string) (*tls.Config, error) { 89 | config := &tls.Config{} 90 | if config.NextProtos == nil { 91 | config.NextProtos = []string{"http/1.1"} 92 | } 93 | 94 | var err error 95 | config.Certificates = make([]tls.Certificate, 1) 96 | config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 97 | if err != nil { 98 | return nil, err 99 | } 100 | return config, nil 101 | } 102 | 103 | // RunTLS starts the web application and serves HTTPS requests for the main server. 104 | func RunTLS(addr string, config *tls.Config) { 105 | mainServer.RunTLS(addr, config) 106 | } 107 | 108 | // RunScgi starts the web application and serves SCGI requests for the main server. 109 | func RunScgi(addr string) { 110 | mainServer.RunScgi(addr) 111 | } 112 | 113 | // RunFcgi starts the web application and serves FastCGI requests for the main server. 114 | func RunFcgi(addr string) { 115 | mainServer.RunFcgi(addr) 116 | } 117 | 118 | // Close stops the main server. 119 | func Close() { 120 | mainServer.Close() 121 | } 122 | 123 | func AutoAction(c ...interface{}) { 124 | mainServer.AutoAction(c...) 125 | } 126 | 127 | func AddAction(c ...interface{}) { 128 | mainServer.AddAction(c...) 129 | } 130 | 131 | func AddTmplVar(name string, varOrFun interface{}) { 132 | mainServer.AddTmplVar(name, varOrFun) 133 | } 134 | 135 | func AddTmplVars(t *T) { 136 | mainServer.AddTmplVars(t) 137 | } 138 | 139 | func AddRouter(url string, c interface{}) { 140 | mainServer.AddRouter(url, c) 141 | } 142 | 143 | func AddFilter(filter Filter) { 144 | mainServer.AddFilter(filter) 145 | } 146 | 147 | func AddApp(a *App) { 148 | mainServer.AddApp(a) 149 | } 150 | 151 | func AddConfig(name string, value interface{}) { 152 | mainServer.AddConfig(name, value) 153 | } 154 | 155 | func AddHook(name string, fns ...interface{}) { 156 | XHook.Bind(name, fns...) 157 | } 158 | 159 | func SetTemplateDir(dir string) { 160 | mainServer.SetTemplateDir(dir) 161 | } 162 | 163 | func SetStaticDir(dir string) { 164 | mainServer.SetStaticDir(dir) 165 | } 166 | 167 | // SetLogger sets the logger for the main server. 168 | func SetLogger(logger *log.Logger) { 169 | mainServer.SetLogger(logger) 170 | } 171 | 172 | func MainServer() *Server { 173 | return mainServer 174 | } 175 | 176 | func RootApp() *App { 177 | return mainServer.RootApp 178 | } 179 | 180 | func Serv(name string) *Server { 181 | server, ok := Servers[name] 182 | if ok { 183 | return server 184 | } 185 | return nil 186 | } 187 | 188 | // Config is the configuration of the main server. 189 | var ( 190 | Config *ServerConfig = &ServerConfig{ 191 | RecoverPanic: true, 192 | EnableGzip: true, 193 | //Profiler: true, 194 | StaticExtensionsToGzip: []string{".css", ".js"}, 195 | } 196 | Servers map[string]*Server = make(map[string]*Server) //[SWH|+] 197 | mainServer *Server = NewServer("main") 198 | ) 199 | -------------------------------------------------------------------------------- /validation/validators.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "time" 8 | "unicode/utf8" 9 | ) 10 | 11 | var MessageTmpls = map[string]string{ 12 | "Required": "Can not be empty", 13 | "Min": "Minimum is %d", 14 | "Max": "Maximum is %d", 15 | "Range": "Range is %d to %d", 16 | "MinSize": "Minimum size is %d", 17 | "MaxSize": "Maximum size is %d", 18 | "Length": "Required length is %d", 19 | "Alpha": "Must be valid alpha characters", 20 | "Numeric": "Must be valid numeric characters", 21 | "AlphaNumeric": "Must be valid alpha or numeric characters", 22 | "Match": "Must match %s", 23 | "NoMatch": "Must not match %s", 24 | "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", 25 | "Email": "Must be a valid email address", 26 | "IP": "Must be a valid ip address", 27 | "Base64": "Must be valid base64 characters", 28 | "Mobile": "Must be valid mobile number", 29 | "Tel": "Must be valid telephone number", 30 | "Phone": "Must be valid telephone or mobile phone number", 31 | "ZipCode": "Must be valid zipcode", 32 | } 33 | 34 | type Validator interface { 35 | IsSatisfied(interface{}) bool 36 | DefaultMessage() string 37 | GetKey() string 38 | GetLimitValue() interface{} 39 | } 40 | 41 | type Required struct { 42 | Key string 43 | } 44 | 45 | func (r Required) IsSatisfied(obj interface{}) bool { 46 | if obj == nil { 47 | return false 48 | } 49 | 50 | if str, ok := obj.(string); ok { 51 | return len(str) > 0 52 | } 53 | if b, ok := obj.(bool); ok { 54 | return b 55 | } 56 | if i, ok := obj.(int); ok { 57 | return i != 0 58 | } 59 | if t, ok := obj.(time.Time); ok { 60 | return !t.IsZero() 61 | } 62 | v := reflect.ValueOf(obj) 63 | if v.Kind() == reflect.Slice { 64 | return v.Len() > 0 65 | } 66 | return true 67 | } 68 | 69 | func (r Required) DefaultMessage() string { 70 | return fmt.Sprint(MessageTmpls["Required"]) 71 | } 72 | 73 | func (r Required) GetKey() string { 74 | return r.Key 75 | } 76 | 77 | func (r Required) GetLimitValue() interface{} { 78 | return nil 79 | } 80 | 81 | type Min struct { 82 | Min int 83 | Key string 84 | } 85 | 86 | func (m Min) IsSatisfied(obj interface{}) bool { 87 | num, ok := obj.(int) 88 | if ok { 89 | return num >= m.Min 90 | } 91 | return false 92 | } 93 | 94 | func (m Min) DefaultMessage() string { 95 | return fmt.Sprintf(MessageTmpls["Min"], m.Min) 96 | } 97 | 98 | func (m Min) GetKey() string { 99 | return m.Key 100 | } 101 | 102 | func (m Min) GetLimitValue() interface{} { 103 | return m.Min 104 | } 105 | 106 | type Max struct { 107 | Max int 108 | Key string 109 | } 110 | 111 | func (m Max) IsSatisfied(obj interface{}) bool { 112 | num, ok := obj.(int) 113 | if ok { 114 | return num <= m.Max 115 | } 116 | return false 117 | } 118 | 119 | func (m Max) DefaultMessage() string { 120 | return fmt.Sprintf(MessageTmpls["Max"], m.Max) 121 | } 122 | 123 | func (m Max) GetKey() string { 124 | return m.Key 125 | } 126 | 127 | func (m Max) GetLimitValue() interface{} { 128 | return m.Max 129 | } 130 | 131 | // Requires an integer to be within Min, Max inclusive. 132 | type Range struct { 133 | Min 134 | Max 135 | Key string 136 | } 137 | 138 | func (r Range) IsSatisfied(obj interface{}) bool { 139 | return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj) 140 | } 141 | 142 | func (r Range) DefaultMessage() string { 143 | return fmt.Sprintf(MessageTmpls["Range"], r.Min.Min, r.Max.Max) 144 | } 145 | 146 | func (r Range) GetKey() string { 147 | return r.Key 148 | } 149 | 150 | func (r Range) GetLimitValue() interface{} { 151 | return []int{r.Min.Min, r.Max.Max} 152 | } 153 | 154 | // Requires an array or string to be at least a given length. 155 | type MinSize struct { 156 | Min int 157 | Key string 158 | } 159 | 160 | func (m MinSize) IsSatisfied(obj interface{}) bool { 161 | if str, ok := obj.(string); ok { 162 | return utf8.RuneCountInString(str) >= m.Min 163 | } 164 | v := reflect.ValueOf(obj) 165 | if v.Kind() == reflect.Slice { 166 | return v.Len() >= m.Min 167 | } 168 | return false 169 | } 170 | 171 | func (m MinSize) DefaultMessage() string { 172 | return fmt.Sprintf(MessageTmpls["MinSize"], m.Min) 173 | } 174 | 175 | func (m MinSize) GetKey() string { 176 | return m.Key 177 | } 178 | 179 | func (m MinSize) GetLimitValue() interface{} { 180 | return m.Min 181 | } 182 | 183 | // Requires an array or string to be at most a given length. 184 | type MaxSize struct { 185 | Max int 186 | Key string 187 | } 188 | 189 | func (m MaxSize) IsSatisfied(obj interface{}) bool { 190 | if str, ok := obj.(string); ok { 191 | return utf8.RuneCountInString(str) <= m.Max 192 | } 193 | v := reflect.ValueOf(obj) 194 | if v.Kind() == reflect.Slice { 195 | return v.Len() <= m.Max 196 | } 197 | return false 198 | } 199 | 200 | func (m MaxSize) DefaultMessage() string { 201 | return fmt.Sprintf(MessageTmpls["MaxSize"], m.Max) 202 | } 203 | 204 | func (m MaxSize) GetKey() string { 205 | return m.Key 206 | } 207 | 208 | func (m MaxSize) GetLimitValue() interface{} { 209 | return m.Max 210 | } 211 | 212 | // Requires an array or string to be exactly a given length. 213 | type Length struct { 214 | N int 215 | Key string 216 | } 217 | 218 | func (l Length) IsSatisfied(obj interface{}) bool { 219 | if str, ok := obj.(string); ok { 220 | return utf8.RuneCountInString(str) == l.N 221 | } 222 | v := reflect.ValueOf(obj) 223 | if v.Kind() == reflect.Slice { 224 | return v.Len() == l.N 225 | } 226 | return false 227 | } 228 | 229 | func (l Length) DefaultMessage() string { 230 | return fmt.Sprintf(MessageTmpls["Length"], l.N) 231 | } 232 | 233 | func (l Length) GetKey() string { 234 | return l.Key 235 | } 236 | 237 | func (l Length) GetLimitValue() interface{} { 238 | return l.N 239 | } 240 | 241 | type Alpha struct { 242 | Key string 243 | } 244 | 245 | func (a Alpha) IsSatisfied(obj interface{}) bool { 246 | if str, ok := obj.(string); ok { 247 | for _, v := range str { 248 | if ('Z' < v || v < 'A') && ('z' < v || v < 'a') { 249 | return false 250 | } 251 | } 252 | return true 253 | } 254 | return false 255 | } 256 | 257 | func (a Alpha) DefaultMessage() string { 258 | return fmt.Sprint(MessageTmpls["Alpha"]) 259 | } 260 | 261 | func (a Alpha) GetKey() string { 262 | return a.Key 263 | } 264 | 265 | func (a Alpha) GetLimitValue() interface{} { 266 | return nil 267 | } 268 | 269 | type Numeric struct { 270 | Key string 271 | } 272 | 273 | func (n Numeric) IsSatisfied(obj interface{}) bool { 274 | if str, ok := obj.(string); ok { 275 | for _, v := range str { 276 | if '9' < v || v < '0' { 277 | return false 278 | } 279 | } 280 | return true 281 | } 282 | return false 283 | } 284 | 285 | func (n Numeric) DefaultMessage() string { 286 | return fmt.Sprint(MessageTmpls["Numeric"]) 287 | } 288 | 289 | func (n Numeric) GetKey() string { 290 | return n.Key 291 | } 292 | 293 | func (n Numeric) GetLimitValue() interface{} { 294 | return nil 295 | } 296 | 297 | type AlphaNumeric struct { 298 | Key string 299 | } 300 | 301 | func (a AlphaNumeric) IsSatisfied(obj interface{}) bool { 302 | if str, ok := obj.(string); ok { 303 | for _, v := range str { 304 | if ('Z' < v || v < 'A') && ('z' < v || v < 'a') && ('9' < v || v < '0') { 305 | return false 306 | } 307 | } 308 | return true 309 | } 310 | return false 311 | } 312 | 313 | func (a AlphaNumeric) DefaultMessage() string { 314 | return fmt.Sprint(MessageTmpls["AlphaNumeric"]) 315 | } 316 | 317 | func (a AlphaNumeric) GetKey() string { 318 | return a.Key 319 | } 320 | 321 | func (a AlphaNumeric) GetLimitValue() interface{} { 322 | return nil 323 | } 324 | 325 | // Requires a string to match a given regex. 326 | type Match struct { 327 | Regexp *regexp.Regexp 328 | Key string 329 | } 330 | 331 | func (m Match) IsSatisfied(obj interface{}) bool { 332 | return m.Regexp.MatchString(fmt.Sprintf("%v", obj)) 333 | } 334 | 335 | func (m Match) DefaultMessage() string { 336 | return fmt.Sprintf(MessageTmpls["Match"], m.Regexp.String()) 337 | } 338 | 339 | func (m Match) GetKey() string { 340 | return m.Key 341 | } 342 | 343 | func (m Match) GetLimitValue() interface{} { 344 | return m.Regexp.String() 345 | } 346 | 347 | // Requires a string to not match a given regex. 348 | type NoMatch struct { 349 | Match 350 | Key string 351 | } 352 | 353 | func (n NoMatch) IsSatisfied(obj interface{}) bool { 354 | return !n.Match.IsSatisfied(obj) 355 | } 356 | 357 | func (n NoMatch) DefaultMessage() string { 358 | return fmt.Sprintf(MessageTmpls["NoMatch"], n.Regexp.String()) 359 | } 360 | 361 | func (n NoMatch) GetKey() string { 362 | return n.Key 363 | } 364 | 365 | func (n NoMatch) GetLimitValue() interface{} { 366 | return n.Regexp.String() 367 | } 368 | 369 | var alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]") 370 | 371 | type AlphaDash struct { 372 | NoMatch 373 | Key string 374 | } 375 | 376 | func (a AlphaDash) DefaultMessage() string { 377 | return fmt.Sprint(MessageTmpls["AlphaDash"]) 378 | } 379 | 380 | func (a AlphaDash) GetKey() string { 381 | return a.Key 382 | } 383 | 384 | func (a AlphaDash) GetLimitValue() interface{} { 385 | return nil 386 | } 387 | 388 | var emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?") 389 | 390 | type Email struct { 391 | Match 392 | Key string 393 | } 394 | 395 | func (e Email) DefaultMessage() string { 396 | return fmt.Sprint(MessageTmpls["Email"]) 397 | } 398 | 399 | func (e Email) GetKey() string { 400 | return e.Key 401 | } 402 | 403 | func (e Email) GetLimitValue() interface{} { 404 | return nil 405 | } 406 | 407 | 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?)$") 408 | 409 | type IP struct { 410 | Match 411 | Key string 412 | } 413 | 414 | func (i IP) DefaultMessage() string { 415 | return fmt.Sprint(MessageTmpls["IP"]) 416 | } 417 | 418 | func (i IP) GetKey() string { 419 | return i.Key 420 | } 421 | 422 | func (i IP) GetLimitValue() interface{} { 423 | return nil 424 | } 425 | 426 | var base64Pattern = regexp.MustCompile("^(?:[A-Za-z0-99+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") 427 | 428 | type Base64 struct { 429 | Match 430 | Key string 431 | } 432 | 433 | func (b Base64) DefaultMessage() string { 434 | return fmt.Sprint(MessageTmpls["Base64"]) 435 | } 436 | 437 | func (b Base64) GetKey() string { 438 | return b.Key 439 | } 440 | 441 | func (b Base64) GetLimitValue() interface{} { 442 | return nil 443 | } 444 | 445 | // just for chinese mobile phone number 446 | var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|(47)|[8][012356789]))\\d{8}$") 447 | 448 | type Mobile struct { 449 | Match 450 | Key string 451 | } 452 | 453 | func (m Mobile) DefaultMessage() string { 454 | return fmt.Sprint(MessageTmpls["Mobile"]) 455 | } 456 | 457 | func (m Mobile) GetKey() string { 458 | return m.Key 459 | } 460 | 461 | func (m Mobile) GetLimitValue() interface{} { 462 | return nil 463 | } 464 | 465 | // just for chinese telephone number 466 | var telPattern = regexp.MustCompile("^(0\\d{2,3}(\\-)?)?\\d{7,8}$") 467 | 468 | type Tel struct { 469 | Match 470 | Key string 471 | } 472 | 473 | func (t Tel) DefaultMessage() string { 474 | return fmt.Sprint(MessageTmpls["Tel"]) 475 | } 476 | 477 | func (t Tel) GetKey() string { 478 | return t.Key 479 | } 480 | 481 | func (t Tel) GetLimitValue() interface{} { 482 | return nil 483 | } 484 | 485 | // just for chinese telephone or mobile phone number 486 | type Phone struct { 487 | Mobile 488 | Tel 489 | Key string 490 | } 491 | 492 | func (p Phone) IsSatisfied(obj interface{}) bool { 493 | return p.Mobile.IsSatisfied(obj) || p.Tel.IsSatisfied(obj) 494 | } 495 | 496 | func (p Phone) DefaultMessage() string { 497 | return fmt.Sprint(MessageTmpls["Phone"]) 498 | } 499 | 500 | func (p Phone) GetKey() string { 501 | return p.Key 502 | } 503 | 504 | func (p Phone) GetLimitValue() interface{} { 505 | return nil 506 | } 507 | 508 | // just for chinese zipcode 509 | var zipCodePattern = regexp.MustCompile("^[1-9]\\d{5}$") 510 | 511 | type ZipCode struct { 512 | Match 513 | Key string 514 | } 515 | 516 | func (z ZipCode) DefaultMessage() string { 517 | return fmt.Sprint(MessageTmpls["ZipCode"]) 518 | } 519 | 520 | func (z ZipCode) GetKey() string { 521 | return z.Key 522 | } 523 | 524 | func (z ZipCode) GetLimitValue() interface{} { 525 | return nil 526 | } 527 | -------------------------------------------------------------------------------- /validation/validation_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestRequired(t *testing.T) { 10 | valid := Validation{} 11 | 12 | if valid.Required(nil, "nil").Ok { 13 | t.Error("nil object should be false") 14 | } 15 | if valid.Required("", "string").Ok { 16 | t.Error("\"'\" string should be false") 17 | } 18 | if !valid.Required("astaxie", "string").Ok { 19 | t.Error("string should be true") 20 | } 21 | if valid.Required(0, "zero").Ok { 22 | t.Error("Integer should not be equal 0") 23 | } 24 | if !valid.Required(1, "int").Ok { 25 | t.Error("Integer except 0 should be true") 26 | } 27 | if !valid.Required(time.Now(), "time").Ok { 28 | t.Error("time should be true") 29 | } 30 | if valid.Required([]string{}, "emptySlice").Ok { 31 | t.Error("empty slice should be false") 32 | } 33 | if !valid.Required([]interface{}{"ok"}, "slice").Ok { 34 | t.Error("slice should be true") 35 | } 36 | } 37 | 38 | func TestMin(t *testing.T) { 39 | valid := Validation{} 40 | 41 | if valid.Min(-1, 0, "min0").Ok { 42 | t.Error("-1 is less than the minimum value of 0 should be false") 43 | } 44 | if !valid.Min(1, 0, "min0").Ok { 45 | t.Error("1 is greater or equal than the minimum value of 0 should be true") 46 | } 47 | } 48 | 49 | func TestMax(t *testing.T) { 50 | valid := Validation{} 51 | 52 | if valid.Max(1, 0, "max0").Ok { 53 | t.Error("1 is greater than the minimum value of 0 should be false") 54 | } 55 | if !valid.Max(-1, 0, "max0").Ok { 56 | t.Error("-1 is less or equal than the maximum value of 0 should be true") 57 | } 58 | } 59 | 60 | func TestRange(t *testing.T) { 61 | valid := Validation{} 62 | 63 | if valid.Range(-1, 0, 1, "range0_1").Ok { 64 | t.Error("-1 is between 0 and 1 should be false") 65 | } 66 | if !valid.Range(1, 0, 1, "range0_1").Ok { 67 | t.Error("1 is between 0 and 1 should be true") 68 | } 69 | } 70 | 71 | func TestMinSize(t *testing.T) { 72 | valid := Validation{} 73 | 74 | if valid.MinSize("", 1, "minSize1").Ok { 75 | t.Error("the length of \"\" is less than the minimum value of 1 should be false") 76 | } 77 | if !valid.MinSize("ok", 1, "minSize1").Ok { 78 | t.Error("the length of \"ok\" is greater or equal than the minimum value of 1 should be true") 79 | } 80 | if valid.MinSize([]string{}, 1, "minSize1").Ok { 81 | t.Error("the length of empty slice is less than the minimum value of 1 should be false") 82 | } 83 | if !valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok { 84 | t.Error("the length of [\"ok\"] is greater or equal than the minimum value of 1 should be true") 85 | } 86 | } 87 | 88 | func TestMaxSize(t *testing.T) { 89 | valid := Validation{} 90 | 91 | if valid.MaxSize("ok", 1, "maxSize1").Ok { 92 | t.Error("the length of \"ok\" is greater than the maximum value of 1 should be false") 93 | } 94 | if !valid.MaxSize("", 1, "maxSize1").Ok { 95 | t.Error("the length of \"\" is less or equal than the maximum value of 1 should be true") 96 | } 97 | if valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok { 98 | t.Error("the length of [\"ok\", false] is greater than the maximum value of 1 should be false") 99 | } 100 | if !valid.MaxSize([]string{}, 1, "maxSize1").Ok { 101 | t.Error("the length of empty slice is less or equal than the maximum value of 1 should be true") 102 | } 103 | } 104 | 105 | func TestLength(t *testing.T) { 106 | valid := Validation{} 107 | 108 | if valid.Length("", 1, "length1").Ok { 109 | t.Error("the length of \"\" must equal 1 should be false") 110 | } 111 | if !valid.Length("1", 1, "length1").Ok { 112 | t.Error("the length of \"1\" must equal 1 should be true") 113 | } 114 | if valid.Length([]string{}, 1, "length1").Ok { 115 | t.Error("the length of empty slice must equal 1 should be false") 116 | } 117 | if !valid.Length([]interface{}{"ok"}, 1, "length1").Ok { 118 | t.Error("the length of [\"ok\"] must equal 1 should be true") 119 | } 120 | } 121 | 122 | func TestAlpha(t *testing.T) { 123 | valid := Validation{} 124 | 125 | if valid.Alpha("a,1-@ $", "alpha").Ok { 126 | t.Error("\"a,1-@ $\" are valid alpha characters should be false") 127 | } 128 | if !valid.Alpha("abCD", "alpha").Ok { 129 | t.Error("\"abCD\" are valid alpha characters should be true") 130 | } 131 | } 132 | 133 | func TestNumeric(t *testing.T) { 134 | valid := Validation{} 135 | 136 | if valid.Numeric("a,1-@ $", "numeric").Ok { 137 | t.Error("\"a,1-@ $\" are valid numeric characters should be false") 138 | } 139 | if !valid.Numeric("1234", "numeric").Ok { 140 | t.Error("\"1234\" are valid numeric characters should be true") 141 | } 142 | } 143 | 144 | func TestAlphaNumeric(t *testing.T) { 145 | valid := Validation{} 146 | 147 | if valid.AlphaNumeric("a,1-@ $", "alphaNumeric").Ok { 148 | t.Error("\"a,1-@ $\" are valid alpha or numeric characters should be false") 149 | } 150 | if !valid.AlphaNumeric("1234aB", "alphaNumeric").Ok { 151 | t.Error("\"1234aB\" are valid alpha or numeric characters should be true") 152 | } 153 | } 154 | 155 | func TestMatch(t *testing.T) { 156 | valid := Validation{} 157 | 158 | if valid.Match("suchuangji@gmail", regexp.MustCompile("^\\w+@\\w+\\.\\w+$"), "match").Ok { 159 | t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be false") 160 | } 161 | if !valid.Match("suchuangji@gmail.com", regexp.MustCompile("^\\w+@\\w+\\.\\w+$"), "match").Ok { 162 | t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be true") 163 | } 164 | } 165 | 166 | func TestNoMatch(t *testing.T) { 167 | valid := Validation{} 168 | 169 | if valid.NoMatch("123@gmail", regexp.MustCompile("[^\\w\\d]"), "nomatch").Ok { 170 | t.Error("\"123@gmail\" not match \"[^\\w\\d]\" should be false") 171 | } 172 | if !valid.NoMatch("123gmail", regexp.MustCompile("[^\\w\\d]"), "match").Ok { 173 | t.Error("\"123@gmail\" not match \"[^\\w\\d@]\" should be true") 174 | } 175 | } 176 | 177 | func TestAlphaDash(t *testing.T) { 178 | valid := Validation{} 179 | 180 | if valid.AlphaDash("a,1-@ $", "alphaDash").Ok { 181 | t.Error("\"a,1-@ $\" are valid alpha or numeric or dash(-_) characters should be false") 182 | } 183 | if !valid.AlphaDash("1234aB-_", "alphaDash").Ok { 184 | t.Error("\"1234aB\" are valid alpha or numeric or dash(-_) characters should be true") 185 | } 186 | } 187 | 188 | func TestEmail(t *testing.T) { 189 | valid := Validation{} 190 | 191 | if valid.Email("not@a email", "email").Ok { 192 | t.Error("\"not@a email\" is a valid email address should be false") 193 | } 194 | if !valid.Email("suchuangji@gmail.com", "email").Ok { 195 | t.Error("\"suchuangji@gmail.com\" is a valid email address should be true") 196 | } 197 | } 198 | 199 | func TestIP(t *testing.T) { 200 | valid := Validation{} 201 | 202 | if valid.IP("11.255.255.256", "IP").Ok { 203 | t.Error("\"11.255.255.256\" is a valid ip address should be false") 204 | } 205 | if !valid.IP("01.11.11.11", "IP").Ok { 206 | t.Error("\"suchuangji@gmail.com\" is a valid ip address should be true") 207 | } 208 | } 209 | 210 | func TestBase64(t *testing.T) { 211 | valid := Validation{} 212 | 213 | if valid.Base64("suchuangji@gmail.com", "base64").Ok { 214 | t.Error("\"suchuangji@gmail.com\" are a valid base64 characters should be false") 215 | } 216 | if !valid.Base64("c3VjaHVhbmdqaUBnbWFpbC5jb20=", "base64").Ok { 217 | t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true") 218 | } 219 | } 220 | 221 | func TestMobile(t *testing.T) { 222 | valid := Validation{} 223 | 224 | if valid.Mobile("19800008888", "mobile").Ok { 225 | t.Error("\"19800008888\" is a valid mobile phone number should be false") 226 | } 227 | if !valid.Mobile("18800008888", "mobile").Ok { 228 | t.Error("\"18800008888\" is a valid mobile phone number should be true") 229 | } 230 | if !valid.Mobile("18000008888", "mobile").Ok { 231 | t.Error("\"18000008888\" is a valid mobile phone number should be true") 232 | } 233 | if !valid.Mobile("8618300008888", "mobile").Ok { 234 | t.Error("\"8618300008888\" is a valid mobile phone number should be true") 235 | } 236 | if !valid.Mobile("+8614700008888", "mobile").Ok { 237 | t.Error("\"+8614700008888\" is a valid mobile phone number should be true") 238 | } 239 | } 240 | 241 | func TestTel(t *testing.T) { 242 | valid := Validation{} 243 | 244 | if valid.Tel("222-00008888", "telephone").Ok { 245 | t.Error("\"222-00008888\" is a valid telephone number should be false") 246 | } 247 | if !valid.Tel("022-70008888", "telephone").Ok { 248 | t.Error("\"022-70008888\" is a valid telephone number should be true") 249 | } 250 | if !valid.Tel("02270008888", "telephone").Ok { 251 | t.Error("\"02270008888\" is a valid telephone number should be true") 252 | } 253 | if !valid.Tel("70008888", "telephone").Ok { 254 | t.Error("\"70008888\" is a valid telephone number should be true") 255 | } 256 | } 257 | 258 | func TestPhone(t *testing.T) { 259 | valid := Validation{} 260 | 261 | if valid.Phone("222-00008888", "phone").Ok { 262 | t.Error("\"222-00008888\" is a valid phone number should be false") 263 | } 264 | if !valid.Mobile("+8614700008888", "phone").Ok { 265 | t.Error("\"+8614700008888\" is a valid phone number should be true") 266 | } 267 | if !valid.Tel("02270008888", "phone").Ok { 268 | t.Error("\"02270008888\" is a valid phone number should be true") 269 | } 270 | } 271 | 272 | func TestZipCode(t *testing.T) { 273 | valid := Validation{} 274 | 275 | if valid.ZipCode("", "zipcode").Ok { 276 | t.Error("\"00008888\" is a valid zipcode should be false") 277 | } 278 | if !valid.ZipCode("536000", "zipcode").Ok { 279 | t.Error("\"536000\" is a valid zipcode should be true") 280 | } 281 | } 282 | 283 | func TestValid(t *testing.T) { 284 | type user2 struct { 285 | Id int 286 | Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` 287 | Age int `valid:"Required;Range(1, 140)"` 288 | } 289 | valid := Validation{} 290 | 291 | u := user2{Name: "test@/test/;com", Age: 40} 292 | b, err := valid.Valid(u) 293 | if err != nil { 294 | t.Fatal(err) 295 | } 296 | if !b { 297 | t.Error("validation should be passed") 298 | } 299 | 300 | uptr := &user2{Name: "test", Age: 40} 301 | valid.Clear() 302 | b, err = valid.Valid(uptr) 303 | if err != nil { 304 | t.Fatal(err) 305 | } 306 | if b { 307 | t.Error("validation should not be passed") 308 | } 309 | if len(valid.Errors) != 1 { 310 | t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) 311 | } 312 | if valid.Errors[0].Key != "Name|Match" { 313 | t.Errorf("Message key should be `Name|Match` but got %s", valid.Errors[0].Key) 314 | } 315 | 316 | u = user2{Name: "test@/test/;com", Age: 180} 317 | valid.Clear() 318 | b, err = valid.Valid(u) 319 | if err != nil { 320 | t.Fatal(err) 321 | } 322 | if b { 323 | t.Error("validation should not be passed") 324 | } 325 | if len(valid.Errors) != 1 { 326 | t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) 327 | } 328 | if valid.Errors[0].Key != "Age|Range" { 329 | t.Errorf("Message key should be `Age|Range` but got %s", valid.Errors[0].Key) 330 | } 331 | 332 | valid.Clear() 333 | b, err = valid.Valid(u, "Name") 334 | if err != nil { 335 | t.Fatal(err) 336 | } 337 | if !b { 338 | t.Error("validation should be passed") 339 | } 340 | 341 | type UserExt1 struct { 342 | Id int 343 | Name string `valid:"Required;Match(/^\\w+$/)"` 344 | Age int `valid:"Required;Range(1, 140)"` 345 | } 346 | type UserExt3 struct { 347 | Id int 348 | Domain string `valid:"Required;Match(/^coscms\\.com$/)"` 349 | } 350 | type UserExt2 struct { 351 | Id int 352 | Email string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` 353 | Sex int `valid:"Required;Range(0, 1)"` 354 | UserExt3 355 | } 356 | type userExt struct { 357 | UserExt1 358 | *UserExt2 359 | } 360 | 361 | ue := userExt{UserExt1: UserExt1{Name: "coscms", Age: 130}, UserExt2: &UserExt2{Email: "test@/test/;com", Sex: 1, UserExt3: UserExt3{Domain: "coscms.com"}}} 362 | valid.Clear() 363 | b, err = valid.Valid(ue) 364 | if err != nil { 365 | t.Fatal(err) 366 | } 367 | 368 | if !b { 369 | t.Error("validation should be passed") 370 | } 371 | 372 | ue = userExt{UserExt1: UserExt1{Name: "coscms.com", Age: 128}, UserExt2: &UserExt2{Email: "test@/test/;com", Sex: 1, UserExt3: UserExt3{Domain: "coscms.com"}}} 373 | valid.Clear() 374 | b, err = valid.Valid(ue) 375 | if err != nil { 376 | t.Fatal(err) 377 | } 378 | if b { 379 | t.Error("validation should not be passed") 380 | } 381 | if len(valid.Errors) != 1 { 382 | t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) 383 | } 384 | //t.Errorf("%v", valid.Errors[0].Message) 385 | if valid.Errors[0].Key != "UserExt1.Name|Match" { 386 | t.Errorf("Message key should be `UserExt1.Name|Match` but got %s", valid.Errors[0].Key) 387 | } 388 | 389 | ue = userExt{UserExt1: UserExt1{Name: "coscms.com", Age: 128}, UserExt2: &UserExt2{Email: "test@/test/;com", Sex: 1, UserExt3: UserExt3{Domain: "www.coscms.com"}}} 390 | valid.Clear() 391 | b, err = valid.Valid(ue, "UserExt2.UserExt3.Domain", "UserExt1.Name") 392 | if err != nil { 393 | t.Fatal(err) 394 | } 395 | if b { 396 | t.Error("validation should not be passed") 397 | } 398 | if len(valid.Errors) != 2 { 399 | t.Fatalf("valid errors len should be 2 but got %d", len(valid.Errors)) 400 | } 401 | //t.Errorf("%v", valid.Errors[0].Message) 402 | if valid.Errors[0].Key != "UserExt2.UserExt3.Domain|Match" { 403 | t.Errorf("Message key should be `UserExt2.UserExt3.Domain|Match` but got %s", valid.Errors[0].Key) 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /examples/simple/static/bootstrap/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /action.go: -------------------------------------------------------------------------------- 1 | package xweb 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "compress/gzip" 7 | "crypto/hmac" 8 | "crypto/md5" 9 | "crypto/sha1" 10 | "encoding/base64" 11 | "encoding/hex" 12 | "encoding/json" 13 | "encoding/xml" 14 | "errors" 15 | "fmt" 16 | "html/template" 17 | "io" 18 | "io/ioutil" 19 | "mime" 20 | "mime/multipart" 21 | "net/http" 22 | "net/url" 23 | "os" 24 | "path" 25 | "reflect" 26 | "strconv" 27 | "strings" 28 | "time" 29 | 30 | "github.com/go-xweb/httpsession" 31 | "github.com/go-xweb/log" 32 | "github.com/go-xweb/uuid" 33 | ) 34 | 35 | type ActionOption struct { 36 | AutoMapForm bool 37 | CheckXsrf bool 38 | } 39 | 40 | // An Action object or it's substruct is created for every incoming HTTP request. 41 | // It provides information 42 | // about the request, including the http.Request object, the GET and POST params, 43 | // and acts as a Writer for the response. 44 | type Action struct { 45 | Request *http.Request 46 | App *App 47 | Option *ActionOption 48 | http.ResponseWriter 49 | C reflect.Value 50 | session *httpsession.Session 51 | T T 52 | f T 53 | RootTemplate *template.Template 54 | RequestBody []byte 55 | StatusCode int 56 | } 57 | 58 | type Mapper struct { 59 | } 60 | 61 | type T map[string]interface{} 62 | 63 | func XsrfName() string { 64 | return XSRF_TAG 65 | } 66 | 67 | //[SWH|+]: 68 | // Protocol returns request protocol name, such as HTTP/1.1 . 69 | func (c *Action) Protocol() string { 70 | return c.Request.Proto 71 | } 72 | 73 | // Uri returns full request url with query string, fragment. 74 | func (c *Action) Uri() string { 75 | return c.Request.RequestURI 76 | } 77 | 78 | // Url returns request url path (without query string, fragment). 79 | func (c *Action) Url() string { 80 | return c.Request.URL.String() 81 | } 82 | 83 | // Site returns base site url as scheme://domain type. 84 | func (c *Action) Site() string { 85 | return c.Scheme() + "://" + c.Domain() 86 | } 87 | 88 | // Scheme returns request scheme as "http" or "https". 89 | func (c *Action) Scheme() string { 90 | if c.Request.URL.Scheme != "" { 91 | return c.Request.URL.Scheme 92 | } else if c.Request.TLS == nil { 93 | return "http" 94 | } else { 95 | return "https" 96 | } 97 | } 98 | 99 | // Domain returns host name. 100 | // Alias of Host method. 101 | func (c *Action) Domain() string { 102 | return c.Host() 103 | } 104 | 105 | // Host returns host name. 106 | // if no host info in request, return localhost. 107 | func (c *Action) Host() string { 108 | if c.Request.Host != "" { 109 | hostParts := strings.Split(c.Request.Host, ":") 110 | if len(hostParts) > 0 { 111 | return hostParts[0] 112 | } 113 | return c.Request.Host 114 | } 115 | return "localhost" 116 | } 117 | 118 | // Is returns boolean of this request is on given method, such as Is("POST"). 119 | func (c *Action) Is(method string) bool { 120 | return c.Method() == method 121 | } 122 | 123 | // IsAjax returns boolean of this request is generated by ajax. 124 | func (c *Action) IsAjax() bool { 125 | return c.Header("X-Requested-With") == "XMLHttpRequest" 126 | } 127 | 128 | // IsSecure returns boolean of this request is in https. 129 | func (c *Action) IsSecure() bool { 130 | return c.Scheme() == "https" 131 | } 132 | 133 | // IsSecure returns boolean of this request is in webSocket. 134 | func (c *Action) IsWebsocket() bool { 135 | return c.Header("Upgrade") == "websocket" 136 | } 137 | 138 | // IsSecure returns boolean of whether file uploads in this request or not.. 139 | func (c *Action) IsUpload() bool { 140 | return c.Request.MultipartForm != nil 141 | } 142 | 143 | // IP returns request client ip. 144 | // if in proxy, return first proxy id. 145 | // if error, return 127.0.0.1. 146 | func (c *Action) IP() string { 147 | ips := c.Proxy() 148 | if len(ips) > 0 && ips[0] != "" { 149 | return ips[0] 150 | } 151 | ip := strings.Split(c.Request.RemoteAddr, ":") 152 | if len(ip) > 0 { 153 | if ip[0] != "[" { 154 | return ip[0] 155 | } 156 | } 157 | return "127.0.0.1" 158 | } 159 | 160 | // Proxy returns proxy client ips slice. 161 | func (c *Action) Proxy() []string { 162 | if ips := c.Header("X-Forwarded-For"); ips != "" { 163 | return strings.Split(ips, ",") 164 | } 165 | return []string{} 166 | } 167 | 168 | // Refer returns http referer header. 169 | func (c *Action) Refer() string { 170 | return c.Header("Referer") 171 | } 172 | 173 | // SubDomains returns sub domain string. 174 | // if aa.bb.domain.com, returns aa.bb . 175 | func (c *Action) SubDomains() string { 176 | parts := strings.Split(c.Host(), ".") 177 | return strings.Join(parts[len(parts)-2:], ".") 178 | } 179 | 180 | // Port returns request client port. 181 | // when error or empty, return 80. 182 | func (c *Action) Port() int { 183 | parts := strings.Split(c.Request.Host, ":") 184 | if len(parts) == 2 { 185 | port, _ := strconv.Atoi(parts[1]) 186 | return port 187 | } 188 | return 80 189 | } 190 | 191 | // UserAgent returns request client user agent string. 192 | func (c *Action) UserAgent() string { 193 | return c.Header("User-Agent") 194 | } 195 | 196 | // Query returns input data item string by a given string. 197 | func (c *Action) Query(key string) string { 198 | c.Request.ParseForm() 199 | return c.Request.Form.Get(key) 200 | } 201 | 202 | // Header returns request header item string by a given string. 203 | func (c *Action) Header(key string) string { 204 | return c.Request.Header.Get(key) 205 | } 206 | 207 | // Cookie returns request cookie item string by a given key. 208 | // if non-existed, return empty string. 209 | func (c *Action) Cookie(key string) string { 210 | ck, err := c.Request.Cookie(key) 211 | if err != nil { 212 | return "" 213 | } 214 | return ck.Value 215 | } 216 | 217 | // Body returns the raw request body data as bytes. 218 | func (c *Action) Body() []byte { 219 | if len(c.RequestBody) > 0 { 220 | return c.RequestBody 221 | } 222 | 223 | requestbody, _ := ioutil.ReadAll(c.Request.Body) 224 | c.Request.Body.Close() 225 | bf := bytes.NewBuffer(requestbody) 226 | c.Request.Body = ioutil.NopCloser(bf) 227 | c.RequestBody = requestbody 228 | return requestbody 229 | } 230 | 231 | func (c *Action) DisableHttpCache() { 232 | c.SetHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT") 233 | c.SetHeader("Last-Modified", webTime(time.Now().UTC())) 234 | c.SetHeader("Cache-Control", "no-store, no-cache, must-revalidate") 235 | c.SetHeader("Cache-Control", "post-check=0, pre-check=0") 236 | c.SetHeader("Pragma", "no-cache") 237 | } 238 | 239 | func (c *Action) HttpCache(content []byte) bool { 240 | h := md5.New() 241 | h.Write(content) 242 | Etag := hex.EncodeToString(h.Sum(nil)) 243 | //c.SetHeader("Connection", "keep-alive") 244 | c.SetHeader("X-Cache", "HIT from COSCMS-Page-Cache") 245 | //c.SetHeader("X-Cache", "HIT from COSCMS-Page-Cache 2013-12-02 17:16:01") 246 | if inm := c.Header("If-None-Match"); inm != "" && inm == Etag { 247 | h := c.ResponseWriter.Header() 248 | delete(h, "Content-Type") 249 | delete(h, "Content-Length") 250 | c.ResponseWriter.WriteHeader(http.StatusNotModified) 251 | return true 252 | } 253 | c.SetHeader("Etag", Etag) 254 | c.SetHeader("Cache-Control", "public,max-age=1") 255 | return false 256 | } 257 | 258 | // Body sets response body content. 259 | // if EnableGzip, compress content string. 260 | // it sends out response body directly. 261 | func (c *Action) SetBody(content []byte) error { 262 | if c.App.AppConfig.EnableHttpCache && c.HttpCache(content) { 263 | return nil 264 | } 265 | output_writer := c.ResponseWriter.(io.Writer) 266 | if c.App.Server.Config.EnableGzip == true && c.Header("Accept-Encoding") != "" { 267 | splitted := strings.SplitN(c.Header("Accept-Encoding"), ",", -1) 268 | encodings := make([]string, len(splitted)) 269 | 270 | for i, val := range splitted { 271 | encodings[i] = strings.TrimSpace(val) 272 | } 273 | for _, val := range encodings { 274 | if val == "gzip" { 275 | c.SetHeader("Content-Encoding", "gzip") 276 | output_writer, _ = gzip.NewWriterLevel(c.ResponseWriter, gzip.BestSpeed) 277 | break 278 | } else if val == "deflate" { 279 | c.SetHeader("Content-Encoding", "deflate") 280 | output_writer, _ = flate.NewWriter(c.ResponseWriter, flate.BestSpeed) 281 | break 282 | } 283 | } 284 | } else { 285 | c.SetHeader("Content-Length", strconv.Itoa(len(content))) 286 | } 287 | _, err := output_writer.Write(content) 288 | switch output_writer.(type) { 289 | case *gzip.Writer: 290 | output_writer.(*gzip.Writer).Close() 291 | case *flate.Writer: 292 | output_writer.(*flate.Writer).Close() 293 | } 294 | return err 295 | } 296 | 297 | //[SWH|+]; 298 | 299 | func (c *Action) XsrfValue() string { 300 | var val string = "" 301 | cookie, err := c.GetCookie(XSRF_TAG) 302 | if err != nil { 303 | val = uuid.NewRandom().String() 304 | c.SetCookie(NewCookie(XSRF_TAG, val, int64(c.App.AppConfig.SessionTimeout))) 305 | } else { 306 | val = cookie.Value 307 | } 308 | return val 309 | } 310 | 311 | func (c *Action) XsrfFormHtml() template.HTML { 312 | if c.App.AppConfig.CheckXsrf { 313 | return template.HTML(fmt.Sprintf(``, 314 | XSRF_TAG, c.XsrfValue())) 315 | } 316 | return template.HTML("") 317 | } 318 | 319 | // WriteString writes string data into the response object. 320 | func (c *Action) WriteBytes(bytes []byte) error { 321 | //_, err := c.ResponseWriter.Write(bytes) 322 | err := c.SetBody(bytes) //[SWH|+] 323 | if err != nil { 324 | c.App.Error("Error during write:", err) 325 | } 326 | return err 327 | } 328 | 329 | func (c *Action) Write(content string, values ...interface{}) error { 330 | if len(values) > 0 { 331 | content = fmt.Sprintf(content, values...) 332 | } 333 | //_, err := c.ResponseWriter.Write([]byte(content)) 334 | err := c.SetBody([]byte(content)) //[SWH|+] 335 | if err != nil { 336 | c.App.Error("Error during write:", err) 337 | } 338 | return err 339 | } 340 | 341 | // Abort is a helper method that sends an HTTP header and an optional 342 | // body. It is useful for returning 4xx or 5xx errors. 343 | // Once it has been called, any return value from the handler will 344 | // not be written to the response. 345 | func (c *Action) Abort(status int, body string) error { 346 | c.StatusCode = status 347 | return c.App.error(c.ResponseWriter, status, body) 348 | } 349 | 350 | // Redirect is a helper method for 3xx redirects. 351 | func (c *Action) Redirect(url string, status ...int) error { 352 | if len(status) == 0 { 353 | c.StatusCode = 302 354 | } else { 355 | c.StatusCode = status[0] 356 | } 357 | return c.App.Redirect(c.ResponseWriter, c.Request.URL.Path, url, status...) 358 | } 359 | 360 | // Notmodified writes a 304 HTTP response 361 | func (c *Action) NotModified() { 362 | c.StatusCode = 304 363 | c.ResponseWriter.WriteHeader(304) 364 | } 365 | 366 | // NotFound writes a 404 HTTP response 367 | func (c *Action) NotFound(message string) error { 368 | c.StatusCode = 404 369 | return c.Abort(404, message) 370 | } 371 | 372 | // ParseStruct mapping forms' name and values to struct's field 373 | // For example: 374 | //
    375 | // 376 | // 377 | // 378 | //
    379 | // 380 | // type User struct { 381 | // Id int64 382 | // Name string 383 | // Age string 384 | // } 385 | // 386 | // var user User 387 | // err := action.MapForm(&user) 388 | // 389 | func (c *Action) MapForm(st interface{}, names ...string) error { 390 | v := reflect.ValueOf(st) 391 | var name string 392 | if len(names) == 0 { 393 | name = UnTitle(v.Type().Elem().Name()) 394 | } else { 395 | name = names[0] 396 | } 397 | return c.App.namedStructMap(v.Elem(), c.Request, name) 398 | } 399 | 400 | // ContentType sets the Content-Type header for an HTTP response. 401 | // For example, c.ContentType("json") sets the content-type to "application/json" 402 | // If the supplied value contains a slash (/) it is set as the Content-Type 403 | // verbatim. The return value is the content type as it was 404 | // set, or an empty string if none was found. 405 | func (c *Action) SetContentType(val string) string { 406 | var ctype string 407 | if strings.ContainsRune(val, '/') { 408 | ctype = val 409 | } else { 410 | if !strings.HasPrefix(val, ".") { 411 | val = "." + val 412 | } 413 | ctype = mime.TypeByExtension(val) 414 | } 415 | if ctype != "" { 416 | c.SetHeader("Content-Type", ctype) 417 | } 418 | return ctype 419 | } 420 | 421 | // SetCookie adds a cookie header to the response. 422 | func (c *Action) SetCookie(cookie *http.Cookie) { 423 | c.SetHeader("Set-Cookie", cookie.String()) 424 | } 425 | 426 | func (c *Action) GetCookie(cookieName string) (*http.Cookie, error) { 427 | return c.Request.Cookie(cookieName) 428 | } 429 | 430 | func getCookieSig(key string, val []byte, timestamp string) string { 431 | hm := hmac.New(sha1.New, []byte(key)) 432 | 433 | hm.Write(val) 434 | hm.Write([]byte(timestamp)) 435 | 436 | hex := fmt.Sprintf("%02x", hm.Sum(nil)) 437 | return hex 438 | } 439 | 440 | func (c *Action) SetSecureCookie(name string, val string, age int64) { 441 | //base64 encode the val 442 | if len(c.App.AppConfig.CookieSecret) == 0 { 443 | c.App.Error("Secret Key for secure cookies has not been set. Please assign a cookie secret to web.Config.CookieSecret.") 444 | return 445 | } 446 | var buf bytes.Buffer 447 | encoder := base64.NewEncoder(base64.StdEncoding, &buf) 448 | encoder.Write([]byte(val)) 449 | encoder.Close() 450 | vs := buf.String() 451 | vb := buf.Bytes() 452 | timestamp := strconv.FormatInt(time.Now().Unix(), 10) 453 | sig := getCookieSig(c.App.AppConfig.CookieSecret, vb, timestamp) 454 | cookie := strings.Join([]string{vs, timestamp, sig}, "|") 455 | c.SetCookie(NewCookie(name, cookie, age)) 456 | } 457 | 458 | func (c *Action) GetSecureCookie(name string) (string, bool) { 459 | for _, cookie := range c.Request.Cookies() { 460 | if cookie.Name != name { 461 | continue 462 | } 463 | 464 | parts := strings.SplitN(cookie.Value, "|", 3) 465 | 466 | val := parts[0] 467 | timestamp := parts[1] 468 | sig := parts[2] 469 | 470 | if getCookieSig(c.App.AppConfig.CookieSecret, []byte(val), timestamp) != sig { 471 | return "", false 472 | } 473 | 474 | ts, _ := strconv.ParseInt(timestamp, 0, 64) 475 | 476 | if time.Now().Unix()-31*86400 > ts { 477 | return "", false 478 | } 479 | 480 | buf := bytes.NewBufferString(val) 481 | encoder := base64.NewDecoder(base64.StdEncoding, buf) 482 | 483 | res, _ := ioutil.ReadAll(encoder) 484 | return string(res), true 485 | } 486 | return "", false 487 | } 488 | 489 | func (c *Action) Method() string { 490 | return c.Request.Method 491 | } 492 | 493 | func (c *Action) Go(m string, anotherc ...interface{}) error { 494 | var t reflect.Type 495 | if len(anotherc) > 0 { 496 | t = reflect.TypeOf(anotherc[0]).Elem() 497 | } else { 498 | t = reflect.TypeOf(c.C.Interface()).Elem() 499 | } 500 | 501 | root, ok := c.App.ActionsPath[t] 502 | if !ok { 503 | return NotFound() 504 | } 505 | 506 | uris := strings.Split(m, "?") 507 | 508 | tag, ok := t.FieldByName(uris[0]) 509 | if !ok { 510 | return NotFound() 511 | } 512 | 513 | tagStr := tag.Tag.Get("xweb") 514 | var rPath string 515 | if tagStr != "" { 516 | p := tagStr 517 | ts := strings.Split(tagStr, " ") 518 | if len(ts) >= 2 { 519 | p = ts[1] 520 | } 521 | rPath = path.Join(root, p, m[len(uris[0]):]) 522 | } else { 523 | rPath = path.Join(root, m) 524 | } 525 | rPath = strings.Replace(rPath, "//", "/", -1) 526 | return c.Redirect(rPath) 527 | } 528 | 529 | func (c *Action) Flush() { 530 | flusher, _ := c.ResponseWriter.(http.Flusher) 531 | flusher.Flush() 532 | } 533 | 534 | func (c *Action) BasePath() string { 535 | return c.App.BasePath 536 | } 537 | 538 | func (c *Action) Namespace() string { 539 | return c.App.ActionsPath[c.C.Type()] 540 | } 541 | 542 | func (c *Action) Debug(params ...interface{}) { 543 | c.App.Debug(params...) 544 | } 545 | 546 | func (c *Action) Info(params ...interface{}) { 547 | c.App.Info(params...) 548 | } 549 | 550 | func (c *Action) Warn(params ...interface{}) { 551 | c.App.Warn(params...) 552 | } 553 | 554 | func (c *Action) Error(params ...interface{}) { 555 | c.App.Error(params...) 556 | } 557 | 558 | func (c *Action) Fatal(params ...interface{}) { 559 | c.App.Fatal(params...) 560 | } 561 | 562 | func (c *Action) Panic(params ...interface{}) { 563 | c.App.Panic(params...) 564 | } 565 | 566 | func (c *Action) Debugf(format string, params ...interface{}) { 567 | c.App.Debugf(format, params...) 568 | } 569 | 570 | func (c *Action) Infof(format string, params ...interface{}) { 571 | c.App.Infof(format, params...) 572 | } 573 | 574 | func (c *Action) Warnf(format string, params ...interface{}) { 575 | c.App.Warnf(format, params...) 576 | } 577 | 578 | func (c *Action) Errorf(format string, params ...interface{}) { 579 | c.App.Errorf(format, params...) 580 | } 581 | 582 | func (c *Action) Fatalf(format string, params ...interface{}) { 583 | c.App.Fatalf(format, params...) 584 | } 585 | 586 | func (c *Action) Panicf(format string, params ...interface{}) { 587 | c.App.Panicf(format, params...) 588 | } 589 | 590 | // Include method provide to template for {{include "xx.tmpl"}} 591 | func (c *Action) Include(tmplName string) interface{} { 592 | t := c.RootTemplate.New(tmplName) 593 | t.Funcs(c.GetFuncs()) 594 | 595 | content, err := c.getTemplate(tmplName) 596 | if err != nil { 597 | c.Errorf("RenderTemplate %v read err: %s", tmplName, err) 598 | return "" 599 | } 600 | 601 | constr := string(content) 602 | //[SWH|+]call hook 603 | if r, err := XHook.Call("BeforeRender", constr, c); err == nil { 604 | constr = XHook.String(r[0]) 605 | } 606 | tmpl, err := t.Parse(constr) 607 | if err != nil { 608 | c.Errorf("Parse %v err: %v", tmplName, err) 609 | return "" 610 | } 611 | newbytes := bytes.NewBufferString("") 612 | err = tmpl.Execute(newbytes, c.C.Elem().Interface()) 613 | if err != nil { 614 | c.Errorf("Parse %v err: %v", tmplName, err) 615 | return "" 616 | } 617 | 618 | tplcontent, err := ioutil.ReadAll(newbytes) 619 | if err != nil { 620 | c.Errorf("Parse %v err: %v", tmplName, err) 621 | return "" 622 | } 623 | return template.HTML(string(tplcontent)) 624 | } 625 | 626 | // render the template with vars map, you can have zero or one map 627 | func (c *Action) NamedRender(name, content string, params ...*T) error { 628 | c.f["include"] = c.Include 629 | if c.App.AppConfig.SessionOn { 630 | c.f["session"] = c.GetSession 631 | } 632 | c.f["cookie"] = c.Cookie 633 | c.f["XsrfFormHtml"] = c.XsrfFormHtml 634 | c.f["XsrfValue"] = c.XsrfValue 635 | if len(params) > 0 { 636 | c.AddTmplVars(params[0]) 637 | } 638 | 639 | c.RootTemplate = template.New(name) 640 | c.RootTemplate.Funcs(c.GetFuncs()) 641 | 642 | //[SWH|+]call hook 643 | if r, err := XHook.Call("BeforeRender", content, c); err == nil { 644 | content = XHook.String(r[0]) 645 | } 646 | tmpl, err := c.RootTemplate.Parse(content) 647 | if err == nil { 648 | newbytes := bytes.NewBufferString("") 649 | err = tmpl.Execute(newbytes, c.C.Elem().Interface()) 650 | if err == nil { 651 | tplcontent, err := ioutil.ReadAll(newbytes) 652 | if err == nil { 653 | //[SWH|+]call hook 654 | if r, err := XHook.Call("AfterRender", tplcontent, c); err == nil { 655 | if ret := XHook.Value(r, 0); ret != nil { 656 | tplcontent = ret.([]byte) 657 | } 658 | } 659 | err = c.SetBody(tplcontent) //[SWH|+] 660 | //_, err = c.ResponseWriter.Write(tplcontent) 661 | } 662 | } 663 | } 664 | return err 665 | } 666 | 667 | func (c *Action) getTemplate(tmpl string) ([]byte, error) { 668 | if c.App.AppConfig.CacheTemplates { 669 | return c.App.TemplateMgr.GetTemplate(tmpl) 670 | } 671 | path := c.App.getTemplatePath(tmpl) 672 | if path == "" { 673 | return nil, errors.New(fmt.Sprintf("No template file %v found", path)) 674 | } 675 | 676 | return ioutil.ReadFile(path) 677 | } 678 | 679 | // render the template with vars map, you can have zero or one map 680 | func (c *Action) Render(tmpl string, params ...*T) error { 681 | content, err := c.getTemplate(tmpl) 682 | if err == nil { 683 | err = c.NamedRender(tmpl, string(content), params...) 684 | } 685 | return err 686 | } 687 | 688 | func (c *Action) GetFuncs() template.FuncMap { 689 | funcs := c.App.FuncMaps 690 | if c.f != nil { 691 | for k, v := range c.f { 692 | funcs[k] = v 693 | } 694 | } 695 | 696 | return funcs 697 | } 698 | 699 | func (c *Action) SetConfig(name string, value interface{}) { 700 | c.App.Config[name] = value 701 | } 702 | 703 | func (c *Action) GetConfig(name string) interface{} { 704 | return c.App.Config[name] 705 | } 706 | 707 | func (c *Action) RenderString(content string, params ...*T) error { 708 | h := md5.New() 709 | h.Write([]byte(content)) 710 | name := h.Sum(nil) 711 | return c.NamedRender(string(name), content, params...) 712 | } 713 | 714 | // SetHeader sets a response header. the current value 715 | // of that header will be overwritten . 716 | func (c *Action) SetHeader(key string, value string) { 717 | c.ResponseWriter.Header().Set(key, value) 718 | } 719 | 720 | // add a name value for template 721 | func (c *Action) AddTmplVar(name string, varOrFunc interface{}) { 722 | if varOrFunc == nil { 723 | c.T[name] = varOrFunc 724 | return 725 | } 726 | 727 | if reflect.ValueOf(varOrFunc).Type().Kind() == reflect.Func { 728 | c.f[name] = varOrFunc 729 | } else { 730 | c.T[name] = varOrFunc 731 | } 732 | } 733 | 734 | // add names and values for template 735 | func (c *Action) AddTmplVars(t *T) { 736 | for name, value := range *t { 737 | c.AddTmplVar(name, value) 738 | } 739 | } 740 | 741 | func (c *Action) ServeJson(obj interface{}) { 742 | content, err := json.MarshalIndent(obj, "", " ") 743 | if err != nil { 744 | http.Error(c.ResponseWriter, err.Error(), http.StatusInternalServerError) 745 | return 746 | } 747 | c.SetHeader("Content-Length", strconv.Itoa(len(content))) 748 | c.ResponseWriter.Header().Set("Content-Type", "application/json") 749 | c.ResponseWriter.Write(content) 750 | } 751 | 752 | func (c *Action) ServeXml(obj interface{}) { 753 | content, err := xml.Marshal(obj) 754 | if err != nil { 755 | http.Error(c.ResponseWriter, err.Error(), http.StatusInternalServerError) 756 | return 757 | } 758 | c.SetHeader("Content-Length", strconv.Itoa(len(content))) 759 | c.ResponseWriter.Header().Set("Content-Type", "application/xml") 760 | c.ResponseWriter.Write(content) 761 | } 762 | 763 | func (c *Action) ServeFile(fpath string) { 764 | c.ResponseWriter.Header().Del("Content-Type") 765 | http.ServeFile(c.ResponseWriter, c.Request, fpath) 766 | } 767 | 768 | func (c *Action) GetSlice(key string) []string { 769 | return c.Request.Form[key] 770 | } 771 | 772 | func (c *Action) GetForm() url.Values { 773 | return c.Request.Form 774 | } 775 | 776 | func (c *Action) GetString(key string) string { 777 | s := c.GetSlice(key) 778 | if len(s) > 0 { 779 | return s[0] 780 | } 781 | return "" 782 | } 783 | 784 | func (c *Action) GetInt(key string) (int64, error) { 785 | return strconv.ParseInt(c.GetString(key), 10, 64) 786 | } 787 | 788 | func (c *Action) GetBool(key string) (bool, error) { 789 | return strconv.ParseBool(c.GetString(key)) 790 | } 791 | 792 | func (c *Action) GetFloat(key string) (float64, error) { 793 | return strconv.ParseFloat(c.GetString(key), 64) 794 | } 795 | 796 | func (c *Action) GetFile(key string) (multipart.File, *multipart.FileHeader, error) { 797 | return c.Request.FormFile(key) 798 | } 799 | 800 | func (c *Action) GetLogger() *log.Logger { 801 | return c.App.Logger 802 | } 803 | 804 | func (c *Action) SaveToFile(fromfile, tofile string) error { 805 | file, _, err := c.Request.FormFile(fromfile) 806 | if err != nil { 807 | return err 808 | } 809 | defer file.Close() 810 | f, err := os.OpenFile(tofile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 811 | if err != nil { 812 | return err 813 | } 814 | defer f.Close() 815 | _, err = io.Copy(f, file) 816 | return err 817 | } 818 | 819 | func (c *Action) Session() *httpsession.Session { 820 | if c.session == nil { 821 | c.session = c.App.SessionManager.Session(c.Request, c.ResponseWriter) 822 | } 823 | return c.session 824 | } 825 | 826 | func (c *Action) GetSession(key string) interface{} { 827 | return c.Session().Get(key) 828 | } 829 | 830 | func (c *Action) SetSession(key string, value interface{}) { 831 | c.Session().Set(key, value) 832 | } 833 | 834 | func (c *Action) DelSession(key string) { 835 | c.Session().Del(key) 836 | } 837 | -------------------------------------------------------------------------------- /examples/simple/static/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('