├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── acceptencoder.go ├── app.go ├── config.go ├── context.go ├── controller.go ├── errcode.go ├── examples ├── input │ ├── main.go │ └── static │ │ └── webcontent │ │ └── index.html ├── listener │ ├── cert.pem │ ├── key.pem │ ├── main.go │ └── static │ │ └── webcontent │ │ └── index.html ├── log │ ├── main.go │ └── seelog.xml ├── quickstart │ └── main.go ├── render │ ├── main.go │ └── static │ │ ├── templates │ │ └── admin │ │ │ └── index.tpl │ │ └── webcontent │ │ └── index.html ├── router │ ├── main.go │ └── static │ │ └── webcontent │ │ └── index.html └── upload │ ├── main.go │ └── static │ └── webcontent │ └── index.html ├── filter.go ├── listen.go ├── log.go ├── mime.go ├── response.go ├── result.go ├── router.go ├── server.go ├── session.go ├── statinfo.go ├── tag.go ├── tag_test.go ├── template.go ├── templatefunc.go ├── trygo.go ├── utils.go └── utils_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [master] 4 | paths: ['README.md'] 5 | pull_request: 6 | branches: [main] 7 | paths: ['README.md'] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | steps: 14 | - uses: actions/checkout@v2 15 | - run: | 16 | curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc 17 | chmod a+x gh-md-toc 18 | ./gh-md-toc --insert --no-backup README.md 19 | rm -f ./gh-md-toc 20 | - uses: stefanzweifel/git-auto-commit-action@v4 21 | with: 22 | commit_message: Auto update markdown TOC 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | 15 | *.exe 16 | task.txt 17 | 18 | local.properties 19 | .classpath 20 | .settings/ 21 | .loadpath 22 | 23 | # External tool builders 24 | .externalToolBuilders/ 25 | 26 | # Locally stored "Eclipse launch configurations" 27 | *.launch 28 | 29 | # CDT-specific 30 | .cproject 31 | 32 | # PDT-specific 33 | .buildpath 34 | 35 | 36 | ################# 37 | ## Visual Studio 38 | ################# 39 | 40 | ## Ignore Visual Studio temporary files, build results, and 41 | ## files generated by popular Visual Studio add-ons. 42 | 43 | # User-specific files 44 | *.suo 45 | *.user 46 | *.sln.docstates 47 | 48 | # Build results 49 | [Dd]ebug/ 50 | [Rr]elease/ 51 | *_i.c 52 | *_p.c 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.vspscc 67 | .builds 68 | *.dotCover 69 | 70 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 71 | #packages/ 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | 80 | # Visual Studio profiler 81 | *.psess 82 | *.vsp 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper* 86 | 87 | # Installshield output folder 88 | [Ee]xpress 89 | 90 | # DocProject is a documentation generator add-in 91 | DocProject/buildhelp/ 92 | DocProject/Help/*.HxT 93 | DocProject/Help/*.HxC 94 | DocProject/Help/*.hhc 95 | DocProject/Help/*.hhk 96 | DocProject/Help/*.hhp 97 | DocProject/Help/Html2 98 | DocProject/Help/html 99 | 100 | # Click-Once directory 101 | publish 102 | 103 | # Others 104 | [Bb]in 105 | [Oo]bj 106 | sql 107 | TestResults 108 | *.Cache 109 | ClientBin 110 | stylecop.* 111 | ~$* 112 | *.dbmdl 113 | Generated_Code #added for RIA/Silverlight projects 114 | 115 | # Backup & report files from converting an old project file to a newer 116 | # Visual Studio version. Backup files are not needed, because we have git ;-) 117 | _UpgradeReport_Files/ 118 | Backup*/ 119 | UpgradeLog*.XML 120 | 121 | 122 | 123 | ############ 124 | ## Windows 125 | ############ 126 | 127 | # Windows image file caches 128 | Thumbs.db 129 | 130 | # Folder config file 131 | Desktop.ini 132 | 133 | 134 | ############# 135 | ## Python 136 | ############# 137 | 138 | *.py[co] 139 | 140 | # Packages 141 | *.egg 142 | *.egg-info 143 | dist 144 | build 145 | eggs 146 | parts 147 | bin 148 | var 149 | sdist 150 | develop-eggs 151 | .installed.cfg 152 | 153 | # Installer logs 154 | pip-log.txt 155 | 156 | # Unit test / coverage reports 157 | .coverage 158 | .tox 159 | 160 | #Translations 161 | *.mo 162 | 163 | #Mr Developer 164 | .mr.developer.cfg 165 | 166 | # Mac crap 167 | .DS_Store 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 | ## trygo 4 | ======= 5 | trygo 是基于Golang的http、web服务框架。此框架的目标并不是想做一个大而全的web服务容器,它主要用于开发底层高性能高可靠性的http服务。支持如下特性:RESTful,MVC,类型内方法路由、正则路由,JSON/JSON(JQueryCallback)/XML结果响应支持,模板,静态文件输出,net.Listener过滤,http.Handler过滤。暂时不支持会话管理模块。 6 | 7 | trygo HTTP and WEB services of framework for Golang. It is mainly used to develop the underlying HTTP service, Support feature:RESTful,MVC,Methods the routing and regular routing,JSON/JSON(JQueryCallback)/XML result response support,template,Static file output, net.Listener filter, http.Handler filter. Temporarily does not support session management module. 8 | 9 | trygo is licensed under the Apache Licence, Version 2.0 10 | (http://www.apache.org/licenses/LICENSE-2.0.html). 11 | 12 | ## Installation 13 | ============ 14 | To install: 15 | 16 | go get -u github.com/tryor/trygo 17 | 18 | ## Quick Start 19 | ============ 20 | Here is the canonical "Hello, world" example app for trygo: 21 | 22 | ```go 23 | package main 24 | 25 | import ( 26 | "fmt" 27 | 28 | "github.com/tryor/trygo" 29 | ) 30 | 31 | func main() { 32 | 33 | trygo.Get("/", func(ctx *trygo.Context) { 34 | ctx.Render("hello world") 35 | }) 36 | 37 | fmt.Println("HTTP ListenAndServe AT ", trygo.DefaultApp.Config.HttpPort) 38 | trygo.Run() 39 | 40 | } 41 | ``` 42 | A better understanding of the trygo example: 43 | 44 | 45 | @see (https://github.com/tryor/trygo/tree/master/examples) 46 | 47 | 48 | ## Router 49 | ============ 50 | ```go 51 | 52 | trygo.Register(method string, path string, c IController, name string, params ...string) 53 | trygo.RegisterHandler(pattern string, h http.Handler) 54 | trygo.RegisterRESTful(pattern string, c IController) 55 | trygo.RegisterFunc(methods, pattern string, f HandlerFunc) 56 | trygo.Get(pattern string, f HandlerFunc) 57 | trygo.Post(pattern string, f HandlerFunc) 58 | trygo.Put(pattern string, f HandlerFunc) 59 | ... 60 | ``` 61 | 62 | for example: 63 | @see (https://github.com/tryor/trygo/tree/master/examples/router) 64 | 65 | 66 | 67 | ## Request 68 | ============ 69 | ```go 70 | 71 | Http handler method parameter is struct, the struct field tag name is `param`, 72 | tag attributes will have name,limit,scope,default,require,pattern,layout for example: 73 | `param:"name,limit:20,scope:[1 2 3],default:1,require,pattern:xxxxx"` 74 | scope: [1 2 3] or [1~100] or [0~] or [~0] or [100~] or [~100] or [~-100 -20~-10 -1 0 1 2 3 10~20 100~] 75 | 76 | type UserForm struct { 77 | Account string `param:"account,limit:20,require"` 78 | Pwd string `param:"pwd,limit:10,require"` 79 | Name string `param:"name,limit:20"` 80 | Sex int `param:"sex,scope:[1 2 3],default:1"` 81 | Age uint `param:"age,scope:[0~200]"` 82 | Birthday time.Time `param:"birthday,layout:2006-01-02|2006-01-02 15:04:05"` 83 | Email string `param:"email,limit:30,pattern:\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*"` 84 | Photo string 85 | } 86 | 87 | 88 | type MainController struct { 89 | trygo.Controller 90 | } 91 | func (this *MainController) Create(userform UserForm) { 92 | trygo.Logger.Info("user=%v", user) 93 | //... 94 | user := service.UserService.Create(userform) 95 | //... 96 | this.Render(user) 97 | } 98 | 99 | trygo.Register("POST", "/user/create", &MainController{}, "Create(userform UserForm)") 100 | 101 | 102 | 103 | ``` 104 | ```go 105 | Http handler method parameter is base data type, support parameter tag. 106 | 107 | const ( 108 | accountTag = `param:"account,limit:20,require"` 109 | pwdTag = `param:"pwd,limit:20,require"` 110 | ) 111 | 112 | var LoginTags = []string{accountTag, pwdTag} 113 | 114 | func (this *MainController) Login(account, pwd string) { 115 | 116 | fmt.Printf("account=%v\n", account) 117 | fmt.Printf("pwd=%v\n", pwd) 118 | 119 | this.Render("sessionid") 120 | } 121 | 122 | 123 | trygo.Register("POST", "/user/login", &MainController{}, "Login(account, pwd string)", LoginTags...) 124 | 125 | 126 | ``` 127 | 128 | ## Render 129 | ============ 130 | All the default render: 131 | 132 | 133 | @see (https://github.com/tryor/trygo/tree/master/examples/render) 134 | 135 | 136 | ## Static files 137 | ============ 138 | trygo.SetStaticPath("/", "static/webroot/") 139 | 140 | 141 | ## View / Template 142 | ============ 143 | 144 | template view path set 145 | 146 | ```go 147 | trygo.SetViewsPath("static/templates/") 148 | ``` 149 | template names 150 | 151 | trygo will find the template from cfg.TemplatePath. the file is set by user like: 152 | ```go 153 | c.TplNames = "admin/add.tpl" 154 | ``` 155 | then trygo will find the file in the path:static/templates/admin/add.tpl 156 | 157 | if you don't set TplNames,sss will find like this: 158 | ```go 159 | c.TplNames = c.ChildName + "/" + c.ActionName + "." + c.TplExt 160 | ``` 161 | 162 | render template 163 | 164 | ```go 165 | c.TplNames = "admin/add.tpl" 166 | c.Data["data"] = you data 167 | c.RenderTemplate() 168 | ``` 169 | 170 | ## Config 171 | ============ 172 | ```go 173 | 174 | type config struct { 175 | Listen listenConfig 176 | 177 | //生产或开发模式,值:PROD, DEV 178 | RunMode int8 179 | 180 | //模板文件位置 181 | TemplatePath string 182 | 183 | //请求主体数据量大小限制, 默认:defaultMaxRequestBodySize 184 | MaxRequestBodySize int64 185 | 186 | //是否自动分析请求参数,默认:true 187 | AutoParseRequest bool 188 | 189 | //如果使用结构体来接收请求参数,可在此设置是否采用域模式传递参数, 默认:false 190 | //如果值为true, 需要这样传递请求参数:user.account, user为方法参数名(为结构类型),account为user结构字段名 191 | FormDomainModel bool 192 | 193 | //指示绑定请求参数时发生错误是否抛出异常, 默认:true 194 | //如果设置为false, 当绑定数据出错时,将采用相应类型的默认值填充数据,并返回error 195 | ThrowBindParamPanic bool 196 | 197 | //指定一个处理Panic异常的函数,如果不指定,将采用默认方式处理 198 | RecoverFunc func(*Context) 199 | //是否打印Panic详细信息, 开发模式肯定会打印, @see defaultRecoverFunc 200 | //如果是自定义RecoverFunc,PrintPanicDetail配置将无效 201 | //默认:true 202 | PrintPanicDetail bool 203 | 204 | Render renderConfig 205 | } 206 | 207 | type listenConfig struct { 208 | //listen addr, format: "[ip]:", ":7086", "0.0.0.0:7086", "127.0.0.1:7086" 209 | Addr string 210 | ReadTimeout time.Duration 211 | WriteTimeout time.Duration 212 | //并发连接的最大数目, 默认:defaultConcurrency 213 | Concurrency int 214 | } 215 | 216 | type renderConfig struct { 217 | 218 | //是否自动从请求参数中解析响应数据格式 219 | //如果被设置,将从请求参数中自动获取的FormatParamName参数以及JsoncallbackParamName参数值 220 | //默认:false 221 | AutoParseFormat bool 222 | 223 | //默认:fmt 224 | FormatParamName string 225 | //默认: jsoncb 226 | JsoncallbackParamName string 227 | 228 | //默认是否使用Result结构对结果进行包装, @see result.go 229 | //如果设置此参数,将默认设置Render.Wrap() 230 | //当Render.Wrap()后,如果不设置响应数据格式,将默认为:json 231 | //默认:false 232 | Wrap bool 233 | 234 | //默认是否对响应数据进行gzip压缩 235 | //默认:false 236 | Gzip bool 237 | } 238 | 239 | func newConfig() *config { 240 | cfg := &config{} 241 | 242 | cfg.RunMode = PROD 243 | cfg.TemplatePath = "" 244 | 245 | cfg.MaxRequestBodySize = defaultMaxRequestBodySize 246 | cfg.AutoParseRequest = true 247 | cfg.FormDomainModel = false 248 | cfg.ThrowBindParamPanic = true 249 | 250 | cfg.RecoverFunc = defaultRecoverFunc 251 | cfg.PrintPanicDetail = true 252 | 253 | cfg.Listen.Addr = "0.0.0.0:7086" 254 | cfg.Listen.ReadTimeout = 0 255 | cfg.Listen.WriteTimeout = 0 256 | cfg.Listen.Concurrency = defaultConcurrency 257 | //cfg.Listen.MaxKeepaliveDuration = 0 258 | 259 | cfg.Render.AutoParseFormat = false 260 | cfg.Render.FormatParamName = "fmt" 261 | cfg.Render.JsoncallbackParamName = "jsoncb" 262 | cfg.Render.Wrap = false 263 | cfg.Render.Gzip = false 264 | return cfg 265 | } 266 | 267 | //生产或开发模式 268 | const ( 269 | PROD = iota 270 | DEV 271 | ) 272 | 273 | //数据渲染格式 274 | const ( 275 | FORMAT_JSON = "json" 276 | FORMAT_XML = "xml" 277 | FORMAT_TXT = "txt" 278 | FORMAT_HTML = "html" 279 | // other ... 280 | ) 281 | 282 | const defaultMaxRequestBodySize = 4 * 1024 * 1024 283 | 284 | const defaultConcurrency = 256 * 1024 285 | 286 | ``` 287 | 288 | ## Thank End 289 | ============ 290 | -------------------------------------------------------------------------------- /acceptencoder.go: -------------------------------------------------------------------------------- 1 | //Initial code source, @see https://github.com/astaxie/beego/blob/master/context/acceptencoder.go 2 | 3 | package trygo 4 | 5 | import ( 6 | "bytes" 7 | "compress/flate" 8 | "compress/gzip" 9 | "compress/zlib" 10 | "io" 11 | "net/http" 12 | "os" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | ) 17 | 18 | var ( 19 | //Default size==100B 20 | defaultGzipMinLength = 100 21 | //Content will only be compressed if content length is either unknown or greater than gzipMinLength. 22 | gzipMinLength = defaultGzipMinLength 23 | //The compression level used for deflate compression. (0-9). 24 | gzipCompressLevel int 25 | //List of HTTP methods to compress. If not set, only GET requests are compressed. 26 | includedMethods map[string]bool 27 | getMethodOnly bool 28 | ) 29 | 30 | func init() { 31 | InitGzip(defaultGzipMinLength, flate.BestSpeed, []string{"GET", "POST"}) 32 | } 33 | 34 | func InitGzip(minLength, compressLevel int, methods []string) { 35 | if minLength >= 0 { 36 | gzipMinLength = minLength 37 | } 38 | gzipCompressLevel = compressLevel 39 | if gzipCompressLevel < flate.NoCompression || gzipCompressLevel > flate.BestCompression { 40 | gzipCompressLevel = flate.BestSpeed 41 | } 42 | getMethodOnly = (len(methods) == 0) || (len(methods) == 1 && strings.ToUpper(methods[0]) == "GET") 43 | includedMethods = make(map[string]bool, len(methods)) 44 | for _, v := range methods { 45 | includedMethods[strings.ToUpper(v)] = true 46 | } 47 | } 48 | 49 | type resetWriter interface { 50 | io.Writer 51 | Reset(w io.Writer) 52 | } 53 | 54 | type nopResetWriter struct { 55 | io.Writer 56 | } 57 | 58 | func (n nopResetWriter) Reset(w io.Writer) { 59 | //do nothing 60 | } 61 | 62 | type acceptEncoder struct { 63 | name string 64 | levelEncode func(int) resetWriter 65 | customCompressLevelPool *sync.Pool 66 | bestCompressionPool *sync.Pool 67 | } 68 | 69 | func (ac acceptEncoder) encode(wr io.Writer, level int) resetWriter { 70 | if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil { 71 | return nopResetWriter{wr} 72 | } 73 | var rwr resetWriter 74 | switch level { 75 | case flate.BestSpeed: 76 | rwr = ac.customCompressLevelPool.Get().(resetWriter) 77 | case flate.BestCompression: 78 | rwr = ac.bestCompressionPool.Get().(resetWriter) 79 | default: 80 | rwr = ac.levelEncode(level) 81 | } 82 | rwr.Reset(wr) 83 | return rwr 84 | } 85 | 86 | func (ac acceptEncoder) put(wr resetWriter, level int) { 87 | if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil { 88 | return 89 | } 90 | wr.Reset(nil) 91 | 92 | //notice 93 | //compressionLevel==BestCompression DOES NOT MATTER 94 | //sync.Pool will not memory leak 95 | 96 | switch level { 97 | case gzipCompressLevel: 98 | ac.customCompressLevelPool.Put(wr) 99 | case flate.BestCompression: 100 | ac.bestCompressionPool.Put(wr) 101 | } 102 | } 103 | 104 | var ( 105 | noneCompressEncoder = acceptEncoder{"", nil, nil, nil} 106 | gzipCompressEncoder = acceptEncoder{ 107 | name: "gzip", 108 | levelEncode: func(level int) resetWriter { wr, _ := gzip.NewWriterLevel(nil, level); return wr }, 109 | customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, gzipCompressLevel); return wr }}, 110 | bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr }}, 111 | } 112 | 113 | //according to the sec :http://tools.ietf.org/html/rfc2616#section-3.5 ,the deflate compress in http is zlib indeed 114 | //deflate 115 | //The "zlib" format defined in RFC 1950 [31] in combination with 116 | //the "deflate" compression mechanism described in RFC 1951 [29]. 117 | deflateCompressEncoder = acceptEncoder{ 118 | name: "deflate", 119 | levelEncode: func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr }, 120 | customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, gzipCompressLevel); return wr }}, 121 | bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestCompression); return wr }}, 122 | } 123 | ) 124 | 125 | var ( 126 | encoderMap = map[string]acceptEncoder{ // all the other compress methods will ignore 127 | "gzip": gzipCompressEncoder, 128 | "deflate": deflateCompressEncoder, 129 | "*": gzipCompressEncoder, // * means any compress will accept,we prefer gzip 130 | "identity": noneCompressEncoder, // identity means none-compress 131 | } 132 | ) 133 | 134 | // WriteFile reads from file and writes to writer by the specific encoding(gzip/deflate) 135 | func WriteFile(encoding string, writer io.Writer, file *os.File, beforeWritingFunc ...func(enable bool, name string) error) (bool, string, error) { 136 | stat, err := file.Stat() 137 | if err != nil { 138 | return false, "", err 139 | } 140 | size := stat.Size() 141 | if encoding == "" || size < int64(gzipMinLength) { 142 | if len(beforeWritingFunc) > 0 { 143 | if err := beforeWritingFunc[0](false, ""); err != nil { 144 | return false, "", err 145 | } 146 | } 147 | _, err = io.Copy(writer, file) 148 | return false, "", err 149 | } 150 | return writeLevel(encoding, writer, file, flate.BestCompression, beforeWritingFunc...) 151 | } 152 | 153 | func WriteStream(encoding string, writer io.Writer, reader io.Reader, beforeWritingFunc ...func(enable bool, name string) error) (bool, string, error) { 154 | return writeLevel(encoding, writer, reader, flate.BestCompression, beforeWritingFunc...) 155 | } 156 | 157 | // WriteBody reads writes content to writer by the specific encoding(gzip/deflate) 158 | func WriteBody(encoding string, writer io.Writer, content []byte, beforeWritingFunc ...func(enable bool, name string) error) (bool, string, error) { 159 | if encoding == "" || len(content) < gzipMinLength { 160 | if len(beforeWritingFunc) > 0 { 161 | if err := beforeWritingFunc[0](false, ""); err != nil { 162 | return false, "", err 163 | } 164 | } 165 | _, err := writer.Write(content) 166 | return false, "", err 167 | } 168 | return writeLevel(encoding, writer, bytes.NewReader(content), gzipCompressLevel, beforeWritingFunc...) 169 | } 170 | 171 | // writeLevel reads from reader,writes to writer by specific encoding and compress level 172 | // the compress level is defined by deflate package 173 | func writeLevel(encoding string, writer io.Writer, reader io.Reader, level int, beforeWritingFunc ...func(enable bool, name string) error) (bool, string, error) { 174 | var outputWriter resetWriter 175 | var err error 176 | var ce = noneCompressEncoder 177 | var encodeEnable bool 178 | 179 | if cf, ok := encoderMap[encoding]; ok { 180 | ce = cf 181 | encodeEnable = true 182 | } 183 | encoding = ce.name 184 | outputWriter = ce.encode(writer, level) 185 | defer ce.put(outputWriter, level) 186 | 187 | if len(beforeWritingFunc) > 0 { 188 | if err := beforeWritingFunc[0](encodeEnable, encoding); err != nil { 189 | return false, "", err 190 | } 191 | } 192 | 193 | _, err = io.Copy(outputWriter, reader) 194 | if err != nil { 195 | return false, "", err 196 | } 197 | 198 | switch outputWriter.(type) { 199 | case io.WriteCloser: 200 | outputWriter.(io.WriteCloser).Close() 201 | } 202 | return encoding != "", encoding, nil 203 | } 204 | 205 | // ParseEncoding will extract the right encoding for response 206 | // the Accept-Encoding's sec is here: 207 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 208 | func ParseEncoding(r *http.Request) string { 209 | if r == nil { 210 | return "" 211 | } 212 | 213 | if (getMethodOnly && r.Method == "GET") || includedMethods[r.Method] { 214 | return parseEncoding(r) 215 | } 216 | return "" 217 | } 218 | 219 | type q struct { 220 | name string 221 | value float64 222 | } 223 | 224 | func parseEncoding(r *http.Request) string { 225 | acceptEncoding := r.Header.Get("Accept-Encoding") 226 | if acceptEncoding == "" { 227 | return "" 228 | } 229 | var lastQ q 230 | for _, v := range strings.Split(acceptEncoding, ",") { 231 | v = strings.TrimSpace(v) 232 | if v == "" { 233 | continue 234 | } 235 | vs := strings.Split(v, ";") 236 | var cf acceptEncoder 237 | var ok bool 238 | if cf, ok = encoderMap[vs[0]]; !ok { 239 | continue 240 | } 241 | if len(vs) == 1 { 242 | return cf.name 243 | } 244 | if len(vs) == 2 { 245 | f, _ := strconv.ParseFloat(strings.Replace(vs[1], "q=", "", -1), 64) 246 | if f == 0 { 247 | continue 248 | } 249 | if f > lastQ.value { 250 | lastQ = q{cf.name, f} 251 | } 252 | } 253 | } 254 | return lastQ.name 255 | } 256 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "path" 7 | "reflect" 8 | "strings" 9 | ) 10 | 11 | type App struct { 12 | Handlers *ControllerRegister 13 | Config *config 14 | StaticDirs map[string]string 15 | TemplateRegister *TemplateRegister 16 | Logger LoggerInterface 17 | Statinfo *statinfo 18 | //filter net.Listener 19 | FilterListener func(app *App, l net.Listener) net.Listener 20 | //filter http.Handler 21 | FilterHandler func(app *App, h http.Handler) http.Handler 22 | 23 | prepared bool 24 | } 25 | 26 | func NewApp() *App { 27 | app := &App{ 28 | Handlers: NewControllerRegister(nil), 29 | TemplateRegister: NewTemplateRegister(nil), 30 | Config: newConfig(), 31 | StaticDirs: make(map[string]string), 32 | Logger: Logger, 33 | } 34 | 35 | app.FilterListener = func(app *App, l net.Listener) net.Listener { return l } 36 | app.FilterHandler = func(app *App, h http.Handler) http.Handler { return h } 37 | app.Statinfo = newStatinfo(app) 38 | app.Handlers.app = app 39 | app.TemplateRegister.app = app 40 | return app 41 | } 42 | 43 | //method - http method, GET,POST,PUT,HEAD,DELETE,PATCH,OPTIONS,* 44 | //pattern - URL path or regexp pattern 45 | //name - method on the container 46 | //tags - function parameter tag info, see struct tag 47 | func (app *App) Register(method string, pattern string, c ControllerInterface, name string, tags ...string) iRouter { 48 | funcname, params := parseMethod(name) 49 | return app.Handlers.Add(method, pattern, c, funcname, params, tags) 50 | } 51 | 52 | func (app *App) RegisterHandler(pattern string, h http.Handler) iRouter { 53 | return app.Handlers.AddHandler(pattern, h) 54 | } 55 | 56 | func (app *App) RegisterFunc(methods, pattern string, f HandlerFunc) iRouter { 57 | return app.Handlers.AddMethod(methods, pattern, f) 58 | } 59 | 60 | func (app *App) RegisterRESTful(pattern string, c ControllerInterface) iRouter { 61 | //app.Register("*", pattern, c, "") 62 | if !isPattern(pattern) { 63 | pattern = path.Join(pattern, "(?P[^/]+)$") 64 | } 65 | return app.Register("*", pattern, c, "") 66 | } 67 | 68 | func (app *App) Get(pattern string, f HandlerFunc) iRouter { 69 | return app.Handlers.Get(pattern, f) 70 | } 71 | 72 | func (app *App) Post(pattern string, f HandlerFunc) iRouter { 73 | return app.Handlers.Post(pattern, f) 74 | } 75 | 76 | func (app *App) Put(pattern string, f HandlerFunc) iRouter { 77 | return app.Handlers.Put(pattern, f) 78 | } 79 | 80 | func (app *App) Delete(pattern string, f HandlerFunc) iRouter { 81 | return app.Handlers.Delete(pattern, f) 82 | } 83 | 84 | func (app *App) Head(pattern string, f HandlerFunc) iRouter { 85 | return app.Handlers.Head(pattern, f) 86 | } 87 | 88 | func (app *App) Patch(pattern string, f HandlerFunc) iRouter { 89 | return app.Handlers.Patch(pattern, f) 90 | } 91 | 92 | func (app *App) Options(pattern string, f HandlerFunc) iRouter { 93 | return app.Handlers.Options(pattern, f) 94 | } 95 | 96 | func (app *App) Any(pattern string, f HandlerFunc) iRouter { 97 | return app.Handlers.Any(pattern, f) 98 | } 99 | 100 | func (app *App) SetStaticPath(url string, path string) *App { 101 | if !strings.HasPrefix(url, "/") { 102 | url = "/" + url 103 | } 104 | if url != "/" { 105 | url = strings.TrimRight(url, "/") 106 | } 107 | app.StaticDirs[url] = path 108 | return app 109 | } 110 | 111 | func (app *App) SetViewsPath(path string) *App { 112 | app.Config.TemplatePath = path 113 | return app 114 | } 115 | 116 | func (app *App) buildTemplate(files ...string) { 117 | if app.Config.TemplatePath != "" { 118 | err := app.TemplateRegister.buildTemplate(app.Config.TemplatePath, files...) 119 | if err != nil { 120 | app.Logger.Error("%v", err) 121 | } 122 | } 123 | } 124 | 125 | func (app *App) Prepare() { 126 | if app.prepared { 127 | return 128 | } 129 | app.prepared = true 130 | app.buildTemplate() 131 | } 132 | 133 | func (app *App) Run(server ...Server) { 134 | var err error 135 | var s Server 136 | if len(server) > 0 { 137 | s = server[0] 138 | } else { 139 | s = &HttpServer{} 140 | } 141 | app.Prepare() 142 | if err = s.ListenAndServe(app); err != nil { 143 | app.Logger.Critical("%v.ListenAndServe: %v", reflect.TypeOf(s), err) 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type config struct { 8 | Listen listenConfig 9 | 10 | //生产或开发模式,值:PROD, DEV 11 | RunMode int8 12 | 13 | //模板文件位置 14 | TemplatePath string 15 | TemplateLeft string 16 | TemplateRight string 17 | 18 | //请求主体数据量大小限制, 默认:defaultMaxRequestBodySize 19 | MaxRequestBodySize int64 20 | 21 | //是否自动分析请求参数,默认:true 22 | AutoParseRequest bool 23 | 24 | //如果使用结构体来接收请求参数,可在此设置是否采用域模式传递参数, 默认:false 25 | //如果值为true, 需要这样传递请求参数:user.account, user为方法参数名(为结构类型),account为user结构字段名 26 | FormDomainModel bool 27 | 28 | //指示绑定请求参数时发生错误是否抛出异常, 默认:true 29 | //如果设置为false, 当绑定数据出错时,将采用相应类型的默认值填充数据,并返回error 30 | ThrowBindParamPanic bool 31 | 32 | //指定一个处理Panic异常的函数,如果不指定,将采用默认方式处理 33 | RecoverFunc func(*Context) 34 | //是否打印Panic详细信息, 开发模式肯定会打印, @see defaultRecoverFunc 35 | //如果是自定义RecoverFunc,PrintPanicDetail配置将无效 36 | //默认:true 37 | PrintPanicDetail bool 38 | 39 | //打开统计信息功能 40 | StatinfoEnable bool 41 | 42 | Render renderConfig 43 | } 44 | 45 | type listenConfig struct { 46 | //listen addr, format: "[ip]:", ":7086", "0.0.0.0:7086", "127.0.0.1:7086" 47 | Addr string 48 | ReadTimeout time.Duration 49 | WriteTimeout time.Duration 50 | //并发连接的最大数目, 默认:defaultConcurrency 51 | Concurrency int 52 | //连接Keep-Alive时间限制, 默认0, 无限制 53 | //MaxKeepaliveDuration time.Duration 54 | } 55 | 56 | type renderConfig struct { 57 | 58 | //是否自动从请求参数中解析响应数据格式 59 | //如果被设置,将从请求参数中自动获取的FormatParamName参数以及JsoncallbackParamName参数值 60 | //默认:false 61 | AutoParseFormat bool 62 | 63 | //默认:fmt 64 | FormatParamName string 65 | //默认: jsoncb 66 | JsoncallbackParamName string 67 | 68 | //默认是否使用Result结构对结果进行包装, @see result.go 69 | //如果设置此参数,将默认设置Render.Wrap() 70 | //当Render.Wrap()后,如果不设置响应数据格式,将默认为:json 71 | //默认:false 72 | Wrap bool 73 | 74 | //默认是否对响应数据进行gzip压缩 75 | //默认:false 76 | Gzip bool 77 | } 78 | 79 | func newConfig() *config { 80 | cfg := &config{} 81 | 82 | cfg.RunMode = PROD 83 | cfg.TemplatePath = "" 84 | 85 | cfg.MaxRequestBodySize = defaultMaxRequestBodySize 86 | cfg.AutoParseRequest = true 87 | cfg.FormDomainModel = false 88 | cfg.ThrowBindParamPanic = true 89 | 90 | cfg.RecoverFunc = defaultRecoverFunc 91 | cfg.PrintPanicDetail = true 92 | cfg.StatinfoEnable = false 93 | 94 | cfg.Listen.Addr = "0.0.0.0:7086" 95 | cfg.Listen.ReadTimeout = 0 96 | cfg.Listen.WriteTimeout = 0 97 | cfg.Listen.Concurrency = defaultConcurrency 98 | //cfg.Listen.MaxKeepaliveDuration = 0 99 | 100 | cfg.Render.AutoParseFormat = false 101 | cfg.Render.FormatParamName = "fmt" 102 | cfg.Render.JsoncallbackParamName = "jsoncb" 103 | cfg.Render.Wrap = false 104 | cfg.Render.Gzip = false 105 | return cfg 106 | } 107 | 108 | //生产或开发模式 109 | const ( 110 | PROD = iota 111 | DEV 112 | ) 113 | 114 | //数据渲染格式 115 | const ( 116 | FORMAT_JSON = "json" 117 | FORMAT_XML = "xml" 118 | FORMAT_TXT = "txt" 119 | FORMAT_HTML = "html" 120 | // other ... 121 | ) 122 | 123 | const defaultMaxRequestBodySize = 4 * 1024 * 1024 124 | 125 | const defaultConcurrency = 256 * 1024 126 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "reflect" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type Context struct { 17 | App *App 18 | ResponseWriter *response 19 | Request *http.Request 20 | Multipart bool 21 | Input *input 22 | //某些错误会放在这里, 必要时可对此进行检查 23 | //比如,配置:Config.ThrowBindParamPanic = false, 且绑定参数发生错误时,可在此检查错误原因 24 | Error error 25 | } 26 | 27 | func newContext() *Context { 28 | ctx := &Context{} 29 | ctx.ResponseWriter = newResponse(ctx) 30 | ctx.Input = newInput(ctx) 31 | return ctx 32 | } 33 | 34 | func NewContext(rw http.ResponseWriter, r *http.Request, app *App) *Context { 35 | ctx := newContext() 36 | ctx.Reset(rw, r, app) 37 | return ctx 38 | } 39 | 40 | func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request, app *App) *Context { 41 | if resp, ok := rw.(*response); ok { 42 | ctx.ResponseWriter = resp 43 | } else { 44 | ctx.ResponseWriter.ResponseWriter = rw 45 | } 46 | ctx.ResponseWriter.render.Reset() 47 | 48 | ctx.Request = r 49 | ctx.App = app 50 | ctx.Multipart = strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") 51 | ctx.Input.Values = nil 52 | return ctx 53 | } 54 | 55 | func (ctx *Context) Redirect(status int, url string) { 56 | http.Redirect(ctx.ResponseWriter, ctx.Request, url, status) 57 | } 58 | 59 | func (ctx *Context) Render(data ...interface{}) *render { 60 | return Render(ctx, data...) 61 | } 62 | 63 | func (ctx *Context) RenderTemplate(templateName string, data map[interface{}]interface{}) *render { 64 | return RenderTemplate(ctx, templateName, data) 65 | } 66 | 67 | func (ctx *Context) RenderFile(filename string) *render { 68 | return RenderFile(ctx, filename) 69 | } 70 | 71 | type input struct { 72 | url.Values 73 | ctx *Context 74 | } 75 | 76 | func newInput(ctx *Context) *input { 77 | inpt := &input{ctx: ctx} 78 | if ctx.Request != nil && ctx.Request.Form != nil { 79 | inpt.Values = ctx.Request.Form 80 | } 81 | return inpt 82 | } 83 | 84 | func (input *input) Parse() error { 85 | if input.Values != nil { 86 | return nil 87 | } 88 | 89 | if input.ctx.Request.Form != nil { 90 | input.Values = input.ctx.Request.Form 91 | return nil 92 | } 93 | 94 | form, err := parseForm(input.ctx.Request, input.ctx.Multipart) 95 | if err != nil { 96 | //Logger.Error("%v", err) 97 | return err 98 | } 99 | input.Values = form 100 | return nil 101 | } 102 | 103 | func (input *input) AddValues(values url.Values) { 104 | if input.Values == nil { 105 | input.Parse() 106 | } 107 | if input.Values != nil { 108 | for k, v := range values { 109 | input.Values[k] = append(input.Values[k], v...) 110 | } 111 | } 112 | } 113 | 114 | func (input *input) GetValue(key string) string { 115 | if input.Values == nil { 116 | err := input.Parse() 117 | if err != nil { 118 | return "" 119 | } 120 | } 121 | return input.Values.Get(key) 122 | } 123 | 124 | func (input *input) GetValues(key string) []string { 125 | if input.Values == nil { 126 | err := input.Parse() 127 | if err != nil { 128 | return []string{} 129 | } 130 | } 131 | return input.Values[key] 132 | } 133 | 134 | func (input *input) Exist(key string) (ok bool) { 135 | if input.Values == nil { 136 | err := input.Parse() 137 | if err != nil { 138 | return false 139 | } 140 | } 141 | _, ok = input.Values[key] 142 | return 143 | } 144 | 145 | func getTaginfo(name string, taginfoses []Taginfos) *tagInfo { 146 | for _, tis := range taginfoses { 147 | if ti, ok := tis[name]; ok && ti != nil { 148 | return ti 149 | } 150 | } 151 | return nil 152 | } 153 | 154 | func (input *input) checkDestStruct(dest interface{}, key string, isjson bool) reflect.Value { 155 | value := reflect.ValueOf(dest) 156 | if value.Kind() != reflect.Ptr { 157 | panic("non-pointer can not bind: " + key) 158 | } 159 | value = value.Elem() 160 | if !value.CanSet() { 161 | panic("non-settable variable can not bind: " + key) 162 | } 163 | kind := value.Type().Kind() 164 | if isjson { 165 | if kind != reflect.Struct && kind != reflect.Slice { 166 | panic("bind dest type is not struct") 167 | } 168 | } else { 169 | if kind != reflect.Struct { 170 | panic("bind dest type is not struct") 171 | } 172 | } 173 | return value 174 | } 175 | 176 | func (input *input) checkData(value *reflect.Value, pname string, taginfos []Taginfos) error { 177 | ctx := input.ctx 178 | ptype := value.Type() 179 | switch ptype.Kind() { 180 | case reflect.Slice: 181 | for i := 0; i < value.Len(); i++ { 182 | sub := value.Index(i) 183 | if err := input.checkData(&sub, pname, taginfos); err != nil { 184 | return err 185 | } 186 | } 187 | case reflect.Struct: 188 | for i := 0; i < ptype.NumField(); i++ { 189 | f := ptype.Field(i) 190 | v := value.Field(i) 191 | var name string 192 | if f.Anonymous { 193 | name = pname 194 | } else { 195 | paramTag := strings.TrimSpace(f.Tag.Get(inputTagname)) 196 | if paramTag == "-" { 197 | continue 198 | } 199 | paramTags := strings.SplitN(paramTag, ",", 2) 200 | if len(paramTag) > 0 { 201 | name = strings.TrimSpace(paramTags[0]) 202 | if name == "-" { 203 | continue 204 | } 205 | } 206 | if len(name) == 0 { 207 | name = strings.ToLower(f.Name[0:1]) + f.Name[1:] 208 | } 209 | if ctx.App.Config.FormDomainModel && pname != "" { 210 | name = pname + "." + name 211 | } 212 | } 213 | if err := input.checkData(&v, name, taginfos); err != nil { 214 | return err 215 | } 216 | } 217 | default: 218 | tagInfo := getTaginfo(pname, taginfos) 219 | if tagInfo != nil { 220 | err := tagInfo.Check(value.Interface()) 221 | if err != nil { 222 | return errors.New(fmt.Sprintf("%v=%v, %v", pname, value.Interface(), err)) 223 | } 224 | } 225 | } 226 | return nil 227 | } 228 | 229 | func (input *input) BindXml(dest interface{}, key string, taginfos ...Taginfos) error { 230 | value := input.checkDestStruct(dest, key, false) 231 | var err error 232 | var data []byte 233 | body := input.ctx.Request.Body 234 | if body != nil { 235 | data, err = ioutil.ReadAll(body) 236 | } 237 | if err == nil && len(data) > 0 { 238 | err = xml.Unmarshal(data, dest) 239 | if len(taginfos) == 0 { 240 | taginfos = append(taginfos, parseTags(map[string]reflect.Type{key: value.Type()}, []string{}, input.ctx.App.Config.FormDomainModel)) 241 | } 242 | if len(taginfos) > 0 { 243 | err = input.checkData(&value, key, taginfos) 244 | } 245 | } else { 246 | err = errors.New("bind xml error, body is empty") 247 | } 248 | if err != nil { 249 | input.ctx.Error = err 250 | if input.ctx.App.Config.ThrowBindParamPanic { 251 | msg := fmt.Sprintf("%v, %s=%v, cause:%v", ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL], key, string(data), err) 252 | panic(NewErrorResult(ERROR_CODE_PARAM_ILLEGAL, msg)) 253 | } 254 | } 255 | return err 256 | } 257 | 258 | func (input *input) BindJson(dest interface{}, key string, taginfos ...Taginfos) error { 259 | value := input.checkDestStruct(dest, key, true) 260 | var err error 261 | var data []byte 262 | body := input.ctx.Request.Body 263 | if body != nil { 264 | data, err = ioutil.ReadAll(body) 265 | } 266 | if err == nil && len(data) > 0 { 267 | err = json.Unmarshal(data, dest) 268 | if len(taginfos) == 0 { 269 | taginfos = append(taginfos, parseTags(map[string]reflect.Type{key: value.Type()}, []string{}, input.ctx.App.Config.FormDomainModel)) 270 | } 271 | if len(taginfos) > 0 { 272 | err = input.checkData(&value, key, taginfos) 273 | } 274 | } else { 275 | err = errors.New("bind json error, body is empty") 276 | } 277 | if err != nil { 278 | input.ctx.Error = err 279 | if input.ctx.App.Config.ThrowBindParamPanic { 280 | msg := fmt.Sprintf("%v, %s=%v, cause:%v", ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL], key, string(data), err) 281 | panic(NewErrorResult(ERROR_CODE_PARAM_ILLEGAL, msg)) 282 | } 283 | } 284 | return err 285 | } 286 | 287 | func (input *input) BindForm(dest interface{}, key string, taginfos ...Taginfos) error { 288 | value := input.checkDestStruct(dest, key, false) 289 | typ := value.Type() 290 | isStruct := typ.Kind() == reflect.Struct 291 | if len(taginfos) == 0 { 292 | taginfos = append(taginfos, parseTags(map[string]reflect.Type{key: typ}, []string{}, input.ctx.App.Config.FormDomainModel)) 293 | } 294 | rv, err := input.bind(key, typ, taginfos...) 295 | if err != nil { 296 | input.ctx.Error = err 297 | if input.ctx.App.Config.ThrowBindParamPanic { 298 | var msg string 299 | if isStruct { 300 | msg = fmt.Sprintf("%v, cause:%s.%v", ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL], key, err) 301 | } else { 302 | msg = fmt.Sprintf("%v, %s=%v, cause:%v", ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL], key, input.ctx.Input.Values[key], err) 303 | } 304 | panic(NewErrorResult(ERROR_CODE_PARAM_ILLEGAL, msg)) 305 | } 306 | return err 307 | } 308 | if !rv.IsValid() { 309 | err := errors.New("reflect value not is valid") 310 | input.ctx.Error = err 311 | if input.ctx.App.Config.ThrowBindParamPanic { 312 | panic(err) 313 | } 314 | return err 315 | } 316 | value.Set(*rv) 317 | return nil 318 | } 319 | 320 | func (input *input) Bind(dest interface{}, key string, taginfos ...Taginfos) error { 321 | value := reflect.ValueOf(dest) 322 | if value.Kind() != reflect.Ptr { 323 | panic("non-pointer can not bind: " + key) 324 | } 325 | value = value.Elem() 326 | if !value.CanSet() { 327 | panic("non-settable variable can not bind: " + key) 328 | } 329 | 330 | typ := value.Type() 331 | isStruct := typ.Kind() == reflect.Struct 332 | if len(taginfos) == 0 && isStruct { 333 | taginfos = append(taginfos, parseTags(map[string]reflect.Type{key: typ}, []string{}, input.ctx.App.Config.FormDomainModel)) 334 | } 335 | 336 | rv, err := input.bind(key, typ, taginfos...) 337 | if err != nil { 338 | input.ctx.Error = err 339 | if input.ctx.App.Config.ThrowBindParamPanic { 340 | var msg string 341 | if isStruct { 342 | msg = fmt.Sprintf("%v, cause:%s.%v", ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL], key, err) 343 | } else { 344 | msg = fmt.Sprintf("%v, %s=%v, cause:%v", ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL], key, input.ctx.Input.Values[key], err) 345 | } 346 | panic(NewErrorResult(ERROR_CODE_PARAM_ILLEGAL, msg)) 347 | } 348 | return err 349 | } 350 | if !rv.IsValid() { 351 | err := errors.New("reflect value not is valid") 352 | input.ctx.Error = err 353 | if input.ctx.App.Config.ThrowBindParamPanic { 354 | panic(err) 355 | } 356 | return err 357 | } 358 | value.Set(*rv) 359 | return nil 360 | } 361 | 362 | var timeType = reflect.TypeOf(time.Now()) 363 | 364 | func (input *input) bind(pname string, ptype reflect.Type, taginfos ...Taginfos) (*reflect.Value, error) { 365 | ctx := input.ctx 366 | vp := reflect.Indirect(reflect.New(ptype)) 367 | kind := ptype.Kind() 368 | switch kind { 369 | case reflect.Slice: 370 | kind = ptype.Elem().Kind() 371 | if reflect.Struct == kind { 372 | return nil, errors.New("the parameter slice type is not supported") 373 | } else { 374 | vals := input.Values[pname] 375 | for _, str := range vals { 376 | v := reflect.Indirect(reflect.New(ptype.Elem())) 377 | if err := input.parseAndCheck(getTaginfo(pname, taginfos), kind, str, true, &v); err != nil { 378 | return nil, errors.New(fmt.Sprintf("%v=%v, %v", pname, vals, err)) 379 | //return nil, err 380 | } 381 | vp = reflect.Append(vp, v) 382 | } 383 | } 384 | 385 | case reflect.Struct: 386 | tp := vp.Type() 387 | if timeType == tp { 388 | err := input.bindDefault(pname, ptype, &vp, taginfos...) 389 | if err != nil { 390 | return nil, err 391 | } 392 | break 393 | } 394 | for i := 0; i < tp.NumField(); i++ { 395 | f := tp.Field(i) 396 | var name string 397 | if f.Anonymous { 398 | name = pname 399 | } else { 400 | paramTag := strings.TrimSpace(f.Tag.Get(inputTagname)) 401 | if paramTag == "-" { 402 | continue 403 | } 404 | paramTags := strings.SplitN(paramTag, ",", 2) 405 | if len(paramTag) > 0 { 406 | name = strings.TrimSpace(paramTags[0]) 407 | if name == "-" { 408 | continue 409 | } 410 | } 411 | if len(name) == 0 { 412 | name = strings.ToLower(f.Name[0:1]) + f.Name[1:] 413 | } 414 | //if _, ok := form[name]; !ok { 415 | // name = f.Name 416 | //} 417 | if ctx.App.Config.FormDomainModel && pname != "" { 418 | name = pname + "." + name 419 | } 420 | } 421 | v, err := input.bind(name, f.Type, taginfos...) 422 | if err != nil { 423 | //return nil, errors.New(fmt.Sprintf("%v=%v, %v", name, ctx.Input.Values[name], err)) 424 | return nil, err 425 | } 426 | vp.Field(i).Set(*v) 427 | } 428 | 429 | case reflect.Map: 430 | vp.Set(reflect.ValueOf(ctx.Input.Values)) 431 | 432 | default: 433 | err := input.bindDefault(pname, ptype, &vp, taginfos...) 434 | if err != nil { 435 | return nil, err 436 | } 437 | } 438 | return &vp, nil 439 | } 440 | 441 | func (input *input) bindDefault(pname string, ptype reflect.Type, vp *reflect.Value, taginfos ...Taginfos) error { 442 | vals, ok := input.Values[pname] 443 | var val string 444 | if ok { 445 | val = vals[0] 446 | } 447 | if err := input.parseAndCheck(getTaginfo(pname, taginfos), ptype.Kind(), val, ok, vp); err != nil { 448 | return errors.New(fmt.Sprintf("%v=%v, %v", pname, val, err)) 449 | } 450 | return nil 451 | } 452 | 453 | //在vp中返回值 454 | func (input *input) parseAndCheck(tagInfo *tagInfo, kind reflect.Kind, val string, ok bool, vp *reflect.Value) error { 455 | if tagInfo != nil { 456 | return tagInfo.Check(val, vp) 457 | } else { 458 | _, err := parseValue(val, kind, vp) 459 | if err != nil { 460 | return err 461 | } 462 | } 463 | return nil 464 | } 465 | -------------------------------------------------------------------------------- /controller.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type ControllerInterface interface { 8 | Init(app *App, ctx *Context, controllerName, methodName string) 9 | Prepare() 10 | Get() 11 | Post() 12 | Delete() 13 | Put() 14 | Head() 15 | Patch() 16 | Options() 17 | Finish() 18 | } 19 | 20 | type Controller struct { 21 | Data map[interface{}]interface{} 22 | Ctx *Context 23 | ControllerName string 24 | ActionName string 25 | TplNames string 26 | TplExt string 27 | App *App 28 | } 29 | 30 | func (c *Controller) Init(app *App, ctx *Context, controllerName, actionName string) { 31 | c.Data = make(map[interface{}]interface{}) 32 | c.App = app 33 | c.ControllerName = controllerName 34 | c.ActionName = actionName 35 | c.Ctx = ctx 36 | c.TplExt = "tpl" 37 | } 38 | 39 | func (c *Controller) Prepare() { 40 | } 41 | 42 | func (c *Controller) Finish() { 43 | } 44 | 45 | func (c *Controller) Get() { 46 | http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 47 | } 48 | 49 | func (c *Controller) Post() { 50 | http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 51 | } 52 | 53 | func (c *Controller) Delete() { 54 | http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 55 | } 56 | 57 | func (c *Controller) Put() { 58 | http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 59 | } 60 | 61 | func (c *Controller) Head() { 62 | http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 63 | } 64 | 65 | func (c *Controller) Patch() { 66 | http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 67 | } 68 | 69 | func (c *Controller) Options() { 70 | http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 71 | } 72 | 73 | func (c *Controller) Redirect(url string, code int) { 74 | c.Ctx.Redirect(code, url) 75 | } 76 | 77 | func (c *Controller) Error(message string, code int) { 78 | c.Ctx.ResponseWriter.Error(message, code) 79 | } 80 | 81 | func (c *Controller) Render(data ...interface{}) *render { 82 | return Render(c.Ctx, data...) 83 | } 84 | 85 | func (c *Controller) RenderTemplate() *render { 86 | if c.TplNames == "" { 87 | c.TplNames = c.ControllerName + "/" + c.ActionName + "." + c.TplExt 88 | } 89 | return RenderTemplate(c.Ctx, c.TplNames, c.Data) 90 | } 91 | 92 | func (c *Controller) RenderFile(filename string) *render { 93 | return RenderFile(c.Ctx, filename) 94 | } 95 | 96 | //func (c *Controller) Render(contentType string, data []byte) (err error) { 97 | // return Render(c.Ctx.ResponseWriter, contentType, data) 98 | //} 99 | 100 | //func (c *Controller) RenderHtml(content string) (err error) { 101 | // return RenderHtml(c.Ctx.ResponseWriter, content) 102 | //} 103 | 104 | //func (c *Controller) RenderText(content string) (err error) { 105 | // return RenderText(c.Ctx.ResponseWriter, content) 106 | //} 107 | 108 | //func (c *Controller) RenderJson(data interface{}) (err error) { 109 | // return RenderJson(c.Ctx.ResponseWriter, data) 110 | //} 111 | 112 | //func (c *Controller) RenderJQueryCallback(jsoncallback string, data interface{}) error { 113 | // return RenderJQueryCallback(c.Ctx.ResponseWriter, jsoncallback, data) 114 | //} 115 | 116 | //func (c *Controller) RenderXml(data interface{}) error { 117 | // return RenderXml(c.Ctx.ResponseWriter, data) 118 | //} 119 | 120 | //func (c *Controller) RenderTemplate(contentType ...string) (err error) { 121 | // if c.TplNames == "" { 122 | // c.TplNames = c.ControllerName + "/" + c.MethodName + "." + c.TplExt 123 | // } 124 | // return RenderTemplate(c.Ctx.ResponseWriter, c.App, c.TplNames, c.Data, contentType...) 125 | //} 126 | 127 | //func (c *Controller) RenderData(format string, data []byte) error { 128 | // return RenderData(c.Ctx.ResponseWriter, format, data) 129 | //} 130 | 131 | //func (c *Controller) RenderError(err interface{}, code int, fmtAndJsoncallback ...string) error { 132 | // return RenderError(c.Ctx.ResponseWriter, err, code, fmtAndJsoncallback...) 133 | //} 134 | 135 | //func (c *Controller) RenderSucceed(data interface{}, fmtAndJsoncallback ...string) error { 136 | // return RenderSucceed(c.Ctx.ResponseWriter, data, fmtAndJsoncallback...) 137 | //} 138 | -------------------------------------------------------------------------------- /errcode.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | const ( 4 | ERROR_CODE_OK = 0 5 | ERROR_CODE_RUNTIME = 1000 //运行时异常 6 | ERROR_CODE_PARAM_IS_EMPTY = 1001 //参数为空 7 | ERROR_CODE_OBJECT_NOT_EXIST = 1002 //对象不存在 8 | ERROR_CODE_OBJECT_ALREADY_EXIST = 1003 //对象已经存在 9 | ERROR_CODE_PARAM_ILLEGAL = 1004 //非法参数 10 | ERROR_CODE_OPERATE_ILLEGAL = 1005 //非法操作 11 | ERROR_CODE_DATABASE_CONNECT_FAILED = 1100 //连接数据库失败 12 | ERROR_CODE_DATABASE_CONNECT_CLOSE_FAILED = 1101 //关闭数据库连接失败 13 | ERROR_CODE_DATABASE_QUERY_FAILED = 1102 //数据库操作失败 14 | ) 15 | 16 | var ERROR_INFO_MAP map[int]string 17 | 18 | func init() { 19 | ERROR_INFO_MAP = make(map[int]string) 20 | ERROR_INFO_MAP[ERROR_CODE_OK] = "ok" 21 | ERROR_INFO_MAP[ERROR_CODE_RUNTIME] = "runtime exception" 22 | ERROR_INFO_MAP[ERROR_CODE_PARAM_IS_EMPTY] = "parameter is empty" 23 | ERROR_INFO_MAP[ERROR_CODE_OBJECT_NOT_EXIST] = "object not exist" 24 | ERROR_INFO_MAP[ERROR_CODE_OBJECT_ALREADY_EXIST] = "object is exists" 25 | ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL] = "illegal parameter" 26 | ERROR_INFO_MAP[ERROR_CODE_OPERATE_ILLEGAL] = "illegal operation" 27 | ERROR_INFO_MAP[ERROR_CODE_DATABASE_CONNECT_FAILED] = "connect to database failed" 28 | ERROR_INFO_MAP[ERROR_CODE_DATABASE_CONNECT_CLOSE_FAILED] = "closing database connection failed" 29 | ERROR_INFO_MAP[ERROR_CODE_DATABASE_QUERY_FAILED] = "database query failed" 30 | 31 | } 32 | -------------------------------------------------------------------------------- /examples/input/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | 8 | "github.com/tryor/trygo" 9 | ) 10 | 11 | /** 12 | * 演示请求参数处理 13 | */ 14 | 15 | func main() { 16 | //input, query parameters 17 | trygo.Get("/input/get", func(ctx *trygo.Context) { 18 | q1 := ctx.Input.GetValue("q1") 19 | q2 := ctx.Input.GetValue("q2") 20 | q3 := ctx.Input.GetValues("q3") 21 | ctx.Render(fmt.Sprintf("(%s)hello world, q1=%s, q2=%s, q3=%v", ctx.Request.Method, q1, q2, q3)) 22 | }) 23 | 24 | //input, query and form parameters 25 | trygo.Post("/input/post", func(ctx *trygo.Context) { 26 | q1 := ctx.Input.GetValue("q1") 27 | q2 := ctx.Input.GetValue("q2") 28 | q3 := ctx.Input.GetValues("q3") 29 | p1 := ctx.Input.GetValue("p1") 30 | p2 := ctx.Input.GetValues("p2") 31 | ctx.Render(fmt.Sprintf("(%s)hello world, q1=%s, q2=%s, q3=%v, p1=%s, p2=%v", ctx.Request.Method, q1, q2, q3, p1, p2)) 32 | }) 33 | 34 | //input, path parameters 35 | trygo.Get("/input/path/(?P[^/]+)?$", func(ctx *trygo.Context) { 36 | id := ctx.Input.GetValue("id") 37 | ctx.Render(fmt.Sprintf("(%s)hello world, id=%s", ctx.Request.Method, id)) 38 | }) 39 | 40 | //input, path parameters 41 | trygo.Get("/input/path/(?P[^/]+)?/(?P[^/]+)?/(?P[^/]+)?$", func(ctx *trygo.Context) { 42 | year := ctx.Input.GetValue("year") 43 | month := ctx.Input.GetValue("month") 44 | day := ctx.Input.GetValue("day") 45 | ctx.Render(fmt.Sprintf("(%s)hello world, year=%s, month=%s, day=%s", ctx.Request.Method, year, month, day)) 46 | }) 47 | 48 | //input, bind parameters 49 | trygo.Get("/input/bind", func(ctx *trygo.Context) { 50 | var q1 string 51 | var q2 int 52 | var q3 []float32 53 | ctx.Input.Bind(&q1, "q1") 54 | ctx.Input.Bind(&q2, "q2") 55 | ctx.Input.Bind(&q3, "q3") 56 | ctx.Render(fmt.Sprintf("(%s)hello world, q1=%s, q2=%d, q3=%v", ctx.Request.Method, q1, q2, q3)) 57 | }) 58 | 59 | //input, bind parameters and check format 60 | trygo.Get("/input/bind/checkformat", func(ctx *trygo.Context) { 61 | var q1 string 62 | var q2 int 63 | var q3 []float32 64 | 65 | taginfos := make(trygo.Taginfos) 66 | taginfos.Parse(reflect.String, "q1,limit:5,require") 67 | taginfos.Parse(reflect.Int, "q2,scope:[1 2 3],default:1") 68 | taginfos.Parse(reflect.Float32, "q3,scope:[-100.0~200.0],default:0") 69 | 70 | ctx.Input.Bind(&q1, "q1", taginfos) 71 | ctx.Input.Bind(&q2, "q2", taginfos) 72 | ctx.Input.Bind(&q3, "q3", taginfos) 73 | ctx.Render(fmt.Sprintf("(%s)hello world, q1=%s, q2=%d, q3=%v", ctx.Request.Method, q1, q2, q3)) 74 | }) 75 | 76 | //input, bind struct parameters and check format, see struct tag "param" 77 | trygo.Post("/input/bind/struct", func(ctx *trygo.Context) { 78 | user := &User{} 79 | ctx.Input.Bind(user, "user") 80 | ctx.Render(user).Json() 81 | }) 82 | 83 | //input, auto bind parameters 84 | trygo.Register("post", "/input/bind/auto", &UserController{}, "Login(account, pwd string, devid int)") 85 | 86 | //input, auto bind parameters and check format 87 | trygo.Register("post", "/input/bind/auto/checkformat", &UserController{}, "Login(account, pwd string, devid int)", "account,limit:20,require", "pwd,limit:20,require", "devid,scope:[1 2 3 4],default:1") 88 | 89 | //input, auto bind struct parameters and check format 90 | trygo.Register("post", "/input/bind/auto/struct", &UserController{}, "Edit(user *User)") 91 | 92 | //设置静态文件根位置 93 | trygo.SetStaticPath("/", "static/webcontent/") 94 | 95 | fmt.Println("HTTP ListenAndServe AT ", trygo.DefaultApp.Config.Listen.Addr) 96 | trygo.Run() 97 | 98 | } 99 | 100 | type UserController struct { 101 | trygo.Controller 102 | } 103 | 104 | func (c *UserController) Login(account, pwd string, devid int) { 105 | c.Render(fmt.Sprintf("account:%s, pwd:%s, devid:%v", account, pwd, devid)) 106 | } 107 | 108 | func (c *UserController) Edit(user *User) { 109 | c.Render(user).Json() 110 | } 111 | 112 | func (c *UserController) GetUser() { 113 | var id int64 114 | c.Ctx.Input.Bind(&id, "id") 115 | user := &User{Id: id, Account: "demo001"} 116 | c.Render(user).Json() 117 | } 118 | 119 | type User struct { 120 | Id int64 `param:"-" json:"id,omitempty" xml:"id,attr,omitempty"` 121 | Account string `param:"account,limit:20,require" json:"account,omitempty" xml:"account,attr,omitempty"` 122 | Name string `param:"name,limit:20,require" json:"name,omitempty" xml:"name,attr,omitempty"` 123 | Pwd string `param:"pwd,limit:20,require" json:"-" xml:"-"` 124 | Sex int `param:"sex,scope:[1 2 3],default:1" json:"sex,omitempty" xml:"sex,attr,omitempty"` 125 | Age int `param:"age,scope:[0~200],default:0" json:"age,omitempty" xml:"age,attr,omitempty"` 126 | Email string `param:"email,limit:30,pattern:\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*" json:"email,omitempty" xml:"email,attr,omitempty"` 127 | Createtime time.Time `param:"-" json:"createtime,omitempty" xml:"createtime,attr,omitempty"` 128 | } 129 | -------------------------------------------------------------------------------- /examples/input/static/webcontent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Router Example 6 | 7 | 8 |

Router Example

9 | 10 | input/get   11 | http://127.0.0.1:7086/input/get?q1=v1&q2=v2&q3=f1&q3=f2&q3=f3
12 | 13 | /input/post   14 |
15 | 16 | 17 | 18 | 19 |
20 | 21 | /input/path/123   22 | http://127.0.0.1:7086/input/path/123
23 | 24 | /input/path/2016/06/08   25 | http://127.0.0.1:7086/input/path/2016/06/08
26 | 27 | 28 | 29 | input/bind   30 | http://127.0.0.1:7086/input/bind?q1=v1&q2=2&q3=1.1&q3=2.2&q3=3.3
31 | 32 | input/bind/checkformat   33 | http://127.0.0.1:7086/input/bind/checkformat?q1=v1&q2=2&q3=1.1&q3=2.2&q3=3.3
34 | 35 | /input/bind/struct   36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 47 | 48 | /input/bind/auto   49 |
50 | 51 | 52 | 53 | 54 |
55 | 56 | /input/bind/auto/checkformat   57 |
58 | 59 | 60 | 61 | 62 |
63 | 64 | /input/bind/auto/struct   65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /examples/listener/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID9TCCAt2gAwIBAgIJAIY9iHWbSiVCMA0GCSqGSIb3DQEBBQUAMIGQMQswCQYD 3 | VQQGEwJDTjELMAkGA1UECAwCSFoxCzAJBgNVBAcMAlNIMSUwIwYDVQQKDBxSb290 4 | IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQLDAR0eXN4MRMwEQYDVQQD 5 | DApNeSBUZXN0IENBMRwwGgYJKoZIhvcNAQkBFg10cnl3ZW5AcXEuY29tMB4XDTE2 6 | MTIwNTA4NTQ1OVoXDTE5MTIwNTA4NTQ1OVowgZAxCzAJBgNVBAYTAkNOMQswCQYD 7 | VQQIDAJIWjELMAkGA1UEBwwCU0gxJTAjBgNVBAoMHFJvb3QgQ2VydGlmaWNhdGlv 8 | biBBdXRob3JpdHkxDTALBgNVBAsMBHR5c3gxEzARBgNVBAMMCk15IFRlc3QgQ0Ex 9 | HDAaBgkqhkiG9w0BCQEWDXRyeXdlbkBxcS5jb20wggEiMA0GCSqGSIb3DQEBAQUA 10 | A4IBDwAwggEKAoIBAQDIFfkeoiiHK4cC1M8lcj4yOGMdVC6sHj9qxupxaoOf4McL 11 | efFyNv4WyDEyt3+/C6ELf69X9jrrAlI411+NOAqkpsg5RXd6Z26NizQGeRI/EUAV 12 | VZ7Hukl4UaPfwxqwaXB3oogPcUeGEZ8qZoXnuZufVOmnweykMYWUeX06ZMtYSz1v 13 | JSoMhS3e5K9mPRuizKFuUnHmeOD1fIWr7sEKKNYzCvnI8kvi3F6ahP4okPUk0mhD 14 | zkorkz4Wv2K8flEhPTY04dSJmLaNGEJ7iLaEc9aNb8Q/TNeUpK/mQQem1EJwz+3D 15 | Cj29uVRH+85ICaP9v1F3FvmbE41f2COnrtZ68ZSBAgMBAAGjUDBOMB0GA1UdDgQW 16 | BBS/ClfT8jxgdD0MmdhJ3S+jEbijkjAfBgNVHSMEGDAWgBS/ClfT8jxgdD0MmdhJ 17 | 3S+jEbijkjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBlfuSHJzCn 18 | Mt1xM1TsdlI2jYK1XgjG1+L3hEHHBv2j1wzeexWXos7syMdnDmc68ztxDL6OJw4q 19 | QMN/rgry9o7168Mc0q3LTyLeySa5uc1YrqfujgjEjjdwHDS2fOk0fmSkfql3TVSh 20 | 42OXFgOzEmoDsp/FsWPIdRfmPKdj1iylbB4utuJO7aeKQcghWOoujqUeGUrmL+1H 21 | eHqCgWApaIoMf53IGcgNFXGedHr8hs9K+nVo0rsMufFmOvdJJxyWe4lwJnrsgSMr 22 | /A+tMsaqgNqAJTiTFMkUVaUoRfmOV6zVMG0tTpWglkVJHBKhW9ZNWSNmehwhR1Rp 23 | 21YcUHnnKjnW 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /examples/listener/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAyBX5HqIohyuHAtTPJXI+MjhjHVQurB4/asbqcWqDn+DHC3nx 3 | cjb+FsgxMrd/vwuhC3+vV/Y66wJSONdfjTgKpKbIOUV3emdujYs0BnkSPxFAFVWe 4 | x7pJeFGj38MasGlwd6KID3FHhhGfKmaF57mbn1Tpp8HspDGFlHl9OmTLWEs9byUq 5 | DIUt3uSvZj0bosyhblJx5njg9XyFq+7BCijWMwr5yPJL4txemoT+KJD1JNJoQ85K 6 | K5M+Fr9ivH5RIT02NOHUiZi2jRhCe4i2hHPWjW/EP0zXlKSv5kEHptRCcM/twwo9 7 | vblUR/vOSAmj/b9Rdxb5mxONX9gjp67WevGUgQIDAQABAoIBAA9V180oQpDxnhxy 8 | cRn+opO4zKfvzs/0VYn6ivd8YXA8iyTvCLlnS5w1ZcfsVocu/f3ioG7OeX+Povum 9 | TjWFqRMrkUcKcvjXuppwo+EnIGXjDZVVBaFrPrxRDY1V59LDkhIpS+JbHU9CRH+8 10 | ceDr0eipWms4Ksn+3a0ejqGOHiO7JdLPIu/q39Bg8Oc7jwUHqIdx+uPJUFLoeBVs 11 | 0MPnb3aFn2uAGSVIZxu7J+kZvRTc9VwodBUdrVTeW5VJ7mpV+La4RgSuLwJlrsoN 12 | PzknCisOjIsMJTGeH2WOSkjaeGAnX+6LS99Uiv3NooPvXuQf+ir0JsoC0wNNwIEP 13 | Za809AECgYEA7eXE6I2nXBzRpN5uKQkrPYcgkvSQNRGPT/RDoGyy8TUNJxNrjMt/ 14 | b1HMBuOnZ/Pa38N92/MaYo+MYfx9Q5HNamdKyHew9Itd6GFptf6mnT1dY9J1Ud1A 15 | iRthJ1T6unAwBfWowEwMUoaMoPRlj2+fpupNouhikkVHEIazuiaaYiECgYEA10+i 16 | UfRnFVNWtYLXWBfwguKH70QOExWT3hzxMgI1jqierGBwPNrWxf7CDlfwungP1Ri1 17 | 8RI1j2iiv1QIaDbjyotv+EwzGcpyLNuvuu8vXgDIpl7XnKm3YKefT58GKljOyhfu 18 | ipy4dSzJa7sBQRLlS7ThTjq89OEj9530FyaxpmECgYEA23fZjGECQKwli9/X6OuY 19 | hI6gsnIEh7DHcY96xAkDnBrBI4d69MzXp67idoiW7AO/rCcBeWRwtvSPIeZ4+VkN 20 | FFhuWTpyeWgJHlSf3VIsC2uNOIXzza4710D3A/4VwOG2rAjRwXVm+Ms/+Uz+VLE5 21 | rBcDwIJ6TU60HL7oF5d9XYECgYEA1mGeN4foU92FCnnavQYpHck8nEng0bO8ZX3f 22 | 0nFMrlzKkMv9NTqYetAsnGeHc8Mz0HQoRH5jAgWndmXPcSSmLvgjZtFIWDew82VS 23 | Vgjt2uVg4/avLHf39K8x+u9WDjgavjKR+0YMi+8qJrAukYKk+HqDk4b8ub6qN1T4 24 | w0xtukECgYBBeSytjckqVX3NaSvF5Ck9rnrt3I2yO59XIcxBtdtLKj7lAEJB+l27 25 | 1ReJyYmaHVVa0oaafjIGZt+RgOBFxsSuWq/OPjCmA4HmdJTxsbrbSnCJXo1tngaJ 26 | vQLL8dSR9hKNFiFCIg75BNEGsaG550ke+MNhvI77uWMLMA5PWfL5Hw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /examples/listener/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "time" 8 | 9 | //"github.com/tryor/trygo" 10 | //"github.com/tryor/trygo-bridge/fasthttp" 11 | //"github.com/tryor/trygo-bridge/graceful" 12 | 13 | "tryor/trygo" 14 | ) 15 | 16 | /** 17 | * 演示所有监听模式或自定义监听模式 18 | */ 19 | 20 | func main() { 21 | 22 | go ListenAndServe(7086) //Default http 23 | //go ListenAndServe(9081, &trygo.HttpServer{Network: "tcp4"}) //Default http, tcp4 24 | //go ListenAndServe(6086, &trygo.FcgiHttpServer{}) //Fcgi 25 | //go ListenAndServe(7087, &trygo.FcgiHttpServer{Network: "unix"}) //Fcgi unix 26 | //go ListenAndServe(4433, &trygo.TLSHttpServer{CertFile: "cert.pem", KeyFile: "key.pem"}) //Https 27 | //go ListenAndServe(9090, &fasthttp.FasthttpServer{}) //FastHttp 28 | //go ListenAndServe(4439, &fasthttp.TLSFasthttpServer{CertFile: "cert.pem", KeyFile: "key.pem"}) //FastHttps 29 | //go ListenAndServe(9060, &graceful.GracefulServer{Timeout: 10 * time.Second}) //graceful 30 | 31 | select {} 32 | 33 | } 34 | 35 | func ListenAndServe(port int, server ...trygo.Server) { 36 | app := trygo.NewApp() 37 | app.Config.Listen.Addr = fmt.Sprintf("0.0.0.0:%v", port) 38 | // app.Config.Listen.ReadTimeout = time.Second * 2 39 | // app.Config.Listen.WriteTimeout = time.Second * 3 40 | //app.Config.Listen.Concurrency = 10 41 | //app.Config.MaxRequestBodySize = 1024 * 1024 * 8 42 | //app.Config.AutoParseRequest = false 43 | app.Config.StatinfoEnable = true 44 | 45 | app.Get("/statinfo", func(ctx *trygo.Context) { 46 | type Statinfo struct { 47 | ConcurrentConns int32 48 | PeakConcurrentConns int32 49 | CurrentRequests int64 50 | TotalRequests int64 51 | } 52 | var statinfo Statinfo 53 | statinfo.ConcurrentConns = app.Statinfo.ConcurrentConns() 54 | statinfo.PeakConcurrentConns = app.Statinfo.PeakConcurrentConns() 55 | statinfo.CurrentRequests = app.Statinfo.CurrentRequests() 56 | statinfo.TotalRequests = app.Statinfo.TotalRequests() 57 | ctx.Render(statinfo).Json() 58 | }) 59 | 60 | app.Post("/reqinfo", func(ctx *trygo.Context) { 61 | reqinfo := "" 62 | req := ctx.Request 63 | 64 | form := req.Form 65 | reqinfo += fmt.Sprintf("ctx.Request.Form() => %v\n", form) 66 | 67 | postForm := req.PostForm 68 | reqinfo += fmt.Sprintf("ctx.Request.PostForm() => %v\n", postForm) 69 | 70 | mform := req.MultipartForm 71 | reqinfo += fmt.Sprintf("ctx.Request.MultipartForm() => %v\n", mform) 72 | 73 | username, password, ok := req.BasicAuth() 74 | reqinfo += fmt.Sprintf("req.BasicAuth() => username:%v, password:%v, ok:%v\n", username, password, ok) 75 | reqinfo += fmt.Sprintf("req.Closed() => %v\n", req.Close) 76 | reqinfo += fmt.Sprintf("req.ContentLength() => %v\n", req.ContentLength) 77 | reqinfo += fmt.Sprintf("req.Header().Get() Content-Type => %v\n", req.Header.Get("Content-Type")) 78 | ck, err := req.Cookie("Cookie2") 79 | reqinfo += fmt.Sprintf("req.Cookie() => %v, err:%v\n", ck, err) 80 | reqinfo += fmt.Sprintf("req.Cookies() => %v\n", req.Cookies()) 81 | 82 | file, _, err := req.FormFile("file1") 83 | if file != nil { 84 | defer file.Close() 85 | } 86 | reqinfo += fmt.Sprintf("req.FormFile() => %v, err:%v\n", file, err) 87 | reqinfo += fmt.Sprintf("req.FormValue().p1 => %v\n", req.FormValue("p1")) 88 | reqinfo += fmt.Sprintf("req.PostFormValue().p2 => %v\n", req.PostFormValue("p2")) 89 | reqinfo += fmt.Sprintf("req.Host => %v\n", req.Host) 90 | reqinfo += fmt.Sprintf("req.Method => %v\n", req.Method) 91 | reqinfo += fmt.Sprintf("req.Proto => %v\n", req.Proto) 92 | reqinfo += fmt.Sprintf("req.ProtoMajor => %v\n", req.ProtoMajor) 93 | reqinfo += fmt.Sprintf("req.ProtoMinor => %v\n", req.ProtoMinor) 94 | reqinfo += fmt.Sprintf("req.ProtoAtLeast => %v\n", req.ProtoAtLeast(req.ProtoMajor, req.ProtoMinor)) 95 | reqinfo += fmt.Sprintf("req.Referer => %v\n", req.Referer()) 96 | reqinfo += fmt.Sprintf("req.RemoteAddr => %v\n", req.RemoteAddr) 97 | reqinfo += fmt.Sprintf("req.RequestURI => %v\n", req.RequestURI) 98 | reqinfo += fmt.Sprintf("req.TLS => %v\n", req.TLS) 99 | reqinfo += fmt.Sprintf("req.TransferEncoding => %v\n", req.TransferEncoding) 100 | reqinfo += fmt.Sprintf("req.URL => %v\n", req.URL) 101 | reqinfo += fmt.Sprintf("req.UserAgent => %v\n", req.UserAgent()) 102 | 103 | body := req.Body 104 | reqinfo += fmt.Sprintf("req.Body() => %v\n", body) 105 | if body != nil { 106 | bodybuf, err := ioutil.ReadAll(body) 107 | if err != nil { 108 | app.Logger.Error("%v", err) 109 | } else { 110 | reqinfo += fmt.Sprintf("req.Body().Data(%v) => %v\n", len(bodybuf), string(bodybuf)) 111 | } 112 | } 113 | ctx.Render(reqinfo). 114 | Cookie(&http.Cookie{Name: "Cookie1", Value: "1", Domain: "127.0.0.1", MaxAge: 100, Expires: time.Now().Add(100 * time.Second), HttpOnly: true}). 115 | Cookie(&http.Cookie{Name: "Cookie2", Value: "2"}) 116 | }) 117 | 118 | app.SetStaticPath("/", "static/webcontent/") 119 | 120 | fmt.Println("ListenAndServe AT ", port) 121 | app.Run(server...) 122 | } 123 | -------------------------------------------------------------------------------- /examples/listener/static/webcontent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Listener Example 6 | 7 | 8 |

Listener Example

9 | 10 | /reqinfo 11 |
12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 | /reqinfo 23 |
24 | 25 | 26 |
27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/log/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tryor/trygo" 7 | 8 | //"github.com/alecthomas/log4go" 9 | //"github.com/tryor/trygo-log-adaptor/seelog" 10 | ) 11 | 12 | /** 13 | * trygo没有功能强大的log服务模块,但可以实现trygo.Logger接口,适配其它log模块, 14 | * 比如:log4go, seelog等 15 | */ 16 | 17 | func main() { 18 | app := trygo.NewApp() 19 | //app.Logger = log4go.NewDefaultLogger(log4go.FINEST) //log4go 20 | //app.Logger, _ = seelog.LoggerFromConfigAsFile("seelog.xml") //seelog 21 | 22 | app.Get("/", func(ctx *trygo.Context) { 23 | ctx.App.Logger.Info("path:%s", ctx.Request.URL.Path) 24 | ctx.App.Logger.Info("form:%v", ctx.Request.Form) 25 | ctx.App.Logger.Warn("test warn") 26 | ctx.App.Logger.Error("test error") 27 | ctx.App.Logger.Critical("test critical") 28 | ctx.App.Logger.Info(123.456, "a", "b", "c") 29 | ctx.Render("ok") 30 | }) 31 | 32 | fmt.Println("HTTP ListenAndServe AT ", app.Config.Listen.Addr) 33 | app.Run() 34 | } 35 | -------------------------------------------------------------------------------- /examples/log/seelog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/quickstart/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tryor/trygo" 7 | ) 8 | 9 | func main() { 10 | 11 | trygo.Get("/", func(ctx *trygo.Context) { 12 | ctx.Render("hello world") 13 | }) 14 | 15 | fmt.Println("HTTP ListenAndServe AT ", trygo.DefaultApp.Config.Listen.Addr) 16 | trygo.Run() 17 | 18 | } 19 | -------------------------------------------------------------------------------- /examples/render/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "time" 8 | 9 | //"github.com/tryor/trygo" 10 | "tryor/trygo" 11 | ) 12 | 13 | /** 14 | * 演示所有渲染方式 15 | */ 16 | 17 | func main() { 18 | //render text 19 | trygo.Get("/render/text", func(ctx *trygo.Context) { 20 | ctx.Render("hello world").Text() 21 | }) 22 | 23 | //render html 24 | trygo.Get("/render/html", func(ctx *trygo.Context) { 25 | ctx.Render("hello world").Html() 26 | }) 27 | 28 | //render json 29 | trygo.Get("/render/json", func(ctx *trygo.Context) { 30 | ctx.Render([]byte("{\"id\":2,\"name\":\"John\"}")).Json() 31 | }) 32 | 33 | //render jsonp 34 | trygo.Get("/render/jsonp", func(ctx *trygo.Context) { 35 | ctx.Render([]byte("{\"id\":2,\"name\":\"John\"}")). 36 | Jsonp( 37 | ctx.Input.GetValue(ctx.App.Config.Render.JsoncallbackParamName), //由前端决定是否JsonCallback格式输出数据 38 | ) 39 | }) 40 | 41 | //render xml 42 | trygo.Get("/render/xml", func(ctx *trygo.Context) { 43 | ctx.Render([]byte("")).Xml().Nowrap() 44 | }) 45 | 46 | //render template 47 | trygo.Get("/render/template", func(ctx *trygo.Context) { 48 | id := ctx.Input.GetValue("id") 49 | name := ctx.Input.GetValue("name") 50 | 51 | tplNames := "admin/index.tpl" //相对trygo.DefaultApp.SetViewsPath()设置的位置 52 | data := make(map[interface{}]interface{}) 53 | data["id"] = id 54 | data["name"] = name 55 | ctx.RenderTemplate(tplNames, data) 56 | }) 57 | 58 | //render gzip 59 | trygo.Get("/render/gzip", func(ctx *trygo.Context) { 60 | data := strings.Repeat("gzip demo,", 100) 61 | ctx.Render(data).Text(). 62 | Gzip() //如果要默认支持Gzip,可修改配置 App.Config.Render.Gzip = true 63 | }) 64 | 65 | //render struct 66 | trygo.Get("/render/struct", func(ctx *trygo.Context) { 67 | user := &User{Id: 123, Account: "demo001", Name: "demo", Sex: 1, Age: 18, Email: "demo@qq.com", Createtime: time.Now()} 68 | ctx.Render(user) 69 | }) 70 | 71 | //render slice 72 | trygo.Get("/render/slice", func(ctx *trygo.Context) { 73 | 74 | users := make([]User, 0) 75 | for i := 1; i < 10; i++ { 76 | user := User{Id: int64(i), Account: "demo" + strconv.Itoa(i), Name: "demo", Sex: 1, Age: 18, Email: "demo@qq.com", Createtime: time.Now()} 77 | users = append(users, user) 78 | } 79 | ctx.Render(users) 80 | }) 81 | 82 | //render page 83 | trygo.Get("/render/page", func(ctx *trygo.Context) { 84 | users := make([]User, 0) 85 | for i := 1; i < 10; i++ { 86 | user := User{Id: int64(i), Account: "demo" + strconv.Itoa(i), Name: "demo", Sex: 1, Age: 18, Email: "demo@qq.com", Createtime: time.Now()} 87 | users = append(users, user) 88 | } 89 | 90 | page := &page{Pno: 1, Psize: 10, Total: 100, Data: users} 91 | 92 | ctx.Render(page) 93 | }) 94 | 95 | //render wrap success 96 | trygo.Get("/render/wrap/success", func(ctx *trygo.Context) { 97 | ctx.Render("ok"). 98 | Wrap() 99 | }) 100 | 101 | //render wrap error 102 | trygo.Get("/render/wrap/error", func(ctx *trygo.Context) { 103 | // panic(*trygo.NewErrorResult(trygo.ERROR_CODE_PARAM_ILLEGAL, trygo.ERROR_INFO_MAP[trygo.ERROR_CODE_PARAM_ILLEGAL])) 104 | ctx.Render("error info"). 105 | Wrap(trygo.ERROR_CODE_PARAM_ILLEGAL).Status(404) 106 | }) 107 | 108 | //render file 109 | trygo.Get("/render/file", func(ctx *trygo.Context) { 110 | ctx.RenderFile("D:\\Go\\api\\go1.txt").Gzip() 111 | }) 112 | 113 | //render stream 114 | trygo.Get("/render/stream", func(ctx *trygo.Context) { 115 | ctx.Render(strings.NewReader(strings.Repeat("stream... ", 1024))).Wrap().Text() 116 | }) 117 | 118 | //set auto wrap 119 | trygo.Get("/render/wrap/set/(?P[^/]+)$", func(ctx *trygo.Context) { 120 | 121 | ctx.Input.Bind(&trygo.DefaultApp.Config.Render.Wrap, "auto") 122 | 123 | ctx.Render("").Html() 124 | }) 125 | 126 | //set auto parse result wrap format 127 | trygo.Get("/render/wrap/format/autoparse/(?P[^/]+)$", func(ctx *trygo.Context) { 128 | 129 | ctx.Input.Bind(&trygo.DefaultApp.Config.Render.AutoParseFormat, "auto") 130 | 131 | ctx.Render("").Html() 132 | }) 133 | 134 | //设置静态文件根位置 135 | trygo.SetStaticPath("/", "static/webcontent/") 136 | 137 | //设置模板文件根位置, 相对或绝对路径 138 | trygo.SetViewsPath("static/templates/") 139 | 140 | trygo.DefaultApp.Config.Render.AutoParseFormat = true 141 | //trygo.DefaultApp.Config.Render.Gzip = true 142 | //trygo.DefaultApp.Config.Render.Wrap = true 143 | 144 | fmt.Println("HTTP ListenAndServe AT ", trygo.DefaultApp.Config.Listen.Addr) 145 | trygo.Run() 146 | } 147 | 148 | type User struct { 149 | Id int64 `json:"id,omitempty" xml:"id,attr,omitempty"` 150 | Account string `json:"account,omitempty" xml:"account,attr,omitempty"` 151 | Name string `json:"name,omitempty" xml:"name,attr,omitempty"` 152 | Pwd string `json:"-" xml:"-"` 153 | Sex int `json:"sex,omitempty" xml:"sex,attr,omitempty"` 154 | Age int `json:"age,omitempty" xml:"age,attr,omitempty"` 155 | Email string `json:"email,omitempty" xml:"email,attr,omitempty"` 156 | Createtime time.Time `json:"createtime,omitempty" xml:"createtime,attr,omitempty"` 157 | } 158 | 159 | func (u *User) String() string { 160 | return fmt.Sprintf("{%d,%s,%s,%d,%d,%s,%v}", u.Id, u.Account, u.Name, u.Sex, u.Age, u.Email, u.Createtime) 161 | } 162 | 163 | type page struct { 164 | Pno int `json:"pno" xml:"pno,attr"` 165 | Psize int `json:"psize" xml:"psize,attr"` 166 | Total int `json:"total" xml:"total,attr"` 167 | Data interface{} `json:"data" xml:"data"` 168 | } 169 | -------------------------------------------------------------------------------- /examples/render/static/templates/admin/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | render template 6 | 7 | 8 | id={{.id}}, name={{.name}} 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/render/static/webcontent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Render Example 6 | 7 | 8 |

Render Example

9 | 10 | http://127.0.0.1:7086/render/text
11 | http://127.0.0.1:7086/render/html
12 | http://127.0.0.1:7086/render/json
13 | http://127.0.0.1:7086/render/jsonp?jsoncb=callbackName
14 | http://127.0.0.1:7086/render/xml
15 | http://127.0.0.1:7086/render/template
16 | http://127.0.0.1:7086/render/gzip
17 |
18 | http://127.0.0.1:7086/render/struct
19 | http://127.0.0.1:7086/render/struct?fmt=json
20 | http://127.0.0.1:7086/render/struct?fmt=xml
21 |
22 | http://127.0.0.1:7086/render/slice?fmt=json
23 | http://127.0.0.1:7086/render/slice?fmt=xml
24 |
25 | http://127.0.0.1:7086/render/page?fmt=json
26 | http://127.0.0.1:7086/render/page?fmt=xml
27 |
28 | http://127.0.0.1:7086/render/wrap/success?fmt=json
29 | http://127.0.0.1:7086/render/wrap/success?fmt=xml
30 | http://127.0.0.1:7086/render/wrap/error?fmt=json
31 | http://127.0.0.1:7086/render/wrap/error?fmt=xml
32 |
33 | http://127.0.0.1:7086/render/file
34 | http://127.0.0.1:7086/render/stream
35 | 36 | 37 |
38 |
39 | Set auto parse result wrap format
40 | Clear auto parse result wrap format
41 |
42 | Set auto wrap
43 | Clear auto wrap
44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/router/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/tryor/trygo" 10 | ) 11 | 12 | /** 13 | * 演示所有路由方式 14 | */ 15 | 16 | func main() { 17 | //router, get 18 | trygo.Get("/router/get", func(ctx *trygo.Context) { 19 | p1 := ctx.Input.Get("p1") 20 | p2 := ctx.Input.Get("p2") 21 | ctx.Render("(" + ctx.Request.Method + ")hello world, p1=" + p1 + ", p2=" + p2) 22 | }) 23 | 24 | //router, post 25 | trygo.Post("/router/post", func(ctx *trygo.Context) { 26 | p1 := ctx.Input.GetValue("p1") 27 | p2 := ctx.Input.GetValue("p2") 28 | ctx.Render("(" + ctx.Request.Method + ")hello world, p1=" + p1 + ", p2=" + p2) 29 | }) 30 | 31 | //router, func 32 | trygo.RegisterFunc("post|get|put", "/router/func", func(ctx *trygo.Context) { 33 | p1 := ctx.Input.GetValue("p1") 34 | p2 := ctx.Input.GetValue("p2") 35 | ctx.Render("(" + ctx.Request.Method + ")hello world, p1=" + p1 + ", p2=" + p2) 36 | }) 37 | 38 | //router, http.Handler 39 | trygo.RegisterHandler("/router/handler", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 40 | ctx := trygo.NewContext(rw, r, trygo.DefaultApp) 41 | p1 := ctx.Input.GetValue("p1") 42 | p2 := ctx.Input.GetValue("p2") 43 | ctx.Render("(" + ctx.Request.Method + ")hello world, p1=" + p1 + ", p2=" + p2) 44 | })) 45 | 46 | //router, RESTful 47 | trygo.RegisterRESTful("/router/restful", &RESTfulController{}) 48 | 49 | //router, trygo normal 50 | trygo.Register("post", "/router/user/create", &UserController{}, "Create") 51 | trygo.Register("get", "/router/user/get", &UserController{}, "GetUser") 52 | 53 | //router, regexp pattern 54 | trygo.Register("*", "/router/user/query/(?P[^/]+)?/(?P[^/]+)?/(?P[^/]+)?$", &UserController{}, "Query") 55 | 56 | //router, bind parameters 57 | trygo.Register("post", "/router/user/login", &UserController{}, "Login(account, pwd string, devid int)") 58 | 59 | //router, bind parameters and set qualifier tag 60 | trygo.Register("post", "/router/user/login2", &UserController{}, "Login(account, pwd string, devid int)", "account,limit:20,require", "pwd,limit:20,require", "devid,scope:[1 2 3 4],default:1") 61 | 62 | //router, bind struct parameters and set qualifier tag in struct 63 | trygo.Register("post", "/router/user/edit", &UserController{}, "Edit(user User)") 64 | 65 | //设置静态文件根位置 66 | trygo.SetStaticPath("/", "static/webcontent/") 67 | 68 | fmt.Println("HTTP ListenAndServe AT ", trygo.DefaultApp.Config.Listen.Addr) 69 | trygo.Run() 70 | 71 | } 72 | 73 | //type Handler struct{} 74 | 75 | //func (h *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 76 | // ctx := trygo.NewContext(rw, r, trygo.DefaultApp) 77 | // p1 := ctx.Input.GetValue("p1") 78 | // p2 := ctx.Input.GetValue("p2") 79 | // ctx.Render("(" + ctx.Request.Method + ")hello world, p1=" + p1 + ", p2=" + p2) 80 | //} 81 | 82 | type RESTfulController struct { 83 | trygo.Controller 84 | } 85 | 86 | func (c *RESTfulController) Get() { 87 | id := c.Ctx.Input.GetValue("id") 88 | c.Render("(" + c.Ctx.Request.Method + ")hello world, id=" + id) 89 | } 90 | 91 | type UserController struct { 92 | trygo.Controller 93 | } 94 | 95 | func (c *UserController) Login(account, pwd string, devid int) { 96 | c.Render(fmt.Sprintf("account:%v, pwd:%v, devid:%v", account, pwd, devid)) 97 | } 98 | 99 | func (c *UserController) Create() { 100 | user := &User{} 101 | c.Ctx.Input.Bind(user, "user") 102 | c.Render(user).Json() 103 | } 104 | 105 | func (c *UserController) Edit(user User) { 106 | c.Render(user).Json() 107 | } 108 | 109 | func (c *UserController) GetUser() { 110 | var id int64 111 | c.Ctx.Input.Bind(&id, "id") 112 | user := &User{Id: id, Account: "demo001"} 113 | c.Render(user).Json() 114 | } 115 | 116 | func (c *UserController) Query() { 117 | var pno, psize int 118 | var orderby string 119 | c.Ctx.Input.Bind(&pno, "pno") 120 | c.Ctx.Input.Bind(&psize, "psize") 121 | c.Ctx.Input.Bind(&orderby, "orderby") 122 | 123 | users := make([]User, 0) 124 | for i := 1; i < psize; i++ { 125 | user := User{Id: int64(i), Account: "demo" + strconv.Itoa(i), Name: "demo", Sex: 1, Age: 18, Email: "demo@qq.com"} 126 | users = append(users, user) 127 | } 128 | 129 | page := &page{Pno: pno, Psize: psize, Total: 100, Data: users} 130 | 131 | c.Render(page).Json() 132 | } 133 | 134 | type User struct { 135 | Id int64 `param:"-" json:"id,omitempty" xml:"id,attr,omitempty"` 136 | Account string `param:"account,limit:20,require" json:"account,omitempty" xml:"account,attr,omitempty"` 137 | Name string `param:"name,limit:20,require" json:"name,omitempty" xml:"name,attr,omitempty"` 138 | Pwd string `param:"pwd,limit:20,require" json:"-" xml:"-"` 139 | Sex int `param:"sex,scope:[1 2 3],default:1" json:"sex,omitempty" xml:"sex,attr,omitempty"` 140 | Age int `param:"age,scope:[0~200],default:0" json:"age,omitempty" xml:"age,attr,omitempty"` 141 | Email string `param:"email,limit:30,pattern:\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*" json:"email,omitempty" xml:"email,attr,omitempty"` 142 | Createtime time.Time `param:"-" json:"createtime,omitempty" xml:"createtime,attr,omitempty"` 143 | } 144 | 145 | type page struct { 146 | Pno int `json:"pno" xml:"pno,attr"` 147 | Psize int `json:"psize" xml:"psize,attr"` 148 | Total int `json:"total" xml:"total,attr"` 149 | Data interface{} `json:"data" xml:"data"` 150 | } 151 | -------------------------------------------------------------------------------- /examples/router/static/webcontent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Router Example 6 | 7 | 8 |

Router Example

9 | 10 | /router/get   11 | http://127.0.0.1:7086/router/get?p1=123&p2=abc
12 | 13 | /router/post 14 |
15 | 16 | 17 | 18 |
19 | 20 | /router/func   21 | http://127.0.0.1:7086/router/func?p1=123&p2=abc
22 | 23 | /router/handler   24 | http://127.0.0.1:7086/router/handler?p1=123&p2=abc
25 | 26 | /router/restful   27 | http://127.0.0.1:7086/router/restful/123
28 | 29 | /router/user/create   30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | /router/user/get   41 | http://127.0.0.1:7086/router/user/get?id=235
42 | 43 | /router/user/query/   44 | http://127.0.0.1:7086/router/user/query/2/10/id desc
45 | 46 | /router/user/login   47 |
48 | 49 | 50 | 51 | 52 |
53 | /router/user/login2   54 |
55 | 56 | 57 | 58 | 59 |
60 | /router/user/edit   61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/upload/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "mime/multipart" 7 | "os" 8 | 9 | "github.com/tryor/trygo" 10 | ) 11 | 12 | func main() { 13 | trygo.DefaultApp.Config.MaxRequestBodySize = 1024 * 1024 * 20 14 | 15 | trygo.Register("post", "/upload", &UploadController{}, "Upload") 16 | 17 | trygo.SetStaticPath("/", "static/webcontent/") 18 | 19 | fmt.Println("HTTP ListenAndServe AT ", trygo.DefaultApp.Config.Listen.Addr) 20 | trygo.Run() 21 | 22 | } 23 | 24 | type UploadController struct { 25 | trygo.Controller 26 | } 27 | 28 | func (c *UploadController) Upload() { 29 | mform := c.Ctx.Request.MultipartForm 30 | for _, files := range mform.File { 31 | for _, file := range files { 32 | c.saveFile(file) 33 | } 34 | } 35 | c.Ctx.Redirect(302, "/files") 36 | } 37 | 38 | func (c *UploadController) saveFile(fh *multipart.FileHeader) { 39 | f, err := fh.Open() 40 | if err != nil { 41 | c.App.Logger.Error("%v", err) 42 | return 43 | } 44 | defer f.Close() 45 | 46 | lf, err := os.Create(trygo.AppPath + "\\static\\webcontent\\files\\" + fh.Filename) 47 | if err != nil { 48 | c.App.Logger.Error("%v", err) 49 | return 50 | } 51 | defer lf.Close() 52 | _, err = io.Copy(lf, f) 53 | if err != nil { 54 | c.App.Logger.Error("%v", err) 55 | return 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/upload/static/webcontent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Upload Example 6 | 7 | 8 |

Upload Example

9 | 10 | /upload 11 |
12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | ) 7 | 8 | func DefaultFilterHandler(app *App, h http.Handler) http.Handler { 9 | h = BodyLimitHandler(app, h) 10 | if app.Config.StatinfoEnable { 11 | h = RequestStatHandler(app, h) 12 | } 13 | return h 14 | } 15 | 16 | func RequestStatHandler(app *App, handler http.Handler) http.Handler { 17 | return &requestStatHandler{app, handler} 18 | } 19 | 20 | type requestStatHandler struct { 21 | app *App 22 | handler http.Handler 23 | } 24 | 25 | func (h *requestStatHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 26 | h.app.Statinfo.incCurrentRequests() 27 | defer h.app.Statinfo.decCurrentRequests() 28 | h.handler.ServeHTTP(rw, r) 29 | } 30 | 31 | func BodyLimitHandler(app *App, handler http.Handler) http.Handler { 32 | return &bodyLimitHandler{app, handler} 33 | } 34 | 35 | type bodyLimitHandler struct { 36 | app *App 37 | handler http.Handler 38 | } 39 | 40 | var ErrBodyTooLarge = errors.New("http: request body too large") 41 | 42 | func (h *bodyLimitHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 43 | if r.ContentLength > h.app.Config.MaxRequestBodySize { 44 | h.app.Logger.Info("%s", buildLoginfo(r, ErrBodyTooLarge)) 45 | Error(rw, ErrBodyTooLarge.Error(), http.StatusRequestEntityTooLarge) 46 | return 47 | } 48 | if r.Body != nil { 49 | r.Body = http.MaxBytesReader(rw, r.Body, h.app.Config.MaxRequestBodySize) 50 | } 51 | h.handler.ServeHTTP(rw, r) 52 | } 53 | -------------------------------------------------------------------------------- /listen.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func DefaultFilterListener(app *App, l net.Listener) net.Listener { 11 | l = TcpKeepAliveListener(l, time.Minute*3) 12 | l = LimitListener(l, app) 13 | //if app.Config.Listen.MaxKeepaliveDuration > 0 { 14 | // l = LimitKeepaliveDurationListener(l, app.Config.Listen.MaxKeepaliveDuration) 15 | //} 16 | return l 17 | } 18 | 19 | func LimitKeepaliveDurationListener(l net.Listener, maxKeepaliveDuration time.Duration) net.Listener { 20 | return &limitKeepaliveDurationListener{l, maxKeepaliveDuration} 21 | } 22 | 23 | var ErrKeepaliveTimeout = errors.New("exceeded MaxKeepaliveDuration") 24 | 25 | type limitKeepaliveDurationListener struct { 26 | net.Listener 27 | maxKeepaliveDuration time.Duration 28 | } 29 | 30 | func (l limitKeepaliveDurationListener) Accept() (net.Conn, error) { 31 | c, err := l.Listener.Accept() 32 | if err != nil { 33 | return nil, err 34 | } 35 | return &limitConnKeepaliveDuration{Conn: c, expire: time.Now().Add(l.maxKeepaliveDuration)}, nil 36 | } 37 | 38 | type limitConnKeepaliveDuration struct { 39 | net.Conn 40 | expire time.Time 41 | } 42 | 43 | func (c *limitConnKeepaliveDuration) Read(b []byte) (n int, err error) { 44 | if time.Now().After(c.expire) { 45 | c.Close() 46 | return 0, ErrKeepaliveTimeout 47 | } 48 | return c.Conn.Read(b) 49 | } 50 | 51 | func TcpKeepAliveListener(l net.Listener, keepalivePeriod time.Duration) net.Listener { 52 | if tc, ok := l.(*net.TCPListener); ok { 53 | return &tcpKeepAliveListener{tc, keepalivePeriod} 54 | } 55 | Logger.Warn("Listen: Listener not is *net.TCPListener, %v", l.Addr()) 56 | return l 57 | } 58 | 59 | type tcpKeepAliveListener struct { 60 | *net.TCPListener 61 | keepalivePeriod time.Duration 62 | } 63 | 64 | func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 65 | tc, err := ln.TCPListener.AcceptTCP() 66 | if err != nil { 67 | return 68 | } 69 | 70 | tc.SetKeepAlive(true) 71 | if ln.keepalivePeriod > 0 { 72 | tc.SetKeepAlivePeriod(ln.keepalivePeriod) 73 | } 74 | return tc, nil 75 | } 76 | 77 | func LimitListener(l net.Listener, app *App) net.Listener { 78 | if app.Config.StatinfoEnable { 79 | return &limitAndStatinfoListener{limitListener: limitListener{l, make(chan struct{}, app.Config.Listen.Concurrency)}, statinfo: app.Statinfo} 80 | } else { 81 | return &limitListener{l, make(chan struct{}, app.Config.Listen.Concurrency)} 82 | } 83 | 84 | } 85 | 86 | type limitListener struct { 87 | net.Listener 88 | sem chan struct{} 89 | } 90 | 91 | func (l *limitListener) acquire() { 92 | l.sem <- struct{}{} 93 | } 94 | func (l *limitListener) release() { 95 | <-l.sem 96 | } 97 | 98 | func (l *limitListener) Accept() (net.Conn, error) { 99 | l.acquire() 100 | c, err := l.Listener.Accept() 101 | if err != nil { 102 | l.release() 103 | return nil, err 104 | } 105 | return &limitListenerConn{Conn: c, release: l.release}, nil 106 | } 107 | 108 | type limitListenerConn struct { 109 | net.Conn 110 | releaseOnce sync.Once 111 | release func() 112 | } 113 | 114 | func (l *limitListenerConn) Close() error { 115 | err := l.Conn.Close() 116 | l.releaseOnce.Do(l.release) 117 | return err 118 | } 119 | 120 | type limitAndStatinfoListener struct { 121 | limitListener 122 | statinfo *statinfo 123 | } 124 | 125 | func (l *limitAndStatinfoListener) Accept() (net.Conn, error) { 126 | l.acquire() 127 | c, err := l.Listener.Accept() 128 | if err != nil { 129 | l.release() 130 | return nil, err 131 | } 132 | return &limitListenerConn{Conn: c, release: l.release}, nil 133 | } 134 | 135 | func (l *limitAndStatinfoListener) acquire() { 136 | l.sem <- struct{}{} 137 | l.statinfo.incConcurrentConns() 138 | } 139 | func (l *limitAndStatinfoListener) release() { 140 | l.statinfo.decConcurrentConns() 141 | <-l.sem 142 | } 143 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "runtime" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type LoggerInterface interface { 12 | Debug(arg0 interface{}, args ...interface{}) 13 | Info(arg0 interface{}, args ...interface{}) 14 | Warn(arg0 interface{}, args ...interface{}) error 15 | Error(arg0 interface{}, args ...interface{}) error 16 | Critical(arg0 interface{}, args ...interface{}) error 17 | } 18 | 19 | var Logger LoggerInterface 20 | 21 | func init() { 22 | Logger = &defaultLogger{} 23 | } 24 | 25 | type defaultLogger struct{} 26 | 27 | func (l *defaultLogger) Write(p []byte) (n int, err error) { 28 | if len(p) == 0 { 29 | return 0, nil 30 | } 31 | if p[len(p)-1] == '\n' { 32 | p = p[0 : len(p)-1] 33 | } 34 | fmt.Printf("%s [LOG] %s %s\n", formatNow(), determine(5), string(p)) 35 | return len(p), nil 36 | } 37 | 38 | func (l *defaultLogger) Printf(format string, args ...interface{}) { 39 | fmt.Printf("%s [LOG] %s %s\n", formatNow(), determine(3), fmt.Sprintf(format, args...)) 40 | } 41 | 42 | func (l *defaultLogger) Debug(arg0 interface{}, args ...interface{}) { 43 | switch f := arg0.(type) { 44 | case string: 45 | l.log("DBG", f, args...) 46 | default: 47 | l.log("DBG", fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 48 | } 49 | } 50 | 51 | func (l *defaultLogger) Info(arg0 interface{}, args ...interface{}) { 52 | switch f := arg0.(type) { 53 | case string: 54 | l.log("INF", f, args...) 55 | default: 56 | l.log("INF", fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 57 | } 58 | } 59 | 60 | func (l *defaultLogger) Warn(arg0 interface{}, args ...interface{}) error { 61 | switch f := arg0.(type) { 62 | case string: 63 | return errors.New(l.log("WRN", f, args...)) 64 | default: 65 | return errors.New(l.log("WRN", fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)) 66 | } 67 | } 68 | 69 | func (l *defaultLogger) Error(arg0 interface{}, args ...interface{}) error { 70 | switch f := arg0.(type) { 71 | case string: 72 | return errors.New(l.log("ERR", f, args...)) 73 | default: 74 | return errors.New(l.log("ERR", fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)) 75 | } 76 | } 77 | 78 | func (l *defaultLogger) Critical(arg0 interface{}, args ...interface{}) error { 79 | switch f := arg0.(type) { 80 | case string: 81 | return errors.New(l.log("CRT", f, args...)) 82 | default: 83 | return errors.New(l.log("CRT", fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)) 84 | } 85 | } 86 | 87 | func (l *defaultLogger) log(lvl string, format string, args ...interface{}) (msg string) { 88 | msg = fmt.Sprintf(format, args...) 89 | fmt.Printf("%s [%s] %s %s\n", formatNow(), lvl, determine(3), msg) 90 | return 91 | } 92 | 93 | func formatNow() string { 94 | return time.Now().Format("2006-01-02 15:04:05.999") 95 | } 96 | 97 | func determine(skip int) string { 98 | pc, file, lineno, ok := runtime.Caller(skip) 99 | src := "" 100 | if ok { 101 | name := runtime.FuncForPC(pc).Name() 102 | nameitems := strings.Split(name, ".") 103 | if len(nameitems) > 2 { 104 | nameitems = nameitems[len(nameitems)-2:] 105 | } 106 | name = strings.Join(nameitems, ".") 107 | 108 | pathitems := strings.Split(file, "/") 109 | if len(pathitems) > 2 { 110 | pathitems = pathitems[len(pathitems)-2:] 111 | } 112 | file = strings.Join(pathitems, "/") 113 | src = fmt.Sprintf("%s:%d(%s)", file, lineno, name) 114 | } 115 | return src 116 | } 117 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "encoding/xml" 8 | "errors" 9 | "fmt" 10 | "io" 11 | 12 | // "io/ioutil" 13 | "net" 14 | "net/http" 15 | "os" 16 | 17 | // "path" 18 | "reflect" 19 | "strconv" 20 | "strings" 21 | "sync/atomic" 22 | ) 23 | 24 | type response struct { 25 | http.ResponseWriter 26 | Ctx *Context 27 | render *render 28 | } 29 | 30 | func newResponse(ctx *Context) *response { 31 | rw := &response{Ctx: ctx} 32 | rw.render = &render{rw: rw} 33 | return rw 34 | } 35 | 36 | func Error(rw http.ResponseWriter, message string, code int) { 37 | rw.Header().Set("Connection", "close") 38 | http.Error(rw, message, code) 39 | if f, ok := rw.(http.Flusher); ok { 40 | f.Flush() 41 | } 42 | } 43 | 44 | func (this *response) Error(message string, code int) { 45 | Error(this, message, code) 46 | return 47 | } 48 | 49 | func (this *response) ContentType(typ string) { 50 | ctype := getContentType(typ) 51 | if ctype != "" { 52 | this.ResponseWriter.Header().Set("Content-Type", ctype) 53 | } else { 54 | this.ResponseWriter.Header().Set("Content-Type", typ) 55 | } 56 | } 57 | 58 | func (this *response) AddHeader(hdr string, val interface{}) { 59 | if v, ok := val.(string); ok { 60 | this.ResponseWriter.Header().Add(hdr, v) 61 | } else { 62 | this.ResponseWriter.Header().Add(hdr, fmt.Sprint(val)) 63 | } 64 | } 65 | 66 | func (this *response) SetHeader(hdr string, val interface{}) { 67 | if v, ok := val.(string); ok { 68 | this.ResponseWriter.Header().Set(hdr, v) 69 | } else { 70 | this.ResponseWriter.Header().Set(hdr, fmt.Sprint(val)) 71 | } 72 | } 73 | 74 | func (this *response) Flush() { 75 | if f, ok := this.ResponseWriter.(http.Flusher); ok { 76 | f.Flush() 77 | } 78 | } 79 | 80 | func (this *response) CloseNotify() <-chan bool { 81 | if cn, ok := this.ResponseWriter.(http.CloseNotifier); ok { 82 | return cn.CloseNotify() 83 | } 84 | return nil 85 | } 86 | 87 | func (this *response) Hijack() (net.Conn, *bufio.ReadWriter, error) { 88 | hj, ok := this.ResponseWriter.(http.Hijacker) 89 | if !ok { 90 | return nil, nil, errors.New("doesn't support hijacking") 91 | } 92 | return hj.Hijack() 93 | } 94 | 95 | func (this *response) AddCookie(c *http.Cookie) { 96 | this.Header().Add("Set-Cookie", c.String()) 97 | } 98 | 99 | type render struct { 100 | rw *response 101 | 102 | format string 103 | contentType string 104 | jsoncallback string 105 | //layout bool 106 | wrap bool 107 | wrapCode int //包装的消息code 108 | noWrap bool 109 | gzip bool //暂时未实现 110 | chunked bool 111 | 112 | //数据 113 | status int //http status 114 | data interface{} 115 | err error 116 | 117 | prepareDataFunc func() 118 | 119 | //标记是否已经开始 120 | started bool 121 | 122 | //标记是否已经被取消渲染 123 | canceled int32 124 | } 125 | 126 | func (this *render) String() string { 127 | length := -1 128 | if d, ok := this.data.([]byte); ok { 129 | length = len(d) 130 | } 131 | return fmt.Sprintf("Render: started:%v, format:%s, contentType:%s, jsoncallback:%s, wrap:%v, status:%d, len(data):%d, error:%v", this.started, this.format, this.contentType, this.jsoncallback, this.wrap, this.status, length, this.err) 132 | } 133 | 134 | func (this *render) Chunked() *render { 135 | this.chunked = true 136 | return this 137 | } 138 | 139 | func (this *render) Cancel() { 140 | atomic.StoreInt32(&this.canceled, 1) 141 | } 142 | 143 | func (this *render) IsCanceled(clear ...bool) bool { 144 | if len(clear) > 0 && clear[0] { 145 | return atomic.SwapInt32(&this.canceled, 0) > 0 146 | } else { 147 | return atomic.LoadInt32(&this.canceled) > 0 148 | } 149 | } 150 | 151 | func (this *render) Status(c int) *render { 152 | this.status = c 153 | return this 154 | } 155 | 156 | func (this *render) ContentType(typ string) *render { 157 | this.contentType = typ 158 | return this 159 | } 160 | 161 | //结果格式, json or xml or txt or html or other 162 | func (this *render) Format(format string) *render { 163 | this.format = format 164 | return this 165 | } 166 | 167 | func (this *render) Wrap(code ...int) *render { 168 | this.wrap = true 169 | if len(code) > 0 { 170 | this.wrapCode = code[0] 171 | } 172 | return this 173 | } 174 | 175 | func (this *render) Nowrap() *render { 176 | this.noWrap = true 177 | return this 178 | } 179 | 180 | func (this *render) Gzip() *render { 181 | this.gzip = true 182 | return this 183 | } 184 | 185 | func (this *render) Html() *render { 186 | this.format = FORMAT_HTML 187 | this.contentType = "html" 188 | return this 189 | } 190 | 191 | func (this *render) Text() *render { 192 | this.format = FORMAT_TXT 193 | this.contentType = "txt" 194 | return this 195 | } 196 | 197 | func (this *render) Json() *render { 198 | this.format = FORMAT_JSON 199 | this.contentType = "application/json; charset=utf-8" 200 | return this 201 | } 202 | 203 | func (this *render) Jsonp(jsoncallback string) *render { 204 | if jsoncallback != "" { 205 | this.jsoncallback = jsoncallback 206 | } 207 | if this.format == "" { 208 | this.Json() 209 | } 210 | return this 211 | } 212 | 213 | func (this *render) Xml() *render { 214 | this.format = FORMAT_XML 215 | this.contentType = "xml" 216 | return this 217 | } 218 | 219 | func (this *render) Header(key string, value ...interface{}) *render { 220 | h := this.rw.Header() 221 | if len(value) == 0 { 222 | h.Set(key, "") 223 | return this 224 | } 225 | if len(value) == 1 { 226 | h.Set(key, toString(value[0])) 227 | return this 228 | } 229 | for _, v := range value { 230 | h.Add(key, toString(v)) 231 | } 232 | return this 233 | } 234 | 235 | func (this *render) Cookie(c *http.Cookie) *render { 236 | this.rw.AddCookie(c) 237 | return this 238 | } 239 | 240 | func (this *render) KeepAlive(b bool) *render { 241 | if b { 242 | this.rw.Header().Set("Connection", "keep-alive") 243 | } else { 244 | this.rw.Header().Set("Connection", "close") 245 | } 246 | return this 247 | } 248 | 249 | //func (this *render) Stream(rc io.Reader) *render { 250 | // this.data = rc 251 | // return this 252 | //} 253 | 254 | func (this *render) File(filename string) *render { 255 | if this.prepareDataFunc != nil { 256 | this.Reset() 257 | panic("Render: data already exists") 258 | } 259 | this.prepareDataFunc = func() { 260 | if this.contentType == "" { 261 | if idx := strings.LastIndex(filename, "."); idx != -1 { 262 | this.contentType = filename[idx:] 263 | } 264 | } 265 | this.data, this.err = os.Open(filename) 266 | if this.err != nil { 267 | this.rw.Ctx.App.Logger.Error("open file error:%v, filename:%s", this.err, filename) 268 | } 269 | } 270 | return this 271 | } 272 | 273 | func (this *render) Template(templateName string, data map[interface{}]interface{}) *render { 274 | if this.prepareDataFunc != nil { 275 | this.Reset() 276 | panic("Render: data already exists") 277 | } 278 | this.prepareDataFunc = func() { 279 | if this.contentType == "" { 280 | this.Html() 281 | } 282 | this.data, this.err = BuildTemplateData(this.rw.Ctx.App, templateName, data) 283 | if this.err != nil { 284 | this.rw.Ctx.App.Logger.Error("template execute error:%v, template:%s", this.err, templateName) 285 | } 286 | } 287 | return this 288 | } 289 | 290 | //data - 如果data为[]byte或io.Reader类型,将直接输出,不再会进行json,xml等编码 291 | func (this *render) Data(data interface{}) *render { 292 | if this.prepareDataFunc != nil { 293 | this.Reset() 294 | panic("Render: data already exists") 295 | } 296 | 297 | switch data.(type) { 298 | case io.Reader: 299 | this.data = data 300 | return this 301 | } 302 | 303 | this.prepareDataFunc = func() { 304 | 305 | if this.wrap && this.format == "" { 306 | //如果设置了wrap, 将默认为json格式 307 | this.Json() 308 | } 309 | 310 | if this.status >= 400 || isErrorResult(data) || this.wrapCode != ERROR_CODE_OK { 311 | if this.jsoncallback == "" { 312 | this.data, this.err = BuildError(data, this.wrap, this.wrapCode, this.format) 313 | } else { 314 | this.data, this.err = BuildError(data, this.wrap, this.wrapCode, this.format, this.jsoncallback) 315 | } 316 | } else { 317 | if this.jsoncallback == "" { 318 | this.data, this.err = BuildSucceed(data, this.wrap, this.format) 319 | } else { 320 | this.data, this.err = BuildSucceed(data, this.wrap, this.format, this.jsoncallback) 321 | } 322 | } 323 | 324 | if this.err != nil { 325 | this.rw.Ctx.App.Logger.Error("error:%v, data:%v", this.err, data) 326 | } 327 | 328 | } 329 | return this 330 | } 331 | 332 | func (this *render) Reset() { 333 | if !this.started { 334 | return 335 | } 336 | 337 | this.contentType = "" 338 | this.data = nil 339 | this.format = "" 340 | this.jsoncallback = "" 341 | this.prepareDataFunc = nil 342 | this.err = nil 343 | this.status = 0 344 | this.started = false 345 | this.wrap = false 346 | this.noWrap = false 347 | this.wrapCode = 0 348 | this.gzip = false 349 | //this.canceled = 0 350 | } 351 | 352 | func (this *render) Exec(flush ...bool) error { 353 | defer this.Reset() 354 | 355 | if !this.started { 356 | return errors.New("the render is not started") 357 | } 358 | 359 | if this.IsCanceled(true) { 360 | return nil 361 | } 362 | 363 | cfg := this.rw.Ctx.App.Config 364 | 365 | if this.noWrap { 366 | if this.wrap { 367 | this.wrap = false 368 | } 369 | } else { 370 | if !this.wrap && cfg.Render.Wrap { 371 | this.wrap = cfg.Render.Wrap 372 | } 373 | } 374 | 375 | if cfg.Render.AutoParseFormat && this.format == "" { 376 | this.format = this.rw.Ctx.Input.GetValue(cfg.Render.FormatParamName) 377 | } 378 | 379 | if cfg.Render.AutoParseFormat && this.jsoncallback == "" { 380 | this.jsoncallback = this.rw.Ctx.Input.GetValue(cfg.Render.JsoncallbackParamName) 381 | } 382 | 383 | //Logger.Debug("this.format:%v", this.format) 384 | //Logger.Debug("this.wrap:%v", this.wrap) 385 | 386 | if this.prepareDataFunc != nil { 387 | this.prepareDataFunc() 388 | } 389 | 390 | if this.err != nil { 391 | this.err = renderError(this.rw, this.err, http.StatusInternalServerError, this.wrap, ERROR_CODE_RUNTIME, this.format, this.jsoncallback) 392 | if len(flush) > 0 && flush[0] { 393 | this.rw.Flush() 394 | } 395 | return this.err 396 | } 397 | 398 | this.contentType = getContentType(this.contentType) 399 | if this.contentType == "" { 400 | this.contentType = toContentType(this.format) 401 | } 402 | if this.contentType == "" { 403 | if _, ok := this.data.(io.Reader); ok { 404 | this.contentType = "application/octet-stream" 405 | } else { 406 | this.contentType = "text/plain; charset=utf-8" 407 | } 408 | } 409 | 410 | var encoding string 411 | if cfg.Render.Gzip || this.gzip { 412 | encoding = ParseEncoding(this.rw.Ctx.Request) 413 | } 414 | 415 | this.rw.Header().Set("Content-Type", this.contentType) 416 | switch data := this.data.(type) { 417 | case []byte: 418 | if _, _, err := WriteBody(encoding, this.rw, data, func(encodingEnable bool, name string) error { 419 | if encodingEnable { 420 | this.rw.SetHeader("Content-Encoding", name) 421 | } else { 422 | if !this.chunked { 423 | this.rw.SetHeader("Content-Length", strconv.Itoa(len(data))) 424 | } 425 | } 426 | if this.status > 0 { 427 | this.rw.WriteHeader(this.status) 428 | } 429 | return nil 430 | }); err != nil { 431 | this.rw.Ctx.App.Logger.Warn("write data error, %v", err) 432 | //this.err = err 433 | } 434 | case *os.File: 435 | defer data.Close() 436 | if _, _, err := WriteFile(encoding, this.rw, data, func(encodingEnable bool, name string) error { 437 | if encodingEnable { 438 | this.rw.SetHeader("Content-Encoding", name) 439 | } else { 440 | stat, err := data.Stat() 441 | if err != nil { 442 | this.rw.Ctx.App.Logger.Error("stat file size error, %v", err) 443 | this.err = err 444 | return err 445 | } else { 446 | if !this.chunked { 447 | this.rw.SetHeader("Content-Length", strconv.FormatInt(stat.Size(), 10)) 448 | } 449 | } 450 | } 451 | if this.status > 0 { 452 | this.rw.WriteHeader(this.status) 453 | } 454 | return nil 455 | }); err != nil { 456 | this.rw.Ctx.App.Logger.Warn("write file error, %v", err) 457 | //this.err = err 458 | } 459 | case io.Reader: 460 | defer func() { 461 | if closer, ok := data.(io.ReadCloser); ok { 462 | closer.Close() 463 | } 464 | }() 465 | if _, _, err := WriteStream(encoding, this.rw, data, func(encodingEnable bool, name string) error { 466 | if encodingEnable { 467 | this.rw.SetHeader("Content-Encoding", name) 468 | } 469 | if this.status > 0 { 470 | this.rw.WriteHeader(this.status) 471 | } 472 | return nil 473 | }); err != nil { 474 | this.rw.Ctx.App.Logger.Warn("write stream error, %v", err) 475 | //this.err = err 476 | } 477 | default: 478 | this.err = errors.New("data type not supported") 479 | this.rw.Ctx.App.Logger.Error("%v", this.err) 480 | } 481 | 482 | if this.err != nil && !this.IsCanceled(true) { 483 | this.err = renderError(this.rw, this.err, http.StatusInternalServerError, this.wrap, ERROR_CODE_RUNTIME, this.format, this.jsoncallback) 484 | } 485 | if len(flush) > 0 && flush[0] { 486 | this.rw.Flush() 487 | } 488 | return this.err 489 | } 490 | 491 | func Render(ctx *Context, data ...interface{}) *render { 492 | render := ctx.ResponseWriter.render 493 | if render.started { 494 | panic("Render: is already started") 495 | } 496 | 497 | render.started = true 498 | switch len(data) { 499 | case 0: 500 | //render.Data("") 501 | case 1: 502 | render.Data(data[0]) 503 | default: 504 | render.Data(data) 505 | } 506 | return render 507 | } 508 | 509 | func RenderFile(ctx *Context, filename string) *render { 510 | return Render(ctx).File(filename) 511 | } 512 | 513 | func RenderTemplate(ctx *Context, templateName string, data map[interface{}]interface{}) *render { 514 | return Render(ctx).Template(templateName, data) 515 | } 516 | 517 | func BuildTemplateData(app *App, tplnames string, data map[interface{}]interface{}) ([]byte, error) { 518 | var buf bytes.Buffer 519 | if app.Config.RunMode == DEV { 520 | /* 521 | buildFiles := []string{tplnames} 522 | if c.Layout != "" { 523 | buildFiles = append(buildFiles, c.Layout) 524 | if c.LayoutSections != nil { 525 | for _, sectionTpl := range c.LayoutSections { 526 | if sectionTpl == "" { 527 | continue 528 | } 529 | buildFiles = append(buildFiles, sectionTpl) 530 | } 531 | } 532 | } 533 | */ 534 | app.buildTemplate() 535 | } 536 | err := executeTemplate(app, &buf, tplnames, data) 537 | if err != nil { 538 | return nil, err 539 | } 540 | 541 | return buf.Bytes(), nil 542 | // content, err := ioutil.ReadAll(buf) 543 | // if err != nil { 544 | // return nil, err 545 | // } 546 | // return content, nil 547 | } 548 | 549 | //func BuildTemplateData(app *App, tplnames string, data map[interface{}]interface{}) ([]byte, error) { 550 | 551 | // _, file := path.Split(tplnames) 552 | // subdir := path.Dir(tplnames) 553 | // ibytes := bytes.NewBufferString("") 554 | 555 | // if app.Config.RunMode == DEV { 556 | // app.buildTemplate() 557 | // } 558 | // fmt.Println("tplnames:", tplnames) 559 | // fmt.Println("subdir:", subdir) 560 | // fmt.Println("app.TemplateRegister.Templates:", app.TemplateRegister.Templates) 561 | // t := app.TemplateRegister.Templates[subdir] 562 | // if t == nil { 563 | // return nil, errors.New(fmt.Sprintf("template not exist, tplnames:%s", tplnames)) 564 | // } 565 | // err := t.ExecuteTemplate(ibytes, file, data) 566 | // if err != nil { 567 | // return nil, err 568 | // } 569 | // content, err := ioutil.ReadAll(ibytes) 570 | // if err != nil { 571 | // return nil, err 572 | // } 573 | // return content, nil 574 | //} 575 | 576 | //fmtAndJsoncallback[0] - format, 值指示响应结果格式,当前支持:json或xml, 默认为:json 577 | //fmtAndJsoncallback[1] - jsoncallback 如果是json格式结果,支持jsoncallback 578 | func renderError(resp *response, errdata interface{}, status int, wrap bool, wrapcode int, fmtAndJsoncallback ...string) error { 579 | var format, jsoncallback string 580 | if len(fmtAndJsoncallback) > 0 { 581 | format = fmtAndJsoncallback[0] 582 | } else if len(fmtAndJsoncallback) > 1 { 583 | jsoncallback = fmtAndJsoncallback[1] 584 | } 585 | 586 | var content []byte 587 | var err error 588 | if jsoncallback == "" { 589 | content, err = BuildError(errdata, wrap, wrapcode, format) 590 | } else { 591 | content, err = BuildError(errdata, wrap, wrapcode, format, jsoncallback) 592 | } 593 | 594 | if err != nil { 595 | //http.Error(rw, err.Error(), http.StatusInternalServerError) 596 | resp.Ctx.App.Logger.Error("format:%v, error:%v, data:%v", format, err, errdata) 597 | return err 598 | } 599 | return renderData(resp, toContentType(format), content, status) 600 | } 601 | 602 | //fmtAndJsoncallback[0] - fmt, 值指示响应结果格式,当前支持:json或xml, 默认为:json 603 | //fmtAndJsoncallback[1] - jsoncallback 如果是json格式结果,支持jsoncallback 604 | func renderSucceed(resp *response, data interface{}, wrap bool, fmtAndJsoncallback ...string) error { 605 | var format, jsoncallback string 606 | if len(fmtAndJsoncallback) > 0 { 607 | format = fmtAndJsoncallback[0] 608 | } else if len(fmtAndJsoncallback) > 1 { 609 | jsoncallback = fmtAndJsoncallback[1] 610 | } 611 | var content []byte 612 | var err error 613 | if jsoncallback == "" { 614 | content, err = BuildSucceed(data, wrap, format) 615 | } else { 616 | content, err = BuildSucceed(data, wrap, format, jsoncallback) 617 | } 618 | if err != nil { 619 | //http.Error(rw, err.Error(), http.StatusInternalServerError) 620 | resp.Ctx.App.Logger.Error("format:%v, error:%v, data:%v", format, err, data) 621 | return err 622 | } 623 | return renderData(resp, toContentType(format), content) 624 | } 625 | 626 | func renderBuffer(rw *response, contentType string, buff *bytes.Buffer, status ...int) error { 627 | rw.Header().Set("Content-Type", getContentType(contentType)) 628 | if len(status) > 0 && status[0] != 0 { 629 | rw.WriteHeader(status[0]) 630 | } 631 | _, err := io.Copy(rw, buff) 632 | if err != nil { 633 | rw.Ctx.App.Logger.Error("error:%v, buff.length:%v", err, buff.Len()) 634 | return err 635 | } 636 | return nil 637 | } 638 | 639 | func renderData(rw *response, contentType string, data []byte, status ...int) error { 640 | rw.Header().Set("Content-Length", strconv.Itoa(len(data))) 641 | rw.Header().Set("Content-Type", getContentType(contentType)) 642 | if len(status) > 0 && status[0] != 0 { 643 | rw.WriteHeader(status[0]) 644 | } 645 | _, err := rw.Write(data) 646 | if err != nil { 647 | rw.Ctx.App.Logger.Error("error:%v, data.length:%v", err, len(data)) 648 | return err 649 | } 650 | return nil 651 | } 652 | 653 | //format 结果格式, 值有:json, xml, 其它(txt, html, ...) 654 | //jsoncallback 当需要将json结果做为js函数参数时,在jsoncallback中指定函数名 655 | func BuildSucceed(data interface{}, wrap bool, format string, jsoncallback ...string) ([]byte, error) { 656 | switch format { 657 | case "json": 658 | return buildJsonSucceed(data, wrap, jsoncallback...) 659 | case "xml": 660 | return buildXmlSucceed(data, wrap) 661 | default: 662 | return buildData(data), nil 663 | } 664 | } 665 | 666 | func buildData(data interface{}) []byte { 667 | switch d := data.(type) { 668 | case string: 669 | return []byte(d) 670 | case []byte: 671 | return d 672 | default: 673 | return []byte(fmt.Sprint(data)) 674 | } 675 | } 676 | 677 | func buildJsonSucceed(data interface{}, wrap bool, jsoncallback ...string) ([]byte, error) { 678 | if wrap { 679 | if _, ok := data.([]byte); !ok { 680 | data = NewSucceedResult(data) 681 | } 682 | } 683 | if len(jsoncallback) > 0 && jsoncallback[0] != "" { 684 | return buildJQueryCallback(jsoncallback[0], data) 685 | } else { 686 | return buildJson(data) 687 | } 688 | } 689 | 690 | func buildXmlSucceed(data interface{}, wrap bool) ([]byte, error) { 691 | if wrap { 692 | if _, ok := data.([]byte); !ok { 693 | data = NewSucceedResult(data) 694 | } 695 | } 696 | return buildXml(data) 697 | } 698 | 699 | type root struct { 700 | Data interface{} `xml:"data"` 701 | } 702 | 703 | func buildXml(data interface{}) ([]byte, error) { 704 | switch reflect.TypeOf(data).Kind() { 705 | case reflect.String: 706 | return []byte(data.(string)), nil 707 | case reflect.Slice: 708 | if content, ok := data.([]byte); ok { 709 | return content, nil 710 | } 711 | //如果是reflect.Slice类型,需要将其放到一个根节点中 712 | data = root{Data: data} 713 | } 714 | 715 | content, err := xml.Marshal(data) 716 | if err != nil { 717 | return nil, err 718 | } 719 | return content, nil 720 | } 721 | 722 | func buildJson(data interface{}) ([]byte, error) { 723 | switch jdata := data.(type) { 724 | case []byte: 725 | //如果是[]byte类型,就认为已经是标准json格式数据 726 | return jdata, nil 727 | default: 728 | jsondata, err := json.Marshal(data) 729 | if err != nil { 730 | return nil, err 731 | } 732 | return jsondata, nil 733 | } 734 | 735 | } 736 | 737 | func buildJQueryCallback(jsoncallback string, data interface{}) ([]byte, error) { 738 | content := bytes.NewBuffer(make([]byte, 0)) 739 | content.WriteString(jsoncallback) 740 | content.WriteByte('(') 741 | switch data.(type) { 742 | case []byte: 743 | //如果是[]byte类型,就认为已经是标准json格式数据 744 | content.Write(data.([]byte)) 745 | default: 746 | jsondata, err := json.Marshal(data) 747 | if err != nil { 748 | return nil, err 749 | } 750 | content.Write(jsondata) 751 | } 752 | 753 | content.WriteByte(')') 754 | return content.Bytes(), nil 755 | } 756 | 757 | //format 结果格式, 值有:json, xml, 其它(txt, html, ...) 758 | //jsoncallback 当需要将json结果做为js函数参数时,在jsoncallback中指定函数名 759 | func BuildError(err interface{}, wrap bool, code int, format string, jsoncallback ...string) ([]byte, error) { 760 | switch format { 761 | case "json": 762 | return buildJsonError(err, wrap, code, jsoncallback...) 763 | case "xml": 764 | return buildXmlError(err, wrap, code) 765 | default: 766 | return buildData(err), nil 767 | } 768 | } 769 | 770 | func buildJsonError(err interface{}, wrap bool, code int, jsoncallback ...string) ([]byte, error) { 771 | if wrap { 772 | err = convertErrorResult(err, code) 773 | } 774 | if len(jsoncallback) > 0 && jsoncallback[0] != "" { 775 | return buildJQueryCallback(jsoncallback[0], err) 776 | } else { 777 | return buildJson(err) 778 | } 779 | } 780 | 781 | func buildXmlError(err interface{}, wrap bool, code int) ([]byte, error) { 782 | if wrap { 783 | err = convertErrorResult(err, code) 784 | } 785 | return buildXml(err) 786 | } 787 | -------------------------------------------------------------------------------- /result.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type Result struct { 10 | Code int `json:"code" xml:"code,attr"` //0为成功,其它值为错误码 11 | Message string `json:"message,omitempty" xml:"message,attr,omitempty"` 12 | Info interface{} `json:"info,omitempty" xml:"info,omitempty"` //具体结果数据, 只有当code为0时,才设置此属性值 13 | } 14 | 15 | func (r *Result) String() string { 16 | if r.Code == ERROR_CODE_OK { 17 | return "[" + strconv.Itoa(r.Code) + "] " + fmt.Sprint(r.Info) 18 | } else { 19 | return "[" + strconv.Itoa(r.Code) + "] " + r.Message 20 | } 21 | } 22 | 23 | func NewResult(code int, convertCodeToMsg bool, data ...interface{}) *Result { 24 | msg := "" 25 | if convertCodeToMsg { 26 | msg = ERROR_INFO_MAP[code] 27 | } 28 | if code == ERROR_CODE_OK { 29 | var info interface{} 30 | if len(data) == 1 { 31 | info = data[0] 32 | } else if len(data) > 1 { 33 | info = data 34 | } 35 | return &Result{Code: code, Info: info, Message: msg} 36 | } else { 37 | if len(data) > 0 { 38 | if msg == "" { 39 | msg = fmt.Sprint(joinMsgs(data...)) 40 | } else { 41 | msg = fmt.Sprint(msg, ", ", joinMsgs(data...)) 42 | } 43 | 44 | } 45 | return &Result{Code: code, Message: msg} 46 | } 47 | } 48 | 49 | func NewErrorResult(code int, msgs ...interface{}) *Result { 50 | if len(msgs) == 0 { 51 | return NewResult(code, true) 52 | } else { 53 | return NewResult(code, false, msgs...) 54 | } 55 | } 56 | 57 | func NewSucceedResult(info interface{}) *Result { 58 | switch s := info.(type) { 59 | case string: 60 | if s == "" { 61 | return &Result{Code: ERROR_CODE_OK} 62 | } 63 | } 64 | return &Result{Code: ERROR_CODE_OK, Info: info} 65 | } 66 | 67 | func joinMsgs(args ...interface{}) string { 68 | if len(args) == 0 { 69 | return "" 70 | } 71 | 72 | if len(args) == 1 { 73 | return fmt.Sprint(args[0]) 74 | } 75 | 76 | strs := make([]string, 0, len(args)*2) 77 | for _, arg := range args { 78 | strs = append(strs, fmt.Sprint(arg)) 79 | } 80 | 81 | return strings.Join(strs, ", ") 82 | } 83 | 84 | func isErrorResult(err interface{}) bool { 85 | switch e := err.(type) { 86 | case *Result: 87 | return e.Code != ERROR_CODE_OK 88 | case Result: 89 | return e.Code != ERROR_CODE_OK 90 | } 91 | return false 92 | } 93 | 94 | func isResult(err interface{}) bool { 95 | switch err.(type) { 96 | case *Result, Result: 97 | return true 98 | } 99 | return false 100 | } 101 | 102 | func convertErrorResult(err interface{}, code ...int) *Result { 103 | switch e := err.(type) { 104 | case *Result: 105 | return e 106 | case Result: 107 | return &e 108 | // case error: 109 | // return NewErrorResult(ERROR_CODE_RUNTIME, e.Error()) 110 | } 111 | 112 | var c int 113 | if len(code) > 0 && code[0] != ERROR_CODE_OK { 114 | c = code[0] 115 | } else { 116 | c = ERROR_CODE_RUNTIME 117 | } 118 | if err != nil { 119 | return NewErrorResult(c, fmt.Sprint(err)) 120 | } 121 | return NewErrorResult(c) 122 | } 123 | -------------------------------------------------------------------------------- /router.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | "reflect" 9 | "regexp" 10 | "runtime" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | ) 15 | 16 | const ( 17 | ANY = iota 18 | GET 19 | POST 20 | PUT 21 | DELETE 22 | PATCH 23 | OPTIONS 24 | HEAD 25 | TRACE 26 | CONNECT 27 | ) 28 | 29 | var ( 30 | HttpMethods = map[string]int8{ 31 | "*": ANY, 32 | "GET": GET, 33 | "POST": POST, 34 | "PUT": PUT, 35 | "DELETE": DELETE, 36 | "PATCH": PATCH, 37 | "OPTIONS": OPTIONS, 38 | "HEAD": HEAD, 39 | "TRACE": TRACE, 40 | "CONNECT": CONNECT, 41 | } 42 | ) 43 | 44 | type HandlerFunc func(*Context) 45 | 46 | type controllerInfo struct { 47 | methods []int8 //HTTP请求方法 48 | router iRouter 49 | patternLength int 50 | } 51 | 52 | type funcParam struct { 53 | name string 54 | isptr bool //是否指针类型 55 | isstruct bool //是否结构类型 56 | typ reflect.Type 57 | } 58 | 59 | func (p *funcParam) String() string { 60 | return p.name 61 | } 62 | 63 | func (p *funcParam) New() reflect.Value { 64 | if p.isptr { 65 | return reflect.New(p.typ) 66 | } else { 67 | return reflect.Indirect(reflect.New(p.typ)) 68 | } 69 | } 70 | 71 | type iRouter interface { 72 | //是否自动解析请求参数 73 | ParseRequest(b bool) iRouter 74 | run(ctx *Context) 75 | parseRequestFlag() int8 76 | } 77 | 78 | const ( 79 | parseRequestDefault = iota 80 | parseRequestYes 81 | parseRequestNo 82 | ) 83 | 84 | type router struct { 85 | //0=def, 1=parse, 2=no parse 86 | parseRequest int8 87 | } 88 | 89 | func (r *router) run(ctx *Context) { 90 | } 91 | 92 | func (r *router) ParseRequest(b bool) iRouter { 93 | if b { 94 | r.parseRequest = parseRequestYes 95 | } else { 96 | r.parseRequest = parseRequestNo 97 | } 98 | return r 99 | } 100 | 101 | func (r *router) parseRequestFlag() int8 { 102 | return r.parseRequest 103 | } 104 | 105 | type defaultRouter struct { 106 | router 107 | app *App 108 | controllerType reflect.Type //控制器类型 109 | funcName string //函数名称 110 | funcType reflect.Type //函数类型 111 | //funcParamNames []string //函数参数名称列表 112 | funcParams []*funcParam //函数参数信息 113 | funcParamTags Taginfos //参数的Tag信息 114 | } 115 | 116 | type restfulRouter struct { 117 | router 118 | handlerFunc HandlerFunc 119 | } 120 | 121 | type handlerRouter struct { 122 | router 123 | handler http.Handler 124 | } 125 | 126 | func (r *restfulRouter) run(ctx *Context) { 127 | r.handlerFunc(ctx) 128 | } 129 | 130 | func (r *handlerRouter) run(ctx *Context) { 131 | r.handler.ServeHTTP(ctx.ResponseWriter, ctx.Request) 132 | } 133 | 134 | func (r *defaultRouter) run(ctx *Context) { 135 | vc := reflect.New(r.controllerType) 136 | controller, ok := vc.Interface().(ControllerInterface) 137 | if !ok { 138 | panic(r.controllerType.String() + " is not ControllerInterface interface") 139 | } 140 | controller.Init(r.app, ctx, r.controllerType.Name(), r.funcName) 141 | 142 | defer controller.Finish() 143 | controller.Prepare() 144 | 145 | if r.funcName == "" { 146 | switch convMethod(ctx.Request.Method) { 147 | case GET: 148 | controller.Get() 149 | case POST: 150 | controller.Post() 151 | case PUT: 152 | controller.Put() 153 | case DELETE: 154 | controller.Delete() 155 | case PATCH: 156 | controller.Patch() 157 | case OPTIONS: 158 | controller.Options() 159 | case HEAD: 160 | controller.Head() 161 | // case TRACE: 162 | // case CONNECT: 163 | default: 164 | http.Error(ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed) 165 | } 166 | 167 | } else { 168 | method := vc.MethodByName(r.funcName) 169 | numIn := r.funcType.NumIn() 170 | inx := make([]reflect.Value, numIn-1) 171 | if numIn > 1 { 172 | //auto bind func parameters 173 | tags := r.funcParamTags 174 | for i := 1; i < numIn; i++ { 175 | idx := i - 1 176 | funcParam := r.funcParams[idx] 177 | name := funcParam.name 178 | typ := funcParam.typ //router.funcType.In(i) 179 | v, err := ctx.Input.bind(name, typ, tags) 180 | if err != nil { 181 | ctx.Error = err 182 | if r.app.Config.ThrowBindParamPanic { 183 | var msg string 184 | if typ.Kind() == reflect.Struct { 185 | msg = fmt.Sprintf("%v, cause:%s.%v", ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL], name, err) 186 | } else { 187 | msg = fmt.Sprintf("%v, %s=%v, cause:%v", ERROR_INFO_MAP[ERROR_CODE_PARAM_ILLEGAL], name, ctx.Input.Values[name], err) 188 | } 189 | panic(NewErrorResult(ERROR_CODE_PARAM_ILLEGAL, msg)) 190 | } 191 | inx[idx] = funcParam.New() 192 | } else { 193 | if funcParam.isptr { 194 | inx[idx] = v.Addr() 195 | } else { 196 | inx[idx] = *v 197 | } 198 | } 199 | } 200 | } 201 | method.Call(inx) 202 | } 203 | 204 | } 205 | 206 | type ControllerRegister struct { 207 | explicitmap map[string]*controllerInfo 208 | relativemap map[string]*controllerInfo 209 | patternmap map[*regexp.Regexp]*controllerInfo 210 | app *App 211 | pool sync.Pool 212 | } 213 | 214 | func NewControllerRegister(app *App) *ControllerRegister { 215 | cr := &ControllerRegister{ 216 | app: app, 217 | explicitmap: make(map[string]*controllerInfo), 218 | relativemap: make(map[string]*controllerInfo), 219 | patternmap: make(map[*regexp.Regexp]*controllerInfo), 220 | } 221 | cr.pool.New = func() interface{} { 222 | return newContext() 223 | } 224 | return cr 225 | } 226 | 227 | //method - http method, GET,POST,PUT,HEAD,DELETE,PATCH,OPTIONS,* 228 | //path- URL path 229 | //name - method on the container 230 | //params - parameter name list 231 | //tags parameter tag info 232 | func (this *ControllerRegister) Add(methods string, pattern string, c ControllerInterface, name string, params []string, tags []string) (r iRouter) { 233 | if c == nil { 234 | panic("http: controller is empty") 235 | } 236 | 237 | var methodType reflect.Type 238 | controller := reflect.ValueOf(c) 239 | if name != "" { 240 | controllerMethod := controller.MethodByName(name) 241 | controllerType := reflect.TypeOf(c) 242 | controllerTypeMethod, ok := controllerType.MethodByName(name) 243 | if !controllerMethod.IsValid() && !ok { 244 | panic(fmt.Sprintf("http: ROUTER METHOD [%v] not find or invalid", name)) 245 | } 246 | methodType = controllerTypeMethod.Type 247 | } 248 | 249 | routerinfo := &defaultRouter{app: this.app, funcName: name, controllerType: reflect.Indirect(controller).Type(), funcType: methodType, funcParams: make([]*funcParam, 0)} 250 | 251 | if params != nil && len(params) > 0 { 252 | for _, p := range params { 253 | paramname := strings.SplitN(strings.TrimSpace(p), " ", 2)[0] 254 | routerinfo.funcParams = append(routerinfo.funcParams, &funcParam{paramname, false, false, nil}) 255 | } 256 | } 257 | 258 | methodParamTypes := make(map[string]reflect.Type) //key为参数名,值为参数类型//make([]reflect.Type, numIn, numIn) 259 | if methodType != nil { 260 | //check paramter num 261 | if methodType.NumIn()-1 != len(routerinfo.funcParams) { 262 | panic(fmt.Sprintf("http: the number of parameter mismatch, %v(%v), %v(%v)", routerinfo.funcParams, len(routerinfo.funcParams), methodType.String(), methodType.NumIn()-1)) 263 | } 264 | 265 | numIn := methodType.NumIn() 266 | for i := 1; i < numIn; i++ { 267 | ptype := methodType.In(i) 268 | funcparam := routerinfo.funcParams[i-1] 269 | if ptype.Kind() == reflect.Ptr { 270 | ptype = ptype.Elem() 271 | funcparam.isptr = true 272 | } 273 | methodParamTypes[funcparam.name] = ptype 274 | //check struct 275 | if ptype.Kind() == reflect.Struct { 276 | for i := 0; i < ptype.NumField(); i++ { 277 | f := ptype.Field(i) 278 | firstChar := f.Name[0:1] 279 | if strings.ToLower(firstChar) == firstChar { 280 | panic(fmt.Sprintf("http: first char is lower, Struct:%v.%v", ptype.Name(), f.Name)) 281 | } 282 | } 283 | funcparam.isstruct = true 284 | } 285 | funcparam.typ = ptype 286 | } 287 | 288 | } 289 | 290 | //parse tags 291 | routerinfo.funcParamTags = parseTags(methodParamTypes, tags, this.app.Config.FormDomainModel) 292 | if this.app.Config.RunMode == DEV { 293 | this.app.Logger.Debug("pattern:%v, action:%v.%v, tags:%v => formatedTags:%v", pattern, reflect.TypeOf(c), name, tags, routerinfo.funcParamTags) 294 | } 295 | 296 | this.add(methods, pattern, routerinfo) 297 | return routerinfo 298 | } 299 | 300 | func (this *ControllerRegister) AddMethod(methods, pattern string, f HandlerFunc) (r iRouter) { 301 | r = &restfulRouter{handlerFunc: f} 302 | this.add(methods, pattern, r) 303 | return 304 | } 305 | 306 | func (this *ControllerRegister) AddHandler(pattern string, h http.Handler) (r iRouter) { 307 | r = &handlerRouter{handler: h} 308 | this.add("*", pattern, r) 309 | return 310 | } 311 | 312 | func (this *ControllerRegister) add(methods, pattern string, router iRouter) { 313 | 314 | if pattern == "" { 315 | panic("http: invalid pattern " + pattern) 316 | } 317 | 318 | if _, ok := this.explicitmap[pattern]; ok { 319 | panic("http: multiple registrations for " + pattern) 320 | } 321 | 322 | if _, ok := this.relativemap[pattern]; ok { 323 | panic("http: multiple registrations for " + pattern) 324 | } 325 | 326 | for r, _ := range this.patternmap { 327 | if r.String() == pattern { 328 | panic("http: multiple registrations for " + pattern) 329 | } 330 | } 331 | 332 | httpMethodstrs := strings.Split(methods, "|") 333 | httpMethods := make([]int8, 0, len(httpMethodstrs)) 334 | for _, m := range httpMethodstrs { 335 | mv := convMethod(m) 336 | if mv == 0 { 337 | for _, v := range HttpMethods { 338 | httpMethods = append(httpMethods, v) 339 | } 340 | } else { 341 | httpMethods = append(httpMethods, mv) 342 | } 343 | } 344 | 345 | if len(httpMethods) == 0 { 346 | panic("http: methods is empty") 347 | } 348 | 349 | controllerInfo := &controllerInfo{methods: httpMethods, router: router} 350 | 351 | if isPattern(pattern) { 352 | //regex router 353 | if pattern[0] != '^' && pattern[0] == '/' { 354 | pattern = "^" + pattern 355 | } 356 | r, err := regexp.Compile(pattern) 357 | if err != nil { 358 | panic(err) 359 | } 360 | controllerInfo.patternLength = len(pattern) 361 | this.patternmap[r] = controllerInfo 362 | } else { 363 | controllerInfo.patternLength = len(pattern) 364 | if pattern[len(pattern)-1] == '/' { 365 | this.relativemap[pattern] = controllerInfo 366 | } else { 367 | this.explicitmap[pattern] = controllerInfo 368 | } 369 | 370 | } 371 | } 372 | 373 | // usage: 374 | // Get("/", func(ctx *ssss.Context){ 375 | // ... 376 | // }) 377 | func (this *ControllerRegister) Get(pattern string, f HandlerFunc) iRouter { 378 | return this.AddMethod("GET", pattern, f) 379 | } 380 | 381 | func (this *ControllerRegister) Post(pattern string, f HandlerFunc) iRouter { 382 | return this.AddMethod("POST", pattern, f) 383 | } 384 | 385 | func (this *ControllerRegister) Put(pattern string, f HandlerFunc) iRouter { 386 | return this.AddMethod("PUT", pattern, f) 387 | } 388 | 389 | func (this *ControllerRegister) Delete(pattern string, f HandlerFunc) iRouter { 390 | return this.AddMethod("DELETE", pattern, f) 391 | } 392 | 393 | func (this *ControllerRegister) Head(pattern string, f HandlerFunc) iRouter { 394 | return this.AddMethod("HEAD", pattern, f) 395 | } 396 | 397 | func (this *ControllerRegister) Patch(pattern string, f HandlerFunc) iRouter { 398 | return this.AddMethod("PATCH", pattern, f) 399 | } 400 | 401 | func (this *ControllerRegister) Options(pattern string, f HandlerFunc) iRouter { 402 | return this.AddMethod("OPTIONS", pattern, f) 403 | } 404 | 405 | func (this *ControllerRegister) Any(pattern string, f HandlerFunc) iRouter { 406 | return this.AddMethod("*", pattern, f) 407 | } 408 | 409 | func (this *ControllerRegister) hasHttpMethod(router *controllerInfo, method int8) bool { 410 | for _, m := range router.methods { 411 | if method == m { 412 | return true 413 | } 414 | } 415 | return false 416 | } 417 | 418 | //*,GET,POST,PUT,HEAD,DELETE,PATCH,OPTIONS,TRACE,CONNECT => 0,1,2,3,4,5,6,7,8,9 419 | func convMethod(m string) int8 { 420 | mv, ok := HttpMethods[strings.ToUpper(m)] 421 | if ok { 422 | return mv 423 | } 424 | panic("(" + m + ") Method is not supported") 425 | } 426 | 427 | func (this *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 428 | ctx := this.pool.Get().(*Context) 429 | ctx.Reset(rw, r, this.app) 430 | defer this.pool.Put(ctx) 431 | 432 | //if this.app.Config.RecoverFunc != nil { 433 | defer this.app.Config.RecoverFunc(ctx) 434 | //} else { 435 | // defer defaultRecoverFunc(ctx) 436 | //} 437 | 438 | path := r.URL.Path 439 | pathlen := len(path) 440 | 441 | //http service router 442 | router, ok := this.explicitmap[path] 443 | if !ok { 444 | var n = 0 445 | for pattern, r := range this.relativemap { 446 | if !(pathlen >= r.patternLength && path[0:r.patternLength] == pattern) { 447 | continue 448 | } 449 | if router == nil || r.patternLength > n { 450 | n = r.patternLength 451 | router = r 452 | } 453 | } 454 | } 455 | if router != nil { 456 | if this.hasHttpMethod(router, convMethod(r.Method)) { 457 | this.call(router, ctx, nil) 458 | return 459 | } 460 | } 461 | 462 | //regex router 463 | for regex, router := range this.patternmap { 464 | items := regex.FindStringSubmatch(path) 465 | if items == nil || len(items) == 0 { 466 | continue 467 | } 468 | restform := make(url.Values) 469 | for i, name := range regex.SubexpNames() { 470 | if i == 0 { 471 | continue 472 | } 473 | if name == "" { 474 | restform.Add("$"+strconv.Itoa(i), items[i]) 475 | } else { 476 | restform.Add(name, items[i]) 477 | } 478 | } 479 | 480 | if this.hasHttpMethod(router, convMethod(r.Method)) { 481 | this.call(router, ctx, restform) 482 | return 483 | } 484 | } 485 | 486 | //static file server 487 | for p, dir := range this.app.StaticDirs { 488 | if strings.HasPrefix(r.URL.Path, p) { 489 | var file string 490 | if p == "/" { 491 | file = dir + path 492 | } else { 493 | file = dir + path[len(p):] 494 | } 495 | http.ServeFile(rw, r, file) 496 | return 497 | } 498 | } 499 | 500 | err := Render(ctx, "Method Not Allowed").Text(). 501 | Status(http.StatusMethodNotAllowed). 502 | Exec(true) 503 | if err != nil { 504 | this.app.Logger.Critical("%s", buildLoginfo(ctx.Request, err)) 505 | //http.Error(ctx.ResponseWriter, err.Error(), http.StatusInternalServerError) 506 | } 507 | } 508 | 509 | func (this *ControllerRegister) call(routerinfo *controllerInfo, ctx *Context, restform url.Values) { 510 | 511 | preqflag := routerinfo.router.parseRequestFlag() 512 | if preqflag == parseRequestYes || (preqflag == parseRequestDefault && this.app.Config.AutoParseRequest) { 513 | err := ctx.Input.Parse() 514 | if err != nil { 515 | if err == io.EOF || err == io.ErrUnexpectedEOF || strings.HasSuffix(err.Error(), io.EOF.Error()) { 516 | this.app.Logger.Warn("client interrupt request, cause:%v", err) 517 | return 518 | } else if strings.Contains(err.Error(), ErrBodyTooLarge.Error()) { 519 | panic(NewErrorResult(ERROR_CODE_RUNTIME, err)) 520 | } else { 521 | panic(err) 522 | } 523 | } 524 | } 525 | 526 | if restform != nil && len(restform) > 0 { 527 | ctx.Input.AddValues(restform) 528 | } 529 | 530 | routerinfo.router.run(ctx) 531 | render := ctx.ResponseWriter.render 532 | if render.started { 533 | err := render.Exec() 534 | if err != nil { 535 | ctx.App.Logger.Critical("%s", buildLoginfo(ctx.Request, err)) 536 | } 537 | } 538 | } 539 | 540 | func pathMatch(pattern, path string) bool { 541 | if len(pattern) == 0 { 542 | return false 543 | } 544 | n := len(pattern) 545 | if pattern[n-1] != '/' { 546 | return pattern == path 547 | } 548 | return len(path) >= n && path[0:n] == pattern 549 | } 550 | 551 | func defaultRecoverFunc(ctx *Context) { 552 | if err := recover(); err != nil { 553 | var errtxt string 554 | var code int 555 | var errdata interface{} 556 | 557 | switch e := err.(type) { 558 | case *Result: 559 | errtxt = e.String() 560 | code = http.StatusBadRequest 561 | errdata = err 562 | //默认认为被Result包装过的异常为业务上的错误,采用Info日志级别 563 | ctx.App.Logger.Info("%s", buildLoginfo(ctx.Request, errdata)) 564 | case Result: 565 | errtxt = e.String() 566 | code = http.StatusBadRequest 567 | errdata = err 568 | //默认认为被Result包装过的异常为业务上的错误,采用Info日志级别 569 | ctx.App.Logger.Info("%s", buildLoginfo(ctx.Request, errdata)) 570 | default: 571 | errtxt = "Internal Server Error" 572 | code = http.StatusInternalServerError 573 | errdata = fmt.Sprintf("%s, %v", errtxt, err) 574 | ctx.App.Logger.Critical("%s", buildLoginfo(ctx.Request, errdata)) 575 | if ctx.App.Config.PrintPanicDetail || ctx.App.Config.RunMode == DEV { 576 | for i := 1; ; i += 1 { 577 | _, file, line, ok := runtime.Caller(i) 578 | if !ok { 579 | break 580 | } 581 | ctx.App.Logger.Error("%s, %d", file, line) 582 | } 583 | } 584 | } 585 | 586 | err := Render(ctx, errdata).Status(code). 587 | Exec(true) 588 | 589 | if err != nil { 590 | ctx.App.Logger.Critical("%s", buildLoginfo(ctx.Request, err)) 591 | //Error(ctx.ResponseWriter, err.Error(), http.StatusInternalServerError) 592 | } 593 | 594 | } 595 | } 596 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "net/http" 11 | "net/http/fcgi" 12 | "os" 13 | ) 14 | 15 | type Server interface { 16 | ListenAndServe(app *App) error 17 | } 18 | 19 | //Default 20 | type HttpServer struct { 21 | http.Server 22 | Network string 23 | } 24 | 25 | func (hsl *HttpServer) ListenAndServe(app *App) error { 26 | app.Prepare() 27 | hsl.ReadTimeout = app.Config.Listen.ReadTimeout 28 | hsl.WriteTimeout = app.Config.Listen.WriteTimeout 29 | hsl.Addr = app.Config.Listen.Addr 30 | hsl.Handler = app.FilterHandler(app, DefaultFilterHandler(app, app.Handlers)) 31 | 32 | if w, ok := app.Logger.(io.Writer); ok { 33 | hsl.ErrorLog = log.New(w, "[HTTP]", 0) 34 | } 35 | if hsl.Network == "" { 36 | hsl.Network = "tcp" 37 | } 38 | l, err := net.Listen(hsl.Network, hsl.Addr) 39 | if err != nil { 40 | return err 41 | } 42 | return hsl.Serve(app.FilterListener(app, DefaultFilterListener(app, l))) 43 | } 44 | 45 | //TLS 46 | type TLSHttpServer struct { 47 | http.Server 48 | CertFile, KeyFile string 49 | } 50 | 51 | func (hsl *TLSHttpServer) ListenAndServe(app *App) error { 52 | app.Prepare() 53 | hsl.ReadTimeout = app.Config.Listen.ReadTimeout 54 | hsl.WriteTimeout = app.Config.Listen.WriteTimeout 55 | hsl.Addr = app.Config.Listen.Addr 56 | hsl.Handler = app.FilterHandler(app, DefaultFilterHandler(app, app.Handlers)) 57 | 58 | if w, ok := app.Logger.(io.Writer); ok { 59 | hsl.ErrorLog = log.New(w, "[HTTPS]", 0) 60 | } 61 | 62 | config, err := hsl.tlsConfig() 63 | if err != nil { 64 | return err 65 | } 66 | 67 | l, err := net.Listen("tcp", hsl.Addr) 68 | if err != nil { 69 | return err 70 | } 71 | tlsListener := tls.NewListener(app.FilterListener(app, DefaultFilterListener(app, l)), config) 72 | return hsl.Serve(tlsListener) 73 | } 74 | 75 | func strSliceContains(ss []string, s string) bool { 76 | for _, v := range ss { 77 | if v == s { 78 | return true 79 | } 80 | } 81 | return false 82 | } 83 | 84 | func (hsl *TLSHttpServer) tlsConfig() (*tls.Config, error) { 85 | config := &tls.Config{} 86 | if !strSliceContains(config.NextProtos, "http/1.1") { 87 | config.NextProtos = append(config.NextProtos, "http/1.1") 88 | } 89 | configHasCert := len(config.Certificates) > 0 || config.GetCertificate != nil 90 | if !configHasCert || hsl.CertFile != "" || hsl.KeyFile != "" { 91 | var err error 92 | config.Certificates = make([]tls.Certificate, 1) 93 | config.Certificates[0], err = tls.LoadX509KeyPair(hsl.CertFile, hsl.KeyFile) 94 | if err != nil { 95 | return nil, err 96 | } 97 | } 98 | return config, nil 99 | } 100 | 101 | //fcgi 102 | type FcgiHttpServer struct { 103 | Network string 104 | EnableStdIo bool 105 | } 106 | 107 | func (hsl *FcgiHttpServer) ListenAndServe(app *App) error { 108 | app.Prepare() 109 | var err error 110 | var l net.Listener 111 | handler := app.FilterHandler(app, DefaultFilterHandler(app, app.Handlers)) 112 | addr := app.Config.Listen.Addr 113 | 114 | if hsl.EnableStdIo { 115 | if err = fcgi.Serve(nil, handler); err == nil { 116 | app.Logger.Info("Use FCGI via standard I/O") 117 | return nil 118 | } else { 119 | return errors.New(fmt.Sprintf("Cannot use FCGI via standard I/O, %v", err)) 120 | } 121 | } 122 | if hsl.Network == "unix" { 123 | if fileExists(addr) { 124 | os.Remove(addr) 125 | } 126 | l, err = net.Listen("unix", addr) 127 | } else { 128 | network := hsl.Network 129 | if network == "" { 130 | network = "tcp" 131 | } 132 | l, err = net.Listen(network, addr) 133 | } 134 | if err != nil { 135 | return errors.New(fmt.Sprintf("Listen: %v", err)) 136 | } 137 | if err = fcgi.Serve(app.FilterListener(app, DefaultFilterListener(app, l)), handler); err != nil { 138 | return errors.New(fmt.Sprintf("Fcgi.Serve: %v", err)) 139 | } 140 | return nil 141 | } 142 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | type SessionInterface interface { 4 | Set(key string, value interface{}) 5 | Get(key string) interface{} 6 | Delete(key string) 7 | SessionID() string 8 | Flush() error //delete all data 9 | } 10 | 11 | type ProviderInterface interface { 12 | SessionInit(gclifetime int64, config string) error 13 | SessionRead(sid string) (SessionInterface, error) 14 | SessionExist(sid string) bool 15 | SessionRegenerate(oldsid, sid string) (SessionInterface, error) 16 | SessionDestroy(sid string) error 17 | SessionAll() int //get all active session 18 | SessionGC() 19 | } 20 | 21 | //type sessionManagerInterface interface { 22 | // SessionStart() SessionInterface 23 | // SetHashFunc(hf hashFunc) 24 | // Destroy(HTTPCookieHandlerInterface) 25 | // RegenerateSession(cookieHandler HTTPCookieHandlerInterface) SessionInterface 26 | // GC() 27 | //} 28 | -------------------------------------------------------------------------------- /statinfo.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | var atomicInt64Enable bool 10 | 11 | func init() { 12 | atomicInt64Enable = reflect.TypeOf(int(0)).Bits() >= 64 13 | } 14 | 15 | type statinfo struct { 16 | app *App 17 | //当前并发连接数量 18 | concurrentConns int32 19 | //最大并发连接峰值 20 | peakConcurrentConns int32 21 | //当前进行中的请求数量 22 | currentRequests int64 23 | //累计请求数量 24 | totalRequests int64 25 | reqsLocker sync.RWMutex 26 | } 27 | 28 | func newStatinfo(app *App) *statinfo { 29 | return &statinfo{app: app} 30 | } 31 | 32 | func (s *statinfo) incConcurrentConns() { 33 | conns := atomic.AddInt32(&s.concurrentConns, 1) 34 | if conns > atomic.LoadInt32(&s.peakConcurrentConns) { 35 | atomic.StoreInt32(&s.peakConcurrentConns, conns) 36 | } 37 | } 38 | 39 | func (s *statinfo) decConcurrentConns() { 40 | atomic.AddInt32(&s.concurrentConns, -1) 41 | } 42 | 43 | func (s *statinfo) ConcurrentConns() int32 { 44 | return atomic.LoadInt32(&s.concurrentConns) 45 | } 46 | 47 | func (s *statinfo) PeakConcurrentConns() int32 { 48 | return atomic.LoadInt32(&s.peakConcurrentConns) 49 | } 50 | 51 | func (s *statinfo) incCurrentRequests() { 52 | if atomicInt64Enable { 53 | atomic.AddInt64(&s.currentRequests, 1) 54 | atomic.AddInt64(&s.totalRequests, 1) 55 | } else { 56 | s.reqsLocker.Lock() 57 | defer s.reqsLocker.Unlock() 58 | s.currentRequests++ 59 | s.totalRequests++ 60 | } 61 | } 62 | 63 | func (s *statinfo) decCurrentRequests() { 64 | if atomicInt64Enable { 65 | atomic.AddInt64(&s.currentRequests, -1) 66 | } else { 67 | s.reqsLocker.Lock() 68 | defer s.reqsLocker.Unlock() 69 | s.currentRequests-- 70 | } 71 | } 72 | 73 | func (s *statinfo) CurrentRequests() int64 { 74 | if atomicInt64Enable { 75 | return atomic.LoadInt64(&s.currentRequests) 76 | } else { 77 | s.reqsLocker.RLock() 78 | defer s.reqsLocker.RUnlock() 79 | return s.currentRequests 80 | } 81 | } 82 | 83 | func (s *statinfo) TotalRequests() int64 { 84 | if atomicInt64Enable { 85 | return atomic.LoadInt64(&s.totalRequests) 86 | } else { 87 | s.reqsLocker.RLock() 88 | defer s.reqsLocker.RUnlock() 89 | return s.totalRequests 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tag.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | //`field:"name,limit:20,scope:[1 2 3],default:1,require,pattern:regex"` 13 | //key: is tag item name, value: indicates whether there is value 14 | var supportedTagitems = map[string]bool{ 15 | "limit": true, 16 | "scope": true, 17 | "default": true, 18 | "require": false, 19 | "pattern": true, 20 | "layout": true, //time format 21 | } 22 | 23 | type Taginfos map[string]*tagInfo 24 | 25 | func (this Taginfos) Get(name string) *tagInfo { 26 | return this[name] 27 | } 28 | 29 | func (this Taginfos) Set(name string, taginfo *tagInfo) { 30 | this[name] = taginfo 31 | } 32 | 33 | func (this Taginfos) Adds(taginfos Taginfos) { 34 | for k, v := range taginfos { 35 | this[k] = v 36 | } 37 | } 38 | 39 | func (this Taginfos) Parse(kind reflect.Kind, tag string) { 40 | if tagInfo := parseOneTag(kind, tag); tagInfo != nil { 41 | this.Set(tagInfo.Name, tagInfo) 42 | } 43 | } 44 | 45 | func (this Taginfos) ParseStruct(name string, structType reflect.Type, formDomainModel ...bool) { 46 | parseTag(name, structType, "", len(formDomainModel) > 0 && formDomainModel[0], this) 47 | //this.Adds(parseStructTag(name, structType, formDomainModel...)) 48 | } 49 | 50 | func (this Taginfos) ParseTags(names map[string]reflect.Type, formDomainModel bool, tags ...string) { 51 | this.Adds(parseTags(names, tags, formDomainModel)) 52 | } 53 | 54 | //`field:"name,limit:20,scope:[1 2 3],default:1,require,pattern:regex"` 55 | type tagInfo struct { 56 | Name string //tag name 57 | TypeKind reflect.Kind //type kind value 58 | Limit limitTag 59 | Require bool 60 | Scope scopeTag 61 | Default defaultTag 62 | Pattern patternTag 63 | Layout layoutTag 64 | } 65 | 66 | func (this *tagInfo) String() string { 67 | return fmt.Sprintf("name:%v(%v) (limit:%v, require:%v, scope:%v, default:%v, pattern:%v)", this.Name, this.TypeKind.String(), this.Limit, this.Require, this.Scope, this.Default, this.Pattern) 68 | } 69 | 70 | //v - 可以是string类型的请求参数 71 | //dest[0] - 如果提供此参数,将会在dest[0]中返回值,dest[0]原数据类型必须与tagInfo.Type类型一致 72 | func (this *tagInfo) Check(v interface{}, dest ...interface{}) error { 73 | rval := reflect.Indirect(reflect.ValueOf(v)) 74 | rvalType := rval.Type() 75 | 76 | rvalTypeKind := rvalType.Kind() 77 | tagTypeKind := this.TypeKind 78 | 79 | var valLen int 80 | var strval string 81 | if rvalType.Kind() == reflect.String { 82 | strval = rval.String() 83 | valLen = len(strval) 84 | } else { 85 | strval = fmt.Sprint(v) 86 | valLen = len(strval) 87 | } 88 | 89 | //Logger.Debug("v:%v", v) 90 | //Logger.Debug("tagInfo:%v", this.String()) 91 | //Logger.Debug("strval:%v", strval) 92 | //Logger.Debug("valLen:%v", valLen) 93 | //Logger.Debug("strval:%v", strval) 94 | 95 | var destValue reflect.Value 96 | var destTypeKind reflect.Kind 97 | if len(dest) > 0 { 98 | if destPtr, ok := dest[0].(*reflect.Value); ok { 99 | destValue = *destPtr 100 | } else { 101 | destValue = reflect.ValueOf(dest[0]) 102 | if destValue.Kind() != reflect.Ptr { 103 | return errors.New("dest is not pointer type") 104 | } 105 | destValue = destValue.Elem() 106 | } 107 | destTypeKind = destValue.Kind() 108 | } 109 | 110 | if valLen == 0 { 111 | //check require 112 | if this.Require { 113 | return errors.New("tag require: value is empty, tag:" + this.Name + "(" + this.TypeKind.String() + ")") 114 | } else if this.Default.Exist { 115 | if len(dest) > 0 { 116 | destValue.Set(this.Default.Value) 117 | } 118 | } 119 | return nil 120 | } 121 | 122 | //check length limit 123 | if this.Limit.Exist && this.Limit.Value > 0 && valLen > this.Limit.Value { 124 | return errors.New(fmt.Sprintf("tag limit: value is too long, limit:%v, tag:%v(%v)", this.Limit.Value, this.Name, this.TypeKind.String())) 125 | } 126 | 127 | //check pattern 128 | if this.Pattern.Exist { 129 | if !this.Pattern.Regexp.MatchString(strval) { 130 | return errors.New(fmt.Sprintf("tag pattern: pattern match fail!, pattern:%v, tag:%v(%v)", this.Pattern.Regexp.String(), this.Name, this.TypeKind.String())) 131 | } 132 | } 133 | 134 | //time layouts 135 | var layouts []string 136 | if timeType == destValue.Type() { 137 | if this.Layout.Exist { 138 | layouts = this.Layout.Terms 139 | } 140 | } 141 | 142 | var val interface{} 143 | if len(dest) > 0 { 144 | if rvalTypeKind != destTypeKind { 145 | var err error 146 | _, err = parseValue(strval, destTypeKind, &destValue, layouts...) 147 | if err != nil { 148 | return err 149 | } 150 | val = destValue.Interface() //rval.Interface() 151 | } else { 152 | destValue.Set(rval) 153 | val = v 154 | } 155 | } 156 | 157 | if val == nil { 158 | if rvalTypeKind != tagTypeKind { 159 | value, err := parseValue(strval, tagTypeKind, nil) 160 | if err != nil { 161 | return err 162 | } 163 | val = value.Interface() 164 | } else { 165 | val = v 166 | } 167 | } 168 | 169 | if this.Scope.Exist && !this.Scope.Items.check(val) { 170 | return errors.New(fmt.Sprintf("tag scope: value is not in scope:[%v], tag:%v(%v)", this.Scope.String(), this.Name, this.TypeKind.String())) 171 | } 172 | 173 | return nil 174 | } 175 | 176 | type tagItem struct { 177 | Exist bool 178 | } 179 | 180 | type limitTag struct { 181 | tagItem 182 | Value int 183 | } 184 | 185 | type scopeTag struct { 186 | tagItem 187 | Items scopeItems 188 | expr string 189 | } 190 | 191 | func (this scopeTag) String() string { 192 | return this.expr 193 | } 194 | 195 | type defaultTag struct { 196 | tagItem 197 | Value reflect.Value 198 | } 199 | 200 | type patternTag struct { 201 | tagItem 202 | Regexp *regexp.Regexp 203 | } 204 | 205 | type layoutTag struct { 206 | tagItem 207 | Terms []string 208 | } 209 | 210 | //pnames key is parmeter name, value is parmeter type 211 | func parseTags(pnametypes map[string]reflect.Type, tags []string, formDomainModel bool) (formatedTags Taginfos) { 212 | if len(pnametypes) == 0 { 213 | return 214 | } 215 | 216 | pnames := make(map[string]reflect.Type) 217 | for k, v := range pnametypes { 218 | pnames[k] = v 219 | } 220 | 221 | formatedTags = make(Taginfos) 222 | for _, tag := range tags { 223 | name, tag := pretreatTag(tag) 224 | if typ, ok := pnames[name]; ok { 225 | parseTag(name, typ, tag, formDomainModel, formatedTags) 226 | delete(pnames, name) 227 | } 228 | } 229 | 230 | for name, typ := range pnames { 231 | parseTag(name, typ, "", formDomainModel, formatedTags) 232 | } 233 | return 234 | } 235 | 236 | func pretreatTag(tag string) (name, rettag string) { 237 | pair := strings.SplitN(tag, ":\"", 2) 238 | if len(pair) > 1 { 239 | tag = pair[1] 240 | if tag[len(tag)-1] == '"' { 241 | tag = tag[0 : len(tag)-1] 242 | } 243 | } 244 | pair = strings.SplitN(tag, ",", 2) 245 | return strings.TrimSpace(pair[0]), strings.TrimSpace(tag) 246 | } 247 | 248 | //分析tag信息 249 | //typ - 属性类型 250 | //tag - 属性tag信息, `:"name,limit:10,scope:[One Two Three],default:Two,require"` 251 | func parseOneTag(kind reflect.Kind, tag string) *tagInfo { 252 | stag := tag 253 | _, tag = pretreatTag(tag) 254 | if tag == "" { 255 | panic("tag is empty, tag:" + stag) 256 | } 257 | //kind := typ.Kind() 258 | switch kind { 259 | case reflect.Slice: 260 | panic("ssss: the slice type is not supported, @see ParseStructTag()") 261 | case reflect.Struct: 262 | panic("ssss: the struct type is not supported, @see ParseStructTag()") 263 | } 264 | 265 | if taginfo := parseTagItems(kind, tag); taginfo != nil { 266 | return taginfo 267 | } else { 268 | panic("parse fail, tag:" + stag) 269 | } 270 | 271 | return nil 272 | } 273 | 274 | //分析tag信息,如果是结构类型,将自动分析结构tag的field字段属性 275 | //name - 属性名 276 | //typ - 属性类型 277 | //tag - 属性tag信息 278 | //formDomainModel[0] @see config.FormDomainModel 279 | //@return taginfos - 在taginfos中返回格式化后的tag信息 280 | 281 | //func parseStructTag(name string, structType reflect.Type, formDomainModel ...bool) (taginfos Taginfos) { 282 | // taginfos = make(Taginfos) 283 | // parseTag(name, structType, "", len(formDomainModel) > 0 && formDomainModel[0], taginfos) 284 | // return 285 | //} 286 | 287 | const inputTagname = "param" 288 | 289 | //var timeType = reflect.TypeOf(time.Now()) 290 | 291 | func parseTag(pname string, ptype reflect.Type, tag string, formDomainModel bool, formatedTags Taginfos) { 292 | kind := ptype.Kind() 293 | if kind == reflect.Struct && timeType != ptype { 294 | for i := 0; i < ptype.NumField(); i++ { 295 | stag := tag 296 | f := ptype.Field(i) 297 | var attrName string 298 | if f.Anonymous { 299 | attrName = pname 300 | } else { 301 | paramTag := strings.TrimSpace(f.Tag.Get(inputTagname)) 302 | if paramTag == "-" { 303 | continue 304 | } 305 | if paramTag != "" { 306 | stag = paramTag 307 | } 308 | paramTags := strings.SplitN(paramTag, ",", 2) 309 | if len(paramTag) > 0 { 310 | attrName = strings.TrimSpace(paramTags[0]) 311 | if attrName == "-" { 312 | continue 313 | } 314 | } 315 | if len(attrName) == 0 { 316 | attrName = strings.ToLower(f.Name[0:1]) + f.Name[1:] 317 | } 318 | if formDomainModel && pname != "" { 319 | attrName = pname + "." + attrName 320 | } 321 | } 322 | parseTag(attrName, f.Type, stag, formDomainModel, formatedTags) 323 | } 324 | 325 | } else if kind == reflect.Slice { 326 | parseTag(pname, ptype.Elem(), tag, formDomainModel, formatedTags) 327 | } else { 328 | if len(tag) > 0 { 329 | if taginfo := parseTagItems(kind, tag); taginfo != nil { 330 | taginfo.Name = pname 331 | formatedTags[pname] = taginfo 332 | } 333 | } 334 | } 335 | 336 | } 337 | 338 | func parseTotagitemmap(tag string) (string, map[string]string) { 339 | items := strings.Split(tag, ",") 340 | if len(items) == 0 { 341 | panic("tag is empty or format error, tag:" + tag) 342 | } 343 | tagitemmap := make(map[string]string) 344 | for _, item := range items[1:] { 345 | pair := strings.SplitN(item, ":", 2) 346 | name := strings.TrimSpace(pair[0]) 347 | 348 | hasval, ok := supportedTagitems[name] 349 | if !ok { 350 | panic("tag item is not supported, item:" + name + ", tag:" + tag) 351 | } 352 | if hasval && (len(pair) < 2 || pair[1] == "") { 353 | panic("tag item (" + name + ") must have value, tag:" + tag) 354 | } 355 | 356 | if len(pair) > 1 { 357 | tagitemmap[name] = pair[1] 358 | } else { 359 | tagitemmap[name] = "" 360 | } 361 | } 362 | return strings.TrimSpace(items[0]), tagitemmap 363 | } 364 | 365 | func parseTagItems(kind reflect.Kind, tag string) *tagInfo { 366 | 367 | name, tagitemmap := parseTotagitemmap(tag) 368 | if name == "" { 369 | panic("tag name is empty, tag:" + tag) 370 | } 371 | 372 | tagInfo := &tagInfo{Name: name} 373 | if limit, ok := tagitemmap["limit"]; ok { 374 | tagInfo.Limit.Value = parseInt(limit) 375 | tagInfo.Limit.Exist = true 376 | } 377 | 378 | if _, ok := tagitemmap["require"]; ok { 379 | tagInfo.Require = true 380 | } 381 | 382 | if scope, ok := tagitemmap["scope"]; ok { 383 | scope = strings.TrimSpace(scope) 384 | scope = strings.Trim(scope, "[]") 385 | tagInfo.Scope.Items = parseScopeTag(strings.Split(scope, " "), kind) 386 | tagInfo.Scope.Exist = true 387 | tagInfo.Scope.expr = scope 388 | } 389 | 390 | if pattern, ok := tagitemmap["pattern"]; ok { 391 | var err error 392 | tagInfo.Pattern.Regexp, err = regexp.Compile(pattern) 393 | if err != nil { 394 | panic(err) 395 | } 396 | tagInfo.Pattern.Exist = true 397 | } 398 | 399 | if def, ok := tagitemmap["default"]; ok { 400 | defvalue, err := parseValue(def, kind, nil) //tagInfo.Default.Value, err = parseValue(def, kind) 401 | if err != nil { 402 | panic(err) 403 | } 404 | tagInfo.Default.Value = *defvalue 405 | tagInfo.Default.Exist = true 406 | 407 | if tagInfo.Scope.Exist && !tagInfo.Scope.Items.check(tagInfo.Default.Value.Interface()) { 408 | panic(fmt.Sprintf("default:%v is not in scope:%s", tagInfo.Default.Value, tagInfo.Scope.expr)) 409 | } 410 | 411 | if tagInfo.Pattern.Exist && !tagInfo.Pattern.Regexp.MatchString(def) { 412 | panic(fmt.Sprintf("default:%s cannot match pattern:%s", def, tagInfo.Pattern.Regexp.String())) 413 | } 414 | 415 | } 416 | 417 | if layout, ok := tagitemmap["layout"]; ok { 418 | tagInfo.Layout.Terms = strings.Split(layout, "|") 419 | tagInfo.Layout.Exist = true 420 | } 421 | 422 | tagInfo.TypeKind = kind 423 | 424 | return tagInfo 425 | } 426 | 427 | //func parseTagItems_old(typ reflect.Type, tag string) *tagInfo { 428 | 429 | // items := strings.Split(tag, ",") 430 | // if len(items) == 0 { 431 | // panic("tag is empty or format error, tag:" + tag) 432 | // } 433 | 434 | // var err error 435 | // tagInfo := &tagInfo{} 436 | // if limitstr, ok := findTag("limit:", items); ok { 437 | // limitstr = strings.TrimLeftFunc(limitstr, unicode.IsSpace) 438 | // tagInfo.Limit.Value = parseInt(limitstr[6:]) 439 | // tagInfo.Limit.Exist = true 440 | // } 441 | 442 | // if _, ok := findTag("require", items); ok { 443 | // tagInfo.Require = true 444 | // } 445 | 446 | // if scopestr, ok := findTag("scope:", items); ok { 447 | // scopestr = strings.TrimLeftFunc(scopestr, unicode.IsSpace) 448 | // scopestr = strings.Trim(scopestr[6:], "[]") 449 | // scopes := strings.Split(scopestr, " ") 450 | // tagInfo.Scope.Items = parseScopeTag(scopes, typ) 451 | // tagInfo.Scope.Exist = true 452 | // tagInfo.Scope.expr = scopestr 453 | // } 454 | 455 | // if patternstr, ok := findTag("pattern:", items); ok { 456 | // patternstr = strings.TrimLeftFunc(patternstr, unicode.IsSpace) 457 | // tagInfo.Pattern.Regexp, err = regexp.Compile(patternstr[8:]) 458 | // tagInfo.Pattern.Exist = true 459 | // } 460 | 461 | // if defstr, ok := findTag("default:", items); ok { 462 | // defstr = strings.TrimLeftFunc(defstr, unicode.IsSpace) 463 | // defstr = defstr[8:] 464 | // tagInfo.Default.Value, err = parseValue(defstr, typ) 465 | // tagInfo.Default.Exist = true 466 | 467 | // if tagInfo.Scope.Exist && !tagInfo.Scope.Items.check(tagInfo.Default.Value.Interface()) { 468 | // panic(fmt.Sprintf("default:%v is not in scope:%s", tagInfo.Default.Value, tagInfo.Scope.expr)) 469 | // } 470 | 471 | // if tagInfo.Pattern.Exist && !tagInfo.Pattern.Regexp.MatchString(defstr) { 472 | // panic(fmt.Sprintf("default:%s cannot match pattern:%s", defstr, tagInfo.Pattern.Regexp.String())) 473 | // } 474 | 475 | // } 476 | 477 | // if err != nil { 478 | // panic(err) 479 | // } 480 | 481 | // tagInfo.Type = typ 482 | 483 | // return tagInfo 484 | //} 485 | 486 | func parseScopeTag(scopes []string, kind reflect.Kind) scopeItems { 487 | var items []scopeItem 488 | var err error 489 | switch kind { 490 | case reflect.String: 491 | items = parseStringScope(scopes) 492 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 493 | items, err = parseIntScope(scopes) 494 | if err != nil { 495 | panic(err) 496 | } 497 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 498 | items, err = parseUintScope(scopes) 499 | if err != nil { 500 | panic(err) 501 | } 502 | case reflect.Float32, reflect.Float64: 503 | items, err = parseFloatScope(scopes) 504 | if err != nil { 505 | panic(err) 506 | } 507 | // case reflect.Slice: 508 | // sliceType := typ.Elem() 509 | // if reflect.Struct == sliceType.Kind() { 510 | // err = errors.New("the parameter type(" + sliceType.String() + ") is not supported") 511 | // } else { 512 | // items = parseScopeTag(scopes, sliceType) 513 | // } 514 | 515 | default: 516 | panic("the type(" + kind.String() + ") parameter scope tag is not supported") 517 | } 518 | 519 | return items 520 | } 521 | 522 | func findTag(name string, items []string) (string, bool) { 523 | for _, v := range items { 524 | if strings.Contains(v, name) { 525 | return v, true 526 | } 527 | } 528 | return "", false 529 | } 530 | 531 | func parseInt(s string) int { 532 | v, err := strconv.Atoi(s) 533 | if err != nil { 534 | panic(err) 535 | } 536 | return v 537 | } 538 | 539 | const scopeDataTypeEqual = 1 540 | const scopeDataTypeLessthan = 2 541 | const scopeDataTypeGreaterthan = 3 542 | const scopeDataTypeBetween = 4 543 | 544 | //scope tag 545 | //[1 2 3] or [1~100] or [0~] or [~0] or [100~] or [~100] or [~-100 -20~-10 -1 0 1 2 3 10~20 100~] 546 | func parseScope(scope []string, 547 | parseData func(sv string) (interface{}, error), 548 | buildScopeItem func(typ int8, v ...interface{}) scopeItem) ([]scopeItem, error) { 549 | 550 | items := make([]scopeItem, 0, len(scope)) 551 | for _, s := range scope { 552 | if len(s) == 0 { 553 | continue 554 | } 555 | parts := strings.SplitN(s, "~", 2) 556 | if len(parts) == 1 { 557 | v, err := parseData(parts[0]) 558 | if err != nil { 559 | return nil, err 560 | } 561 | items = append(items, buildScopeItem(scopeDataTypeEqual, v)) 562 | } else { //if len(parts) == 2 { 563 | if len(parts[0]) == 0 { 564 | v, err := parseData(parts[1]) 565 | if err != nil { 566 | return nil, err 567 | } 568 | items = append(items, buildScopeItem(scopeDataTypeLessthan, v)) 569 | } else if len(parts[1]) == 0 { 570 | v, err := parseData(parts[0]) 571 | if err != nil { 572 | return nil, err 573 | } 574 | items = append(items, buildScopeItem(scopeDataTypeGreaterthan, v)) 575 | } else { //len(parts[0]) > 0 && len(parts[1]) > 0 576 | start, err := parseData(parts[0]) 577 | if err != nil { 578 | return nil, err 579 | } 580 | end, err := parseData(parts[1]) 581 | if err != nil { 582 | return nil, err 583 | } 584 | items = append(items, buildScopeItem(scopeDataTypeBetween, start, end)) 585 | } 586 | } 587 | } 588 | 589 | return items, nil 590 | } 591 | 592 | func parseIntScope(scope []string) ([]scopeItem, error) { 593 | return parseScope(scope, func(sv string) (interface{}, error) { 594 | v, err := strconv.ParseInt(sv, 10, 64) 595 | if err != nil { 596 | return nil, err 597 | } 598 | return v, nil 599 | }, func(typ int8, v ...interface{}) scopeItem { 600 | switch typ { 601 | case scopeDataTypeEqual: 602 | return intEqualScopeItem(v[0].(int64)) 603 | case scopeDataTypeLessthan: 604 | return intLessthanScopeItem(v[0].(int64)) 605 | case scopeDataTypeGreaterthan: 606 | return intGreaterthanScopeItem(v[0].(int64)) 607 | case scopeDataTypeBetween: 608 | return intBetweenScopeItem{v[0].(int64), v[1].(int64)} 609 | } 610 | panic("type is unsupported") 611 | }) 612 | } 613 | 614 | func parseUintScope(scope []string) ([]scopeItem, error) { 615 | return parseScope(scope, func(sv string) (interface{}, error) { 616 | v, err := strconv.ParseUint(sv, 10, 64) 617 | if err != nil { 618 | return nil, err 619 | } 620 | return v, nil 621 | }, func(typ int8, v ...interface{}) scopeItem { 622 | switch typ { 623 | case scopeDataTypeEqual: 624 | return uintEqualScopeItem(v[0].(uint64)) 625 | case scopeDataTypeLessthan: 626 | return uintLessthanScopeItem(v[0].(uint64)) 627 | case scopeDataTypeGreaterthan: 628 | return uintGreaterthanScopeItem(v[0].(uint64)) 629 | case scopeDataTypeBetween: 630 | return uintBetweenScopeItem{v[0].(uint64), v[1].(uint64)} 631 | } 632 | panic("type is unsupported") 633 | }) 634 | } 635 | 636 | func parseFloatScope(scope []string) ([]scopeItem, error) { 637 | return parseScope(scope, func(sv string) (interface{}, error) { 638 | v, err := strconv.ParseFloat(sv, 64) 639 | if err != nil { 640 | return nil, err 641 | } 642 | return v, nil 643 | }, func(typ int8, v ...interface{}) scopeItem { 644 | switch typ { 645 | case scopeDataTypeEqual: 646 | return floatEqualScopeItem(v[0].(float64)) 647 | case scopeDataTypeLessthan: 648 | return floatLessthanScopeItem(v[0].(float64)) 649 | case scopeDataTypeGreaterthan: 650 | return floatGreaterthanScopeItem(v[0].(float64)) 651 | case scopeDataTypeBetween: 652 | return floatBetweenScopeItem{v[0].(float64), v[1].(float64)} 653 | } 654 | panic("type is unsupported") 655 | }) 656 | } 657 | 658 | func parseStringScope(scope []string) []scopeItem { 659 | return []scopeItem{stringScopeItems(scope)} 660 | } 661 | 662 | type scopeItem interface { 663 | check(v interface{}) bool 664 | } 665 | 666 | type scopeItems []scopeItem 667 | 668 | func (this scopeItems) check(v interface{}) bool { 669 | for _, item := range []scopeItem(this) { 670 | if item.check(v) { 671 | return true 672 | } 673 | } 674 | return false 675 | } 676 | 677 | //string 678 | type stringScopeItems []string 679 | 680 | func (this stringScopeItems) check(v interface{}) bool { 681 | for _, s := range []string(this) { 682 | if s == v.(string) { 683 | return true 684 | } 685 | } 686 | return false 687 | } 688 | 689 | //int 690 | type intEqualScopeItem int64 691 | 692 | func (this intEqualScopeItem) check(v interface{}) bool { 693 | return int64(this) == toInt64(v) 694 | } 695 | 696 | type intBetweenScopeItem struct { 697 | Start int64 698 | End int64 699 | } 700 | 701 | func (this intBetweenScopeItem) check(v interface{}) bool { 702 | vi64 := toInt64(v) //reflect.ValueOf(v).Int() 703 | return vi64 >= this.Start && vi64 <= this.End 704 | } 705 | 706 | type intLessthanScopeItem int64 707 | 708 | func (this intLessthanScopeItem) check(v interface{}) bool { 709 | return toInt64(v) <= int64(this) 710 | } 711 | 712 | type intGreaterthanScopeItem int64 713 | 714 | func (this intGreaterthanScopeItem) check(v interface{}) bool { 715 | return toInt64(v) >= int64(this) 716 | } 717 | 718 | //uint 719 | type uintEqualScopeItem uint64 720 | 721 | func (this uintEqualScopeItem) check(v interface{}) bool { 722 | return uint64(this) == toUint64(v) //reflect.ValueOf(v).Uint() 723 | } 724 | 725 | type uintBetweenScopeItem struct { 726 | Start uint64 727 | End uint64 728 | } 729 | 730 | func (this uintBetweenScopeItem) check(v interface{}) bool { 731 | vu64 := toUint64(v) //reflect.ValueOf(v).Uint() 732 | return vu64 >= this.Start && vu64 <= this.End 733 | } 734 | 735 | type uintLessthanScopeItem uint64 736 | 737 | func (this uintLessthanScopeItem) check(v interface{}) bool { 738 | return toUint64(v) <= uint64(this) 739 | } 740 | 741 | type uintGreaterthanScopeItem uint64 742 | 743 | func (this uintGreaterthanScopeItem) check(v interface{}) bool { 744 | return toUint64(v) >= uint64(this) 745 | } 746 | 747 | //float 748 | type floatEqualScopeItem float64 749 | 750 | func (this floatEqualScopeItem) check(v interface{}) bool { 751 | return float64(this) == toFloat64(v) //reflect.ValueOf(v).Float() 752 | } 753 | 754 | type floatBetweenScopeItem struct { 755 | Start float64 756 | End float64 757 | } 758 | 759 | func (this floatBetweenScopeItem) check(v interface{}) bool { 760 | vf64 := toFloat64(v) //reflect.ValueOf(v).Float() 761 | return vf64 >= this.Start && vf64 <= this.End 762 | } 763 | 764 | type floatLessthanScopeItem float64 765 | 766 | func (this floatLessthanScopeItem) check(v interface{}) bool { 767 | return toFloat64(v) <= float64(this) 768 | } 769 | 770 | type floatGreaterthanScopeItem float64 771 | 772 | func (this floatGreaterthanScopeItem) check(v interface{}) bool { 773 | return toFloat64(v) >= float64(this) 774 | } 775 | -------------------------------------------------------------------------------- /tag_test.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func Test_ParseTag(t *testing.T) { 10 | //"name,limit:20,scope:[1 2 3],default:1,require,pattern:regex" 11 | var str string 12 | var i64 int64 13 | var f64 float64 14 | 15 | taginfos := make(Taginfos) 16 | taginfos.Parse(reflect.String, `field:"teststr,limit:10,scope:[One Two Three],default:Two,require"`) 17 | 18 | strTaginfo := taginfos.Get("teststr") //parseOneTag(reflect.String, `field:"teststr,limit:10,scope:[One Two Three],default:Two,require"`) 19 | 20 | if !strTaginfo.Default.Exist || strTaginfo.Default.Value.String() != "Two" { 21 | t.Errorf("string tag: default value does not exist, must exist and value is Two") 22 | } 23 | 24 | if !strTaginfo.Limit.Exist || strTaginfo.Limit.Value != 10 { 25 | t.Errorf("string tag: limit does not exist, must exist and value is 10") 26 | } 27 | 28 | if !strTaginfo.Require { 29 | t.Errorf("string tag: require not is true ") 30 | } 31 | 32 | if !strTaginfo.Scope.Exist || !(strTaginfo.Scope.Items.check("One") && strTaginfo.Scope.Items.check("Two") && strTaginfo.Scope.Items.check("Three")) { 33 | t.Errorf("string tag: scope does not exist, must exist and value is [One Two Three]") 34 | } 35 | 36 | str = "Four" 37 | if err := strTaginfo.Check(str); err == nil { 38 | t.Errorf("string tag: %v", err) 39 | } 40 | 41 | str = "" 42 | if err := strTaginfo.Check(str); err == nil { 43 | t.Errorf("string tag: value is empty, tag is require") 44 | } 45 | 46 | str = "Three" 47 | var str2 string 48 | if err := strTaginfo.Check(str, &str2); err != nil || str2 != str { 49 | t.Errorf("string tag: %v, str2:%v, str:%v", err, str2, str) 50 | } 51 | 52 | taginfos.Parse(reflect.Int64, "testi64,limit:3,scope:[~-100 -2 -1 0 1 2 100~],default:1,require") 53 | i64Taginfo := taginfos.Get("testi64") 54 | 55 | if err := i64Taginfo.Check(""); err == nil || err.Error() != "tag require: value is empty, tag:testi64(int64)" { 56 | t.Errorf("int64 tag: %v", err) 57 | } 58 | 59 | if err := i64Taginfo.Check("5000"); err == nil || err.Error() != "tag limit: value is too long, limit:3, tag:testi64(int64)" { 60 | t.Errorf("int64 tag: %v", err) 61 | } 62 | if err := i64Taginfo.Check(int16(5000)); err == nil || err.Error() != "tag limit: value is too long, limit:3, tag:testi64(int64)" { 63 | t.Errorf("int64 tag: %v", err) 64 | } 65 | 66 | if err := i64Taginfo.Check("6"); err == nil || err.Error() != "tag scope: value is not in scope:[~-100 -2 -1 0 1 2 100~], tag:testi64(int64)" { 67 | t.Errorf("int64 tag: %v", err) 68 | } 69 | if err := i64Taginfo.Check(int8(6)); err == nil || err.Error() != "tag scope: value is not in scope:[~-100 -2 -1 0 1 2 100~], tag:testi64(int64)" { 70 | t.Errorf("int64 tag: %v", err) 71 | } 72 | 73 | if err := i64Taginfo.Check("1"); err != nil { 74 | t.Errorf("int64 tag: %v", err) 75 | } 76 | if err := i64Taginfo.Check(int32(1)); err != nil { 77 | t.Errorf("int64 tag: %v", err) 78 | } 79 | if err := i64Taginfo.Check(int64(999)); err != nil { 80 | t.Errorf("int64 tag: %v", err) 81 | } 82 | 83 | if err := i64Taginfo.Check("300", &i64); err != nil || i64 != 300 { 84 | t.Errorf("int64 tag: %v", err) 85 | } 86 | 87 | if err := i64Taginfo.Check(int64(999), &i64); err != nil || i64 != 999 { 88 | t.Errorf("int64 tag: %v", err) 89 | } 90 | 91 | var i16 int16 92 | if err := i64Taginfo.Check(250, &i16); err != nil || i16 != 250 { 93 | t.Errorf("int64 tag: %v", err) 94 | } 95 | 96 | taginfos.Parse(reflect.Float64, "testf64,limit:6,scope:[100.10 200.20~500.50],default:250.25") 97 | f64Taginfo := taginfos.Get("testf64") 98 | 99 | if err := f64Taginfo.Check("", &f64); err != nil || f64 != 250.25 { 100 | t.Errorf("float64 tag: %v, f64:%v", err, f64) 101 | } 102 | 103 | if err := f64Taginfo.Check("443.45", &f64); err != nil || f64 != 443.45 { 104 | t.Errorf("float64 tag: %v, f64:%v", err, f64) 105 | } 106 | 107 | if err := f64Taginfo.Check("A443.4", &f64); err == nil || err.Error() != "strconv.ParseFloat: parsing \"A443.4\": invalid syntax" { 108 | t.Errorf("float64 tag: %v, f64:%v", err, f64) 109 | } 110 | 111 | if err := f64Taginfo.Check(float32(343.68), &f64); err != nil || f64 != 343.68 { 112 | t.Errorf("float64 tag: %v, f64:%v", err, f64) 113 | } 114 | 115 | if err := f64Taginfo.Check(int(235), &f64); err != nil || f64 != 235 { 116 | t.Errorf("float64 tag: %v, f64:%v", err, f64) 117 | } 118 | 119 | taginfos.Parse(reflect.String, "email,limit:30,require,pattern:\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*") 120 | emailTaginfo := taginfos.Get("email") 121 | if err := emailTaginfo.Check(""); err == nil || err.Error() != "tag require: value is empty, tag:email(string)" { 122 | t.Errorf("email tag: %v", err) 123 | } 124 | 125 | if err := emailTaginfo.Check("trywen@qq.com", &str); err != nil || str != "trywen@qq.com" { 126 | t.Errorf("email tag: %v, str:%v", err, str) 127 | } 128 | 129 | } 130 | 131 | func Test_ParseStruct(t *testing.T) { 132 | taginfos := make(Taginfos) 133 | 134 | type UserForm struct { 135 | Account string `field:"account,limit:20,require"` 136 | Pwd string `field:"pwd,limit:20,require"` 137 | Name string `field:"name,limit:20"` 138 | Sex int `field:"sex,scope:[1 2 3],default:1"` 139 | Age int `field:"age,scope:[0~200],default:0"` 140 | Email string `field:"email,limit:30,pattern:\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*"` 141 | Stature float32 `field:"stature,scope:[0.0~],default:0.0"` 142 | } 143 | 144 | type QueryUserForm struct { 145 | Status []int `field:"status,scope:[1 2 3 4]"` 146 | Orderby []string 147 | Psize int `field:"psize,scope:[10 20 50 100]"` 148 | Pno int `field:"pno,scope:[1~]"` 149 | } 150 | 151 | taginfos.ParseStruct("user", reflect.TypeOf(UserForm{}), true) 152 | accountTaginfo := taginfos.Get("user.account") 153 | 154 | if accountTaginfo == nil || accountTaginfo.String() != "name:user.account(string) (limit:{{true} 20}, require:true, scope:, default:{{false} }, pattern:{{false} })" { 155 | t.Errorf("ParseStruct error, formDomainModel=true, accountTaginfo:%v", accountTaginfo) 156 | } 157 | sexTaginfo := taginfos.Get("user.sex") 158 | if sexTaginfo == nil || sexTaginfo.String() != "name:user.sex(int) (limit:{{false} 0}, require:false, scope:1 2 3, default:{{true} }, pattern:{{false} })" { 159 | t.Errorf("ParseStruct error, formDomainModel=true, sexTaginfo:%v", sexTaginfo) 160 | } 161 | 162 | emailTaginfo := taginfos.Get("user.email") 163 | if emailTaginfo == nil { 164 | t.Errorf("ParseStruct error, formDomainModel=true, emailTaginfo:%v", emailTaginfo) 165 | } 166 | if err := emailTaginfo.Check("trywen@qq.com"); err != nil { 167 | t.Errorf("ParseStruct error, err:%v", err) 168 | } 169 | if err := emailTaginfo.Check("trywen@qq"); err == nil || !strings.HasPrefix(err.Error(), "tag pattern: pattern match fail!") { 170 | t.Errorf("ParseStruct error, err:%v", err) 171 | } 172 | 173 | taginfos.ParseStruct("user", reflect.TypeOf(QueryUserForm{}), true) 174 | statusTaginfo := taginfos.Get("user.status") 175 | if statusTaginfo == nil || statusTaginfo.String() != "name:user.status(int) (limit:{{false} 0}, require:false, scope:1 2 3 4, default:{{false} }, pattern:{{false} })" { 176 | t.Errorf("ParseStruct error, formDomainModel=true, statusTaginfo:%v", statusTaginfo) 177 | } 178 | 179 | var status int 180 | if err := statusTaginfo.Check("2", &status); err != nil || status != 2 { 181 | t.Errorf("ParseStruct error, err:%v", err) 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /template.go: -------------------------------------------------------------------------------- 1 | //Initial code source, @see https://github.com/astaxie/beego/blob/master/template.go 2 | 3 | package trygo 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "html/template" 9 | "log" 10 | "os" 11 | // "path" 12 | "io" 13 | "io/ioutil" 14 | "path/filepath" 15 | "regexp" 16 | "strings" 17 | "sync" 18 | ) 19 | 20 | func executeTemplate(app *App, wr io.Writer, name string, data interface{}) error { 21 | if app.Config.RunMode == DEV { 22 | app.TemplateRegister.templatesLock.RLock() 23 | defer app.TemplateRegister.templatesLock.RUnlock() 24 | } 25 | if t, ok := app.TemplateRegister.Templates[name]; ok { 26 | var err error 27 | if t.Lookup(name) != nil { 28 | err = t.ExecuteTemplate(wr, name, data) 29 | } else { 30 | err = t.Execute(wr, data) 31 | } 32 | if err != nil { 33 | log.Println("template Execute err:", err) 34 | } 35 | return err 36 | } 37 | panic("can't find templatefile in the path:" + name) 38 | } 39 | 40 | type TemplateRegister struct { 41 | app *App 42 | tplFuncMap template.FuncMap 43 | Templates map[string]*template.Template 44 | TemplateExt []string 45 | templatesLock sync.RWMutex 46 | 47 | templateEngines map[string]templatePreProcessor 48 | } 49 | 50 | func NewTemplateRegister(app *App) *TemplateRegister { 51 | tr := &TemplateRegister{app: app} 52 | tr.Templates = make(map[string]*template.Template) 53 | tr.tplFuncMap = make(template.FuncMap) 54 | tr.TemplateExt = make([]string, 0) 55 | tr.TemplateExt = append(tr.TemplateExt, "tpl", "html") 56 | tr.templateEngines = map[string]templatePreProcessor{} 57 | //beegoTplFuncMap = make(template.FuncMap) 58 | 59 | tr.tplFuncMap["dateformat"] = dateFormat 60 | tr.tplFuncMap["date"] = date 61 | tr.tplFuncMap["compare"] = compare 62 | tr.tplFuncMap["compare_not"] = compareNot 63 | tr.tplFuncMap["not_nil"] = notNil 64 | tr.tplFuncMap["not_null"] = notNil 65 | tr.tplFuncMap["substr"] = substr 66 | tr.tplFuncMap["html2str"] = html2str 67 | tr.tplFuncMap["str2html"] = str2html 68 | tr.tplFuncMap["htmlquote"] = htmlquote 69 | tr.tplFuncMap["htmlunquote"] = htmlunquote 70 | tr.tplFuncMap["renderform"] = renderForm 71 | tr.tplFuncMap["assets_js"] = assetsJs 72 | tr.tplFuncMap["assets_css"] = assetsCSS 73 | //tr.tplFuncMap["config"] = GetConfig 74 | tr.tplFuncMap["map_get"] = mapGet 75 | 76 | // Comparisons 77 | tr.tplFuncMap["eq"] = eq // == 78 | tr.tplFuncMap["ge"] = ge // >= 79 | tr.tplFuncMap["gt"] = gt // > 80 | tr.tplFuncMap["le"] = le // <= 81 | tr.tplFuncMap["lt"] = lt // < 82 | tr.tplFuncMap["ne"] = ne // != 83 | 84 | return tr 85 | } 86 | 87 | // AddFuncMap let user to register a func in the template 88 | func (this *TemplateRegister) AddFuncMap(key string, funname interface{}) error { 89 | if _, ok := this.tplFuncMap[key]; ok { 90 | return errors.New("funcmap already has the key") 91 | } 92 | this.tplFuncMap[key] = funname 93 | return nil 94 | } 95 | 96 | type templatePreProcessor func(root, path string, funcs template.FuncMap) (*template.Template, error) 97 | 98 | type templatefile struct { 99 | root string 100 | files map[string][]string 101 | } 102 | 103 | func (tf *templatefile) visit(tr *TemplateRegister, paths string, f os.FileInfo, err error) error { 104 | if f == nil { 105 | return err 106 | } 107 | if f.IsDir() || (f.Mode()&os.ModeSymlink) > 0 { 108 | return nil 109 | } 110 | if !hasTemplateExt(tr, paths) { 111 | return nil 112 | } 113 | 114 | replace := strings.NewReplacer("\\", "/") 115 | file := strings.TrimLeft(replace.Replace(paths[len(tf.root):]), "/") 116 | subDir := filepath.Dir(file) 117 | 118 | tf.files[subDir] = append(tf.files[subDir], file) 119 | return nil 120 | } 121 | 122 | func hasTemplateExt(tr *TemplateRegister, paths string) bool { 123 | for _, v := range tr.TemplateExt { 124 | if strings.HasSuffix(paths, "."+v) { 125 | return true 126 | } 127 | } 128 | return false 129 | } 130 | 131 | //func (self *templatefile) visit(tr *TemplateRegister, paths string, f os.FileInfo, err error) error { 132 | // if f == nil { 133 | // return err 134 | // } 135 | // if f.IsDir() { 136 | // return nil 137 | // } else if (f.Mode() & os.ModeSymlink) > 0 { 138 | // return nil 139 | // } else { 140 | // hasExt := false 141 | // for _, v := range tr.TemplateExt { 142 | // if strings.HasSuffix(paths, v) { 143 | // hasExt = true 144 | // break 145 | // } 146 | // } 147 | // if hasExt { 148 | // replace := strings.NewReplacer("\\", "/") 149 | // a := []byte(paths) 150 | // a = a[len([]byte(self.root)):] 151 | // fmt.Println("a:", string(a)) 152 | // subdir := path.Dir(strings.TrimLeft(replace.Replace(string(a)), "/")) 153 | // fmt.Println("subdir:", (subdir)) 154 | // if _, ok := self.files[subdir]; ok { 155 | // self.files[subdir] = append(self.files[subdir], paths) 156 | // } else { 157 | // m := make([]string, 1) 158 | // m[0] = paths 159 | // self.files[subdir] = m 160 | // } 161 | 162 | // } 163 | // } 164 | // return nil 165 | //} 166 | 167 | func (this *TemplateRegister) AddTemplateExt(ext string) { 168 | for _, v := range this.TemplateExt { 169 | if v == ext { 170 | return 171 | } 172 | } 173 | this.TemplateExt = append(this.TemplateExt, ext) 174 | } 175 | 176 | func (this *TemplateRegister) buildTemplate(dir string, files ...string) error { 177 | if _, err := os.Stat(dir); err != nil { 178 | if os.IsNotExist(err) { 179 | return nil 180 | } 181 | return errors.New("dir open err") 182 | } 183 | self := &templatefile{ 184 | root: dir, 185 | files: make(map[string][]string), 186 | } 187 | err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { 188 | return self.visit(this, path, f, err) 189 | }) 190 | if err != nil { 191 | fmt.Printf("filepath.Walk() returned %v\n", err) 192 | return err 193 | } 194 | 195 | buildAllFiles := len(files) == 0 196 | for _, v := range self.files { 197 | for _, file := range v { 198 | if buildAllFiles || inSlice(file, files) { 199 | func() { 200 | this.templatesLock.Lock() 201 | defer this.templatesLock.Unlock() 202 | ext := filepath.Ext(file) 203 | var t *template.Template 204 | if len(ext) == 0 { 205 | t, err = this.getTemplate(self.root, file, v...) 206 | } else if fn, ok := this.templateEngines[ext[1:]]; ok { 207 | t, err = fn(self.root, file, this.tplFuncMap) 208 | } else { 209 | t, err = this.getTemplate(self.root, file, v...) 210 | } 211 | if err != nil { 212 | //logs.Trace("parse template err:", file, err) 213 | log.Println("parse template err:", file, err) 214 | } else { 215 | this.Templates[file] = t 216 | } 217 | //this.templatesLock.Unlock() 218 | }() 219 | } 220 | } 221 | } 222 | return nil 223 | } 224 | 225 | //func (this *TemplateRegister) buildTemplate(dir string) error { 226 | // if _, err := os.Stat(dir); err != nil { 227 | // if os.IsNotExist(err) { 228 | // return err 229 | // } else { 230 | // return errors.New("dir open error") 231 | // } 232 | // } 233 | // self := templatefile{ 234 | // root: dir, 235 | // files: make(map[string][]string), 236 | // } 237 | // err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { 238 | // fmt.Println(dir, path) 239 | // return self.visit(this, path, f, err) 240 | // }) 241 | // if err != nil { 242 | // this.app.Logger.Error("filepath.Walk() returned %v", err) 243 | // return err 244 | // } 245 | // for k, v := range self.files { 246 | // this.Templates[k] = template.Must(template.New("template" + k).Funcs(this.tplFuncMap).ParseFiles(v...)) 247 | // } 248 | // return nil 249 | //} 250 | 251 | func (this *TemplateRegister) getTemplate(root, file string, others ...string) (t *template.Template, err error) { 252 | t = template.New(file).Delims(this.app.Config.TemplateLeft, this.app.Config.TemplateRight).Funcs(this.tplFuncMap) 253 | var subMods [][]string 254 | t, subMods, err = this.getTplDeep(root, file, "", t) 255 | if err != nil { 256 | return nil, err 257 | } 258 | t, err = this._getTemplate(t, root, subMods, others...) 259 | 260 | if err != nil { 261 | return nil, err 262 | } 263 | return 264 | } 265 | 266 | func (this *TemplateRegister) _getTemplate(t0 *template.Template, root string, subMods [][]string, others ...string) (t *template.Template, err error) { 267 | t = t0 268 | for _, m := range subMods { 269 | if len(m) == 2 { 270 | tpl := t.Lookup(m[1]) 271 | if tpl != nil { 272 | continue 273 | } 274 | //first check filename 275 | for _, otherFile := range others { 276 | if otherFile == m[1] { 277 | var subMods1 [][]string 278 | t, subMods1, err = this.getTplDeep(root, otherFile, "", t) 279 | if err != nil { 280 | //logs.Trace("template parse file err:", err) 281 | log.Println("template parse file err:", err) 282 | } else if subMods1 != nil && len(subMods1) > 0 { 283 | t, err = this._getTemplate(t, root, subMods1, others...) 284 | } 285 | break 286 | } 287 | } 288 | //second check define 289 | for _, otherFile := range others { 290 | fileAbsPath := filepath.Join(root, otherFile) 291 | data, err := ioutil.ReadFile(fileAbsPath) 292 | if err != nil { 293 | continue 294 | } 295 | reg := regexp.MustCompile(this.app.Config.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"") 296 | allSub := reg.FindAllStringSubmatch(string(data), -1) 297 | for _, sub := range allSub { 298 | if len(sub) == 2 && sub[1] == m[1] { 299 | var subMods1 [][]string 300 | t, subMods1, err = this.getTplDeep(root, otherFile, "", t) 301 | if err != nil { 302 | log.Println("template parse file err:", err) 303 | } else if subMods1 != nil && len(subMods1) > 0 { 304 | t, err = this._getTemplate(t, root, subMods1, others...) 305 | } 306 | break 307 | } 308 | } 309 | } 310 | } 311 | 312 | } 313 | return 314 | } 315 | 316 | func (this *TemplateRegister) getTplDeep(root, file, parent string, t *template.Template) (*template.Template, [][]string, error) { 317 | var fileAbsPath string 318 | if filepath.HasPrefix(file, "../") { 319 | fileAbsPath = filepath.Join(root, filepath.Dir(parent), file) 320 | } else { 321 | fileAbsPath = filepath.Join(root, file) 322 | } 323 | if e := fileExists(fileAbsPath); !e { 324 | panic("can't find template file:" + file) 325 | } 326 | data, err := ioutil.ReadFile(fileAbsPath) 327 | if err != nil { 328 | return nil, [][]string{}, err 329 | } 330 | t, err = t.New(file).Parse(string(data)) 331 | if err != nil { 332 | return nil, [][]string{}, err 333 | } 334 | reg := regexp.MustCompile(this.app.Config.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"") 335 | allSub := reg.FindAllStringSubmatch(string(data), -1) 336 | for _, m := range allSub { 337 | if len(m) == 2 { 338 | tl := t.Lookup(m[1]) 339 | if tl != nil { 340 | continue 341 | } 342 | if !hasTemplateExt(this, m[1]) { 343 | continue 344 | } 345 | t, _, err = this.getTplDeep(root, m[1], file, t) 346 | if err != nil { 347 | return nil, [][]string{}, err 348 | } 349 | } 350 | } 351 | return t, allSub, nil 352 | } 353 | 354 | //func fileExists(name string) bool { 355 | // if _, err := os.Stat(name); err != nil { 356 | // if os.IsNotExist(err) { 357 | // return false 358 | // } 359 | // } 360 | // return true 361 | //} 362 | 363 | func inSlice(v string, sl []string) bool { 364 | for _, vv := range sl { 365 | if vv == v { 366 | return true 367 | } 368 | } 369 | return false 370 | } 371 | -------------------------------------------------------------------------------- /templatefunc.go: -------------------------------------------------------------------------------- 1 | //Initial code source, @see https://github.com/astaxie/beego/blob/master/templatefunc.go 2 | package trygo 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "html/template" 8 | "net/url" 9 | "reflect" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | // Substr returns the substr from start to length. 17 | func substr(s string, start, length int) string { 18 | bt := []rune(s) 19 | if start < 0 { 20 | start = 0 21 | } 22 | if start > len(bt) { 23 | start = start % len(bt) 24 | } 25 | var end int 26 | if (start + length) > (len(bt) - 1) { 27 | end = len(bt) 28 | } else { 29 | end = start + length 30 | } 31 | return string(bt[start:end]) 32 | } 33 | 34 | // HTML2str returns escaping text convert from html. 35 | func html2str(html string) string { 36 | src := string(html) 37 | 38 | re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") 39 | src = re.ReplaceAllStringFunc(src, strings.ToLower) 40 | 41 | //remove STYLE 42 | re, _ = regexp.Compile("\\") 43 | src = re.ReplaceAllString(src, "") 44 | 45 | //remove SCRIPT 46 | re, _ = regexp.Compile("\\") 47 | src = re.ReplaceAllString(src, "") 48 | 49 | re, _ = regexp.Compile("\\<[\\S\\s]+?\\>") 50 | src = re.ReplaceAllString(src, "\n") 51 | 52 | re, _ = regexp.Compile("\\s{2,}") 53 | src = re.ReplaceAllString(src, "\n") 54 | 55 | return strings.TrimSpace(src) 56 | } 57 | 58 | // DateFormat takes a time and a layout string and returns a string with the formatted date. Used by the template parser as "dateformat" 59 | func dateFormat(t time.Time, layout string) (datestring string) { 60 | datestring = t.Format(layout) 61 | return 62 | } 63 | 64 | // DateFormat pattern rules. 65 | var datePatterns = []string{ 66 | // year 67 | "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 68 | "y", "06", //A two digit representation of a year Examples: 99 or 03 69 | 70 | // month 71 | "m", "01", // Numeric representation of a month, with leading zeros 01 through 12 72 | "n", "1", // Numeric representation of a month, without leading zeros 1 through 12 73 | "M", "Jan", // A short textual representation of a month, three letters Jan through Dec 74 | "F", "January", // A full textual representation of a month, such as January or March January through December 75 | 76 | // day 77 | "d", "02", // Day of the month, 2 digits with leading zeros 01 to 31 78 | "j", "2", // Day of the month without leading zeros 1 to 31 79 | 80 | // week 81 | "D", "Mon", // A textual representation of a day, three letters Mon through Sun 82 | "l", "Monday", // A full textual representation of the day of the week Sunday through Saturday 83 | 84 | // time 85 | "g", "3", // 12-hour format of an hour without leading zeros 1 through 12 86 | "G", "15", // 24-hour format of an hour without leading zeros 0 through 23 87 | "h", "03", // 12-hour format of an hour with leading zeros 01 through 12 88 | "H", "15", // 24-hour format of an hour with leading zeros 00 through 23 89 | 90 | "a", "pm", // Lowercase Ante meridiem and Post meridiem am or pm 91 | "A", "PM", // Uppercase Ante meridiem and Post meridiem AM or PM 92 | 93 | "i", "04", // Minutes with leading zeros 00 to 59 94 | "s", "05", // Seconds, with leading zeros 00 through 59 95 | 96 | // time zone 97 | "T", "MST", 98 | "P", "-07:00", 99 | "O", "-0700", 100 | 101 | // RFC 2822 102 | "r", time.RFC1123Z, 103 | } 104 | 105 | // DateParse Parse Date use PHP time format. 106 | func DateParse(dateString, format string) (time.Time, error) { 107 | replacer := strings.NewReplacer(datePatterns...) 108 | format = replacer.Replace(format) 109 | return time.ParseInLocation(format, dateString, time.Local) 110 | } 111 | 112 | // Date takes a PHP like date func to Go's time format. 113 | func date(t time.Time, format string) string { 114 | replacer := strings.NewReplacer(datePatterns...) 115 | format = replacer.Replace(format) 116 | return t.Format(format) 117 | } 118 | 119 | // Compare is a quick and dirty comparison function. It will convert whatever you give it to strings and see if the two values are equal. 120 | // Whitespace is trimmed. Used by the template parser as "eq". 121 | func compare(a, b interface{}) (equal bool) { 122 | equal = false 123 | if strings.TrimSpace(fmt.Sprintf("%v", a)) == strings.TrimSpace(fmt.Sprintf("%v", b)) { 124 | equal = true 125 | } 126 | return 127 | } 128 | 129 | // CompareNot !Compare 130 | func compareNot(a, b interface{}) (equal bool) { 131 | return !compare(a, b) 132 | } 133 | 134 | // NotNil the same as CompareNot 135 | func notNil(a interface{}) (isNil bool) { 136 | return compareNot(a, nil) 137 | } 138 | 139 | // GetConfig get the Appconfig 140 | //func GetConfig(returnType, key string, defaultVal interface{}) (value interface{}, err error) { 141 | // switch returnType { 142 | // case "String": 143 | // value = AppConfig.String(key) 144 | // case "Bool": 145 | // value, err = AppConfig.Bool(key) 146 | // case "Int": 147 | // value, err = AppConfig.Int(key) 148 | // case "Int64": 149 | // value, err = AppConfig.Int64(key) 150 | // case "Float": 151 | // value, err = AppConfig.Float(key) 152 | // case "DIY": 153 | // value, err = AppConfig.DIY(key) 154 | // default: 155 | // err = errors.New("Config keys must be of type String, Bool, Int, Int64, Float, or DIY") 156 | // } 157 | 158 | // if err != nil { 159 | // if reflect.TypeOf(returnType) != reflect.TypeOf(defaultVal) { 160 | // err = errors.New("defaultVal type does not match returnType") 161 | // } else { 162 | // value, err = defaultVal, nil 163 | // } 164 | // } else if reflect.TypeOf(value).Kind() == reflect.String { 165 | // if value == "" { 166 | // if reflect.TypeOf(defaultVal).Kind() != reflect.String { 167 | // err = errors.New("defaultVal type must be a String if the returnType is a String") 168 | // } else { 169 | // value = defaultVal.(string) 170 | // } 171 | // } 172 | // } 173 | 174 | // return 175 | //} 176 | 177 | // Str2html Convert string to template.HTML type. 178 | func str2html(raw string) template.HTML { 179 | return template.HTML(raw) 180 | } 181 | 182 | // Htmlquote returns quoted html string. 183 | func htmlquote(src string) string { 184 | //HTML编码为实体符号 185 | /* 186 | Encodes `text` for raw use in HTML. 187 | >>> htmlquote("<'&\\">") 188 | '<'&">' 189 | */ 190 | 191 | text := string(src) 192 | 193 | text = strings.Replace(text, "&", "&", -1) // Must be done first! 194 | text = strings.Replace(text, "<", "<", -1) 195 | text = strings.Replace(text, ">", ">", -1) 196 | text = strings.Replace(text, "'", "'", -1) 197 | text = strings.Replace(text, "\"", """, -1) 198 | text = strings.Replace(text, "“", "“", -1) 199 | text = strings.Replace(text, "”", "”", -1) 200 | text = strings.Replace(text, " ", " ", -1) 201 | 202 | return strings.TrimSpace(text) 203 | } 204 | 205 | // Htmlunquote returns unquoted html string. 206 | func htmlunquote(src string) string { 207 | //实体符号解释为HTML 208 | /* 209 | Decodes `text` that's HTML quoted. 210 | >>> htmlunquote('<'&">') 211 | '<\\'&">' 212 | */ 213 | 214 | // strings.Replace(s, old, new, n) 215 | // 在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换 216 | 217 | text := string(src) 218 | text = strings.Replace(text, " ", " ", -1) 219 | text = strings.Replace(text, "”", "”", -1) 220 | text = strings.Replace(text, "“", "“", -1) 221 | text = strings.Replace(text, """, "\"", -1) 222 | text = strings.Replace(text, "'", "'", -1) 223 | text = strings.Replace(text, ">", ">", -1) 224 | text = strings.Replace(text, "<", "<", -1) 225 | text = strings.Replace(text, "&", "&", -1) // Must be done last! 226 | 227 | return strings.TrimSpace(text) 228 | } 229 | 230 | // URLFor returns url string with another registered controller handler with params. 231 | // usage: 232 | // 233 | // URLFor(".index") 234 | // print URLFor("index") 235 | // router /login 236 | // print URLFor("login") 237 | // print URLFor("login", "next","/"") 238 | // router /profile/:username 239 | // print UrlFor("profile", ":username","John Doe") 240 | // result: 241 | // / 242 | // /login 243 | // /login?next=/ 244 | // /user/John%20Doe 245 | // 246 | // more detail http://beego.me/docs/mvc/controller/urlbuilding.md 247 | //func URLFor(endpoint string, values ...interface{}) string { 248 | // return BeeApp.Handlers.URLFor(endpoint, values...) 249 | //} 250 | 251 | // AssetsJs returns script tag with src string. 252 | func assetsJs(src string) template.HTML { 253 | text := string(src) 254 | 255 | text = "" 256 | 257 | return template.HTML(text) 258 | } 259 | 260 | // AssetsCSS returns stylesheet link tag with src string. 261 | func assetsCSS(src string) template.HTML { 262 | text := string(src) 263 | 264 | text = "" 265 | 266 | return template.HTML(text) 267 | } 268 | 269 | // ParseForm will parse form values to struct via tag. 270 | // Support for anonymous struct. 271 | func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) error { 272 | for i := 0; i < objT.NumField(); i++ { 273 | fieldV := objV.Field(i) 274 | if !fieldV.CanSet() { 275 | continue 276 | } 277 | 278 | fieldT := objT.Field(i) 279 | if fieldT.Anonymous && fieldT.Type.Kind() == reflect.Struct { 280 | err := parseFormToStruct(form, fieldT.Type, fieldV) 281 | if err != nil { 282 | return err 283 | } 284 | continue 285 | } 286 | 287 | tags := strings.Split(fieldT.Tag.Get("form"), ",") 288 | var tag string 289 | if len(tags) == 0 || len(tags[0]) == 0 { 290 | tag = fieldT.Name 291 | } else if tags[0] == "-" { 292 | continue 293 | } else { 294 | tag = tags[0] 295 | } 296 | 297 | value := form.Get(tag) 298 | if len(value) == 0 { 299 | continue 300 | } 301 | 302 | switch fieldT.Type.Kind() { 303 | case reflect.Bool: 304 | if strings.ToLower(value) == "on" || strings.ToLower(value) == "1" || strings.ToLower(value) == "yes" { 305 | fieldV.SetBool(true) 306 | continue 307 | } 308 | if strings.ToLower(value) == "off" || strings.ToLower(value) == "0" || strings.ToLower(value) == "no" { 309 | fieldV.SetBool(false) 310 | continue 311 | } 312 | b, err := strconv.ParseBool(value) 313 | if err != nil { 314 | return err 315 | } 316 | fieldV.SetBool(b) 317 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 318 | x, err := strconv.ParseInt(value, 10, 64) 319 | if err != nil { 320 | return err 321 | } 322 | fieldV.SetInt(x) 323 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 324 | x, err := strconv.ParseUint(value, 10, 64) 325 | if err != nil { 326 | return err 327 | } 328 | fieldV.SetUint(x) 329 | case reflect.Float32, reflect.Float64: 330 | x, err := strconv.ParseFloat(value, 64) 331 | if err != nil { 332 | return err 333 | } 334 | fieldV.SetFloat(x) 335 | case reflect.Interface: 336 | fieldV.Set(reflect.ValueOf(value)) 337 | case reflect.String: 338 | fieldV.SetString(value) 339 | case reflect.Struct: 340 | switch fieldT.Type.String() { 341 | case "time.Time": 342 | format := time.RFC3339 343 | if len(tags) > 1 { 344 | format = tags[1] 345 | } 346 | t, err := time.ParseInLocation(format, value, time.Local) 347 | if err != nil { 348 | return err 349 | } 350 | fieldV.Set(reflect.ValueOf(t)) 351 | } 352 | case reflect.Slice: 353 | if fieldT.Type == sliceOfInts { 354 | formVals := form[tag] 355 | fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(int(1))), len(formVals), len(formVals))) 356 | for i := 0; i < len(formVals); i++ { 357 | val, err := strconv.Atoi(formVals[i]) 358 | if err != nil { 359 | return err 360 | } 361 | fieldV.Index(i).SetInt(int64(val)) 362 | } 363 | } else if fieldT.Type == sliceOfStrings { 364 | formVals := form[tag] 365 | fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("")), len(formVals), len(formVals))) 366 | for i := 0; i < len(formVals); i++ { 367 | fieldV.Index(i).SetString(formVals[i]) 368 | } 369 | } 370 | } 371 | } 372 | return nil 373 | } 374 | 375 | // ParseForm will parse form values to struct via tag. 376 | func ParseForm(form url.Values, obj interface{}) error { 377 | objT := reflect.TypeOf(obj) 378 | objV := reflect.ValueOf(obj) 379 | if !isStructPtr(objT) { 380 | return fmt.Errorf("%v must be a struct pointer", obj) 381 | } 382 | objT = objT.Elem() 383 | objV = objV.Elem() 384 | 385 | return parseFormToStruct(form, objT, objV) 386 | } 387 | 388 | var sliceOfInts = reflect.TypeOf([]int(nil)) 389 | var sliceOfStrings = reflect.TypeOf([]string(nil)) 390 | 391 | var unKind = map[reflect.Kind]bool{ 392 | reflect.Uintptr: true, 393 | reflect.Complex64: true, 394 | reflect.Complex128: true, 395 | reflect.Array: true, 396 | reflect.Chan: true, 397 | reflect.Func: true, 398 | reflect.Map: true, 399 | reflect.Ptr: true, 400 | reflect.Slice: true, 401 | reflect.Struct: true, 402 | reflect.UnsafePointer: true, 403 | } 404 | 405 | // RenderForm will render object to form html. 406 | // obj must be a struct pointer. 407 | func renderForm(obj interface{}) template.HTML { 408 | objT := reflect.TypeOf(obj) 409 | objV := reflect.ValueOf(obj) 410 | if !isStructPtr(objT) { 411 | return template.HTML("") 412 | } 413 | objT = objT.Elem() 414 | objV = objV.Elem() 415 | 416 | var raw []string 417 | for i := 0; i < objT.NumField(); i++ { 418 | fieldV := objV.Field(i) 419 | if !fieldV.CanSet() || unKind[fieldV.Kind()] { 420 | continue 421 | } 422 | 423 | fieldT := objT.Field(i) 424 | 425 | label, name, fType, id, class, ignored, required := parseFormTag(fieldT) 426 | if ignored { 427 | continue 428 | } 429 | 430 | raw = append(raw, renderFormField(label, name, fType, fieldV.Interface(), id, class, required)) 431 | } 432 | return template.HTML(strings.Join(raw, "
")) 433 | } 434 | 435 | // renderFormField returns a string containing HTML of a single form field. 436 | func renderFormField(label, name, fType string, value interface{}, id string, class string, required bool) string { 437 | if id != "" { 438 | id = " id=\"" + id + "\"" 439 | } 440 | 441 | if class != "" { 442 | class = " class=\"" + class + "\"" 443 | } 444 | 445 | requiredString := "" 446 | if required { 447 | requiredString = " required" 448 | } 449 | 450 | if isValidForInput(fType) { 451 | return fmt.Sprintf(`%v`, label, id, class, name, fType, value, requiredString) 452 | } 453 | 454 | return fmt.Sprintf(`%v<%v%v%v name="%v"%v>%v`, label, fType, id, class, name, requiredString, value, fType) 455 | } 456 | 457 | // isValidForInput checks if fType is a valid value for the `type` property of an HTML input element. 458 | func isValidForInput(fType string) bool { 459 | validInputTypes := strings.Fields("text password checkbox radio submit reset hidden image file button search email url tel number range date month week time datetime datetime-local color") 460 | for _, validType := range validInputTypes { 461 | if fType == validType { 462 | return true 463 | } 464 | } 465 | return false 466 | } 467 | 468 | // parseFormTag takes the stuct-tag of a StructField and parses the `form` value. 469 | // returned are the form label, name-property, type and wether the field should be ignored. 470 | func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id string, class string, ignored bool, required bool) { 471 | tags := strings.Split(fieldT.Tag.Get("form"), ",") 472 | label = fieldT.Name + ": " 473 | name = fieldT.Name 474 | fType = "text" 475 | ignored = false 476 | id = fieldT.Tag.Get("id") 477 | class = fieldT.Tag.Get("class") 478 | 479 | required = false 480 | required_field := fieldT.Tag.Get("required") 481 | if required_field != "-" && required_field != "" { 482 | required, _ = strconv.ParseBool(required_field) 483 | } 484 | 485 | switch len(tags) { 486 | case 1: 487 | if tags[0] == "-" { 488 | ignored = true 489 | } 490 | if len(tags[0]) > 0 { 491 | name = tags[0] 492 | } 493 | case 2: 494 | if len(tags[0]) > 0 { 495 | name = tags[0] 496 | } 497 | if len(tags[1]) > 0 { 498 | fType = tags[1] 499 | } 500 | case 3: 501 | if len(tags[0]) > 0 { 502 | name = tags[0] 503 | } 504 | if len(tags[1]) > 0 { 505 | fType = tags[1] 506 | } 507 | if len(tags[2]) > 0 { 508 | label = tags[2] 509 | } 510 | } 511 | 512 | return 513 | } 514 | 515 | func isStructPtr(t reflect.Type) bool { 516 | return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct 517 | } 518 | 519 | // go1.2 added template funcs. begin 520 | var ( 521 | errBadComparisonType = errors.New("invalid type for comparison") 522 | errBadComparison = errors.New("incompatible types for comparison") 523 | errNoComparison = errors.New("missing argument for comparison") 524 | ) 525 | 526 | type kind int 527 | 528 | const ( 529 | invalidKind kind = iota 530 | boolKind 531 | complexKind 532 | intKind 533 | floatKind 534 | stringKind 535 | uintKind 536 | ) 537 | 538 | func basicKind(v reflect.Value) (kind, error) { 539 | switch v.Kind() { 540 | case reflect.Bool: 541 | return boolKind, nil 542 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 543 | return intKind, nil 544 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 545 | return uintKind, nil 546 | case reflect.Float32, reflect.Float64: 547 | return floatKind, nil 548 | case reflect.Complex64, reflect.Complex128: 549 | return complexKind, nil 550 | case reflect.String: 551 | return stringKind, nil 552 | } 553 | return invalidKind, errBadComparisonType 554 | } 555 | 556 | // eq evaluates the comparison a == b || a == c || ... 557 | func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) { 558 | v1 := reflect.ValueOf(arg1) 559 | k1, err := basicKind(v1) 560 | if err != nil { 561 | return false, err 562 | } 563 | if len(arg2) == 0 { 564 | return false, errNoComparison 565 | } 566 | for _, arg := range arg2 { 567 | v2 := reflect.ValueOf(arg) 568 | k2, err := basicKind(v2) 569 | if err != nil { 570 | return false, err 571 | } 572 | if k1 != k2 { 573 | return false, errBadComparison 574 | } 575 | truth := false 576 | switch k1 { 577 | case boolKind: 578 | truth = v1.Bool() == v2.Bool() 579 | case complexKind: 580 | truth = v1.Complex() == v2.Complex() 581 | case floatKind: 582 | truth = v1.Float() == v2.Float() 583 | case intKind: 584 | truth = v1.Int() == v2.Int() 585 | case stringKind: 586 | truth = v1.String() == v2.String() 587 | case uintKind: 588 | truth = v1.Uint() == v2.Uint() 589 | default: 590 | panic("invalid kind") 591 | } 592 | if truth { 593 | return true, nil 594 | } 595 | } 596 | return false, nil 597 | } 598 | 599 | // ne evaluates the comparison a != b. 600 | func ne(arg1, arg2 interface{}) (bool, error) { 601 | // != is the inverse of ==. 602 | equal, err := eq(arg1, arg2) 603 | return !equal, err 604 | } 605 | 606 | // lt evaluates the comparison a < b. 607 | func lt(arg1, arg2 interface{}) (bool, error) { 608 | v1 := reflect.ValueOf(arg1) 609 | k1, err := basicKind(v1) 610 | if err != nil { 611 | return false, err 612 | } 613 | v2 := reflect.ValueOf(arg2) 614 | k2, err := basicKind(v2) 615 | if err != nil { 616 | return false, err 617 | } 618 | if k1 != k2 { 619 | return false, errBadComparison 620 | } 621 | truth := false 622 | switch k1 { 623 | case boolKind, complexKind: 624 | return false, errBadComparisonType 625 | case floatKind: 626 | truth = v1.Float() < v2.Float() 627 | case intKind: 628 | truth = v1.Int() < v2.Int() 629 | case stringKind: 630 | truth = v1.String() < v2.String() 631 | case uintKind: 632 | truth = v1.Uint() < v2.Uint() 633 | default: 634 | panic("invalid kind") 635 | } 636 | return truth, nil 637 | } 638 | 639 | // le evaluates the comparison <= b. 640 | func le(arg1, arg2 interface{}) (bool, error) { 641 | // <= is < or ==. 642 | lessThan, err := lt(arg1, arg2) 643 | if lessThan || err != nil { 644 | return lessThan, err 645 | } 646 | return eq(arg1, arg2) 647 | } 648 | 649 | // gt evaluates the comparison a > b. 650 | func gt(arg1, arg2 interface{}) (bool, error) { 651 | // > is the inverse of <=. 652 | lessOrEqual, err := le(arg1, arg2) 653 | if err != nil { 654 | return false, err 655 | } 656 | return !lessOrEqual, nil 657 | } 658 | 659 | // ge evaluates the comparison a >= b. 660 | func ge(arg1, arg2 interface{}) (bool, error) { 661 | // >= is the inverse of <. 662 | lessThan, err := lt(arg1, arg2) 663 | if err != nil { 664 | return false, err 665 | } 666 | return !lessThan, nil 667 | } 668 | 669 | // MapGet getting value from map by keys 670 | // usage: 671 | // Data["m"] = map[string]interface{} { 672 | // "a": 1, 673 | // "1": map[string]float64{ 674 | // "c": 4, 675 | // }, 676 | // } 677 | // 678 | // {{ map_get m "a" }} // return 1 679 | // {{ map_get m 1 "c" }} // return 4 680 | func mapGet(arg1 interface{}, arg2 ...interface{}) (interface{}, error) { 681 | arg1Type := reflect.TypeOf(arg1) 682 | arg1Val := reflect.ValueOf(arg1) 683 | 684 | if arg1Type.Kind() == reflect.Map && len(arg2) > 0 { 685 | // check whether arg2[0] type equals to arg1 key type 686 | // if they are different, make conversion 687 | arg2Val := reflect.ValueOf(arg2[0]) 688 | arg2Type := reflect.TypeOf(arg2[0]) 689 | if arg2Type.Kind() != arg1Type.Key().Kind() { 690 | // convert arg2Value to string 691 | var arg2ConvertedVal interface{} 692 | arg2String := fmt.Sprintf("%v", arg2[0]) 693 | 694 | // convert string representation to any other type 695 | switch arg1Type.Key().Kind() { 696 | case reflect.Bool: 697 | arg2ConvertedVal, _ = strconv.ParseBool(arg2String) 698 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 699 | arg2ConvertedVal, _ = strconv.ParseInt(arg2String, 0, 64) 700 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 701 | arg2ConvertedVal, _ = strconv.ParseUint(arg2String, 0, 64) 702 | case reflect.Float32, reflect.Float64: 703 | arg2ConvertedVal, _ = strconv.ParseFloat(arg2String, 64) 704 | case reflect.String: 705 | arg2ConvertedVal = arg2String 706 | default: 707 | arg2ConvertedVal = arg2Val.Interface() 708 | } 709 | arg2Val = reflect.ValueOf(arg2ConvertedVal) 710 | } 711 | 712 | storedVal := arg1Val.MapIndex(arg2Val) 713 | 714 | if storedVal.IsValid() { 715 | var result interface{} 716 | 717 | switch arg1Type.Elem().Kind() { 718 | case reflect.Bool: 719 | result = storedVal.Bool() 720 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 721 | result = storedVal.Int() 722 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 723 | result = storedVal.Uint() 724 | case reflect.Float32, reflect.Float64: 725 | result = storedVal.Float() 726 | case reflect.String: 727 | result = storedVal.String() 728 | default: 729 | result = storedVal.Interface() 730 | } 731 | 732 | // if there is more keys, handle this recursively 733 | if len(arg2) > 1 { 734 | return mapGet(result, arg2[1:]...) 735 | } 736 | return result, nil 737 | } 738 | return nil, nil 739 | 740 | } 741 | return nil, nil 742 | } 743 | -------------------------------------------------------------------------------- /trygo.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | ) 7 | 8 | const VERSION = "0.1.0" 9 | 10 | var ( 11 | DefaultApp *App 12 | AppPath string 13 | ) 14 | 15 | func init() { 16 | DefaultApp = NewApp() 17 | AppPath, _ = os.Getwd() 18 | } 19 | 20 | func Register(method string, pattern string, c ControllerInterface, name string, tags ...string) iRouter { 21 | return DefaultApp.Register(method, pattern, c, name, tags...) 22 | } 23 | 24 | func RegisterHandler(pattern string, h http.Handler) iRouter { 25 | return DefaultApp.RegisterHandler(pattern, h) 26 | } 27 | 28 | func RegisterRESTful(pattern string, c ControllerInterface) iRouter { 29 | return DefaultApp.RegisterRESTful(pattern, c) 30 | } 31 | 32 | func RegisterFunc(methods, pattern string, f HandlerFunc) iRouter { 33 | return DefaultApp.RegisterFunc(methods, pattern, f) 34 | } 35 | 36 | func Get(pattern string, f HandlerFunc) iRouter { 37 | return DefaultApp.Get(pattern, f) 38 | } 39 | 40 | func Post(pattern string, f HandlerFunc) iRouter { 41 | return DefaultApp.Post(pattern, f) 42 | } 43 | 44 | func Put(pattern string, f HandlerFunc) iRouter { 45 | return DefaultApp.Put(pattern, f) 46 | } 47 | 48 | func Delete(pattern string, f HandlerFunc) iRouter { 49 | return DefaultApp.Delete(pattern, f) 50 | } 51 | 52 | func Head(pattern string, f HandlerFunc) iRouter { 53 | return DefaultApp.Head(pattern, f) 54 | } 55 | 56 | func Patch(pattern string, f HandlerFunc) iRouter { 57 | return DefaultApp.Patch(pattern, f) 58 | } 59 | 60 | func Options(pattern string, f HandlerFunc) iRouter { 61 | return DefaultApp.Handlers.Options(pattern, f) 62 | } 63 | 64 | func Any(pattern string, f HandlerFunc) iRouter { 65 | return DefaultApp.Any(pattern, f) 66 | } 67 | 68 | func SetStaticPath(url string, path string) *App { 69 | DefaultApp.SetStaticPath(url, path) 70 | return DefaultApp 71 | } 72 | 73 | func SetViewsPath(path string) *App { 74 | DefaultApp.SetViewsPath(path) 75 | return DefaultApp 76 | } 77 | 78 | func AddTemplateExt(ext string) { 79 | DefaultApp.TemplateRegister.AddTemplateExt(ext) 80 | } 81 | 82 | func AddTemplateFunc(key string, funname interface{}) error { 83 | return DefaultApp.TemplateRegister.AddFuncMap(key, funname) 84 | } 85 | 86 | func Run(server ...Server) { 87 | DefaultApp.Run(server...) 88 | } 89 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "mime" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "reflect" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const ( 17 | maxValueLength = 4096 18 | maxHeaderLines = 1024 19 | chunkSize = 4 << 10 // 4 KB chunks 20 | defaultMaxMemory = 32 << 20 // 32 MB 21 | ) 22 | 23 | func parseTime(layouts []string, str string) (t time.Time, err error) { 24 | for _, layout := range layouts { 25 | t, err = time.Parse(layout, str) 26 | if err == nil { 27 | return 28 | } 29 | } 30 | return 31 | } 32 | 33 | func toString(v interface{}) string { 34 | switch s := v.(type) { 35 | case string: 36 | return s 37 | case *string: 38 | return *s 39 | default: 40 | return fmt.Sprint(v) 41 | } 42 | 43 | } 44 | 45 | func fileExists(name string) bool { 46 | if _, err := os.Stat(name); err != nil { 47 | if os.IsNotExist(err) { 48 | return false 49 | } 50 | } 51 | return true 52 | } 53 | 54 | func parseForm(r *http.Request, multipart bool) (url.Values, error) { 55 | if multipart { 56 | err := r.ParseMultipartForm(defaultMaxMemory) 57 | if err != nil { 58 | return nil, err 59 | } 60 | } else { 61 | err := r.ParseForm() 62 | if err != nil { 63 | return nil, err 64 | } 65 | } 66 | return r.Form, nil 67 | } 68 | 69 | func isPattern(path string) bool { 70 | return strings.ContainsAny(path, "^.()[]|*+?{}\\,<>:=!$") 71 | 72 | } 73 | 74 | func newValueOf(kind reflect.Kind) (*reflect.Value, error) { 75 | var v reflect.Value 76 | switch kind { 77 | case reflect.String: 78 | v = reflect.New(reflect.TypeOf("")) 79 | case reflect.Bool: 80 | v = reflect.New(reflect.TypeOf(false)) 81 | case reflect.Int: 82 | v = reflect.New(reflect.TypeOf(int(0))) 83 | case reflect.Int8: 84 | v = reflect.New(reflect.TypeOf(int8(0))) 85 | case reflect.Int16: 86 | v = reflect.New(reflect.TypeOf(int16(0))) 87 | case reflect.Int32: 88 | v = reflect.New(reflect.TypeOf(int32(0))) 89 | case reflect.Int64: 90 | v = reflect.New(reflect.TypeOf(int64(0))) 91 | 92 | case reflect.Uint: 93 | v = reflect.New(reflect.TypeOf(uint(0))) 94 | case reflect.Uint8: 95 | v = reflect.New(reflect.TypeOf(uint8(0))) 96 | case reflect.Uint16: 97 | v = reflect.New(reflect.TypeOf(uint16(0))) 98 | case reflect.Uint32: 99 | v = reflect.New(reflect.TypeOf(uint32(0))) 100 | case reflect.Uint64: 101 | v = reflect.New(reflect.TypeOf(uint64(0))) 102 | case reflect.Float32: 103 | v = reflect.New(reflect.TypeOf(float32(0.0))) 104 | case reflect.Float64: 105 | v = reflect.New(reflect.TypeOf(float64(0.0))) 106 | default: 107 | return nil, errors.New("the type (" + kind.String() + ") is not supported") 108 | } 109 | 110 | v = reflect.Indirect(v) 111 | 112 | return &v, nil 113 | } 114 | 115 | //解析string类型值到指定其它类型值 116 | //srcValue - 原值 117 | //destValue - 在destValue中返回与之相同类型的值, 如果不指定destValue,将自动创建 //destType - 转换目标类型 118 | //@return 与destType相同类型的值 119 | func parseValue(srcValue string, destTypeKind reflect.Kind, destValue *reflect.Value, layouts ...string) (*reflect.Value, error) { 120 | var err error 121 | if destValue == nil { 122 | destValue, err = newValueOf(destTypeKind) 123 | if err != nil { 124 | return nil, err 125 | } 126 | } 127 | 128 | var v interface{} 129 | switch destTypeKind { 130 | case reflect.String: 131 | destValue.SetString(srcValue) 132 | case reflect.Bool: 133 | v, err = strconv.ParseBool(srcValue) 134 | if err == nil { 135 | destValue.SetBool(v.(bool)) 136 | } 137 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 138 | v, err = strconv.ParseInt(srcValue, 10, 64) 139 | if err == nil { 140 | destValue.SetInt(v.(int64)) 141 | } 142 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 143 | v, err = strconv.ParseUint(srcValue, 10, 64) 144 | if err == nil { 145 | destValue.SetUint(v.(uint64)) 146 | } 147 | case reflect.Float32: 148 | v, err = strconv.ParseFloat(srcValue, 32) 149 | if err == nil { 150 | destValue.SetFloat(v.(float64)) 151 | } 152 | case reflect.Float64: 153 | v, err = strconv.ParseFloat(srcValue, 64) 154 | if err == nil { 155 | destValue.SetFloat(v.(float64)) 156 | } 157 | default: 158 | if timeType == destValue.Type() { 159 | if len(layouts) == 0 { 160 | layouts = []string{"2006-01-02", "2006-01-02 15:04:05"} 161 | } 162 | var t time.Time 163 | t, err = parseTime(layouts, srcValue) 164 | if err != nil { 165 | break 166 | } 167 | destValue.Set(reflect.ValueOf(t)) 168 | break 169 | } 170 | 171 | err = errors.New("the type (" + destTypeKind.String() + ") is not supported") 172 | } 173 | 174 | return destValue, err 175 | } 176 | 177 | const ( 178 | tcUnknown = iota 179 | tcSigned 180 | tcUnsigned 181 | tcString 182 | tcBool 183 | tcFloat 184 | ) 185 | 186 | func typeClassify(typ reflect.Type) int8 { 187 | switch typ.Kind() { 188 | case reflect.String: 189 | return tcString 190 | case reflect.Bool: 191 | return tcBool 192 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 193 | return tcSigned 194 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 195 | return tcUnsigned 196 | case reflect.Float32, reflect.Float64: 197 | return tcFloat 198 | default: 199 | return tcUnknown 200 | } 201 | } 202 | 203 | func toInt64(v interface{}) int64 { 204 | switch vi := v.(type) { 205 | case int: 206 | return int64(vi) 207 | case int64: 208 | return vi 209 | case int32: 210 | return int64(vi) 211 | case int16: 212 | return int64(vi) 213 | case int8: 214 | return int64(vi) 215 | 216 | case uint: 217 | return int64(vi) 218 | case uint64: 219 | return int64(vi) 220 | case uint32: 221 | return int64(vi) 222 | case uint16: 223 | return int64(vi) 224 | case uint8: 225 | return int64(vi) 226 | 227 | case float32: 228 | return int64(vi) 229 | case float64: 230 | return int64(vi) 231 | 232 | case bool: 233 | if vi { 234 | return 1 235 | } else { 236 | return 0 237 | } 238 | 239 | case string: 240 | vi64, err := strconv.ParseInt(vi, 10, 64) 241 | if err != nil { 242 | panic(err) 243 | } 244 | return vi64 245 | default: 246 | panic(errors.New("unknown data type")) 247 | } 248 | } 249 | 250 | func toUint64(v interface{}) uint64 { 251 | switch vi := v.(type) { 252 | case uint: 253 | return uint64(vi) 254 | case uint64: 255 | return vi 256 | case uint32: 257 | return uint64(vi) 258 | case uint16: 259 | return uint64(vi) 260 | case uint8: 261 | return uint64(vi) 262 | 263 | case int: 264 | return uint64(vi) 265 | case int64: 266 | return uint64(vi) 267 | case int32: 268 | return uint64(vi) 269 | case int16: 270 | return uint64(vi) 271 | case int8: 272 | return uint64(vi) 273 | 274 | case float32: 275 | return uint64(vi) 276 | case float64: 277 | return uint64(vi) 278 | 279 | case bool: 280 | if vi { 281 | return 1 282 | } else { 283 | return 0 284 | } 285 | case string: 286 | vu64, err := strconv.ParseUint(vi, 10, 64) 287 | if err != nil { 288 | panic(err) 289 | } 290 | return vu64 291 | default: 292 | panic(errors.New("unknown data type")) 293 | } 294 | } 295 | 296 | func toFloat64(v interface{}) float64 { 297 | switch vi := v.(type) { 298 | case int: 299 | return float64(vi) 300 | case int64: 301 | return float64(vi) 302 | case int32: 303 | return float64(vi) 304 | case int16: 305 | return float64(vi) 306 | case int8: 307 | return float64(vi) 308 | 309 | case uint: 310 | return float64(vi) 311 | case uint64: 312 | return float64(vi) 313 | case uint32: 314 | return float64(vi) 315 | case uint16: 316 | return float64(vi) 317 | case uint8: 318 | return float64(vi) 319 | 320 | case float32: 321 | return float64(vi) 322 | case float64: 323 | return vi 324 | 325 | case bool: 326 | if vi { 327 | return 1.0 328 | } else { 329 | return 0.0 330 | } 331 | 332 | case string: 333 | vi64, err := strconv.ParseFloat(vi, 64) 334 | if err != nil { 335 | panic(err) 336 | } 337 | return vi64 338 | default: 339 | panic(errors.New("unknown data type")) 340 | } 341 | } 342 | 343 | //从方法中分离出方法名称和参数 344 | //Login(account, pwd string) 345 | func parseMethod(method string) (name string, params []string) { 346 | pairs := strings.SplitN(method, "(", 2) 347 | name = strings.TrimSpace(pairs[0]) 348 | if len(pairs) > 1 { 349 | paramsstr := strings.TrimSpace(strings.Replace(pairs[1], ")", "", -1)) 350 | if len(paramsstr) > 0 { 351 | params = strings.Split(paramsstr, ",") 352 | } 353 | } 354 | return 355 | } 356 | 357 | func getContentType(typ string) string { 358 | ext := typ 359 | if !strings.HasPrefix(typ, ".") { 360 | ext = "." + typ 361 | } 362 | 363 | ct := mimemaps[ext] 364 | if ct != "" { 365 | return ct 366 | } 367 | 368 | ct = mime.TypeByExtension(ext) 369 | if ct == "" { 370 | return typ 371 | } 372 | return ct 373 | } 374 | 375 | func toContentType(format string) string { 376 | switch format { 377 | case "json": 378 | return "application/json; charset=utf-8" 379 | case "xml": 380 | return "text/xml; charset=utf-8" 381 | case "txt": 382 | return "text/plain; charset=utf-8" 383 | case "html": 384 | return "text/html; charset=utf-8" 385 | } 386 | return getContentType(format) 387 | } 388 | 389 | func buildLoginfo(r *http.Request, args ...interface{}) string { 390 | return fmt.Sprintf("%s \"%s\"<->\"%s\": %s", r.URL.Path, r.Host, r.RemoteAddr, fmt.Sprint(args...)) 391 | } 392 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package trygo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_toInt64(t *testing.T) { 8 | if toInt64(int8(8)) != int64(8) { 9 | t.Fatal("type cast, int8 -> int64, fail!") 10 | } 11 | if toInt64(int16(16)) != int64(16) { 12 | t.Fatal("type cast, int16 -> int64, fail!") 13 | } 14 | if toInt64(int32(32)) != int64(32) { 15 | t.Fatal("type cast, int32 -> int64, fail!") 16 | } 17 | if toInt64(int64(64)) != int64(64) { 18 | t.Fatal("type cast, int64 -> int64, fail!") 19 | } 20 | 21 | if toInt64(uint8(8)) != int64(8) { 22 | t.Fatal("type cast, uint8 -> int64, fail!") 23 | } 24 | if toInt64(uint16(16)) != int64(16) { 25 | t.Fatal("type cast, uint16 -> int64, fail!") 26 | } 27 | if toInt64(uint32(32)) != int64(32) { 28 | t.Fatal("type cast, uint32 -> int64, fail!") 29 | } 30 | if toInt64(uint64(64)) != int64(64) { 31 | t.Fatal("type cast, uint64 -> int64, fail!") 32 | } 33 | 34 | if toInt64(float32(32.99)) != int64(32) { 35 | t.Fatal("type cast, float32 -> int64, fail!") 36 | } 37 | if toInt64(float64(64.99)) != int64(64) { 38 | t.Fatal("type cast, float64 -> int64, fail!") 39 | } 40 | 41 | if toInt64(true) != int64(1) { 42 | t.Fatal("type cast, bool -> int64, fail!") 43 | } 44 | if toInt64(false) != int64(0) { 45 | t.Fatal("type cast, bool -> int64, fail!") 46 | } 47 | 48 | if toInt64("231") != int64(231) { 49 | t.Fatal("type cast, string -> int64, fail!") 50 | } 51 | } 52 | 53 | func Test_toUint64(t *testing.T) { 54 | if toUint64(int8(8)) != uint64(8) { 55 | t.Fatal("type cast, int8 -> uint64, fail!") 56 | } 57 | if toUint64(int16(16)) != uint64(16) { 58 | t.Fatal("type cast, int16 -> uint64, fail!") 59 | } 60 | if toUint64(int32(32)) != uint64(32) { 61 | t.Fatal("type cast, int32 -> uint64, fail!") 62 | } 63 | if toUint64(int64(64)) != uint64(64) { 64 | t.Fatal("type cast, int64 -> uint64, fail!") 65 | } 66 | 67 | if toUint64(uint8(8)) != uint64(8) { 68 | t.Fatal("type cast, uint8 -> uint64, fail!") 69 | } 70 | if toUint64(uint16(16)) != uint64(16) { 71 | t.Fatal("type cast, uint16 -> uint64, fail!") 72 | } 73 | if toUint64(uint32(32)) != uint64(32) { 74 | t.Fatal("type cast, uint32 -> uint64, fail!") 75 | } 76 | if toUint64(uint64(64)) != uint64(64) { 77 | t.Fatal("type cast, uint64 -> uint64, fail!") 78 | } 79 | 80 | if toUint64(float32(32.99)) != uint64(32) { 81 | t.Fatal("type cast, float32 -> uint64, fail!") 82 | } 83 | if toUint64(float64(64.99)) != uint64(64) { 84 | t.Fatal("type cast, float64 -> uint64, fail!") 85 | } 86 | 87 | if toUint64(true) != uint64(1) { 88 | t.Fatal("type cast, bool -> uint64, fail!") 89 | } 90 | if toUint64(false) != uint64(0) { 91 | t.Fatal("type cast, bool -> uint64, fail!") 92 | } 93 | 94 | if toUint64("231") != uint64(231) { 95 | t.Fatal("type cast, string -> uint64, fail!") 96 | } 97 | } 98 | 99 | func Test_toFloat64(t *testing.T) { 100 | if toFloat64(int8(8)) != float64(8) { 101 | t.Fatal("type cast, int8 -> float64, fail!") 102 | } 103 | if toFloat64(int16(16)) != float64(16) { 104 | t.Fatal("type cast, int16 -> float64, fail!") 105 | } 106 | if toFloat64(int32(32)) != float64(32) { 107 | t.Fatal("type cast, int32 -> float64, fail!") 108 | } 109 | if toFloat64(int64(64)) != float64(64) { 110 | t.Fatal("type cast, int64 -> float64, fail!") 111 | } 112 | 113 | if toFloat64(uint8(8)) != float64(8) { 114 | t.Fatal("type cast, uint8 -> float64, fail!") 115 | } 116 | if toFloat64(uint16(16)) != float64(16) { 117 | t.Fatal("type cast, uint16 -> float64, fail!") 118 | } 119 | if toFloat64(uint32(32)) != float64(32) { 120 | t.Fatal("type cast, uint32 -> float64, fail!") 121 | } 122 | if toFloat64(uint64(64)) != float64(64) { 123 | t.Fatal("type cast, uint64 -> float64, fail!") 124 | } 125 | 126 | if toFloat64(float32(32.95)) != float64(32.95000076293945) { 127 | t.Fatal("type cast, float32 -> float64, fail!", toFloat64(float32(32.95)), float64(32.95)) 128 | } 129 | if toFloat64(float64(64.99)) != float64(64.99) { 130 | t.Fatal("type cast, float64 -> float64, fail!") 131 | } 132 | 133 | if toFloat64(true) != float64(1) { 134 | t.Fatal("type cast, bool -> float64, fail!") 135 | } 136 | if toFloat64(false) != float64(0) { 137 | t.Fatal("type cast, bool -> float64, fail!") 138 | } 139 | 140 | if toFloat64("231.66") != float64(231.66) { 141 | t.Fatal("type cast, string -> float64, fail!", toFloat64("231.66")) 142 | } 143 | } 144 | --------------------------------------------------------------------------------