├── .gitignore ├── LICENSE ├── README.md └── macaron-1.1.8 ├── README.md ├── context.go ├── context_test.go ├── fixtures ├── basic │ ├── admin │ │ └── index.tmpl │ ├── another_layout.tmpl │ ├── content.tmpl │ ├── current_layout.tmpl │ ├── custom │ │ └── hello.tmpl │ ├── delims.tmpl │ ├── hello.tmpl │ ├── hypertext.html │ └── layout.tmpl ├── basic2 │ ├── hello.tmpl │ └── hello2.tmpl ├── custom_funcs │ └── index.tmpl └── symlink │ ├── admin │ └── index.tmpl │ ├── another_layout.tmpl │ ├── content.tmpl │ ├── current_layout.tmpl │ ├── custom │ └── hello.tmpl │ ├── delims.tmpl │ ├── hello.tmpl │ ├── hypertext.html │ └── layout.tmpl ├── logger.go ├── logger_test.go ├── macaron.go ├── macaron_test.go ├── recovery.go ├── recovery_test.go ├── render.go ├── render_test.go ├── response_writer.go ├── response_writer_test.go ├── return_handler.go ├── return_handler_test.go ├── router.go ├── router_test.go ├── static.go ├── static_test.go └── tree.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 经典项目源码注解 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # annotated-go-macaron 2 | 3 | - macaron 项目源码注解. 4 | 5 | ## Macaron 简介: 6 | 7 | - [macaron - github](https://github.com/go-macaron/macaron) 8 | - [macaron - 官网](https://go-macaron.com/) 9 | - Macaron: 一款具有高生产力和模块化设计的 Go Web 框架 10 | - 项目灵感: 来源于[martini](https://github.com/go-martini/martini) 框架 11 | 12 | 13 | ## 源码版本: 14 | 15 | - [macaron-1.1.8](./macaron-1.1.8) 16 | - 当前最新版本. `release: on 27 Aug 2016` 17 | - 代码行数统计: 4483 (含测试代码: 2006行) 18 | 19 | ## 源码结构: 20 | 21 | ``` 22 | 23 | -> % tree ./macaron-1.1.8 -L 2 24 | 25 | ./macaron-1.1.8 26 | ├── README.md 27 | ├── context.go 28 | ├── context_test.go 29 | ├── fixtures 30 | │   ├── basic 31 | │   ├── basic2 32 | │   ├── custom_funcs 33 | │   └── symlink 34 | ├── logger.go 35 | ├── logger_test.go 36 | ├── macaron.go // 项目全局入口 37 | ├── macaron_test.go 38 | ├── recovery.go 39 | ├── recovery_test.go 40 | ├── render.go 41 | ├── render_test.go 42 | ├── response_writer.go 43 | ├── response_writer_test.go 44 | ├── return_handler.go 45 | ├── return_handler_test.go 46 | ├── router.go 47 | ├── router_test.go 48 | ├── static.go 49 | ├── static_test.go 50 | └── tree.go 51 | 52 | 5 directories, 20 files 53 | 54 | 55 | ``` 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /macaron-1.1.8/README.md: -------------------------------------------------------------------------------- 1 | # Macaron 源码阅读说明: 2 | 3 | ## 源码推荐阅读顺序: 4 | 5 | - macaron.go 入口 6 | 7 | 8 | 9 | 10 | 11 | --- 12 | 13 | Macaron [![Build Status](https://travis-ci.org/go-macaron/macaron.svg?branch=v1)](https://travis-ci.org/go-macaron/macaron) [![](http://gocover.io/_badge/github.com/go-macaron/macaron)](http://gocover.io/github.com/go-macaron/macaron) 14 | ======================= 15 | 16 | Package macaron is a high productive and modular web framework in Go. 17 | 18 | ## Getting Started 19 | 20 | The minimum requirement of Go is **1.3**. 21 | 22 | To install Macaron: 23 | 24 | go get gopkg.in/macaron.v1 25 | 26 | The very basic usage of Macaron: 27 | 28 | ```go 29 | package main 30 | 31 | import "gopkg.in/macaron.v1" 32 | 33 | func main() { 34 | m := macaron.Classic() 35 | m.Get("/", func() string { 36 | return "Hello world!" 37 | }) 38 | m.Run() 39 | } 40 | ``` 41 | 42 | ## Features 43 | 44 | - Powerful routing with suburl. 45 | - Flexible routes combinations. 46 | - Unlimited nested group routers. 47 | - Directly integrate with existing services. 48 | - Dynamically change template files at runtime. 49 | - Allow to use in-memory template and static files. 50 | - Easy to plugin/unplugin features with modular design. 51 | - Handy dependency injection powered by [inject](https://github.com/codegangsta/inject). 52 | - Better router layer and less reflection make faster speed. 53 | 54 | ## Middlewares 55 | 56 | Middlewares allow you easily plugin/unplugin features for your Macaron applications. 57 | 58 | There are already many [middlewares](https://github.com/go-macaron) to simplify your work: 59 | 60 | - render - Go template engine 61 | - static - Serves static files 62 | - [gzip](https://github.com/go-macaron/gzip) - Gzip compression to all responses 63 | - [binding](https://github.com/go-macaron/binding) - Request data binding and validation 64 | - [i18n](https://github.com/go-macaron/i18n) - Internationalization and Localization 65 | - [cache](https://github.com/go-macaron/cache) - Cache manager 66 | - [session](https://github.com/go-macaron/session) - Session manager 67 | - [csrf](https://github.com/go-macaron/csrf) - Generates and validates csrf tokens 68 | - [captcha](https://github.com/go-macaron/captcha) - Captcha service 69 | - [pongo2](https://github.com/go-macaron/pongo2) - Pongo2 template engine support 70 | - [sockets](https://github.com/go-macaron/sockets) - WebSockets channels binding 71 | - [bindata](https://github.com/go-macaron/bindata) - Embed binary data as static and template files 72 | - [toolbox](https://github.com/go-macaron/toolbox) - Health check, pprof, profile and statistic services 73 | - [oauth2](https://github.com/go-macaron/oauth2) - OAuth 2.0 backend 74 | - [switcher](https://github.com/go-macaron/switcher) - Multiple-site support 75 | - [method](https://github.com/go-macaron/method) - HTTP method override 76 | - [permissions2](https://github.com/xyproto/permissions2) - Cookies, users and permissions 77 | - [renders](https://github.com/go-macaron/renders) - Beego-like render engine(Macaron has built-in template engine, this is another option) 78 | 79 | ## Use Cases 80 | 81 | - [Gogs](https://gogs.io): A painless self-hosted Git Service 82 | - [Peach](https://peachdocs.org): A modern web documentation server 83 | - [Go Walker](https://gowalker.org): Go online API documentation 84 | - [Switch](https://gopm.io): Gopm registry 85 | - [YouGam](http://yougam.com): Online Forum 86 | - [Critical Stack Intel](https://intel.criticalstack.com/): A 100% free intel marketplace from Critical Stack, Inc. 87 | 88 | ## Getting Help 89 | 90 | - [API Reference](https://gowalker.org/gopkg.in/macaron.v1) 91 | - [Documentation](https://go-macaron.com) 92 | - [FAQs](https://go-macaron.com/docs/faqs) 93 | - [![Join the chat at https://gitter.im/Unknwon/macaron](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-macaron/macaron?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 94 | -------------------------------------------------------------------------------- /macaron-1.1.8/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package macaron 16 | 17 | import ( 18 | "crypto/md5" 19 | "encoding/hex" 20 | "html/template" 21 | "io" 22 | "io/ioutil" 23 | "mime/multipart" 24 | "net/http" 25 | "net/url" 26 | "os" 27 | "path" 28 | "path/filepath" 29 | "reflect" 30 | "strconv" 31 | "strings" 32 | "time" 33 | 34 | "github.com/Unknwon/com" 35 | 36 | "github.com/go-macaron/inject" 37 | ) 38 | 39 | // Locale reprents a localization interface. 40 | type Locale interface { 41 | Language() string 42 | Tr(string, ...interface{}) string 43 | } 44 | 45 | // RequestBody represents a request body. 46 | type RequestBody struct { 47 | reader io.ReadCloser 48 | } 49 | 50 | // Bytes reads and returns content of request body in bytes. 51 | func (rb *RequestBody) Bytes() ([]byte, error) { 52 | return ioutil.ReadAll(rb.reader) 53 | } 54 | 55 | // String reads and returns content of request body in string. 56 | func (rb *RequestBody) String() (string, error) { 57 | data, err := rb.Bytes() 58 | return string(data), err 59 | } 60 | 61 | // ReadCloser returns a ReadCloser for request body. 62 | func (rb *RequestBody) ReadCloser() io.ReadCloser { 63 | return rb.reader 64 | } 65 | 66 | // Request represents an HTTP request received by a server or to be sent by a client. 67 | type Request struct { 68 | *http.Request 69 | } 70 | 71 | func (r *Request) Body() *RequestBody { 72 | return &RequestBody{r.Request.Body} 73 | } 74 | 75 | // Context represents the runtime context of current request of Macaron instance. 76 | // It is the integration of most frequently used middlewares and helper methods. 77 | type Context struct { 78 | inject.Injector 79 | handlers []Handler 80 | action Handler 81 | index int 82 | 83 | *Router 84 | Req Request 85 | Resp ResponseWriter 86 | params Params 87 | Render 88 | Locale 89 | Data map[string]interface{} 90 | } 91 | 92 | func (c *Context) handler() Handler { 93 | if c.index < len(c.handlers) { 94 | return c.handlers[c.index] 95 | } 96 | if c.index == len(c.handlers) { 97 | return c.action 98 | } 99 | panic("invalid index for context handler") 100 | } 101 | 102 | func (c *Context) Next() { 103 | c.index += 1 104 | c.run() 105 | } 106 | 107 | func (c *Context) Written() bool { 108 | return c.Resp.Written() 109 | } 110 | 111 | func (c *Context) run() { 112 | for c.index <= len(c.handlers) { 113 | vals, err := c.Invoke(c.handler()) 114 | if err != nil { 115 | panic(err) 116 | } 117 | c.index += 1 118 | 119 | // if the handler returned something, write it to the http response 120 | if len(vals) > 0 { 121 | ev := c.GetVal(reflect.TypeOf(ReturnHandler(nil))) 122 | handleReturn := ev.Interface().(ReturnHandler) 123 | handleReturn(c, vals) 124 | } 125 | 126 | if c.Written() { 127 | return 128 | } 129 | } 130 | } 131 | 132 | // RemoteAddr returns more real IP address. 133 | func (ctx *Context) RemoteAddr() string { 134 | addr := ctx.Req.Header.Get("X-Real-IP") 135 | if len(addr) == 0 { 136 | addr = ctx.Req.Header.Get("X-Forwarded-For") 137 | if addr == "" { 138 | addr = ctx.Req.RemoteAddr 139 | if i := strings.LastIndex(addr, ":"); i > -1 { 140 | addr = addr[:i] 141 | } 142 | } 143 | } 144 | return addr 145 | } 146 | 147 | func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) { 148 | if len(data) <= 0 { 149 | ctx.Render.HTMLSet(status, setName, tplName, ctx.Data) 150 | } else if len(data) == 1 { 151 | ctx.Render.HTMLSet(status, setName, tplName, data[0]) 152 | } else { 153 | ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions)) 154 | } 155 | } 156 | 157 | // HTML calls Render.HTML but allows less arguments. 158 | func (ctx *Context) HTML(status int, name string, data ...interface{}) { 159 | ctx.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data...) 160 | } 161 | 162 | // HTML calls Render.HTMLSet but allows less arguments. 163 | func (ctx *Context) HTMLSet(status int, setName, tplName string, data ...interface{}) { 164 | ctx.renderHTML(status, setName, tplName, data...) 165 | } 166 | 167 | func (ctx *Context) Redirect(location string, status ...int) { 168 | code := http.StatusFound 169 | if len(status) == 1 { 170 | code = status[0] 171 | } 172 | 173 | http.Redirect(ctx.Resp, ctx.Req.Request, location, code) 174 | } 175 | 176 | // Maximum amount of memory to use when parsing a multipart form. 177 | // Set this to whatever value you prefer; default is 10 MB. 178 | var MaxMemory = int64(1024 * 1024 * 10) 179 | 180 | func (ctx *Context) parseForm() { 181 | if ctx.Req.Form != nil { 182 | return 183 | } 184 | 185 | contentType := ctx.Req.Header.Get(_CONTENT_TYPE) 186 | if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") && 187 | len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") { 188 | ctx.Req.ParseMultipartForm(MaxMemory) 189 | } else { 190 | ctx.Req.ParseForm() 191 | } 192 | } 193 | 194 | // Query querys form parameter. 195 | func (ctx *Context) Query(name string) string { 196 | ctx.parseForm() 197 | return ctx.Req.Form.Get(name) 198 | } 199 | 200 | // QueryTrim querys and trims spaces form parameter. 201 | func (ctx *Context) QueryTrim(name string) string { 202 | return strings.TrimSpace(ctx.Query(name)) 203 | } 204 | 205 | // QueryStrings returns a list of results by given query name. 206 | func (ctx *Context) QueryStrings(name string) []string { 207 | ctx.parseForm() 208 | 209 | vals, ok := ctx.Req.Form[name] 210 | if !ok { 211 | return []string{} 212 | } 213 | return vals 214 | } 215 | 216 | // QueryEscape returns escapred query result. 217 | func (ctx *Context) QueryEscape(name string) string { 218 | return template.HTMLEscapeString(ctx.Query(name)) 219 | } 220 | 221 | // QueryBool returns query result in bool type. 222 | func (ctx *Context) QueryBool(name string) bool { 223 | v, _ := strconv.ParseBool(ctx.Query(name)) 224 | return v 225 | } 226 | 227 | // QueryInt returns query result in int type. 228 | func (ctx *Context) QueryInt(name string) int { 229 | return com.StrTo(ctx.Query(name)).MustInt() 230 | } 231 | 232 | // QueryInt64 returns query result in int64 type. 233 | func (ctx *Context) QueryInt64(name string) int64 { 234 | return com.StrTo(ctx.Query(name)).MustInt64() 235 | } 236 | 237 | // QueryFloat64 returns query result in float64 type. 238 | func (ctx *Context) QueryFloat64(name string) float64 { 239 | v, _ := strconv.ParseFloat(ctx.Query(name), 64) 240 | return v 241 | } 242 | 243 | // Params returns value of given param name. 244 | // e.g. ctx.Params(":uid") or ctx.Params("uid") 245 | func (ctx *Context) Params(name string) string { 246 | if len(name) == 0 { 247 | return "" 248 | } 249 | if len(name) > 1 && name[0] != ':' { 250 | name = ":" + name 251 | } 252 | return ctx.params[name] 253 | } 254 | 255 | // SetParams sets value of param with given name. 256 | func (ctx *Context) SetParams(name, val string) { 257 | if !strings.HasPrefix(name, ":") { 258 | name = ":" + name 259 | } 260 | ctx.params[name] = val 261 | } 262 | 263 | // ParamsEscape returns escapred params result. 264 | // e.g. ctx.ParamsEscape(":uname") 265 | func (ctx *Context) ParamsEscape(name string) string { 266 | return template.HTMLEscapeString(ctx.Params(name)) 267 | } 268 | 269 | // ParamsInt returns params result in int type. 270 | // e.g. ctx.ParamsInt(":uid") 271 | func (ctx *Context) ParamsInt(name string) int { 272 | return com.StrTo(ctx.Params(name)).MustInt() 273 | } 274 | 275 | // ParamsInt64 returns params result in int64 type. 276 | // e.g. ctx.ParamsInt64(":uid") 277 | func (ctx *Context) ParamsInt64(name string) int64 { 278 | return com.StrTo(ctx.Params(name)).MustInt64() 279 | } 280 | 281 | // ParamsFloat64 returns params result in int64 type. 282 | // e.g. ctx.ParamsFloat64(":uid") 283 | func (ctx *Context) ParamsFloat64(name string) float64 { 284 | v, _ := strconv.ParseFloat(ctx.Params(name), 64) 285 | return v 286 | } 287 | 288 | // GetFile returns information about user upload file by given form field name. 289 | func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) { 290 | return ctx.Req.FormFile(name) 291 | } 292 | 293 | // SaveToFile reads a file from request by field name and saves to given path. 294 | func (ctx *Context) SaveToFile(name, savePath string) error { 295 | fr, _, err := ctx.GetFile(name) 296 | if err != nil { 297 | return err 298 | } 299 | defer fr.Close() 300 | 301 | fw, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 302 | if err != nil { 303 | return err 304 | } 305 | defer fw.Close() 306 | 307 | _, err = io.Copy(fw, fr) 308 | return err 309 | } 310 | 311 | // SetCookie sets given cookie value to response header. 312 | // FIXME: IE support? http://golanghome.com/post/620#reply2 313 | func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { 314 | cookie := http.Cookie{} 315 | cookie.Name = name 316 | cookie.Value = url.QueryEscape(value) 317 | 318 | if len(others) > 0 { 319 | switch v := others[0].(type) { 320 | case int: 321 | cookie.MaxAge = v 322 | case int64: 323 | cookie.MaxAge = int(v) 324 | case int32: 325 | cookie.MaxAge = int(v) 326 | } 327 | } 328 | 329 | cookie.Path = "/" 330 | if len(others) > 1 { 331 | if v, ok := others[1].(string); ok && len(v) > 0 { 332 | cookie.Path = v 333 | } 334 | } 335 | 336 | if len(others) > 2 { 337 | if v, ok := others[2].(string); ok && len(v) > 0 { 338 | cookie.Domain = v 339 | } 340 | } 341 | 342 | if len(others) > 3 { 343 | switch v := others[3].(type) { 344 | case bool: 345 | cookie.Secure = v 346 | default: 347 | if others[3] != nil { 348 | cookie.Secure = true 349 | } 350 | } 351 | } 352 | 353 | if len(others) > 4 { 354 | if v, ok := others[4].(bool); ok && v { 355 | cookie.HttpOnly = true 356 | } 357 | } 358 | 359 | if len(others) > 5 { 360 | if v, ok := others[5].(time.Time); ok { 361 | cookie.Expires = v 362 | cookie.RawExpires = v.Format(time.UnixDate) 363 | } 364 | } 365 | 366 | ctx.Resp.Header().Add("Set-Cookie", cookie.String()) 367 | } 368 | 369 | // GetCookie returns given cookie value from request header. 370 | func (ctx *Context) GetCookie(name string) string { 371 | cookie, err := ctx.Req.Cookie(name) 372 | if err != nil { 373 | return "" 374 | } 375 | val, _ := url.QueryUnescape(cookie.Value) 376 | return val 377 | } 378 | 379 | // GetCookieInt returns cookie result in int type. 380 | func (ctx *Context) GetCookieInt(name string) int { 381 | return com.StrTo(ctx.GetCookie(name)).MustInt() 382 | } 383 | 384 | // GetCookieInt64 returns cookie result in int64 type. 385 | func (ctx *Context) GetCookieInt64(name string) int64 { 386 | return com.StrTo(ctx.GetCookie(name)).MustInt64() 387 | } 388 | 389 | // GetCookieFloat64 returns cookie result in float64 type. 390 | func (ctx *Context) GetCookieFloat64(name string) float64 { 391 | v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64) 392 | return v 393 | } 394 | 395 | var defaultCookieSecret string 396 | 397 | // SetDefaultCookieSecret sets global default secure cookie secret. 398 | func (m *Macaron) SetDefaultCookieSecret(secret string) { 399 | defaultCookieSecret = secret 400 | } 401 | 402 | // SetSecureCookie sets given cookie value to response header with default secret string. 403 | func (ctx *Context) SetSecureCookie(name, value string, others ...interface{}) { 404 | ctx.SetSuperSecureCookie(defaultCookieSecret, name, value, others...) 405 | } 406 | 407 | // GetSecureCookie returns given cookie value from request header with default secret string. 408 | func (ctx *Context) GetSecureCookie(key string) (string, bool) { 409 | return ctx.GetSuperSecureCookie(defaultCookieSecret, key) 410 | } 411 | 412 | // SetSuperSecureCookie sets given cookie value to response header with secret string. 413 | func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) { 414 | m := md5.Sum([]byte(secret)) 415 | secret = hex.EncodeToString(m[:]) 416 | text, err := com.AESEncrypt([]byte(secret), []byte(value)) 417 | if err != nil { 418 | panic("error encrypting cookie: " + err.Error()) 419 | } 420 | ctx.SetCookie(name, hex.EncodeToString(text), others...) 421 | } 422 | 423 | // GetSuperSecureCookie returns given cookie value from request header with secret string. 424 | func (ctx *Context) GetSuperSecureCookie(secret, key string) (string, bool) { 425 | val := ctx.GetCookie(key) 426 | if val == "" { 427 | return "", false 428 | } 429 | 430 | data, err := hex.DecodeString(val) 431 | if err != nil { 432 | return "", false 433 | } 434 | 435 | m := md5.Sum([]byte(secret)) 436 | secret = hex.EncodeToString(m[:]) 437 | text, err := com.AESDecrypt([]byte(secret), data) 438 | return string(text), err == nil 439 | } 440 | 441 | func (ctx *Context) setRawContentHeader() { 442 | ctx.Resp.Header().Set("Content-Description", "Raw content") 443 | ctx.Resp.Header().Set("Content-Type", "text/plain") 444 | ctx.Resp.Header().Set("Expires", "0") 445 | ctx.Resp.Header().Set("Cache-Control", "must-revalidate") 446 | ctx.Resp.Header().Set("Pragma", "public") 447 | } 448 | 449 | // ServeContent serves given content to response. 450 | func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) { 451 | modtime := time.Now() 452 | for _, p := range params { 453 | switch v := p.(type) { 454 | case time.Time: 455 | modtime = v 456 | } 457 | } 458 | 459 | ctx.setRawContentHeader() 460 | http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r) 461 | } 462 | 463 | // ServeFileContent serves given file as content to response. 464 | func (ctx *Context) ServeFileContent(file string, names ...string) { 465 | var name string 466 | if len(names) > 0 { 467 | name = names[0] 468 | } else { 469 | name = path.Base(file) 470 | } 471 | 472 | f, err := os.Open(file) 473 | if err != nil { 474 | if Env == PROD { 475 | http.Error(ctx.Resp, "Internal Server Error", 500) 476 | } else { 477 | http.Error(ctx.Resp, err.Error(), 500) 478 | } 479 | return 480 | } 481 | defer f.Close() 482 | 483 | ctx.setRawContentHeader() 484 | http.ServeContent(ctx.Resp, ctx.Req.Request, name, time.Now(), f) 485 | } 486 | 487 | // ServeFile serves given file to response. 488 | func (ctx *Context) ServeFile(file string, names ...string) { 489 | var name string 490 | if len(names) > 0 { 491 | name = names[0] 492 | } else { 493 | name = path.Base(file) 494 | } 495 | ctx.Resp.Header().Set("Content-Description", "File Transfer") 496 | ctx.Resp.Header().Set("Content-Type", "application/octet-stream") 497 | ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name) 498 | ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary") 499 | ctx.Resp.Header().Set("Expires", "0") 500 | ctx.Resp.Header().Set("Cache-Control", "must-revalidate") 501 | ctx.Resp.Header().Set("Pragma", "public") 502 | http.ServeFile(ctx.Resp, ctx.Req.Request, file) 503 | } 504 | 505 | // ChangeStaticPath changes static path from old to new one. 506 | func (ctx *Context) ChangeStaticPath(oldPath, newPath string) { 507 | if !filepath.IsAbs(oldPath) { 508 | oldPath = filepath.Join(Root, oldPath) 509 | } 510 | dir := statics.Get(oldPath) 511 | if dir != nil { 512 | statics.Delete(oldPath) 513 | 514 | if !filepath.IsAbs(newPath) { 515 | newPath = filepath.Join(Root, newPath) 516 | } 517 | *dir = http.Dir(newPath) 518 | statics.Set(dir) 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /macaron-1.1.8/context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package macaron 16 | 17 | import ( 18 | "bytes" 19 | "io/ioutil" 20 | "net/http" 21 | "net/http/httptest" 22 | "net/url" 23 | "strings" 24 | "testing" 25 | "time" 26 | 27 | "github.com/Unknwon/com" 28 | 29 | . "github.com/smartystreets/goconvey/convey" 30 | ) 31 | 32 | func Test_Context(t *testing.T) { 33 | Convey("Do advanced encapsulation operations", t, func() { 34 | m := Classic() 35 | m.Use(Renderers(RenderOptions{ 36 | Directory: "fixtures/basic", 37 | }, "fixtures/basic2")) 38 | 39 | Convey("Get request body", func() { 40 | m.Get("/body1", func(ctx *Context) { 41 | data, err := ioutil.ReadAll(ctx.Req.Body().ReadCloser()) 42 | So(err, ShouldBeNil) 43 | So(string(data), ShouldEqual, "This is my request body") 44 | }) 45 | m.Get("/body2", func(ctx *Context) { 46 | data, err := ctx.Req.Body().Bytes() 47 | So(err, ShouldBeNil) 48 | So(string(data), ShouldEqual, "This is my request body") 49 | }) 50 | m.Get("/body3", func(ctx *Context) { 51 | data, err := ctx.Req.Body().String() 52 | So(err, ShouldBeNil) 53 | So(data, ShouldEqual, "This is my request body") 54 | }) 55 | 56 | for i := 1; i <= 3; i++ { 57 | resp := httptest.NewRecorder() 58 | req, err := http.NewRequest("GET", "/body"+com.ToStr(i), nil) 59 | req.Body = ioutil.NopCloser(bytes.NewBufferString("This is my request body")) 60 | So(err, ShouldBeNil) 61 | m.ServeHTTP(resp, req) 62 | } 63 | }) 64 | 65 | Convey("Get remote IP address", func() { 66 | m.Get("/remoteaddr", func(ctx *Context) string { 67 | return ctx.RemoteAddr() 68 | }) 69 | 70 | resp := httptest.NewRecorder() 71 | req, err := http.NewRequest("GET", "/remoteaddr", nil) 72 | req.RemoteAddr = "127.0.0.1:3333" 73 | So(err, ShouldBeNil) 74 | m.ServeHTTP(resp, req) 75 | So(resp.Body.String(), ShouldEqual, "127.0.0.1") 76 | }) 77 | 78 | Convey("Render HTML", func() { 79 | 80 | Convey("Normal HTML", func() { 81 | m.Get("/html", func(ctx *Context) { 82 | ctx.HTML(304, "hello", "Unknwon") // 304 for logger test. 83 | }) 84 | 85 | resp := httptest.NewRecorder() 86 | req, err := http.NewRequest("GET", "/html", nil) 87 | So(err, ShouldBeNil) 88 | m.ServeHTTP(resp, req) 89 | So(resp.Body.String(), ShouldEqual, "

Hello Unknwon

") 90 | }) 91 | 92 | Convey("HTML template set", func() { 93 | m.Get("/html2", func(ctx *Context) { 94 | ctx.Data["Name"] = "Unknwon" 95 | ctx.HTMLSet(200, "basic2", "hello2") 96 | }) 97 | 98 | resp := httptest.NewRecorder() 99 | req, err := http.NewRequest("GET", "/html2", nil) 100 | So(err, ShouldBeNil) 101 | m.ServeHTTP(resp, req) 102 | So(resp.Body.String(), ShouldEqual, "

Hello Unknwon

") 103 | }) 104 | 105 | Convey("With layout", func() { 106 | m.Get("/layout", func(ctx *Context) { 107 | ctx.HTML(200, "hello", "Unknwon", HTMLOptions{"layout"}) 108 | }) 109 | 110 | resp := httptest.NewRecorder() 111 | req, err := http.NewRequest("GET", "/layout", nil) 112 | So(err, ShouldBeNil) 113 | m.ServeHTTP(resp, req) 114 | So(resp.Body.String(), ShouldEqual, "head

Hello Unknwon

foot") 115 | }) 116 | }) 117 | 118 | Convey("Parse from and query", func() { 119 | m.Get("/query", func(ctx *Context) string { 120 | var buf bytes.Buffer 121 | buf.WriteString(ctx.QueryTrim("name") + " ") 122 | buf.WriteString(ctx.QueryEscape("name") + " ") 123 | buf.WriteString(com.ToStr(ctx.QueryBool("bool")) + " ") 124 | buf.WriteString(com.ToStr(ctx.QueryInt("int")) + " ") 125 | buf.WriteString(com.ToStr(ctx.QueryInt64("int64")) + " ") 126 | buf.WriteString(com.ToStr(ctx.QueryFloat64("float64")) + " ") 127 | return buf.String() 128 | }) 129 | m.Get("/query2", func(ctx *Context) string { 130 | var buf bytes.Buffer 131 | buf.WriteString(strings.Join(ctx.QueryStrings("list"), ",") + " ") 132 | buf.WriteString(strings.Join(ctx.QueryStrings("404"), ",") + " ") 133 | return buf.String() 134 | }) 135 | 136 | resp := httptest.NewRecorder() 137 | req, err := http.NewRequest("GET", "/query?name=Unknwon&bool=t&int=12&int64=123&float64=1.25", nil) 138 | So(err, ShouldBeNil) 139 | m.ServeHTTP(resp, req) 140 | So(resp.Body.String(), ShouldEqual, "Unknwon Unknwon true 12 123 1.25 ") 141 | 142 | resp = httptest.NewRecorder() 143 | req, err = http.NewRequest("GET", "/query2?list=item1&list=item2", nil) 144 | So(err, ShouldBeNil) 145 | m.ServeHTTP(resp, req) 146 | So(resp.Body.String(), ShouldEqual, "item1,item2 ") 147 | }) 148 | 149 | Convey("URL parameter", func() { 150 | m.Get("/:name/:int/:int64/:float64", func(ctx *Context) string { 151 | var buf bytes.Buffer 152 | ctx.SetParams("name", ctx.Params("name")) 153 | buf.WriteString(ctx.Params("")) 154 | buf.WriteString(ctx.Params(":name") + " ") 155 | buf.WriteString(ctx.ParamsEscape(":name") + " ") 156 | buf.WriteString(com.ToStr(ctx.ParamsInt(":int")) + " ") 157 | buf.WriteString(com.ToStr(ctx.ParamsInt64(":int64")) + " ") 158 | buf.WriteString(com.ToStr(ctx.ParamsFloat64(":float64")) + " ") 159 | return buf.String() 160 | }) 161 | 162 | resp := httptest.NewRecorder() 163 | req, err := http.NewRequest("GET", "/user/1/13/1.24", nil) 164 | So(err, ShouldBeNil) 165 | m.ServeHTTP(resp, req) 166 | So(resp.Body.String(), ShouldEqual, "user user 1 13 1.24 ") 167 | }) 168 | 169 | Convey("Get file", func() { 170 | m.Post("/getfile", func(ctx *Context) { 171 | ctx.Query("") 172 | ctx.GetFile("hi") 173 | }) 174 | 175 | resp := httptest.NewRecorder() 176 | req, err := http.NewRequest("POST", "/getfile", nil) 177 | So(err, ShouldBeNil) 178 | req.Header.Set("Content-Type", "multipart/form-data") 179 | m.ServeHTTP(resp, req) 180 | }) 181 | 182 | Convey("Set and get cookie", func() { 183 | m.Get("/set", func(ctx *Context) { 184 | t, err := time.Parse(time.RFC1123, "Sun, 13 Mar 2016 01:29:26 UTC") 185 | So(err, ShouldBeNil) 186 | ctx.SetCookie("user", "Unknwon", 1, "/", "localhost", true, true, t) 187 | ctx.SetCookie("user", "Unknwon", int32(1), "/", "localhost", 1) 188 | ctx.SetCookie("user", "Unknwon", int64(1)) 189 | }) 190 | 191 | resp := httptest.NewRecorder() 192 | req, err := http.NewRequest("GET", "/set", nil) 193 | So(err, ShouldBeNil) 194 | m.ServeHTTP(resp, req) 195 | So(resp.Header().Get("Set-Cookie"), ShouldEqual, "user=Unknwon; Path=/; Domain=localhost; Expires=Sun, 13 Mar 2016 01:29:26 GMT; Max-Age=1; HttpOnly; Secure") 196 | 197 | m.Get("/get", func(ctx *Context) string { 198 | ctx.GetCookie("404") 199 | So(ctx.GetCookieInt("uid"), ShouldEqual, 1) 200 | So(ctx.GetCookieInt64("uid"), ShouldEqual, 1) 201 | So(ctx.GetCookieFloat64("balance"), ShouldEqual, 1.25) 202 | return ctx.GetCookie("user") 203 | }) 204 | 205 | resp = httptest.NewRecorder() 206 | req, err = http.NewRequest("GET", "/get", nil) 207 | So(err, ShouldBeNil) 208 | req.Header.Set("Cookie", "user=Unknwon; uid=1; balance=1.25") 209 | m.ServeHTTP(resp, req) 210 | So(resp.Body.String(), ShouldEqual, "Unknwon") 211 | }) 212 | 213 | Convey("Set and get secure cookie", func() { 214 | m.SetDefaultCookieSecret("macaron") 215 | m.Get("/set", func(ctx *Context) { 216 | ctx.SetSecureCookie("user", "Unknwon", 1) 217 | }) 218 | 219 | resp := httptest.NewRecorder() 220 | req, err := http.NewRequest("GET", "/set", nil) 221 | So(err, ShouldBeNil) 222 | m.ServeHTTP(resp, req) 223 | 224 | cookie := resp.Header().Get("Set-Cookie") 225 | 226 | m.Get("/get", func(ctx *Context) string { 227 | name, ok := ctx.GetSecureCookie("user") 228 | So(ok, ShouldBeTrue) 229 | return name 230 | }) 231 | 232 | resp = httptest.NewRecorder() 233 | req, err = http.NewRequest("GET", "/get", nil) 234 | So(err, ShouldBeNil) 235 | req.Header.Set("Cookie", cookie) 236 | m.ServeHTTP(resp, req) 237 | So(resp.Body.String(), ShouldEqual, "Unknwon") 238 | }) 239 | 240 | Convey("Serve files", func() { 241 | m.Get("/file", func(ctx *Context) { 242 | ctx.ServeFile("fixtures/custom_funcs/index.tmpl") 243 | }) 244 | 245 | resp := httptest.NewRecorder() 246 | req, err := http.NewRequest("GET", "/file", nil) 247 | So(err, ShouldBeNil) 248 | m.ServeHTTP(resp, req) 249 | So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}") 250 | 251 | m.Get("/file2", func(ctx *Context) { 252 | ctx.ServeFile("fixtures/custom_funcs/index.tmpl", "ok.tmpl") 253 | }) 254 | 255 | resp = httptest.NewRecorder() 256 | req, err = http.NewRequest("GET", "/file2", nil) 257 | So(err, ShouldBeNil) 258 | m.ServeHTTP(resp, req) 259 | So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}") 260 | }) 261 | 262 | Convey("Serve file content", func() { 263 | m.Get("/file", func(ctx *Context) { 264 | ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl") 265 | }) 266 | 267 | resp := httptest.NewRecorder() 268 | req, err := http.NewRequest("GET", "/file", nil) 269 | So(err, ShouldBeNil) 270 | m.ServeHTTP(resp, req) 271 | So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}") 272 | 273 | m.Get("/file2", func(ctx *Context) { 274 | ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl", "ok.tmpl") 275 | }) 276 | 277 | resp = httptest.NewRecorder() 278 | req, err = http.NewRequest("GET", "/file2", nil) 279 | So(err, ShouldBeNil) 280 | m.ServeHTTP(resp, req) 281 | So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}") 282 | 283 | m.Get("/file3", func(ctx *Context) { 284 | ctx.ServeFileContent("404.tmpl") 285 | }) 286 | 287 | resp = httptest.NewRecorder() 288 | req, err = http.NewRequest("GET", "/file3", nil) 289 | So(err, ShouldBeNil) 290 | m.ServeHTTP(resp, req) 291 | So(resp.Body.String(), ShouldEqual, "open 404.tmpl: no such file or directory\n") 292 | So(resp.Code, ShouldEqual, 500) 293 | }) 294 | 295 | Convey("Serve content", func() { 296 | m.Get("/content", func(ctx *Context) { 297 | ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!"))) 298 | }) 299 | 300 | resp := httptest.NewRecorder() 301 | req, err := http.NewRequest("GET", "/content", nil) 302 | So(err, ShouldBeNil) 303 | m.ServeHTTP(resp, req) 304 | So(resp.Body.String(), ShouldEqual, "Hello world!") 305 | 306 | m.Get("/content2", func(ctx *Context) { 307 | ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!")), time.Now()) 308 | }) 309 | 310 | resp = httptest.NewRecorder() 311 | req, err = http.NewRequest("GET", "/content2", nil) 312 | So(err, ShouldBeNil) 313 | m.ServeHTTP(resp, req) 314 | So(resp.Body.String(), ShouldEqual, "Hello world!") 315 | }) 316 | }) 317 | } 318 | 319 | func Test_Context_Render(t *testing.T) { 320 | Convey("Invalid render", t, func() { 321 | defer func() { 322 | So(recover(), ShouldNotBeNil) 323 | }() 324 | 325 | m := New() 326 | m.Get("/", func(ctx *Context) { 327 | ctx.HTML(200, "hey") 328 | }) 329 | 330 | resp := httptest.NewRecorder() 331 | req, err := http.NewRequest("GET", "/", nil) 332 | So(err, ShouldBeNil) 333 | m.ServeHTTP(resp, req) 334 | }) 335 | } 336 | 337 | func Test_Context_Redirect(t *testing.T) { 338 | Convey("Context with default redirect", t, func() { 339 | url, err := url.Parse("http://localhost/path/one") 340 | So(err, ShouldBeNil) 341 | resp := httptest.NewRecorder() 342 | req := http.Request{ 343 | Method: "GET", 344 | URL: url, 345 | } 346 | ctx := &Context{ 347 | Req: Request{&req}, 348 | Resp: NewResponseWriter(resp), 349 | Data: make(map[string]interface{}), 350 | } 351 | ctx.Redirect("two") 352 | 353 | So(resp.Code, ShouldEqual, http.StatusFound) 354 | So(resp.HeaderMap["Location"][0], ShouldEqual, "/path/two") 355 | }) 356 | 357 | Convey("Context with custom redirect", t, func() { 358 | url, err := url.Parse("http://localhost/path/one") 359 | So(err, ShouldBeNil) 360 | resp := httptest.NewRecorder() 361 | req := http.Request{ 362 | Method: "GET", 363 | URL: url, 364 | } 365 | ctx := &Context{ 366 | Req: Request{&req}, 367 | Resp: NewResponseWriter(resp), 368 | Data: make(map[string]interface{}), 369 | } 370 | ctx.Redirect("two", 307) 371 | 372 | So(resp.Code, ShouldEqual, http.StatusTemporaryRedirect) 373 | So(resp.HeaderMap["Location"][0], ShouldEqual, "/path/two") 374 | }) 375 | } 376 | -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/admin/index.tmpl: -------------------------------------------------------------------------------- 1 |

Admin {{.}}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/another_layout.tmpl: -------------------------------------------------------------------------------- 1 | another head{{ yield }}another foot -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/content.tmpl: -------------------------------------------------------------------------------- 1 |

{{ . }}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/current_layout.tmpl: -------------------------------------------------------------------------------- 1 | {{ current }} head{{ yield }}{{ current }} foot -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/custom/hello.tmpl: -------------------------------------------------------------------------------- 1 |

This is custom version of: Hello {{.}}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/delims.tmpl: -------------------------------------------------------------------------------- 1 |

Hello {[{.}]}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/hello.tmpl: -------------------------------------------------------------------------------- 1 |

Hello {{.}}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/hypertext.html: -------------------------------------------------------------------------------- 1 | Hypertext! -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic/layout.tmpl: -------------------------------------------------------------------------------- 1 | head{{ yield }}foot -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic2/hello.tmpl: -------------------------------------------------------------------------------- 1 |

What's up, {{.}}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/basic2/hello2.tmpl: -------------------------------------------------------------------------------- 1 |

Hello {{.Name}}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/custom_funcs/index.tmpl: -------------------------------------------------------------------------------- 1 | {{ myCustomFunc }} -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/admin/index.tmpl: -------------------------------------------------------------------------------- 1 |

Admin {{.}}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/another_layout.tmpl: -------------------------------------------------------------------------------- 1 | another head{{ yield }}another foot -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/content.tmpl: -------------------------------------------------------------------------------- 1 |

{{ . }}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/current_layout.tmpl: -------------------------------------------------------------------------------- 1 | {{ current }} head{{ yield }}{{ current }} foot -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/custom/hello.tmpl: -------------------------------------------------------------------------------- 1 |

This is custom version of: Hello {{.}}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/delims.tmpl: -------------------------------------------------------------------------------- 1 |

Hello {[{.}]}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/hello.tmpl: -------------------------------------------------------------------------------- 1 |

Hello {{.}}

-------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/hypertext.html: -------------------------------------------------------------------------------- 1 | Hypertext! -------------------------------------------------------------------------------- /macaron-1.1.8/fixtures/symlink/layout.tmpl: -------------------------------------------------------------------------------- 1 | head{{ yield }}foot -------------------------------------------------------------------------------- /macaron-1.1.8/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "fmt" 20 | "log" 21 | "net/http" 22 | "runtime" 23 | "time" 24 | ) 25 | 26 | var ( 27 | ColorLog = true 28 | LogTimeFormat = "2006-01-02 15:04:05" 29 | ) 30 | 31 | func init() { 32 | ColorLog = runtime.GOOS != "windows" 33 | } 34 | 35 | // Logger returns a middleware handler that logs the request as it goes in and the response as it goes out. 36 | func Logger() Handler { 37 | return func(ctx *Context, log *log.Logger) { 38 | start := time.Now() 39 | 40 | log.Printf("%s: Started %s %s for %s", time.Now().Format(LogTimeFormat), ctx.Req.Method, ctx.Req.RequestURI, ctx.RemoteAddr()) 41 | 42 | rw := ctx.Resp.(ResponseWriter) 43 | ctx.Next() 44 | 45 | content := fmt.Sprintf("%s: Completed %s %v %s in %v", time.Now().Format(LogTimeFormat), ctx.Req.RequestURI, rw.Status(), http.StatusText(rw.Status()), time.Since(start)) 46 | if ColorLog { 47 | switch rw.Status() { 48 | case 200, 201, 202: 49 | content = fmt.Sprintf("\033[1;32m%s\033[0m", content) 50 | case 301, 302: 51 | content = fmt.Sprintf("\033[1;37m%s\033[0m", content) 52 | case 304: 53 | content = fmt.Sprintf("\033[1;33m%s\033[0m", content) 54 | case 401, 403: 55 | content = fmt.Sprintf("\033[4;31m%s\033[0m", content) 56 | case 404: 57 | content = fmt.Sprintf("\033[1;31m%s\033[0m", content) 58 | case 500: 59 | content = fmt.Sprintf("\033[1;36m%s\033[0m", content) 60 | } 61 | } 62 | log.Println(content) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /macaron-1.1.8/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "bytes" 20 | "log" 21 | "net/http" 22 | "net/http/httptest" 23 | "testing" 24 | 25 | "github.com/Unknwon/com" 26 | 27 | . "github.com/smartystreets/goconvey/convey" 28 | ) 29 | 30 | func Test_Logger(t *testing.T) { 31 | Convey("Global logger", t, func() { 32 | buf := bytes.NewBufferString("") 33 | m := New() 34 | m.Map(log.New(buf, "[Macaron] ", 0)) 35 | m.Use(Logger()) 36 | m.Use(func(res http.ResponseWriter) { 37 | res.WriteHeader(http.StatusNotFound) 38 | }) 39 | m.Get("/", func() {}) 40 | 41 | resp := httptest.NewRecorder() 42 | req, err := http.NewRequest("GET", "http://localhost:4000/", nil) 43 | So(err, ShouldBeNil) 44 | m.ServeHTTP(resp, req) 45 | So(resp.Code, ShouldEqual, http.StatusNotFound) 46 | So(len(buf.String()), ShouldBeGreaterThan, 0) 47 | }) 48 | 49 | if ColorLog { 50 | Convey("Color console output", t, func() { 51 | m := Classic() 52 | m.Get("/:code:int", func(ctx *Context) (int, string) { 53 | return ctx.ParamsInt(":code"), "" 54 | }) 55 | 56 | // Just for testing if logger would capture. 57 | codes := []int{200, 201, 202, 301, 302, 304, 401, 403, 404, 500} 58 | for _, code := range codes { 59 | resp := httptest.NewRecorder() 60 | req, err := http.NewRequest("GET", "http://localhost:4000/"+com.ToStr(code), nil) 61 | So(err, ShouldBeNil) 62 | m.ServeHTTP(resp, req) 63 | So(resp.Code, ShouldEqual, code) 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /macaron-1.1.8/macaron.go: -------------------------------------------------------------------------------- 1 | // +build go1.3 2 | 3 | // Copyright 2014 The Macaron Authors 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 6 | // not use this file except in compliance with the License. You may obtain 7 | // a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | // License for the specific language governing permissions and limitations 15 | // under the License. 16 | 17 | // Package macaron is a high productive and modular web framework in Go. 18 | package macaron 19 | 20 | import ( 21 | "io" 22 | "log" 23 | "net/http" 24 | "os" 25 | "reflect" 26 | "strings" 27 | "sync" 28 | 29 | "github.com/Unknwon/com" 30 | "gopkg.in/ini.v1" 31 | 32 | "github.com/go-macaron/inject" 33 | ) 34 | 35 | const _VERSION = "1.1.8.0826" 36 | 37 | func Version() string { 38 | return _VERSION 39 | } 40 | 41 | //----------------------------------------------------- 42 | // Handler 类型: 空接口 43 | // - 支持传入任意类型, 但要求传入类型为: 可调用函数 44 | // Handler can be any callable function. 45 | // Macaron attempts to inject services into the handler's argument list, 46 | // and panics if an argument could not be fullfilled via dependency injection. 47 | type Handler interface{} 48 | 49 | // 校验 Handler 类型. 50 | // validateHandler makes sure a handler is a callable function, 51 | // and panics if it is not. 52 | func validateHandler(h Handler) { 53 | if reflect.TypeOf(h).Kind() != reflect.Func { 54 | panic("Macaron handler must be a callable function") 55 | } 56 | } 57 | 58 | // 批量校验 59 | // validateHandlers makes sure handlers are callable functions, 60 | // and panics if any of them is not. 61 | func validateHandlers(handlers []Handler) { 62 | for _, h := range handlers { 63 | validateHandler(h) 64 | } 65 | } 66 | 67 | //----------------------------------------------------- 68 | 69 | 70 | // Macaron represents the top level web application. 71 | // inject.Injector methods can be invoked to map services on a global level. 72 | type Macaron struct { 73 | inject.Injector // 反射 74 | befores []BeforeHandler // Handler 钩子 75 | handlers []Handler // Handler 集合 76 | action Handler 77 | 78 | hasURLPrefix bool // 是否有 URL 前缀 79 | urlPrefix string // For suburl support. 80 | *Router // 路由模块, 注意Router的定义, (类似链表结构) 81 | // 这里需要理解一下: 是 嵌入, 不是 循环引用, 跟Macaron定义不冲突 82 | 83 | logger *log.Logger // 日志记录器 84 | } 85 | 86 | // NewWithLogger creates a bare bones Macaron instance. 87 | // Use this method if you want to have full control over the middleware that is used. 88 | // You can specify logger output writer with this function. 89 | func NewWithLogger(out io.Writer) *Macaron { 90 | m := &Macaron{ 91 | Injector: inject.New(), 92 | action: func() {}, 93 | Router: NewRouter(), 94 | logger: log.New(out, "[Macaron] ", 0), 95 | } 96 | m.Router.m = m 97 | m.Map(m.logger) 98 | m.Map(defaultReturnHandler()) 99 | m.NotFound(http.NotFound) 100 | m.InternalServerError(func(rw http.ResponseWriter, err error) { 101 | http.Error(rw, err.Error(), 500) 102 | }) 103 | return m 104 | } 105 | 106 | // New creates a bare bones Macaron instance. 107 | // Use this method if you want to have full control over the middleware that is used. 108 | func New() *Macaron { 109 | return NewWithLogger(os.Stdout) 110 | } 111 | 112 | // Classic creates a classic Macaron with some basic default middleware: 113 | // mocaron.Logger, mocaron.Recovery and mocaron.Static. 114 | func Classic() *Macaron { 115 | m := New() 116 | m.Use(Logger()) // 添加到 handler 列表 117 | m.Use(Recovery()) // 添加到 handler 列表 118 | m.Use(Static("public")) // 添加到 handler 列表 119 | return m 120 | } 121 | 122 | // Handlers sets the entire middleware stack with the given Handlers. 123 | // This will clear any current middleware handlers, 124 | // and panics if any of the handlers is not a callable function 125 | func (m *Macaron) Handlers(handlers ...Handler) { 126 | m.handlers = make([]Handler, 0) 127 | for _, handler := range handlers { 128 | m.Use(handler) // 添加到 handler 列表 129 | } 130 | } 131 | 132 | // Action sets the handler that will be called after all the middleware has been invoked. 133 | // This is set to macaron.Router in a macaron.Classic(). 134 | func (m *Macaron) Action(handler Handler) { 135 | validateHandler(handler) // 校验 136 | m.action = handler // 激活 137 | } 138 | 139 | //************************************* 140 | // 钩子函数类型定义 141 | // 在 handler 调用之前执行 142 | // BeforeHandler represents a handler executes at beginning of every request. 143 | // Macaron stops future process when it returns true. 144 | type BeforeHandler func(rw http.ResponseWriter, req *http.Request) bool 145 | 146 | // 添加钩子集合: 147 | // 将所有 `BeforeHandler` 类型的函数钩子, 添加到 befores 列表. 148 | func (m *Macaron) Before(handler BeforeHandler) { 149 | m.befores = append(m.befores, handler) 150 | } 151 | 152 | 153 | // 关键方法: 154 | // Use adds a middleware Handler to the stack, 155 | // and panics if the handler is not a callable func. 156 | // Middleware Handlers are invoked in the order that they are added. 157 | func (m *Macaron) Use(handler Handler) { 158 | validateHandler(handler) // 校验 159 | m.handlers = append(m.handlers, handler) // 添加到方法列表 160 | } 161 | 162 | 163 | // 请求上下文创建: 164 | // - 类似 flask 的上下文概念 165 | // - 这部分代码的实现, 注意阅读, 不好理解 166 | func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Context { 167 | c := &Context{ 168 | Injector: inject.New(), 169 | handlers: m.handlers, 170 | action: m.action, 171 | index: 0, 172 | Router: m.Router, 173 | Req: Request{req}, 174 | Resp: NewResponseWriter(rw), 175 | Render: &DummyRender{rw}, 176 | Data: make(map[string]interface{}), 177 | } 178 | c.SetParent(m) // 关键方法 179 | c.Map(c) 180 | c.MapTo(c.Resp, (*http.ResponseWriter)(nil)) 181 | c.Map(req) 182 | return c 183 | } 184 | 185 | //************************************* 186 | // 关键方法: 187 | // ServeHTTP is the HTTP Entry point for a Macaron instance. 188 | // Useful if you want to control your own HTTP server. 189 | // Be aware that none of middleware will run without registering any router. 190 | func (m *Macaron) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 191 | if m.hasURLPrefix { 192 | req.URL.Path = strings.TrimPrefix(req.URL.Path, m.urlPrefix) 193 | } 194 | 195 | // handler 钩子方法列表不空, 先行执行钩子方法 196 | for _, h := range m.befores { 197 | if h(rw, req) { 198 | return 199 | } 200 | } 201 | 202 | // 启动服务 203 | m.Router.ServeHTTP(rw, req) 204 | } 205 | 206 | func GetDefaultListenInfo() (string, int) { 207 | host := os.Getenv("HOST") 208 | if len(host) == 0 { 209 | host = "0.0.0.0" // 默认 IP 210 | } 211 | port := com.StrTo(os.Getenv("PORT")).MustInt() 212 | if port == 0 { 213 | port = 4000 // 默认端口 214 | } 215 | return host, port 216 | } 217 | 218 | //************************************* 219 | // 模块入口: 220 | // 221 | // Run the http server. Listening on os.GetEnv("PORT") or 4000 by default. 222 | func (m *Macaron) Run(args ...interface{}) { 223 | host, port := GetDefaultListenInfo() // 获取默认IP+端口 224 | if len(args) == 1 { 225 | switch arg := args[0].(type) { 226 | case string: 227 | host = arg 228 | case int: 229 | port = arg 230 | } 231 | } else if len(args) >= 2 { 232 | if arg, ok := args[0].(string); ok { 233 | host = arg 234 | } 235 | if arg, ok := args[1].(int); ok { 236 | port = arg 237 | } 238 | } 239 | 240 | addr := host + ":" + com.ToStr(port) // IP + 端口 241 | logger := m.GetVal(reflect.TypeOf(m.logger)).Interface().(*log.Logger) 242 | logger.Printf("listening on %s (%s)\n", addr, safeEnv()) 243 | logger.Fatalln(http.ListenAndServe(addr, m)) // 启动监听服务 244 | } 245 | 246 | // SetURLPrefix sets URL prefix of router layer, so that it support suburl. 247 | func (m *Macaron) SetURLPrefix(prefix string) { 248 | m.urlPrefix = prefix 249 | m.hasURLPrefix = len(m.urlPrefix) > 0 250 | } 251 | 252 | // ____ ____ .__ ___. .__ 253 | // \ \ / /____ _______|__|____ \_ |__ | | ____ ______ 254 | // \ Y /\__ \\_ __ \ \__ \ | __ \| | _/ __ \ / ___/ 255 | // \ / / __ \| | \/ |/ __ \| \_\ \ |_\ ___/ \___ \ 256 | // \___/ (____ /__| |__(____ /___ /____/\___ >____ > 257 | // \/ \/ \/ \/ \/ 258 | 259 | const ( 260 | DEV = "development" 261 | PROD = "production" 262 | TEST = "test" 263 | ) 264 | 265 | var ( 266 | // Env is the environment that Macaron is executing in. 267 | // The MACARON_ENV is read on initialization to set this variable. 268 | Env = DEV 269 | envLock sync.Mutex 270 | 271 | // Path of work directory. 272 | Root string 273 | 274 | // Flash applies to current request. 275 | FlashNow bool 276 | 277 | // Configuration convention object. 278 | cfg *ini.File 279 | ) 280 | 281 | func setENV(e string) { 282 | envLock.Lock() 283 | defer envLock.Unlock() 284 | 285 | if len(e) > 0 { 286 | Env = e 287 | } 288 | } 289 | 290 | func safeEnv() string { 291 | envLock.Lock() 292 | defer envLock.Unlock() 293 | 294 | return Env 295 | } 296 | 297 | func init() { 298 | setENV(os.Getenv("MACARON_ENV")) 299 | 300 | var err error 301 | Root, err = os.Getwd() 302 | if err != nil { 303 | panic("error getting work directory: " + err.Error()) 304 | } 305 | } 306 | 307 | // SetConfig sets data sources for configuration. 308 | func SetConfig(source interface{}, others ...interface{}) (_ *ini.File, err error) { 309 | cfg, err = ini.Load(source, others...) 310 | return Config(), err 311 | } 312 | 313 | // Config returns configuration convention object. 314 | // It returns an empty object if there is no one available. 315 | func Config() *ini.File { 316 | if cfg == nil { 317 | return ini.Empty() 318 | } 319 | return cfg 320 | } 321 | -------------------------------------------------------------------------------- /macaron-1.1.8/macaron_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "net/http" 20 | "net/http/httptest" 21 | "os" 22 | "testing" 23 | "time" 24 | 25 | . "github.com/smartystreets/goconvey/convey" 26 | ) 27 | 28 | func Test_Version(t *testing.T) { 29 | Convey("Get version", t, func() { 30 | So(Version(), ShouldEqual, _VERSION) 31 | }) 32 | } 33 | 34 | func Test_New(t *testing.T) { 35 | Convey("Initialize a new instance", t, func() { 36 | So(New(), ShouldNotBeNil) 37 | }) 38 | 39 | Convey("Just test that Run doesn't bomb", t, func() { 40 | go New().Run() 41 | time.Sleep(1 * time.Second) 42 | os.Setenv("PORT", "4001") 43 | go New().Run("0.0.0.0") 44 | go New().Run(4002) 45 | go New().Run("0.0.0.0", 4003) 46 | }) 47 | } 48 | 49 | func Test_Macaron_Before(t *testing.T) { 50 | Convey("Register before handlers", t, func() { 51 | m := New() 52 | m.Before(func(rw http.ResponseWriter, req *http.Request) bool { 53 | return false 54 | }) 55 | m.Before(func(rw http.ResponseWriter, req *http.Request) bool { 56 | return true 57 | }) 58 | resp := httptest.NewRecorder() 59 | req, err := http.NewRequest("GET", "/", nil) 60 | So(err, ShouldBeNil) 61 | m.ServeHTTP(resp, req) 62 | }) 63 | } 64 | 65 | func Test_Macaron_ServeHTTP(t *testing.T) { 66 | Convey("Serve HTTP requests", t, func() { 67 | result := "" 68 | m := New() 69 | m.Use(func(c *Context) { 70 | result += "foo" 71 | c.Next() 72 | result += "ban" 73 | }) 74 | m.Use(func(c *Context) { 75 | result += "bar" 76 | c.Next() 77 | result += "baz" 78 | }) 79 | m.Get("/", func() {}) 80 | m.Action(func(res http.ResponseWriter, req *http.Request) { 81 | result += "bat" 82 | res.WriteHeader(http.StatusBadRequest) 83 | }) 84 | 85 | resp := httptest.NewRecorder() 86 | req, err := http.NewRequest("GET", "/", nil) 87 | So(err, ShouldBeNil) 88 | m.ServeHTTP(resp, req) 89 | So(result, ShouldEqual, "foobarbatbazban") 90 | So(resp.Code, ShouldEqual, http.StatusBadRequest) 91 | }) 92 | } 93 | 94 | func Test_Macaron_Handlers(t *testing.T) { 95 | Convey("Add custom handlers", t, func() { 96 | result := "" 97 | batman := func(c *Context) { 98 | result += "batman!" 99 | } 100 | 101 | m := New() 102 | m.Use(func(c *Context) { 103 | result += "foo" 104 | c.Next() 105 | result += "ban" 106 | }) 107 | m.Handlers( 108 | batman, 109 | batman, 110 | batman, 111 | ) 112 | 113 | Convey("Add not callable function", func() { 114 | defer func() { 115 | So(recover(), ShouldNotBeNil) 116 | }() 117 | m.Use("shit") 118 | }) 119 | 120 | m.Get("/", func() {}) 121 | m.Action(func(res http.ResponseWriter, req *http.Request) { 122 | result += "bat" 123 | res.WriteHeader(http.StatusBadRequest) 124 | }) 125 | 126 | resp := httptest.NewRecorder() 127 | req, err := http.NewRequest("GET", "/", nil) 128 | So(err, ShouldBeNil) 129 | m.ServeHTTP(resp, req) 130 | So(result, ShouldEqual, "batman!batman!batman!bat") 131 | So(resp.Code, ShouldEqual, http.StatusBadRequest) 132 | }) 133 | } 134 | 135 | func Test_Macaron_EarlyWrite(t *testing.T) { 136 | Convey("Write early content to response", t, func() { 137 | result := "" 138 | m := New() 139 | m.Use(func(res http.ResponseWriter) { 140 | result += "foobar" 141 | res.Write([]byte("Hello world")) 142 | }) 143 | m.Use(func() { 144 | result += "bat" 145 | }) 146 | m.Get("/", func() {}) 147 | m.Action(func(res http.ResponseWriter) { 148 | result += "baz" 149 | res.WriteHeader(http.StatusBadRequest) 150 | }) 151 | 152 | resp := httptest.NewRecorder() 153 | req, err := http.NewRequest("GET", "/", nil) 154 | So(err, ShouldBeNil) 155 | m.ServeHTTP(resp, req) 156 | So(result, ShouldEqual, "foobar") 157 | So(resp.Code, ShouldEqual, http.StatusOK) 158 | }) 159 | } 160 | 161 | func Test_Macaron_Written(t *testing.T) { 162 | Convey("Written sign", t, func() { 163 | resp := httptest.NewRecorder() 164 | m := New() 165 | m.Handlers(func(res http.ResponseWriter) { 166 | res.WriteHeader(http.StatusOK) 167 | }) 168 | 169 | ctx := m.createContext(resp, &http.Request{Method: "GET"}) 170 | So(ctx.Written(), ShouldBeFalse) 171 | 172 | ctx.run() 173 | So(ctx.Written(), ShouldBeTrue) 174 | }) 175 | } 176 | 177 | func Test_Macaron_Basic_NoRace(t *testing.T) { 178 | Convey("Make sure no race between requests", t, func() { 179 | m := New() 180 | handlers := []Handler{func() {}, func() {}} 181 | // Ensure append will not realloc to trigger the race condition 182 | m.handlers = handlers[:1] 183 | m.Get("/", func() {}) 184 | for i := 0; i < 2; i++ { 185 | go func() { 186 | req, _ := http.NewRequest("GET", "/", nil) 187 | resp := httptest.NewRecorder() 188 | m.ServeHTTP(resp, req) 189 | }() 190 | } 191 | }) 192 | } 193 | 194 | func Test_SetENV(t *testing.T) { 195 | Convey("Get and save environment variable", t, func() { 196 | tests := []struct { 197 | in string 198 | out string 199 | }{ 200 | {"", "development"}, 201 | {"not_development", "not_development"}, 202 | } 203 | 204 | for _, test := range tests { 205 | setENV(test.in) 206 | So(Env, ShouldEqual, test.out) 207 | } 208 | }) 209 | } 210 | 211 | func Test_Config(t *testing.T) { 212 | Convey("Set and get configuration object", t, func() { 213 | So(Config(), ShouldNotBeNil) 214 | cfg, err := SetConfig([]byte("")) 215 | So(err, ShouldBeNil) 216 | So(cfg, ShouldNotBeNil) 217 | }) 218 | } 219 | -------------------------------------------------------------------------------- /macaron-1.1.8/recovery.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "bytes" 20 | "fmt" 21 | "io/ioutil" 22 | "log" 23 | "net/http" 24 | "runtime" 25 | 26 | "github.com/go-macaron/inject" 27 | ) 28 | 29 | const ( 30 | panicHtml = ` 31 | PANIC: %s 32 | 33 | 58 | 59 |

PANIC

60 |
%s
61 |
%s
62 | 63 | ` 64 | ) 65 | 66 | var ( 67 | dunno = []byte("???") 68 | centerDot = []byte("·") 69 | dot = []byte(".") 70 | slash = []byte("/") 71 | ) 72 | 73 | // stack returns a nicely formated stack frame, skipping skip frames 74 | func stack(skip int) []byte { 75 | buf := new(bytes.Buffer) // the returned data 76 | // As we loop, we open files and read them. These variables record the currently 77 | // loaded file. 78 | var lines [][]byte 79 | var lastFile string 80 | for i := skip; ; i++ { // Skip the expected number of frames 81 | pc, file, line, ok := runtime.Caller(i) 82 | if !ok { 83 | break 84 | } 85 | // Print this much at least. If we can't find the source, it won't show. 86 | fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) 87 | if file != lastFile { 88 | data, err := ioutil.ReadFile(file) 89 | if err != nil { 90 | continue 91 | } 92 | lines = bytes.Split(data, []byte{'\n'}) 93 | lastFile = file 94 | } 95 | fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) 96 | } 97 | return buf.Bytes() 98 | } 99 | 100 | // source returns a space-trimmed slice of the n'th line. 101 | func source(lines [][]byte, n int) []byte { 102 | n-- // in stack trace, lines are 1-indexed but our array is 0-indexed 103 | if n < 0 || n >= len(lines) { 104 | return dunno 105 | } 106 | return bytes.TrimSpace(lines[n]) 107 | } 108 | 109 | // function returns, if possible, the name of the function containing the PC. 110 | func function(pc uintptr) []byte { 111 | fn := runtime.FuncForPC(pc) 112 | if fn == nil { 113 | return dunno 114 | } 115 | name := []byte(fn.Name()) 116 | // The name includes the path name to the package, which is unnecessary 117 | // since the file name is already included. Plus, it has center dots. 118 | // That is, we see 119 | // runtime/debug.*T·ptrmethod 120 | // and want 121 | // *T.ptrmethod 122 | // Also the package path might contains dot (e.g. code.google.com/...), 123 | // so first eliminate the path prefix 124 | if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 { 125 | name = name[lastslash+1:] 126 | } 127 | if period := bytes.Index(name, dot); period >= 0 { 128 | name = name[period+1:] 129 | } 130 | name = bytes.Replace(name, centerDot, dot, -1) 131 | return name 132 | } 133 | 134 | // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. 135 | // While Martini is in development mode, Recovery will also output the panic as HTML. 136 | func Recovery() Handler { 137 | return func(c *Context, log *log.Logger) { 138 | defer func() { 139 | if err := recover(); err != nil { 140 | stack := stack(3) 141 | log.Printf("PANIC: %s\n%s", err, stack) 142 | 143 | // Lookup the current responsewriter 144 | val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil))) 145 | res := val.Interface().(http.ResponseWriter) 146 | 147 | // respond with panic message while in development mode 148 | var body []byte 149 | if Env == DEV { 150 | res.Header().Set("Content-Type", "text/html") 151 | body = []byte(fmt.Sprintf(panicHtml, err, err, stack)) 152 | } 153 | 154 | res.WriteHeader(http.StatusInternalServerError) 155 | if nil != body { 156 | res.Write(body) 157 | } 158 | } 159 | }() 160 | 161 | c.Next() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /macaron-1.1.8/recovery_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "bytes" 20 | "log" 21 | "net/http" 22 | "net/http/httptest" 23 | "testing" 24 | 25 | . "github.com/smartystreets/goconvey/convey" 26 | ) 27 | 28 | func Test_Recovery(t *testing.T) { 29 | Convey("Recovery from panic", t, func() { 30 | buf := bytes.NewBufferString("") 31 | setENV(DEV) 32 | 33 | m := New() 34 | m.Map(log.New(buf, "[Macaron] ", 0)) 35 | m.Use(func(res http.ResponseWriter, req *http.Request) { 36 | res.Header().Set("Content-Type", "unpredictable") 37 | }) 38 | m.Use(Recovery()) 39 | m.Use(func(res http.ResponseWriter, req *http.Request) { 40 | panic("here is a panic!") 41 | }) 42 | m.Get("/", func() {}) 43 | 44 | resp := httptest.NewRecorder() 45 | req, err := http.NewRequest("GET", "/", nil) 46 | So(err, ShouldBeNil) 47 | m.ServeHTTP(resp, req) 48 | So(resp.Code, ShouldEqual, http.StatusInternalServerError) 49 | So(resp.HeaderMap.Get("Content-Type"), ShouldEqual, "text/html") 50 | So(buf.String(), ShouldNotBeEmpty) 51 | }) 52 | 53 | Convey("Revocery panic to another response writer", t, func() { 54 | resp := httptest.NewRecorder() 55 | resp2 := httptest.NewRecorder() 56 | setENV(DEV) 57 | 58 | m := New() 59 | m.Use(Recovery()) 60 | m.Use(func(c *Context) { 61 | c.MapTo(resp2, (*http.ResponseWriter)(nil)) 62 | panic("here is a panic!") 63 | }) 64 | m.Get("/", func() {}) 65 | 66 | req, err := http.NewRequest("GET", "/", nil) 67 | So(err, ShouldBeNil) 68 | m.ServeHTTP(resp, req) 69 | 70 | So(resp2.Code, ShouldEqual, http.StatusInternalServerError) 71 | So(resp2.HeaderMap.Get("Content-Type"), ShouldEqual, "text/html") 72 | So(resp2.Body.Len(), ShouldBeGreaterThan, 0) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /macaron-1.1.8/render.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "bytes" 20 | "encoding/json" 21 | "encoding/xml" 22 | "fmt" 23 | "html/template" 24 | "io/ioutil" 25 | "net/http" 26 | "os" 27 | "path" 28 | "path/filepath" 29 | "strings" 30 | "sync" 31 | "time" 32 | 33 | "github.com/Unknwon/com" 34 | ) 35 | 36 | const ( 37 | _CONTENT_TYPE = "Content-Type" 38 | _CONTENT_LENGTH = "Content-Length" 39 | _CONTENT_BINARY = "application/octet-stream" 40 | _CONTENT_JSON = "application/json" 41 | _CONTENT_HTML = "text/html" 42 | _CONTENT_PLAIN = "text/plain" 43 | _CONTENT_XHTML = "application/xhtml+xml" 44 | _CONTENT_XML = "text/xml" 45 | _DEFAULT_CHARSET = "UTF-8" 46 | ) 47 | 48 | var ( 49 | // Provides a temporary buffer to execute templates into and catch errors. 50 | bufpool = sync.Pool{ 51 | New: func() interface{} { return new(bytes.Buffer) }, 52 | } 53 | 54 | // Included helper functions for use when rendering html 55 | helperFuncs = template.FuncMap{ 56 | "yield": func() (string, error) { 57 | return "", fmt.Errorf("yield called with no layout defined") 58 | }, 59 | "current": func() (string, error) { 60 | return "", nil 61 | }, 62 | } 63 | ) 64 | 65 | type ( 66 | // TemplateFile represents a interface of template file that has name and can be read. 67 | TemplateFile interface { 68 | Name() string 69 | Data() []byte 70 | Ext() string 71 | } 72 | // TemplateFileSystem represents a interface of template file system that able to list all files. 73 | TemplateFileSystem interface { 74 | ListFiles() []TemplateFile 75 | } 76 | 77 | // Delims represents a set of Left and Right delimiters for HTML template rendering 78 | Delims struct { 79 | // Left delimiter, defaults to {{ 80 | Left string 81 | // Right delimiter, defaults to }} 82 | Right string 83 | } 84 | 85 | // RenderOptions represents a struct for specifying configuration options for the Render middleware. 86 | RenderOptions struct { 87 | // Directory to load templates. Default is "templates". 88 | Directory string 89 | // Addtional directories to overwite templates. 90 | AppendDirectories []string 91 | // Layout template name. Will not render a layout if "". Default is to "". 92 | Layout string 93 | // Extensions to parse template files from. Defaults are [".tmpl", ".html"]. 94 | Extensions []string 95 | // Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is []. 96 | Funcs []template.FuncMap 97 | // Delims sets the action delimiters to the specified strings in the Delims struct. 98 | Delims Delims 99 | // Appends the given charset to the Content-Type header. Default is "UTF-8". 100 | Charset string 101 | // Outputs human readable JSON. 102 | IndentJSON bool 103 | // Outputs human readable XML. 104 | IndentXML bool 105 | // Prefixes the JSON output with the given bytes. 106 | PrefixJSON []byte 107 | // Prefixes the XML output with the given bytes. 108 | PrefixXML []byte 109 | // Allows changing of output to XHTML instead of HTML. Default is "text/html" 110 | HTMLContentType string 111 | // TemplateFileSystem is the interface for supporting any implmentation of template file system. 112 | TemplateFileSystem 113 | } 114 | 115 | // HTMLOptions is a struct for overriding some rendering Options for specific HTML call 116 | HTMLOptions struct { 117 | // Layout template name. Overrides Options.Layout. 118 | Layout string 119 | } 120 | 121 | Render interface { 122 | http.ResponseWriter 123 | SetResponseWriter(http.ResponseWriter) 124 | 125 | JSON(int, interface{}) 126 | JSONString(interface{}) (string, error) 127 | RawData(int, []byte) // Serve content as binary 128 | PlainText(int, []byte) // Serve content as plain text 129 | HTML(int, string, interface{}, ...HTMLOptions) 130 | HTMLSet(int, string, string, interface{}, ...HTMLOptions) 131 | HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) 132 | HTMLString(string, interface{}, ...HTMLOptions) (string, error) 133 | HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) 134 | HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) 135 | XML(int, interface{}) 136 | Error(int, ...string) 137 | Status(int) 138 | SetTemplatePath(string, string) 139 | HasTemplateSet(string) bool 140 | } 141 | ) 142 | 143 | // TplFile implements TemplateFile interface. 144 | type TplFile struct { 145 | name string 146 | data []byte 147 | ext string 148 | } 149 | 150 | // NewTplFile cerates new template file with given name and data. 151 | func NewTplFile(name string, data []byte, ext string) *TplFile { 152 | return &TplFile{name, data, ext} 153 | } 154 | 155 | func (f *TplFile) Name() string { 156 | return f.name 157 | } 158 | 159 | func (f *TplFile) Data() []byte { 160 | return f.data 161 | } 162 | 163 | func (f *TplFile) Ext() string { 164 | return f.ext 165 | } 166 | 167 | // TplFileSystem implements TemplateFileSystem interface. 168 | type TplFileSystem struct { 169 | files []TemplateFile 170 | } 171 | 172 | // NewTemplateFileSystem creates new template file system with given options. 173 | func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem { 174 | fs := TplFileSystem{} 175 | fs.files = make([]TemplateFile, 0, 10) 176 | 177 | // Directories are composed in reverse order because later one overwrites previous ones, 178 | // so once found, we can directly jump out of the loop. 179 | dirs := make([]string, 0, len(opt.AppendDirectories)+1) 180 | for i := len(opt.AppendDirectories) - 1; i >= 0; i-- { 181 | dirs = append(dirs, opt.AppendDirectories[i]) 182 | } 183 | dirs = append(dirs, opt.Directory) 184 | 185 | var err error 186 | for i := range dirs { 187 | // Skip ones that does not exists for symlink test, 188 | // but allow non-symlink ones added after start. 189 | if !com.IsExist(dirs[i]) { 190 | continue 191 | } 192 | 193 | dirs[i], err = filepath.EvalSymlinks(dirs[i]) 194 | if err != nil { 195 | panic("EvalSymlinks(" + dirs[i] + "): " + err.Error()) 196 | } 197 | } 198 | lastDir := dirs[len(dirs)-1] 199 | 200 | // We still walk the last (original) directory because it's non-sense we load templates not exist in original directory. 201 | if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, err error) error { 202 | r, err := filepath.Rel(lastDir, path) 203 | if err != nil { 204 | return err 205 | } 206 | 207 | ext := GetExt(r) 208 | 209 | for _, extension := range opt.Extensions { 210 | if ext != extension { 211 | continue 212 | } 213 | 214 | var data []byte 215 | if !omitData { 216 | // Loop over candidates of directory, break out once found. 217 | // The file always exists because it's inside the walk function, 218 | // and read original file is the worst case. 219 | for i := range dirs { 220 | path = filepath.Join(dirs[i], r) 221 | if !com.IsFile(path) { 222 | continue 223 | } 224 | 225 | data, err = ioutil.ReadFile(path) 226 | if err != nil { 227 | return err 228 | } 229 | break 230 | } 231 | } 232 | 233 | name := filepath.ToSlash((r[0 : len(r)-len(ext)])) 234 | fs.files = append(fs.files, NewTplFile(name, data, ext)) 235 | } 236 | 237 | return nil 238 | }); err != nil { 239 | panic("NewTemplateFileSystem: " + err.Error()) 240 | } 241 | 242 | return fs 243 | } 244 | 245 | func (fs TplFileSystem) ListFiles() []TemplateFile { 246 | return fs.files 247 | } 248 | 249 | func PrepareCharset(charset string) string { 250 | if len(charset) != 0 { 251 | return "; charset=" + charset 252 | } 253 | 254 | return "; charset=" + _DEFAULT_CHARSET 255 | } 256 | 257 | func GetExt(s string) string { 258 | index := strings.Index(s, ".") 259 | if index == -1 { 260 | return "" 261 | } 262 | return s[index:] 263 | } 264 | 265 | func compile(opt RenderOptions) *template.Template { 266 | t := template.New(opt.Directory) 267 | t.Delims(opt.Delims.Left, opt.Delims.Right) 268 | // Parse an initial template in case we don't have any. 269 | template.Must(t.Parse("Macaron")) 270 | 271 | if opt.TemplateFileSystem == nil { 272 | opt.TemplateFileSystem = NewTemplateFileSystem(opt, false) 273 | } 274 | 275 | for _, f := range opt.TemplateFileSystem.ListFiles() { 276 | tmpl := t.New(f.Name()) 277 | for _, funcs := range opt.Funcs { 278 | tmpl.Funcs(funcs) 279 | } 280 | // Bomb out if parse fails. We don't want any silent server starts. 281 | template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data()))) 282 | } 283 | 284 | return t 285 | } 286 | 287 | const ( 288 | DEFAULT_TPL_SET_NAME = "DEFAULT" 289 | ) 290 | 291 | // TemplateSet represents a template set of type *template.Template. 292 | type TemplateSet struct { 293 | lock sync.RWMutex 294 | sets map[string]*template.Template 295 | dirs map[string]string 296 | } 297 | 298 | // NewTemplateSet initializes a new empty template set. 299 | func NewTemplateSet() *TemplateSet { 300 | return &TemplateSet{ 301 | sets: make(map[string]*template.Template), 302 | dirs: make(map[string]string), 303 | } 304 | } 305 | 306 | func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template { 307 | t := compile(*opt) 308 | 309 | ts.lock.Lock() 310 | defer ts.lock.Unlock() 311 | 312 | ts.sets[name] = t 313 | ts.dirs[name] = opt.Directory 314 | return t 315 | } 316 | 317 | func (ts *TemplateSet) Get(name string) *template.Template { 318 | ts.lock.RLock() 319 | defer ts.lock.RUnlock() 320 | 321 | return ts.sets[name] 322 | } 323 | 324 | func (ts *TemplateSet) GetDir(name string) string { 325 | ts.lock.RLock() 326 | defer ts.lock.RUnlock() 327 | 328 | return ts.dirs[name] 329 | } 330 | 331 | func prepareRenderOptions(options []RenderOptions) RenderOptions { 332 | var opt RenderOptions 333 | if len(options) > 0 { 334 | opt = options[0] 335 | } 336 | 337 | // Defaults. 338 | if len(opt.Directory) == 0 { 339 | opt.Directory = "templates" 340 | } 341 | if len(opt.Extensions) == 0 { 342 | opt.Extensions = []string{".tmpl", ".html"} 343 | } 344 | if len(opt.HTMLContentType) == 0 { 345 | opt.HTMLContentType = _CONTENT_HTML 346 | } 347 | 348 | return opt 349 | } 350 | 351 | func ParseTplSet(tplSet string) (tplName string, tplDir string) { 352 | tplSet = strings.TrimSpace(tplSet) 353 | if len(tplSet) == 0 { 354 | panic("empty template set argument") 355 | } 356 | infos := strings.Split(tplSet, ":") 357 | if len(infos) == 1 { 358 | tplDir = infos[0] 359 | tplName = path.Base(tplDir) 360 | } else { 361 | tplName = infos[0] 362 | tplDir = infos[1] 363 | } 364 | 365 | if !com.IsDir(tplDir) { 366 | panic("template set path does not exist or is not a directory") 367 | } 368 | return tplName, tplDir 369 | } 370 | 371 | func renderHandler(opt RenderOptions, tplSets []string) Handler { 372 | cs := PrepareCharset(opt.Charset) 373 | ts := NewTemplateSet() 374 | ts.Set(DEFAULT_TPL_SET_NAME, &opt) 375 | 376 | var tmpOpt RenderOptions 377 | for _, tplSet := range tplSets { 378 | tplName, tplDir := ParseTplSet(tplSet) 379 | tmpOpt = opt 380 | tmpOpt.Directory = tplDir 381 | ts.Set(tplName, &tmpOpt) 382 | } 383 | 384 | return func(ctx *Context) { 385 | r := &TplRender{ 386 | ResponseWriter: ctx.Resp, 387 | TemplateSet: ts, 388 | Opt: &opt, 389 | CompiledCharset: cs, 390 | } 391 | ctx.Data["TmplLoadTimes"] = func() string { 392 | if r.startTime.IsZero() { 393 | return "" 394 | } 395 | return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms" 396 | } 397 | 398 | ctx.Render = r 399 | ctx.MapTo(r, (*Render)(nil)) 400 | } 401 | } 402 | 403 | // Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain. 404 | // An single variadic macaron.RenderOptions struct can be optionally provided to configure 405 | // HTML rendering. The default directory for templates is "templates" and the default 406 | // file extension is ".tmpl" and ".html". 407 | // 408 | // If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the 409 | // MACARON_ENV environment variable to "production". 410 | func Renderer(options ...RenderOptions) Handler { 411 | return renderHandler(prepareRenderOptions(options), []string{}) 412 | } 413 | 414 | func Renderers(options RenderOptions, tplSets ...string) Handler { 415 | return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets) 416 | } 417 | 418 | type TplRender struct { 419 | http.ResponseWriter 420 | *TemplateSet 421 | Opt *RenderOptions 422 | CompiledCharset string 423 | 424 | startTime time.Time 425 | } 426 | 427 | func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) { 428 | r.ResponseWriter = rw 429 | } 430 | 431 | func (r *TplRender) JSON(status int, v interface{}) { 432 | var ( 433 | result []byte 434 | err error 435 | ) 436 | if r.Opt.IndentJSON { 437 | result, err = json.MarshalIndent(v, "", " ") 438 | } else { 439 | result, err = json.Marshal(v) 440 | } 441 | if err != nil { 442 | http.Error(r, err.Error(), 500) 443 | return 444 | } 445 | 446 | // json rendered fine, write out the result 447 | r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset) 448 | r.WriteHeader(status) 449 | if len(r.Opt.PrefixJSON) > 0 { 450 | r.Write(r.Opt.PrefixJSON) 451 | } 452 | r.Write(result) 453 | } 454 | 455 | func (r *TplRender) JSONString(v interface{}) (string, error) { 456 | var result []byte 457 | var err error 458 | if r.Opt.IndentJSON { 459 | result, err = json.MarshalIndent(v, "", " ") 460 | } else { 461 | result, err = json.Marshal(v) 462 | } 463 | if err != nil { 464 | return "", err 465 | } 466 | return string(result), nil 467 | } 468 | 469 | func (r *TplRender) XML(status int, v interface{}) { 470 | var result []byte 471 | var err error 472 | if r.Opt.IndentXML { 473 | result, err = xml.MarshalIndent(v, "", " ") 474 | } else { 475 | result, err = xml.Marshal(v) 476 | } 477 | if err != nil { 478 | http.Error(r, err.Error(), 500) 479 | return 480 | } 481 | 482 | // XML rendered fine, write out the result 483 | r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset) 484 | r.WriteHeader(status) 485 | if len(r.Opt.PrefixXML) > 0 { 486 | r.Write(r.Opt.PrefixXML) 487 | } 488 | r.Write(result) 489 | } 490 | 491 | func (r *TplRender) data(status int, contentType string, v []byte) { 492 | if r.Header().Get(_CONTENT_TYPE) == "" { 493 | r.Header().Set(_CONTENT_TYPE, contentType) 494 | } 495 | r.WriteHeader(status) 496 | r.Write(v) 497 | } 498 | 499 | func (r *TplRender) RawData(status int, v []byte) { 500 | r.data(status, _CONTENT_BINARY, v) 501 | } 502 | 503 | func (r *TplRender) PlainText(status int, v []byte) { 504 | r.data(status, _CONTENT_PLAIN, v) 505 | } 506 | 507 | func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) { 508 | buf := bufpool.Get().(*bytes.Buffer) 509 | return buf, t.ExecuteTemplate(buf, name, data) 510 | } 511 | 512 | func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) { 513 | funcs := template.FuncMap{ 514 | "yield": func() (template.HTML, error) { 515 | buf, err := r.execute(t, tplName, data) 516 | // return safe html here since we are rendering our own template 517 | return template.HTML(buf.String()), err 518 | }, 519 | "current": func() (string, error) { 520 | return tplName, nil 521 | }, 522 | } 523 | t.Funcs(funcs) 524 | } 525 | 526 | func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) { 527 | t := r.TemplateSet.Get(setName) 528 | if Env == DEV { 529 | opt := *r.Opt 530 | opt.Directory = r.TemplateSet.GetDir(setName) 531 | t = r.TemplateSet.Set(setName, &opt) 532 | } 533 | if t == nil { 534 | return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName) 535 | } 536 | 537 | opt := r.prepareHTMLOptions(htmlOpt) 538 | 539 | if len(opt.Layout) > 0 { 540 | r.addYield(t, tplName, data) 541 | tplName = opt.Layout 542 | } 543 | 544 | out, err := r.execute(t, tplName, data) 545 | if err != nil { 546 | return nil, err 547 | } 548 | 549 | return out, nil 550 | } 551 | 552 | func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) { 553 | r.startTime = time.Now() 554 | 555 | out, err := r.renderBytes(setName, tplName, data, htmlOpt...) 556 | if err != nil { 557 | http.Error(r, err.Error(), http.StatusInternalServerError) 558 | return 559 | } 560 | 561 | r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset) 562 | r.WriteHeader(status) 563 | 564 | if _, err := out.WriteTo(r); err != nil { 565 | out.Reset() 566 | } 567 | bufpool.Put(out) 568 | } 569 | 570 | func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) { 571 | r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...) 572 | } 573 | 574 | func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) { 575 | r.renderHTML(status, setName, tplName, data, htmlOpt...) 576 | } 577 | 578 | func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) { 579 | out, err := r.renderBytes(setName, tplName, data, htmlOpt...) 580 | if err != nil { 581 | return []byte(""), err 582 | } 583 | return out.Bytes(), nil 584 | } 585 | 586 | func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) { 587 | return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...) 588 | } 589 | 590 | func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) { 591 | p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...) 592 | return string(p), err 593 | } 594 | 595 | func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) { 596 | p, err := r.HTMLBytes(name, data, htmlOpt...) 597 | return string(p), err 598 | } 599 | 600 | // Error writes the given HTTP status to the current ResponseWriter 601 | func (r *TplRender) Error(status int, message ...string) { 602 | r.WriteHeader(status) 603 | if len(message) > 0 { 604 | r.Write([]byte(message[0])) 605 | } 606 | } 607 | 608 | func (r *TplRender) Status(status int) { 609 | r.WriteHeader(status) 610 | } 611 | 612 | func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions { 613 | if len(htmlOpt) > 0 { 614 | return htmlOpt[0] 615 | } 616 | 617 | return HTMLOptions{ 618 | Layout: r.Opt.Layout, 619 | } 620 | } 621 | 622 | func (r *TplRender) SetTemplatePath(setName, dir string) { 623 | if len(setName) == 0 { 624 | setName = DEFAULT_TPL_SET_NAME 625 | } 626 | opt := *r.Opt 627 | opt.Directory = dir 628 | r.TemplateSet.Set(setName, &opt) 629 | } 630 | 631 | func (r *TplRender) HasTemplateSet(name string) bool { 632 | return r.TemplateSet.Get(name) != nil 633 | } 634 | 635 | // DummyRender is used when user does not choose any real render to use. 636 | // This way, we can print out friendly message which asks them to register one, 637 | // instead of ugly and confusing 'nil pointer' panic. 638 | type DummyRender struct { 639 | http.ResponseWriter 640 | } 641 | 642 | func renderNotRegistered() { 643 | panic("middleware render hasn't been registered") 644 | } 645 | 646 | func (r *DummyRender) SetResponseWriter(http.ResponseWriter) { 647 | renderNotRegistered() 648 | } 649 | 650 | func (r *DummyRender) JSON(int, interface{}) { 651 | renderNotRegistered() 652 | } 653 | 654 | func (r *DummyRender) JSONString(interface{}) (string, error) { 655 | renderNotRegistered() 656 | return "", nil 657 | } 658 | 659 | func (r *DummyRender) RawData(int, []byte) { 660 | renderNotRegistered() 661 | } 662 | 663 | func (r *DummyRender) PlainText(int, []byte) { 664 | renderNotRegistered() 665 | } 666 | 667 | func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) { 668 | renderNotRegistered() 669 | } 670 | 671 | func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) { 672 | renderNotRegistered() 673 | } 674 | 675 | func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) { 676 | renderNotRegistered() 677 | return "", nil 678 | } 679 | 680 | func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) { 681 | renderNotRegistered() 682 | return "", nil 683 | } 684 | 685 | func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) { 686 | renderNotRegistered() 687 | return nil, nil 688 | } 689 | 690 | func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) { 691 | renderNotRegistered() 692 | return nil, nil 693 | } 694 | 695 | func (r *DummyRender) XML(int, interface{}) { 696 | renderNotRegistered() 697 | } 698 | 699 | func (r *DummyRender) Error(int, ...string) { 700 | renderNotRegistered() 701 | } 702 | 703 | func (r *DummyRender) Status(int) { 704 | renderNotRegistered() 705 | } 706 | 707 | func (r *DummyRender) SetTemplatePath(string, string) { 708 | renderNotRegistered() 709 | } 710 | 711 | func (r *DummyRender) HasTemplateSet(string) bool { 712 | renderNotRegistered() 713 | return false 714 | } 715 | -------------------------------------------------------------------------------- /macaron-1.1.8/render_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "encoding/xml" 20 | "html/template" 21 | "net/http" 22 | "net/http/httptest" 23 | "testing" 24 | "time" 25 | 26 | . "github.com/smartystreets/goconvey/convey" 27 | ) 28 | 29 | type Greeting struct { 30 | One string `json:"one"` 31 | Two string `json:"two"` 32 | } 33 | 34 | type GreetingXML struct { 35 | XMLName xml.Name `xml:"greeting"` 36 | One string `xml:"one,attr"` 37 | Two string `xml:"two,attr"` 38 | } 39 | 40 | func Test_Render_JSON(t *testing.T) { 41 | Convey("Render JSON", t, func() { 42 | m := Classic() 43 | m.Use(Renderer()) 44 | m.Get("/foobar", func(r Render) { 45 | r.JSON(300, Greeting{"hello", "world"}) 46 | }) 47 | 48 | resp := httptest.NewRecorder() 49 | req, err := http.NewRequest("GET", "/foobar", nil) 50 | So(err, ShouldBeNil) 51 | m.ServeHTTP(resp, req) 52 | 53 | So(resp.Code, ShouldEqual, http.StatusMultipleChoices) 54 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_JSON+"; charset=UTF-8") 55 | So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`) 56 | }) 57 | 58 | Convey("Render JSON with prefix", t, func() { 59 | m := Classic() 60 | prefix := ")]}',\n" 61 | m.Use(Renderer(RenderOptions{ 62 | PrefixJSON: []byte(prefix), 63 | })) 64 | m.Get("/foobar", func(r Render) { 65 | r.JSON(300, Greeting{"hello", "world"}) 66 | }) 67 | 68 | resp := httptest.NewRecorder() 69 | req, err := http.NewRequest("GET", "/foobar", nil) 70 | So(err, ShouldBeNil) 71 | m.ServeHTTP(resp, req) 72 | 73 | So(resp.Code, ShouldEqual, http.StatusMultipleChoices) 74 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_JSON+"; charset=UTF-8") 75 | So(resp.Body.String(), ShouldEqual, prefix+`{"one":"hello","two":"world"}`) 76 | }) 77 | 78 | Convey("Render Indented JSON", t, func() { 79 | m := Classic() 80 | m.Use(Renderer(RenderOptions{ 81 | IndentJSON: true, 82 | })) 83 | m.Get("/foobar", func(r Render) { 84 | r.JSON(300, Greeting{"hello", "world"}) 85 | }) 86 | 87 | resp := httptest.NewRecorder() 88 | req, err := http.NewRequest("GET", "/foobar", nil) 89 | So(err, ShouldBeNil) 90 | m.ServeHTTP(resp, req) 91 | 92 | So(resp.Code, ShouldEqual, http.StatusMultipleChoices) 93 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_JSON+"; charset=UTF-8") 94 | So(resp.Body.String(), ShouldEqual, `{ 95 | "one": "hello", 96 | "two": "world" 97 | }`) 98 | }) 99 | 100 | Convey("Render JSON and return string", t, func() { 101 | m := Classic() 102 | m.Use(Renderer()) 103 | m.Get("/foobar", func(r Render) { 104 | result, err := r.JSONString(Greeting{"hello", "world"}) 105 | So(err, ShouldBeNil) 106 | So(result, ShouldEqual, `{"one":"hello","two":"world"}`) 107 | }) 108 | 109 | resp := httptest.NewRecorder() 110 | req, err := http.NewRequest("GET", "/foobar", nil) 111 | So(err, ShouldBeNil) 112 | m.ServeHTTP(resp, req) 113 | }) 114 | 115 | Convey("Render with charset JSON", t, func() { 116 | m := Classic() 117 | m.Use(Renderer(RenderOptions{ 118 | Charset: "foobar", 119 | })) 120 | m.Get("/foobar", func(r Render) { 121 | r.JSON(300, Greeting{"hello", "world"}) 122 | }) 123 | 124 | resp := httptest.NewRecorder() 125 | req, err := http.NewRequest("GET", "/foobar", nil) 126 | So(err, ShouldBeNil) 127 | m.ServeHTTP(resp, req) 128 | 129 | So(resp.Code, ShouldEqual, http.StatusMultipleChoices) 130 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_JSON+"; charset=foobar") 131 | So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`) 132 | }) 133 | } 134 | 135 | func Test_Render_XML(t *testing.T) { 136 | Convey("Render XML", t, func() { 137 | m := Classic() 138 | m.Use(Renderer()) 139 | m.Get("/foobar", func(r Render) { 140 | r.XML(300, GreetingXML{One: "hello", Two: "world"}) 141 | }) 142 | 143 | resp := httptest.NewRecorder() 144 | req, err := http.NewRequest("GET", "/foobar", nil) 145 | So(err, ShouldBeNil) 146 | m.ServeHTTP(resp, req) 147 | 148 | So(resp.Code, ShouldEqual, http.StatusMultipleChoices) 149 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_XML+"; charset=UTF-8") 150 | So(resp.Body.String(), ShouldEqual, ``) 151 | }) 152 | 153 | Convey("Render XML with prefix", t, func() { 154 | m := Classic() 155 | prefix := ")]}',\n" 156 | m.Use(Renderer(RenderOptions{ 157 | PrefixXML: []byte(prefix), 158 | })) 159 | m.Get("/foobar", func(r Render) { 160 | r.XML(300, GreetingXML{One: "hello", Two: "world"}) 161 | }) 162 | 163 | resp := httptest.NewRecorder() 164 | req, err := http.NewRequest("GET", "/foobar", nil) 165 | So(err, ShouldBeNil) 166 | m.ServeHTTP(resp, req) 167 | 168 | So(resp.Code, ShouldEqual, http.StatusMultipleChoices) 169 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_XML+"; charset=UTF-8") 170 | So(resp.Body.String(), ShouldEqual, prefix+``) 171 | }) 172 | 173 | Convey("Render Indented XML", t, func() { 174 | m := Classic() 175 | m.Use(Renderer(RenderOptions{ 176 | IndentXML: true, 177 | })) 178 | m.Get("/foobar", func(r Render) { 179 | r.XML(300, GreetingXML{One: "hello", Two: "world"}) 180 | }) 181 | 182 | resp := httptest.NewRecorder() 183 | req, err := http.NewRequest("GET", "/foobar", nil) 184 | So(err, ShouldBeNil) 185 | m.ServeHTTP(resp, req) 186 | 187 | So(resp.Code, ShouldEqual, http.StatusMultipleChoices) 188 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_XML+"; charset=UTF-8") 189 | So(resp.Body.String(), ShouldEqual, ``) 190 | }) 191 | } 192 | 193 | func Test_Render_HTML(t *testing.T) { 194 | Convey("Render HTML", t, func() { 195 | m := Classic() 196 | m.Use(Renderers(RenderOptions{ 197 | Directory: "fixtures/basic", 198 | }, "fixtures/basic2")) 199 | m.Get("/foobar", func(r Render) { 200 | r.SetResponseWriter(r.(*TplRender).ResponseWriter) 201 | r.HTML(200, "hello", "jeremy") 202 | r.SetTemplatePath("", "fixtures/basic2") 203 | }) 204 | m.Get("/foobar2", func(r Render) { 205 | if r.HasTemplateSet("basic2") { 206 | r.HTMLSet(200, "basic2", "hello", "jeremy") 207 | } 208 | }) 209 | 210 | resp := httptest.NewRecorder() 211 | req, err := http.NewRequest("GET", "/foobar", nil) 212 | So(err, ShouldBeNil) 213 | m.ServeHTTP(resp, req) 214 | 215 | So(resp.Code, ShouldEqual, http.StatusOK) 216 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") 217 | So(resp.Body.String(), ShouldEqual, "

Hello jeremy

") 218 | 219 | resp = httptest.NewRecorder() 220 | req, err = http.NewRequest("GET", "/foobar2", nil) 221 | So(err, ShouldBeNil) 222 | m.ServeHTTP(resp, req) 223 | 224 | So(resp.Code, ShouldEqual, http.StatusOK) 225 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") 226 | So(resp.Body.String(), ShouldEqual, "

What's up, jeremy

") 227 | 228 | Convey("Change render templates path", func() { 229 | resp := httptest.NewRecorder() 230 | req, err := http.NewRequest("GET", "/foobar", nil) 231 | So(err, ShouldBeNil) 232 | m.ServeHTTP(resp, req) 233 | 234 | So(resp.Code, ShouldEqual, http.StatusOK) 235 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") 236 | So(resp.Body.String(), ShouldEqual, "

What's up, jeremy

") 237 | }) 238 | }) 239 | 240 | Convey("Render HTML and return string", t, func() { 241 | m := Classic() 242 | m.Use(Renderers(RenderOptions{ 243 | Directory: "fixtures/basic", 244 | }, "basic2:fixtures/basic2")) 245 | m.Get("/foobar", func(r Render) { 246 | result, err := r.HTMLString("hello", "jeremy") 247 | So(err, ShouldBeNil) 248 | So(result, ShouldEqual, "

Hello jeremy

") 249 | }) 250 | m.Get("/foobar2", func(r Render) { 251 | result, err := r.HTMLSetString("basic2", "hello", "jeremy") 252 | So(err, ShouldBeNil) 253 | So(result, ShouldEqual, "

What's up, jeremy

") 254 | }) 255 | 256 | resp := httptest.NewRecorder() 257 | req, err := http.NewRequest("GET", "/foobar", nil) 258 | So(err, ShouldBeNil) 259 | m.ServeHTTP(resp, req) 260 | 261 | resp = httptest.NewRecorder() 262 | req, err = http.NewRequest("GET", "/foobar2", nil) 263 | So(err, ShouldBeNil) 264 | m.ServeHTTP(resp, req) 265 | }) 266 | 267 | Convey("Render with nested HTML", t, func() { 268 | m := Classic() 269 | m.Use(Renderer(RenderOptions{ 270 | Directory: "fixtures/basic", 271 | })) 272 | m.Get("/foobar", func(r Render) { 273 | r.HTML(200, "admin/index", "jeremy") 274 | }) 275 | 276 | resp := httptest.NewRecorder() 277 | req, err := http.NewRequest("GET", "/foobar", nil) 278 | So(err, ShouldBeNil) 279 | m.ServeHTTP(resp, req) 280 | 281 | So(resp.Code, ShouldEqual, http.StatusOK) 282 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") 283 | So(resp.Body.String(), ShouldEqual, "

Admin jeremy

") 284 | }) 285 | 286 | Convey("Render bad HTML", t, func() { 287 | m := Classic() 288 | m.Use(Renderer(RenderOptions{ 289 | Directory: "fixtures/basic", 290 | })) 291 | m.Get("/foobar", func(r Render) { 292 | r.HTML(200, "nope", nil) 293 | }) 294 | 295 | resp := httptest.NewRecorder() 296 | req, err := http.NewRequest("GET", "/foobar", nil) 297 | So(err, ShouldBeNil) 298 | m.ServeHTTP(resp, req) 299 | 300 | So(resp.Code, ShouldEqual, http.StatusInternalServerError) 301 | So(resp.Body.String(), ShouldEqual, "html/template: \"nope\" is undefined\n") 302 | }) 303 | 304 | Convey("Invalid template set", t, func() { 305 | Convey("Empty template set argument", func() { 306 | defer func() { 307 | So(recover(), ShouldNotBeNil) 308 | }() 309 | m := Classic() 310 | m.Use(Renderers(RenderOptions{ 311 | Directory: "fixtures/basic", 312 | }, "")) 313 | }) 314 | 315 | Convey("Bad template set path", func() { 316 | defer func() { 317 | So(recover(), ShouldNotBeNil) 318 | }() 319 | m := Classic() 320 | m.Use(Renderers(RenderOptions{ 321 | Directory: "fixtures/basic", 322 | }, "404")) 323 | }) 324 | }) 325 | } 326 | 327 | func Test_Render_XHTML(t *testing.T) { 328 | Convey("Render XHTML", t, func() { 329 | m := Classic() 330 | m.Use(Renderer(RenderOptions{ 331 | Directory: "fixtures/basic", 332 | HTMLContentType: _CONTENT_XHTML, 333 | })) 334 | m.Get("/foobar", func(r Render) { 335 | r.HTML(200, "hello", "jeremy") 336 | }) 337 | 338 | resp := httptest.NewRecorder() 339 | req, err := http.NewRequest("GET", "/foobar", nil) 340 | So(err, ShouldBeNil) 341 | m.ServeHTTP(resp, req) 342 | 343 | So(resp.Code, ShouldEqual, http.StatusOK) 344 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_XHTML+"; charset=UTF-8") 345 | So(resp.Body.String(), ShouldEqual, "

Hello jeremy

") 346 | }) 347 | } 348 | 349 | func Test_Render_Extensions(t *testing.T) { 350 | Convey("Render with extensions", t, func() { 351 | m := Classic() 352 | m.Use(Renderer(RenderOptions{ 353 | Directory: "fixtures/basic", 354 | Extensions: []string{".tmpl", ".html"}, 355 | })) 356 | m.Get("/foobar", func(r Render) { 357 | r.HTML(200, "hypertext", nil) 358 | }) 359 | 360 | resp := httptest.NewRecorder() 361 | req, err := http.NewRequest("GET", "/foobar", nil) 362 | So(err, ShouldBeNil) 363 | m.ServeHTTP(resp, req) 364 | 365 | So(resp.Code, ShouldEqual, http.StatusOK) 366 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") 367 | So(resp.Body.String(), ShouldEqual, "Hypertext!") 368 | }) 369 | } 370 | 371 | func Test_Render_Funcs(t *testing.T) { 372 | Convey("Render with functions", t, func() { 373 | m := Classic() 374 | m.Use(Renderer(RenderOptions{ 375 | Directory: "fixtures/custom_funcs", 376 | Funcs: []template.FuncMap{ 377 | { 378 | "myCustomFunc": func() string { 379 | return "My custom function" 380 | }, 381 | }, 382 | }, 383 | })) 384 | m.Get("/foobar", func(r Render) { 385 | r.HTML(200, "index", "jeremy") 386 | }) 387 | 388 | resp := httptest.NewRecorder() 389 | req, err := http.NewRequest("GET", "/foobar", nil) 390 | So(err, ShouldBeNil) 391 | m.ServeHTTP(resp, req) 392 | 393 | So(resp.Body.String(), ShouldEqual, "My custom function") 394 | }) 395 | } 396 | 397 | func Test_Render_Layout(t *testing.T) { 398 | Convey("Render with layout", t, func() { 399 | m := Classic() 400 | m.Use(Renderer(RenderOptions{ 401 | Directory: "fixtures/basic", 402 | Layout: "layout", 403 | })) 404 | m.Get("/foobar", func(r Render) { 405 | r.HTML(200, "content", "jeremy") 406 | }) 407 | 408 | resp := httptest.NewRecorder() 409 | req, err := http.NewRequest("GET", "/foobar", nil) 410 | So(err, ShouldBeNil) 411 | m.ServeHTTP(resp, req) 412 | 413 | So(resp.Body.String(), ShouldEqual, "head

jeremy

foot") 414 | }) 415 | 416 | Convey("Render with current layout", t, func() { 417 | m := Classic() 418 | m.Use(Renderer(RenderOptions{ 419 | Directory: "fixtures/basic", 420 | Layout: "current_layout", 421 | })) 422 | m.Get("/foobar", func(r Render) { 423 | r.HTML(200, "content", "jeremy") 424 | }) 425 | 426 | resp := httptest.NewRecorder() 427 | req, err := http.NewRequest("GET", "/foobar", nil) 428 | So(err, ShouldBeNil) 429 | m.ServeHTTP(resp, req) 430 | 431 | So(resp.Body.String(), ShouldEqual, "content head

jeremy

content foot") 432 | }) 433 | 434 | Convey("Render with override layout", t, func() { 435 | m := Classic() 436 | m.Use(Renderer(RenderOptions{ 437 | Directory: "fixtures/basic", 438 | Layout: "layout", 439 | })) 440 | m.Get("/foobar", func(r Render) { 441 | r.HTML(200, "content", "jeremy", HTMLOptions{ 442 | Layout: "another_layout", 443 | }) 444 | }) 445 | 446 | resp := httptest.NewRecorder() 447 | req, err := http.NewRequest("GET", "/foobar", nil) 448 | So(err, ShouldBeNil) 449 | m.ServeHTTP(resp, req) 450 | 451 | So(resp.Code, ShouldEqual, http.StatusOK) 452 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") 453 | So(resp.Body.String(), ShouldEqual, "another head

jeremy

another foot") 454 | }) 455 | } 456 | 457 | func Test_Render_Delimiters(t *testing.T) { 458 | Convey("Render with delimiters", t, func() { 459 | m := Classic() 460 | m.Use(Renderer(RenderOptions{ 461 | Delims: Delims{"{[{", "}]}"}, 462 | Directory: "fixtures/basic", 463 | })) 464 | m.Get("/foobar", func(r Render) { 465 | r.HTML(200, "delims", "jeremy") 466 | }) 467 | 468 | resp := httptest.NewRecorder() 469 | req, err := http.NewRequest("GET", "/foobar", nil) 470 | So(err, ShouldBeNil) 471 | m.ServeHTTP(resp, req) 472 | 473 | So(resp.Code, ShouldEqual, http.StatusOK) 474 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") 475 | So(resp.Body.String(), ShouldEqual, "

Hello jeremy

") 476 | }) 477 | } 478 | 479 | func Test_Render_BinaryData(t *testing.T) { 480 | Convey("Render binary data", t, func() { 481 | m := Classic() 482 | m.Use(Renderer()) 483 | m.Get("/foobar", func(r Render) { 484 | r.RawData(200, []byte("hello there")) 485 | }) 486 | m.Get("/foobar2", func(r Render) { 487 | r.PlainText(200, []byte("hello there")) 488 | }) 489 | 490 | resp := httptest.NewRecorder() 491 | req, err := http.NewRequest("GET", "/foobar", nil) 492 | So(err, ShouldBeNil) 493 | m.ServeHTTP(resp, req) 494 | 495 | So(resp.Code, ShouldEqual, http.StatusOK) 496 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_BINARY) 497 | So(resp.Body.String(), ShouldEqual, "hello there") 498 | 499 | resp = httptest.NewRecorder() 500 | req, err = http.NewRequest("GET", "/foobar2", nil) 501 | So(err, ShouldBeNil) 502 | m.ServeHTTP(resp, req) 503 | 504 | So(resp.Code, ShouldEqual, http.StatusOK) 505 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_PLAIN) 506 | So(resp.Body.String(), ShouldEqual, "hello there") 507 | }) 508 | 509 | Convey("Render binary data with mime type", t, func() { 510 | m := Classic() 511 | m.Use(Renderer()) 512 | m.Get("/foobar", func(r Render) { 513 | r.(*TplRender).ResponseWriter.Header().Set(_CONTENT_TYPE, "image/jpeg") 514 | r.RawData(200, []byte("..jpeg data..")) 515 | }) 516 | 517 | resp := httptest.NewRecorder() 518 | req, err := http.NewRequest("GET", "/foobar", nil) 519 | So(err, ShouldBeNil) 520 | m.ServeHTTP(resp, req) 521 | 522 | So(resp.Code, ShouldEqual, http.StatusOK) 523 | So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, "image/jpeg") 524 | So(resp.Body.String(), ShouldEqual, "..jpeg data..") 525 | }) 526 | } 527 | 528 | func Test_Render_Status(t *testing.T) { 529 | Convey("Render with status 204", t, func() { 530 | resp := httptest.NewRecorder() 531 | r := TplRender{resp, NewTemplateSet(), &RenderOptions{}, "", time.Now()} 532 | r.Status(204) 533 | So(resp.Code, ShouldEqual, http.StatusNoContent) 534 | }) 535 | 536 | Convey("Render with status 404", t, func() { 537 | resp := httptest.NewRecorder() 538 | r := TplRender{resp, NewTemplateSet(), &RenderOptions{}, "", time.Now()} 539 | r.Error(404) 540 | So(resp.Code, ShouldEqual, http.StatusNotFound) 541 | }) 542 | 543 | Convey("Render with status 500", t, func() { 544 | resp := httptest.NewRecorder() 545 | r := TplRender{resp, NewTemplateSet(), &RenderOptions{}, "", time.Now()} 546 | r.Error(500) 547 | So(resp.Code, ShouldEqual, http.StatusInternalServerError) 548 | }) 549 | } 550 | 551 | func Test_Render_NoRace(t *testing.T) { 552 | Convey("Make sure render has no race", t, func() { 553 | m := Classic() 554 | m.Use(Renderer(RenderOptions{ 555 | Directory: "fixtures/basic", 556 | })) 557 | m.Get("/foobar", func(r Render) { 558 | r.HTML(200, "hello", "world") 559 | }) 560 | 561 | done := make(chan bool) 562 | doreq := func() { 563 | resp := httptest.NewRecorder() 564 | req, _ := http.NewRequest("GET", "/foobar", nil) 565 | m.ServeHTTP(resp, req) 566 | done <- true 567 | } 568 | // Run two requests to check there is no race condition 569 | go doreq() 570 | go doreq() 571 | <-done 572 | <-done 573 | }) 574 | } 575 | 576 | func Test_Render_Symlink(t *testing.T) { 577 | Convey("Render can follow symlinks", t, func() { 578 | m := Classic() 579 | m.Use(Renderer(RenderOptions{ 580 | Directory: "fixtures/symlink", 581 | })) 582 | m.Get("/foobar", func(r Render) { 583 | r.HTML(200, "hello", "world") 584 | }) 585 | 586 | resp := httptest.NewRecorder() 587 | req, err := http.NewRequest("GET", "/foobar", nil) 588 | So(err, ShouldBeNil) 589 | m.ServeHTTP(resp, req) 590 | 591 | So(resp.Code, ShouldEqual, http.StatusOK) 592 | }) 593 | } 594 | 595 | func Test_Render_AppendDirectories(t *testing.T) { 596 | Convey("Render with additional templates", t, func() { 597 | m := Classic() 598 | m.Use(Renderer(RenderOptions{ 599 | Directory: "fixtures/basic", 600 | AppendDirectories: []string{"fixtures/basic/custom"}, 601 | })) 602 | 603 | Convey("Request normal template", func() { 604 | m.Get("/normal", func(r Render) { 605 | r.HTML(200, "content", "Macaron") 606 | }) 607 | 608 | resp := httptest.NewRecorder() 609 | req, err := http.NewRequest("GET", "/normal", nil) 610 | So(err, ShouldBeNil) 611 | m.ServeHTTP(resp, req) 612 | 613 | So(resp.Body.String(), ShouldEqual, "

Macaron

") 614 | So(resp.Code, ShouldEqual, http.StatusOK) 615 | }) 616 | 617 | Convey("Request overwritten template", func() { 618 | m.Get("/custom", func(r Render) { 619 | r.HTML(200, "hello", "world") 620 | }) 621 | 622 | resp := httptest.NewRecorder() 623 | req, err := http.NewRequest("GET", "/custom", nil) 624 | So(err, ShouldBeNil) 625 | m.ServeHTTP(resp, req) 626 | 627 | So(resp.Body.String(), ShouldEqual, "

This is custom version of: Hello world

") 628 | So(resp.Code, ShouldEqual, http.StatusOK) 629 | }) 630 | 631 | }) 632 | } 633 | 634 | func Test_GetExt(t *testing.T) { 635 | Convey("Get extension", t, func() { 636 | So(GetExt("test"), ShouldBeBlank) 637 | So(GetExt("test.tmpl"), ShouldEqual, ".tmpl") 638 | So(GetExt("test.go.tmpl"), ShouldEqual, ".go.tmpl") 639 | }) 640 | } 641 | 642 | func Test_dummyRender(t *testing.T) { 643 | shouldPanic := func() { So(recover(), ShouldNotBeNil) } 644 | 645 | Convey("Use dummy render to gracefully handle panic", t, func() { 646 | m := New() 647 | 648 | performRequest := func(method, path string) { 649 | resp := httptest.NewRecorder() 650 | req, err := http.NewRequest(method, path, nil) 651 | So(err, ShouldBeNil) 652 | m.ServeHTTP(resp, req) 653 | } 654 | 655 | m.Get("/set_response_writer", func(ctx *Context) { 656 | defer shouldPanic() 657 | ctx.SetResponseWriter(nil) 658 | }) 659 | m.Get("/json", func(ctx *Context) { 660 | defer shouldPanic() 661 | ctx.JSON(0, nil) 662 | }) 663 | m.Get("/jsonstring", func(ctx *Context) { 664 | defer shouldPanic() 665 | ctx.JSONString(nil) 666 | }) 667 | m.Get("/rawdata", func(ctx *Context) { 668 | defer shouldPanic() 669 | ctx.RawData(0, nil) 670 | }) 671 | m.Get("/plaintext", func(ctx *Context) { 672 | defer shouldPanic() 673 | ctx.PlainText(0, nil) 674 | }) 675 | m.Get("/html", func(ctx *Context) { 676 | defer shouldPanic() 677 | ctx.Render.HTML(0, "", nil) 678 | }) 679 | m.Get("/htmlset", func(ctx *Context) { 680 | defer shouldPanic() 681 | ctx.Render.HTMLSet(0, "", "", nil) 682 | }) 683 | m.Get("/htmlsetstring", func(ctx *Context) { 684 | defer shouldPanic() 685 | ctx.Render.HTMLSetString("", "", nil) 686 | }) 687 | m.Get("/htmlstring", func(ctx *Context) { 688 | defer shouldPanic() 689 | ctx.Render.HTMLString("", nil) 690 | }) 691 | m.Get("/htmlsetbytes", func(ctx *Context) { 692 | defer shouldPanic() 693 | ctx.Render.HTMLSetBytes("", "", nil) 694 | }) 695 | m.Get("/htmlbytes", func(ctx *Context) { 696 | defer shouldPanic() 697 | ctx.Render.HTMLBytes("", nil) 698 | }) 699 | m.Get("/xml", func(ctx *Context) { 700 | defer shouldPanic() 701 | ctx.XML(0, nil) 702 | }) 703 | m.Get("/error", func(ctx *Context) { 704 | defer shouldPanic() 705 | ctx.Error(0) 706 | }) 707 | m.Get("/status", func(ctx *Context) { 708 | defer shouldPanic() 709 | ctx.Status(0) 710 | }) 711 | m.Get("/settemplatepath", func(ctx *Context) { 712 | defer shouldPanic() 713 | ctx.SetTemplatePath("", "") 714 | }) 715 | m.Get("/hastemplateset", func(ctx *Context) { 716 | defer shouldPanic() 717 | ctx.HasTemplateSet("") 718 | }) 719 | 720 | performRequest("GET", "/set_response_writer") 721 | performRequest("GET", "/json") 722 | performRequest("GET", "/jsonstring") 723 | performRequest("GET", "/rawdata") 724 | performRequest("GET", "/jsonstring") 725 | performRequest("GET", "/plaintext") 726 | performRequest("GET", "/html") 727 | performRequest("GET", "/htmlset") 728 | performRequest("GET", "/htmlsetstring") 729 | performRequest("GET", "/htmlstring") 730 | performRequest("GET", "/htmlsetbytes") 731 | performRequest("GET", "/htmlbytes") 732 | performRequest("GET", "/xml") 733 | performRequest("GET", "/error") 734 | performRequest("GET", "/status") 735 | performRequest("GET", "/settemplatepath") 736 | performRequest("GET", "/hastemplateset") 737 | }) 738 | } 739 | -------------------------------------------------------------------------------- /macaron-1.1.8/response_writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package macaron 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "net" 21 | "net/http" 22 | ) 23 | 24 | // ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about 25 | // the response. It is recommended that middleware handlers use this construct to wrap a responsewriter 26 | // if the functionality calls for it. 27 | type ResponseWriter interface { 28 | http.ResponseWriter 29 | http.Flusher 30 | // Status returns the status code of the response or 0 if the response has not been written. 31 | Status() int 32 | // Written returns whether or not the ResponseWriter has been written. 33 | Written() bool 34 | // Size returns the size of the response body. 35 | Size() int 36 | // Before allows for a function to be called before the ResponseWriter has been written to. This is 37 | // useful for setting headers or any other operations that must happen before a response has been written. 38 | Before(BeforeFunc) 39 | } 40 | 41 | // BeforeFunc is a function that is called before the ResponseWriter has been written to. 42 | type BeforeFunc func(ResponseWriter) 43 | 44 | // NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter 45 | func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { 46 | return &responseWriter{rw, 0, 0, nil} 47 | } 48 | 49 | type responseWriter struct { 50 | http.ResponseWriter 51 | status int 52 | size int 53 | beforeFuncs []BeforeFunc 54 | } 55 | 56 | func (rw *responseWriter) WriteHeader(s int) { 57 | rw.callBefore() 58 | rw.ResponseWriter.WriteHeader(s) 59 | rw.status = s 60 | } 61 | 62 | func (rw *responseWriter) Write(b []byte) (int, error) { 63 | if !rw.Written() { 64 | // The status will be StatusOK if WriteHeader has not been called yet 65 | rw.WriteHeader(http.StatusOK) 66 | } 67 | size, err := rw.ResponseWriter.Write(b) 68 | rw.size += size 69 | return size, err 70 | } 71 | 72 | func (rw *responseWriter) Status() int { 73 | return rw.status 74 | } 75 | 76 | func (rw *responseWriter) Size() int { 77 | return rw.size 78 | } 79 | 80 | func (rw *responseWriter) Written() bool { 81 | return rw.status != 0 82 | } 83 | 84 | func (rw *responseWriter) Before(before BeforeFunc) { 85 | rw.beforeFuncs = append(rw.beforeFuncs, before) 86 | } 87 | 88 | func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 89 | hijacker, ok := rw.ResponseWriter.(http.Hijacker) 90 | if !ok { 91 | return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") 92 | } 93 | return hijacker.Hijack() 94 | } 95 | 96 | func (rw *responseWriter) CloseNotify() <-chan bool { 97 | return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() 98 | } 99 | 100 | func (rw *responseWriter) callBefore() { 101 | for i := len(rw.beforeFuncs) - 1; i >= 0; i-- { 102 | rw.beforeFuncs[i](rw) 103 | } 104 | } 105 | 106 | func (rw *responseWriter) Flush() { 107 | flusher, ok := rw.ResponseWriter.(http.Flusher) 108 | if ok { 109 | flusher.Flush() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /macaron-1.1.8/response_writer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "bufio" 20 | "io" 21 | "net" 22 | "net/http" 23 | "net/http/httptest" 24 | "testing" 25 | "time" 26 | 27 | . "github.com/smartystreets/goconvey/convey" 28 | ) 29 | 30 | type closeNotifyingRecorder struct { 31 | *httptest.ResponseRecorder 32 | closed chan bool 33 | } 34 | 35 | func newCloseNotifyingRecorder() *closeNotifyingRecorder { 36 | return &closeNotifyingRecorder{ 37 | httptest.NewRecorder(), 38 | make(chan bool, 1), 39 | } 40 | } 41 | 42 | func (c *closeNotifyingRecorder) close() { 43 | c.closed <- true 44 | } 45 | 46 | func (c *closeNotifyingRecorder) CloseNotify() <-chan bool { 47 | return c.closed 48 | } 49 | 50 | type hijackableResponse struct { 51 | Hijacked bool 52 | } 53 | 54 | func newHijackableResponse() *hijackableResponse { 55 | return &hijackableResponse{} 56 | } 57 | 58 | func (h *hijackableResponse) Header() http.Header { return nil } 59 | func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil } 60 | func (h *hijackableResponse) WriteHeader(code int) {} 61 | func (h *hijackableResponse) Flush() {} 62 | func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) { 63 | h.Hijacked = true 64 | return nil, nil, nil 65 | } 66 | 67 | func Test_ResponseWriter(t *testing.T) { 68 | Convey("Write string to response writer", t, func() { 69 | resp := httptest.NewRecorder() 70 | rw := NewResponseWriter(resp) 71 | rw.Write([]byte("Hello world")) 72 | 73 | So(resp.Code, ShouldEqual, rw.Status()) 74 | So(resp.Body.String(), ShouldEqual, "Hello world") 75 | So(rw.Status(), ShouldEqual, http.StatusOK) 76 | So(rw.Size(), ShouldEqual, 11) 77 | So(rw.Written(), ShouldBeTrue) 78 | }) 79 | 80 | Convey("Write strings to response writer", t, func() { 81 | resp := httptest.NewRecorder() 82 | rw := NewResponseWriter(resp) 83 | rw.Write([]byte("Hello world")) 84 | rw.Write([]byte("foo bar bat baz")) 85 | 86 | So(resp.Code, ShouldEqual, rw.Status()) 87 | So(resp.Body.String(), ShouldEqual, "Hello worldfoo bar bat baz") 88 | So(rw.Status(), ShouldEqual, http.StatusOK) 89 | So(rw.Size(), ShouldEqual, 26) 90 | So(rw.Written(), ShouldBeTrue) 91 | }) 92 | 93 | Convey("Write header to response writer", t, func() { 94 | resp := httptest.NewRecorder() 95 | rw := NewResponseWriter(resp) 96 | rw.WriteHeader(http.StatusNotFound) 97 | 98 | So(resp.Code, ShouldEqual, rw.Status()) 99 | So(resp.Body.String(), ShouldBeBlank) 100 | So(rw.Status(), ShouldEqual, http.StatusNotFound) 101 | So(rw.Size(), ShouldEqual, 0) 102 | }) 103 | 104 | Convey("Write before response write", t, func() { 105 | result := "" 106 | resp := httptest.NewRecorder() 107 | rw := NewResponseWriter(resp) 108 | rw.Before(func(ResponseWriter) { 109 | result += "foo" 110 | }) 111 | rw.Before(func(ResponseWriter) { 112 | result += "bar" 113 | }) 114 | rw.WriteHeader(http.StatusNotFound) 115 | 116 | So(resp.Code, ShouldEqual, rw.Status()) 117 | So(resp.Body.String(), ShouldBeBlank) 118 | So(rw.Status(), ShouldEqual, http.StatusNotFound) 119 | So(rw.Size(), ShouldEqual, 0) 120 | So(result, ShouldEqual, "barfoo") 121 | }) 122 | 123 | Convey("Response writer with Hijack", t, func() { 124 | hijackable := newHijackableResponse() 125 | rw := NewResponseWriter(hijackable) 126 | hijacker, ok := rw.(http.Hijacker) 127 | So(ok, ShouldBeTrue) 128 | _, _, err := hijacker.Hijack() 129 | So(err, ShouldBeNil) 130 | So(hijackable.Hijacked, ShouldBeTrue) 131 | }) 132 | 133 | Convey("Response writer with bad Hijack", t, func() { 134 | hijackable := new(http.ResponseWriter) 135 | rw := NewResponseWriter(*hijackable) 136 | hijacker, ok := rw.(http.Hijacker) 137 | So(ok, ShouldBeTrue) 138 | _, _, err := hijacker.Hijack() 139 | So(err, ShouldNotBeNil) 140 | }) 141 | 142 | Convey("Response writer with close notify", t, func() { 143 | resp := newCloseNotifyingRecorder() 144 | rw := NewResponseWriter(resp) 145 | closed := false 146 | notifier := rw.(http.CloseNotifier).CloseNotify() 147 | resp.close() 148 | select { 149 | case <-notifier: 150 | closed = true 151 | case <-time.After(time.Second): 152 | } 153 | So(closed, ShouldBeTrue) 154 | }) 155 | 156 | Convey("Response writer with flusher", t, func() { 157 | resp := httptest.NewRecorder() 158 | rw := NewResponseWriter(resp) 159 | _, ok := rw.(http.Flusher) 160 | So(ok, ShouldBeTrue) 161 | }) 162 | 163 | Convey("Response writer with flusher handler", t, func() { 164 | m := Classic() 165 | m.Get("/events", func(w http.ResponseWriter, r *http.Request) { 166 | f, ok := w.(http.Flusher) 167 | So(ok, ShouldBeTrue) 168 | 169 | w.Header().Set("Content-Type", "text/event-stream") 170 | w.Header().Set("Cache-Control", "no-cache") 171 | w.Header().Set("Connection", "keep-alive") 172 | 173 | for i := 0; i < 2; i++ { 174 | time.Sleep(10 * time.Millisecond) 175 | io.WriteString(w, "data: Hello\n\n") 176 | f.Flush() 177 | } 178 | }) 179 | 180 | resp := httptest.NewRecorder() 181 | req, err := http.NewRequest("GET", "/events", nil) 182 | So(err, ShouldBeNil) 183 | m.ServeHTTP(resp, req) 184 | 185 | So(resp.Code, ShouldEqual, http.StatusOK) 186 | So(resp.Body.String(), ShouldEqual, "data: Hello\n\ndata: Hello\n\n") 187 | }) 188 | } 189 | -------------------------------------------------------------------------------- /macaron-1.1.8/return_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "net/http" 20 | "reflect" 21 | 22 | "github.com/go-macaron/inject" 23 | ) 24 | 25 | // ReturnHandler is a service that Martini provides that is called 26 | // when a route handler returns something. The ReturnHandler is 27 | // responsible for writing to the ResponseWriter based on the values 28 | // that are passed into this function. 29 | type ReturnHandler func(*Context, []reflect.Value) 30 | 31 | func canDeref(val reflect.Value) bool { 32 | return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr 33 | } 34 | 35 | func isError(val reflect.Value) bool { 36 | _, ok := val.Interface().(error) 37 | return ok 38 | } 39 | 40 | func isByteSlice(val reflect.Value) bool { 41 | return val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8 42 | } 43 | 44 | func defaultReturnHandler() ReturnHandler { 45 | return func(ctx *Context, vals []reflect.Value) { 46 | rv := ctx.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil))) 47 | resp := rv.Interface().(http.ResponseWriter) 48 | var respVal reflect.Value 49 | if len(vals) > 1 && vals[0].Kind() == reflect.Int { 50 | resp.WriteHeader(int(vals[0].Int())) 51 | respVal = vals[1] 52 | } else if len(vals) > 0 { 53 | respVal = vals[0] 54 | 55 | if isError(respVal) { 56 | err := respVal.Interface().(error) 57 | if err != nil { 58 | ctx.internalServerError(ctx, err) 59 | } 60 | return 61 | } else if canDeref(respVal) { 62 | if respVal.IsNil() { 63 | return // Ignore nil error 64 | } 65 | } 66 | } 67 | if canDeref(respVal) { 68 | respVal = respVal.Elem() 69 | } 70 | if isByteSlice(respVal) { 71 | resp.Write(respVal.Bytes()) 72 | } else { 73 | resp.Write([]byte(respVal.String())) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /macaron-1.1.8/return_handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package macaron 16 | 17 | import ( 18 | "errors" 19 | "net/http" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | . "github.com/smartystreets/goconvey/convey" 24 | ) 25 | 26 | func Test_Return_Handler(t *testing.T) { 27 | Convey("Return with status and body", t, func() { 28 | m := New() 29 | m.Get("/", func() (int, string) { 30 | return 418, "i'm a teapot" 31 | }) 32 | 33 | resp := httptest.NewRecorder() 34 | req, err := http.NewRequest("GET", "/", nil) 35 | So(err, ShouldBeNil) 36 | m.ServeHTTP(resp, req) 37 | 38 | So(resp.Code, ShouldEqual, http.StatusTeapot) 39 | So(resp.Body.String(), ShouldEqual, "i'm a teapot") 40 | }) 41 | 42 | Convey("Return with error", t, func() { 43 | m := New() 44 | m.Get("/", func() error { 45 | return errors.New("what the hell!!!") 46 | }) 47 | 48 | resp := httptest.NewRecorder() 49 | req, err := http.NewRequest("GET", "/", nil) 50 | So(err, ShouldBeNil) 51 | m.ServeHTTP(resp, req) 52 | 53 | So(resp.Code, ShouldEqual, http.StatusInternalServerError) 54 | So(resp.Body.String(), ShouldEqual, "what the hell!!!\n") 55 | 56 | Convey("Return with nil error", func() { 57 | m := New() 58 | m.Get("/", func() error { 59 | return nil 60 | }, func() (int, string) { 61 | return 200, "Awesome" 62 | }) 63 | 64 | resp := httptest.NewRecorder() 65 | req, err := http.NewRequest("GET", "/", nil) 66 | So(err, ShouldBeNil) 67 | m.ServeHTTP(resp, req) 68 | 69 | So(resp.Code, ShouldEqual, http.StatusOK) 70 | So(resp.Body.String(), ShouldEqual, "Awesome") 71 | }) 72 | }) 73 | 74 | Convey("Return with pointer", t, func() { 75 | m := New() 76 | m.Get("/", func() *string { 77 | str := "hello world" 78 | return &str 79 | }) 80 | 81 | resp := httptest.NewRecorder() 82 | req, err := http.NewRequest("GET", "/", nil) 83 | So(err, ShouldBeNil) 84 | m.ServeHTTP(resp, req) 85 | 86 | So(resp.Body.String(), ShouldEqual, "hello world") 87 | }) 88 | 89 | Convey("Return with byte slice", t, func() { 90 | m := New() 91 | m.Get("/", func() []byte { 92 | return []byte("hello world") 93 | }) 94 | 95 | resp := httptest.NewRecorder() 96 | req, err := http.NewRequest("GET", "/", nil) 97 | So(err, ShouldBeNil) 98 | m.ServeHTTP(resp, req) 99 | 100 | So(resp.Body.String(), ShouldEqual, "hello world") 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /macaron-1.1.8/router.go: -------------------------------------------------------------------------------- 1 | 2 | package macaron 3 | 4 | import ( 5 | "net/http" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | 11 | // 12 | // HTTP标准方法: 13 | // 14 | var ( 15 | // Known HTTP methods. 16 | _HTTP_METHODS = map[string]bool{ 17 | "GET": true, 18 | "POST": true, 19 | "PUT": true, 20 | "DELETE": true, 21 | "PATCH": true, 22 | "OPTIONS": true, 23 | "HEAD": true, 24 | } 25 | ) 26 | 27 | // routeMap represents a thread-safe map for route tree. 28 | type routeMap struct { 29 | lock sync.RWMutex 30 | routes map[string]map[string]*Leaf 31 | } 32 | 33 | // NewRouteMap initializes and returns a new routeMap. 34 | func NewRouteMap() *routeMap { 35 | rm := &routeMap{ 36 | routes: make(map[string]map[string]*Leaf), // tree::Leaf 37 | } 38 | for m := range _HTTP_METHODS { 39 | rm.routes[m] = make(map[string]*Leaf) 40 | } 41 | return rm 42 | } 43 | 44 | // getLeaf returns Leaf object if a route has been registered. 45 | func (rm *routeMap) getLeaf(method, pattern string) *Leaf { 46 | rm.lock.RLock() 47 | defer rm.lock.RUnlock() 48 | 49 | return rm.routes[method][pattern] 50 | } 51 | 52 | // add adds new route to route tree map. 53 | func (rm *routeMap) add(method, pattern string, leaf *Leaf) { 54 | rm.lock.Lock() 55 | defer rm.lock.Unlock() 56 | 57 | rm.routes[method][pattern] = leaf 58 | } 59 | 60 | type group struct { 61 | pattern string 62 | handlers []Handler 63 | } 64 | 65 | //------------------------------------------- 66 | // 路由: 67 | // Router represents a Macaron router layer. 68 | type Router struct { 69 | m *Macaron // 指针 70 | autoHead bool 71 | routers map[string]*Tree // 路由 72 | *routeMap 73 | namedRoutes map[string]*Leaf 74 | 75 | groups []group 76 | notFound http.HandlerFunc 77 | internalServerError func(*Context, error) 78 | } 79 | 80 | func NewRouter() *Router { 81 | return &Router{ 82 | routers: make(map[string]*Tree), 83 | routeMap: NewRouteMap(), 84 | namedRoutes: make(map[string]*Leaf), 85 | } 86 | } 87 | 88 | // SetAutoHead sets the value who determines whether add HEAD method automatically 89 | // when GET method is added. Combo router will not be affected by this value. 90 | func (r *Router) SetAutoHead(v bool) { 91 | r.autoHead = v 92 | } 93 | 94 | type Params map[string]string 95 | 96 | // Handle is a function that can be registered to a route to handle HTTP requests. 97 | // Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables). 98 | type Handle func(http.ResponseWriter, *http.Request, Params) 99 | 100 | // Route represents a wrapper of leaf route and upper level router. 101 | type Route struct { 102 | router *Router 103 | leaf *Leaf 104 | } 105 | 106 | // Name sets name of route. 107 | func (r *Route) Name(name string) { 108 | if len(name) == 0 { 109 | panic("route name cannot be empty") 110 | } else if r.router.namedRoutes[name] != nil { 111 | panic("route with given name already exists") 112 | } 113 | r.router.namedRoutes[name] = r.leaf 114 | } 115 | 116 | // handle adds new route to the router tree. 117 | func (r *Router) handle(method, pattern string, handle Handle) *Route { 118 | method = strings.ToUpper(method) 119 | 120 | var leaf *Leaf 121 | // Prevent duplicate routes. 122 | if leaf = r.getLeaf(method, pattern); leaf != nil { 123 | return &Route{r, leaf} 124 | } 125 | 126 | // Validate HTTP methods. 127 | if !_HTTP_METHODS[method] && method != "*" { 128 | panic("unknown HTTP method: " + method) 129 | } 130 | 131 | // Generate methods need register. 132 | methods := make(map[string]bool) 133 | if method == "*" { 134 | for m := range _HTTP_METHODS { 135 | methods[m] = true 136 | } 137 | } else { 138 | methods[method] = true 139 | } 140 | 141 | // Add to router tree. 142 | for m := range methods { 143 | if t, ok := r.routers[m]; ok { 144 | leaf = t.Add(pattern, handle) 145 | } else { 146 | t := NewTree() 147 | leaf = t.Add(pattern, handle) 148 | r.routers[m] = t 149 | } 150 | r.add(m, pattern, leaf) 151 | } 152 | return &Route{r, leaf} 153 | } 154 | 155 | // Handle registers a new request handle with the given pattern, method and handlers. 156 | func (r *Router) Handle(method string, pattern string, handlers []Handler) *Route { 157 | if len(r.groups) > 0 { 158 | groupPattern := "" 159 | h := make([]Handler, 0) 160 | for _, g := range r.groups { 161 | groupPattern += g.pattern 162 | h = append(h, g.handlers...) 163 | } 164 | 165 | pattern = groupPattern + pattern 166 | h = append(h, handlers...) 167 | handlers = h 168 | } 169 | validateHandlers(handlers) 170 | 171 | return r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params Params) { 172 | c := r.m.createContext(resp, req) 173 | c.params = params 174 | c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers)) 175 | c.handlers = append(c.handlers, r.m.handlers...) 176 | c.handlers = append(c.handlers, handlers...) 177 | c.run() 178 | }) 179 | } 180 | 181 | func (r *Router) Group(pattern string, fn func(), h ...Handler) { 182 | r.groups = append(r.groups, group{pattern, h}) 183 | fn() 184 | r.groups = r.groups[:len(r.groups)-1] 185 | } 186 | 187 | // Get is a shortcut for r.Handle("GET", pattern, handlers) 188 | func (r *Router) Get(pattern string, h ...Handler) (leaf *Route) { 189 | leaf = r.Handle("GET", pattern, h) 190 | if r.autoHead { 191 | r.Head(pattern, h...) 192 | } 193 | return leaf 194 | } 195 | 196 | // Patch is a shortcut for r.Handle("PATCH", pattern, handlers) 197 | func (r *Router) Patch(pattern string, h ...Handler) *Route { 198 | return r.Handle("PATCH", pattern, h) 199 | } 200 | 201 | // Post is a shortcut for r.Handle("POST", pattern, handlers) 202 | func (r *Router) Post(pattern string, h ...Handler) *Route { 203 | return r.Handle("POST", pattern, h) 204 | } 205 | 206 | // Put is a shortcut for r.Handle("PUT", pattern, handlers) 207 | func (r *Router) Put(pattern string, h ...Handler) *Route { 208 | return r.Handle("PUT", pattern, h) 209 | } 210 | 211 | // Delete is a shortcut for r.Handle("DELETE", pattern, handlers) 212 | func (r *Router) Delete(pattern string, h ...Handler) *Route { 213 | return r.Handle("DELETE", pattern, h) 214 | } 215 | 216 | // Options is a shortcut for r.Handle("OPTIONS", pattern, handlers) 217 | func (r *Router) Options(pattern string, h ...Handler) *Route { 218 | return r.Handle("OPTIONS", pattern, h) 219 | } 220 | 221 | // Head is a shortcut for r.Handle("HEAD", pattern, handlers) 222 | func (r *Router) Head(pattern string, h ...Handler) *Route { 223 | return r.Handle("HEAD", pattern, h) 224 | } 225 | 226 | // Any is a shortcut for r.Handle("*", pattern, handlers) 227 | func (r *Router) Any(pattern string, h ...Handler) *Route { 228 | return r.Handle("*", pattern, h) 229 | } 230 | 231 | // Route is a shortcut for same handlers but different HTTP methods. 232 | // 233 | // Example: 234 | // m.Route("/", "GET,POST", h) 235 | func (r *Router) Route(pattern, methods string, h ...Handler) (route *Route) { 236 | for _, m := range strings.Split(methods, ",") { 237 | route = r.Handle(strings.TrimSpace(m), pattern, h) 238 | } 239 | return route 240 | } 241 | 242 | // Combo returns a combo router. 243 | func (r *Router) Combo(pattern string, h ...Handler) *ComboRouter { 244 | return &ComboRouter{r, pattern, h, map[string]bool{}, nil} 245 | } 246 | 247 | // Configurable http.HandlerFunc which is called when no matching route is 248 | // found. If it is not set, http.NotFound is used. 249 | // Be sure to set 404 response code in your handler. 250 | func (r *Router) NotFound(handlers ...Handler) { 251 | validateHandlers(handlers) 252 | r.notFound = func(rw http.ResponseWriter, req *http.Request) { 253 | c := r.m.createContext(rw, req) 254 | c.handlers = append(r.m.handlers, handlers...) 255 | c.run() 256 | } 257 | } 258 | 259 | // Configurable handler which is called when route handler returns 260 | // error. If it is not set, default handler is used. 261 | // Be sure to set 500 response code in your handler. 262 | func (r *Router) InternalServerError(handlers ...Handler) { 263 | validateHandlers(handlers) 264 | r.internalServerError = func(c *Context, err error) { 265 | c.index = 0 266 | c.handlers = handlers 267 | c.Map(err) 268 | c.run() 269 | } 270 | } 271 | 272 | func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 273 | if t, ok := r.routers[req.Method]; ok { 274 | h, p, ok := t.Match(req.URL.Path) 275 | if ok { 276 | if splat, ok := p["*0"]; ok { 277 | p["*"] = splat // Easy name. 278 | } 279 | h(rw, req, p) 280 | return 281 | } 282 | } 283 | 284 | r.notFound(rw, req) 285 | } 286 | 287 | // URLFor builds path part of URL by given pair values. 288 | func (r *Router) URLFor(name string, pairs ...string) string { 289 | leaf, ok := r.namedRoutes[name] 290 | if !ok { 291 | panic("route with given name does not exists: " + name) 292 | } 293 | return leaf.URLPath(pairs...) 294 | } 295 | 296 | // ComboRouter represents a combo router. 297 | type ComboRouter struct { 298 | router *Router 299 | pattern string 300 | handlers []Handler 301 | methods map[string]bool // Registered methods. 302 | 303 | lastRoute *Route 304 | } 305 | 306 | func (cr *ComboRouter) checkMethod(name string) { 307 | if cr.methods[name] { 308 | panic("method '" + name + "' has already been registered") 309 | } 310 | cr.methods[name] = true 311 | } 312 | 313 | func (cr *ComboRouter) route(fn func(string, ...Handler) *Route, method string, h ...Handler) *ComboRouter { 314 | cr.checkMethod(method) 315 | cr.lastRoute = fn(cr.pattern, append(cr.handlers, h...)...) 316 | return cr 317 | } 318 | 319 | func (cr *ComboRouter) Get(h ...Handler) *ComboRouter { 320 | return cr.route(cr.router.Get, "GET", h...) 321 | } 322 | 323 | func (cr *ComboRouter) Patch(h ...Handler) *ComboRouter { 324 | return cr.route(cr.router.Patch, "PATCH", h...) 325 | } 326 | 327 | func (cr *ComboRouter) Post(h ...Handler) *ComboRouter { 328 | return cr.route(cr.router.Post, "POST", h...) 329 | } 330 | 331 | func (cr *ComboRouter) Put(h ...Handler) *ComboRouter { 332 | return cr.route(cr.router.Put, "PUT", h...) 333 | } 334 | 335 | func (cr *ComboRouter) Delete(h ...Handler) *ComboRouter { 336 | return cr.route(cr.router.Delete, "DELETE", h...) 337 | } 338 | 339 | func (cr *ComboRouter) Options(h ...Handler) *ComboRouter { 340 | return cr.route(cr.router.Options, "OPTIONS", h...) 341 | } 342 | 343 | func (cr *ComboRouter) Head(h ...Handler) *ComboRouter { 344 | return cr.route(cr.router.Head, "HEAD", h...) 345 | } 346 | 347 | // Name sets name of ComboRouter route. 348 | func (cr *ComboRouter) Name(name string) { 349 | if cr.lastRoute == nil { 350 | panic("no corresponding route to be named") 351 | } 352 | cr.lastRoute.Name(name) 353 | } 354 | -------------------------------------------------------------------------------- /macaron-1.1.8/router_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package macaron 16 | 17 | import ( 18 | "errors" 19 | "net/http" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | . "github.com/smartystreets/goconvey/convey" 24 | ) 25 | 26 | func Test_Router_Handle(t *testing.T) { 27 | Convey("Register all HTTP methods routes", t, func() { 28 | m := New() 29 | m.Get("/get", func() string { 30 | return "GET" 31 | }) 32 | resp := httptest.NewRecorder() 33 | req, err := http.NewRequest("GET", "/get", nil) 34 | So(err, ShouldBeNil) 35 | m.ServeHTTP(resp, req) 36 | So(resp.Body.String(), ShouldEqual, "GET") 37 | 38 | m.Patch("/patch", func() string { 39 | return "PATCH" 40 | }) 41 | resp = httptest.NewRecorder() 42 | req, err = http.NewRequest("PATCH", "/patch", nil) 43 | So(err, ShouldBeNil) 44 | m.ServeHTTP(resp, req) 45 | So(resp.Body.String(), ShouldEqual, "PATCH") 46 | 47 | m.Post("/post", func() string { 48 | return "POST" 49 | }) 50 | resp = httptest.NewRecorder() 51 | req, err = http.NewRequest("POST", "/post", nil) 52 | So(err, ShouldBeNil) 53 | m.ServeHTTP(resp, req) 54 | So(resp.Body.String(), ShouldEqual, "POST") 55 | 56 | m.Put("/put", func() string { 57 | return "PUT" 58 | }) 59 | resp = httptest.NewRecorder() 60 | req, err = http.NewRequest("PUT", "/put", nil) 61 | So(err, ShouldBeNil) 62 | m.ServeHTTP(resp, req) 63 | So(resp.Body.String(), ShouldEqual, "PUT") 64 | 65 | m.Delete("/delete", func() string { 66 | return "DELETE" 67 | }) 68 | resp = httptest.NewRecorder() 69 | req, err = http.NewRequest("DELETE", "/delete", nil) 70 | So(err, ShouldBeNil) 71 | m.ServeHTTP(resp, req) 72 | So(resp.Body.String(), ShouldEqual, "DELETE") 73 | 74 | m.Options("/options", func() string { 75 | return "OPTIONS" 76 | }) 77 | resp = httptest.NewRecorder() 78 | req, err = http.NewRequest("OPTIONS", "/options", nil) 79 | So(err, ShouldBeNil) 80 | m.ServeHTTP(resp, req) 81 | So(resp.Body.String(), ShouldEqual, "OPTIONS") 82 | 83 | m.Head("/head", func() string { 84 | return "HEAD" 85 | }) 86 | resp = httptest.NewRecorder() 87 | req, err = http.NewRequest("HEAD", "/head", nil) 88 | So(err, ShouldBeNil) 89 | m.ServeHTTP(resp, req) 90 | So(resp.Body.String(), ShouldEqual, "HEAD") 91 | 92 | m.Any("/any", func() string { 93 | return "ANY" 94 | }) 95 | resp = httptest.NewRecorder() 96 | req, err = http.NewRequest("GET", "/any", nil) 97 | So(err, ShouldBeNil) 98 | m.ServeHTTP(resp, req) 99 | So(resp.Body.String(), ShouldEqual, "ANY") 100 | 101 | m.Route("/route", "GET,POST", func() string { 102 | return "ROUTE" 103 | }) 104 | resp = httptest.NewRecorder() 105 | req, err = http.NewRequest("POST", "/route", nil) 106 | So(err, ShouldBeNil) 107 | m.ServeHTTP(resp, req) 108 | So(resp.Body.String(), ShouldEqual, "ROUTE") 109 | }) 110 | 111 | Convey("Register with or without auto head", t, func() { 112 | Convey("Without auto head", func() { 113 | m := New() 114 | m.Get("/", func() string { 115 | return "GET" 116 | }) 117 | resp := httptest.NewRecorder() 118 | req, err := http.NewRequest("HEAD", "/", nil) 119 | So(err, ShouldBeNil) 120 | m.ServeHTTP(resp, req) 121 | So(resp.Code, ShouldEqual, 404) 122 | }) 123 | 124 | Convey("With auto head", func() { 125 | m := New() 126 | m.SetAutoHead(true) 127 | m.Get("/", func() string { 128 | return "GET" 129 | }) 130 | resp := httptest.NewRecorder() 131 | req, err := http.NewRequest("HEAD", "/", nil) 132 | So(err, ShouldBeNil) 133 | m.ServeHTTP(resp, req) 134 | So(resp.Code, ShouldEqual, 200) 135 | }) 136 | }) 137 | 138 | Convey("Register all HTTP methods routes with combo", t, func() { 139 | m := New() 140 | m.SetURLPrefix("/prefix") 141 | m.Use(Renderer()) 142 | m.Combo("/", func(ctx *Context) { 143 | ctx.Data["prefix"] = "Prefix_" 144 | }). 145 | Get(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "GET" }). 146 | Patch(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PATCH" }). 147 | Post(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "POST" }). 148 | Put(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PUT" }). 149 | Delete(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "DELETE" }). 150 | Options(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "OPTIONS" }). 151 | Head(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "HEAD" }) 152 | 153 | for name := range _HTTP_METHODS { 154 | resp := httptest.NewRecorder() 155 | req, err := http.NewRequest(name, "/", nil) 156 | So(err, ShouldBeNil) 157 | m.ServeHTTP(resp, req) 158 | So(resp.Body.String(), ShouldEqual, "Prefix_"+name) 159 | } 160 | 161 | defer func() { 162 | So(recover(), ShouldNotBeNil) 163 | }() 164 | m.Combo("/").Get(func() {}).Get(nil) 165 | }) 166 | 167 | Convey("Register duplicated routes", t, func() { 168 | r := NewRouter() 169 | r.Get("/") 170 | r.Get("/") 171 | }) 172 | 173 | Convey("Register invalid HTTP method", t, func() { 174 | defer func() { 175 | So(recover(), ShouldNotBeNil) 176 | }() 177 | r := NewRouter() 178 | r.Handle("404", "/", nil) 179 | }) 180 | } 181 | 182 | func Test_Route_Name(t *testing.T) { 183 | Convey("Set route name", t, func() { 184 | m := New() 185 | m.Get("/", func() {}).Name("home") 186 | 187 | defer func() { 188 | So(recover(), ShouldNotBeNil) 189 | }() 190 | m.Get("/", func() {}).Name("home") 191 | }) 192 | 193 | Convey("Set combo router name", t, func() { 194 | m := New() 195 | m.Combo("/").Get(func() {}).Name("home") 196 | 197 | defer func() { 198 | So(recover(), ShouldNotBeNil) 199 | }() 200 | m.Combo("/").Name("home") 201 | }) 202 | } 203 | 204 | func Test_Router_URLFor(t *testing.T) { 205 | Convey("Build URL path", t, func() { 206 | m := New() 207 | m.Get("/user/:id", func() {}).Name("user_id") 208 | m.Get("/user/:id/:name", func() {}).Name("user_id_name") 209 | m.Get("cms_:id_:page.html", func() {}).Name("id_page") 210 | 211 | So(m.URLFor("user_id", "id", "12"), ShouldEqual, "/user/12") 212 | So(m.URLFor("user_id_name", "id", "12", "name", "unknwon"), ShouldEqual, "/user/12/unknwon") 213 | So(m.URLFor("id_page", "id", "12", "page", "profile"), ShouldEqual, "/cms_12_profile.html") 214 | 215 | Convey("Number of pair values does not match", func() { 216 | defer func() { 217 | So(recover(), ShouldNotBeNil) 218 | }() 219 | m.URLFor("user_id", "id") 220 | }) 221 | 222 | Convey("Empty pair value", func() { 223 | defer func() { 224 | So(recover(), ShouldNotBeNil) 225 | }() 226 | m.URLFor("user_id", "", "") 227 | }) 228 | 229 | Convey("Empty route name", func() { 230 | defer func() { 231 | So(recover(), ShouldNotBeNil) 232 | }() 233 | m.Get("/user/:id", func() {}).Name("") 234 | }) 235 | 236 | Convey("Invalid route name", func() { 237 | defer func() { 238 | So(recover(), ShouldNotBeNil) 239 | }() 240 | m.URLFor("404") 241 | }) 242 | }) 243 | } 244 | 245 | func Test_Router_Group(t *testing.T) { 246 | Convey("Register route group", t, func() { 247 | m := New() 248 | m.Group("/api", func() { 249 | m.Group("/v1", func() { 250 | m.Get("/list", func() string { 251 | return "Well done!" 252 | }) 253 | }) 254 | }) 255 | resp := httptest.NewRecorder() 256 | req, err := http.NewRequest("GET", "/api/v1/list", nil) 257 | So(err, ShouldBeNil) 258 | m.ServeHTTP(resp, req) 259 | So(resp.Body.String(), ShouldEqual, "Well done!") 260 | }) 261 | } 262 | 263 | func Test_Router_NotFound(t *testing.T) { 264 | Convey("Custom not found handler", t, func() { 265 | m := New() 266 | m.Get("/", func() {}) 267 | m.NotFound(func() string { 268 | return "Custom not found" 269 | }) 270 | resp := httptest.NewRecorder() 271 | req, err := http.NewRequest("GET", "/404", nil) 272 | So(err, ShouldBeNil) 273 | m.ServeHTTP(resp, req) 274 | So(resp.Body.String(), ShouldEqual, "Custom not found") 275 | }) 276 | } 277 | 278 | func Test_Router_InternalServerError(t *testing.T) { 279 | Convey("Custom internal server error handler", t, func() { 280 | m := New() 281 | m.Get("/", func() error { 282 | return errors.New("Custom internal server error") 283 | }) 284 | m.InternalServerError(func(rw http.ResponseWriter, err error) { 285 | rw.WriteHeader(500) 286 | rw.Write([]byte(err.Error())) 287 | }) 288 | resp := httptest.NewRecorder() 289 | req, err := http.NewRequest("GET", "/", nil) 290 | So(err, ShouldBeNil) 291 | m.ServeHTTP(resp, req) 292 | So(resp.Code, ShouldEqual, 500) 293 | So(resp.Body.String(), ShouldEqual, "Custom internal server error") 294 | }) 295 | } 296 | 297 | func Test_Router_splat(t *testing.T) { 298 | Convey("Register router with glob", t, func() { 299 | m := New() 300 | m.Get("/*", func(ctx *Context) string { 301 | return ctx.Params("*") 302 | }) 303 | resp := httptest.NewRecorder() 304 | req, err := http.NewRequest("GET", "/hahaha", nil) 305 | So(err, ShouldBeNil) 306 | m.ServeHTTP(resp, req) 307 | So(resp.Body.String(), ShouldEqual, "hahaha") 308 | }) 309 | } 310 | -------------------------------------------------------------------------------- /macaron-1.1.8/static.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "log" 20 | "net/http" 21 | "path" 22 | "path/filepath" 23 | "strings" 24 | "sync" 25 | ) 26 | 27 | // StaticOptions is a struct for specifying configuration options for the macaron.Static middleware. 28 | type StaticOptions struct { 29 | // Prefix is the optional prefix used to serve the static directory content 30 | Prefix string 31 | // SkipLogging will disable [Static] log messages when a static file is served. 32 | SkipLogging bool 33 | // IndexFile defines which file to serve as index if it exists. 34 | IndexFile string 35 | // Expires defines which user-defined function to use for producing a HTTP Expires Header 36 | // https://developers.google.com/speed/docs/insights/LeverageBrowserCaching 37 | Expires func() string 38 | // FileSystem is the interface for supporting any implmentation of file system. 39 | FileSystem http.FileSystem 40 | } 41 | 42 | // FIXME: to be deleted. 43 | type staticMap struct { 44 | lock sync.RWMutex 45 | data map[string]*http.Dir 46 | } 47 | 48 | func (sm *staticMap) Set(dir *http.Dir) { 49 | sm.lock.Lock() 50 | defer sm.lock.Unlock() 51 | 52 | sm.data[string(*dir)] = dir 53 | } 54 | 55 | func (sm *staticMap) Get(name string) *http.Dir { 56 | sm.lock.RLock() 57 | defer sm.lock.RUnlock() 58 | 59 | return sm.data[name] 60 | } 61 | 62 | func (sm *staticMap) Delete(name string) { 63 | sm.lock.Lock() 64 | defer sm.lock.Unlock() 65 | 66 | delete(sm.data, name) 67 | } 68 | 69 | var statics = staticMap{sync.RWMutex{}, map[string]*http.Dir{}} 70 | 71 | // staticFileSystem implements http.FileSystem interface. 72 | type staticFileSystem struct { 73 | dir *http.Dir 74 | } 75 | 76 | func newStaticFileSystem(directory string) staticFileSystem { 77 | if !filepath.IsAbs(directory) { 78 | directory = filepath.Join(Root, directory) 79 | } 80 | dir := http.Dir(directory) 81 | statics.Set(&dir) 82 | return staticFileSystem{&dir} 83 | } 84 | 85 | func (fs staticFileSystem) Open(name string) (http.File, error) { 86 | return fs.dir.Open(name) 87 | } 88 | 89 | func prepareStaticOption(dir string, opt StaticOptions) StaticOptions { 90 | // Defaults 91 | if len(opt.IndexFile) == 0 { 92 | opt.IndexFile = "index.html" 93 | } 94 | // Normalize the prefix if provided 95 | if opt.Prefix != "" { 96 | // Ensure we have a leading '/' 97 | if opt.Prefix[0] != '/' { 98 | opt.Prefix = "/" + opt.Prefix 99 | } 100 | // Remove any trailing '/' 101 | opt.Prefix = strings.TrimRight(opt.Prefix, "/") 102 | } 103 | if opt.FileSystem == nil { 104 | opt.FileSystem = newStaticFileSystem(dir) 105 | } 106 | return opt 107 | } 108 | 109 | func prepareStaticOptions(dir string, options []StaticOptions) StaticOptions { 110 | var opt StaticOptions 111 | if len(options) > 0 { 112 | opt = options[0] 113 | } 114 | return prepareStaticOption(dir, opt) 115 | } 116 | 117 | func staticHandler(ctx *Context, log *log.Logger, opt StaticOptions) bool { 118 | if ctx.Req.Method != "GET" && ctx.Req.Method != "HEAD" { 119 | return false 120 | } 121 | 122 | file := ctx.Req.URL.Path 123 | // if we have a prefix, filter requests by stripping the prefix 124 | if opt.Prefix != "" { 125 | if !strings.HasPrefix(file, opt.Prefix) { 126 | return false 127 | } 128 | file = file[len(opt.Prefix):] 129 | if file != "" && file[0] != '/' { 130 | return false 131 | } 132 | } 133 | 134 | f, err := opt.FileSystem.Open(file) 135 | if err != nil { 136 | return false 137 | } 138 | defer f.Close() 139 | 140 | fi, err := f.Stat() 141 | if err != nil { 142 | return true // File exists but fail to open. 143 | } 144 | 145 | // Try to serve index file 146 | if fi.IsDir() { 147 | // Redirect if missing trailing slash. 148 | if !strings.HasSuffix(ctx.Req.URL.Path, "/") { 149 | http.Redirect(ctx.Resp, ctx.Req.Request, ctx.Req.URL.Path+"/", http.StatusFound) 150 | return true 151 | } 152 | 153 | file = path.Join(file, opt.IndexFile) 154 | f, err = opt.FileSystem.Open(file) 155 | if err != nil { 156 | return false // Discard error. 157 | } 158 | defer f.Close() 159 | 160 | fi, err = f.Stat() 161 | if err != nil || fi.IsDir() { 162 | return true 163 | } 164 | } 165 | 166 | if !opt.SkipLogging { 167 | log.Println("[Static] Serving " + file) 168 | } 169 | 170 | // Add an Expires header to the static content 171 | if opt.Expires != nil { 172 | ctx.Resp.Header().Set("Expires", opt.Expires()) 173 | } 174 | 175 | http.ServeContent(ctx.Resp, ctx.Req.Request, file, fi.ModTime(), f) 176 | return true 177 | } 178 | 179 | // Static returns a middleware handler that serves static files in the given directory. 180 | func Static(directory string, staticOpt ...StaticOptions) Handler { 181 | opt := prepareStaticOptions(directory, staticOpt) 182 | 183 | return func(ctx *Context, log *log.Logger) { 184 | staticHandler(ctx, log, opt) 185 | } 186 | } 187 | 188 | // Statics registers multiple static middleware handlers all at once. 189 | func Statics(opt StaticOptions, dirs ...string) Handler { 190 | if len(dirs) == 0 { 191 | panic("no static directory is given") 192 | } 193 | opts := make([]StaticOptions, len(dirs)) 194 | for i := range dirs { 195 | opts[i] = prepareStaticOption(dirs[i], opt) 196 | } 197 | 198 | return func(ctx *Context, log *log.Logger) { 199 | for i := range opts { 200 | if staticHandler(ctx, log, opts[i]) { 201 | return 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /macaron-1.1.8/static_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Martini Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package macaron 17 | 18 | import ( 19 | "bytes" 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httptest" 23 | "os" 24 | "path" 25 | "strings" 26 | "testing" 27 | 28 | . "github.com/smartystreets/goconvey/convey" 29 | ) 30 | 31 | var currentRoot, _ = os.Getwd() 32 | 33 | func Test_Static(t *testing.T) { 34 | Convey("Serve static files", t, func() { 35 | m := New() 36 | m.Use(Static("./")) 37 | 38 | resp := httptest.NewRecorder() 39 | resp.Body = new(bytes.Buffer) 40 | req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) 41 | So(err, ShouldBeNil) 42 | m.ServeHTTP(resp, req) 43 | So(resp.Code, ShouldEqual, http.StatusOK) 44 | So(resp.Header().Get("Expires"), ShouldBeBlank) 45 | So(resp.Body.Len(), ShouldBeGreaterThan, 0) 46 | 47 | Convey("Change static path", func() { 48 | m.Get("/", func(ctx *Context) { 49 | ctx.ChangeStaticPath("./", "fixtures/basic2") 50 | }) 51 | 52 | resp := httptest.NewRecorder() 53 | req, err := http.NewRequest("GET", "/", nil) 54 | So(err, ShouldBeNil) 55 | m.ServeHTTP(resp, req) 56 | 57 | resp = httptest.NewRecorder() 58 | resp.Body = new(bytes.Buffer) 59 | req, err = http.NewRequest("GET", "http://localhost:4000/hello.tmpl", nil) 60 | So(err, ShouldBeNil) 61 | m.ServeHTTP(resp, req) 62 | So(resp.Code, ShouldEqual, http.StatusOK) 63 | So(resp.Header().Get("Expires"), ShouldBeBlank) 64 | So(resp.Body.Len(), ShouldBeGreaterThan, 0) 65 | }) 66 | }) 67 | 68 | Convey("Serve static files with local path", t, func() { 69 | Root = os.TempDir() 70 | f, err := ioutil.TempFile(Root, "static_content") 71 | So(err, ShouldBeNil) 72 | f.WriteString("Expected Content") 73 | f.Close() 74 | 75 | m := New() 76 | m.Use(Static(".")) 77 | 78 | resp := httptest.NewRecorder() 79 | resp.Body = new(bytes.Buffer) 80 | req, err := http.NewRequest("GET", "http://localhost:4000/"+path.Base(strings.Replace(f.Name(), "\\", "/", -1)), nil) 81 | So(err, ShouldBeNil) 82 | m.ServeHTTP(resp, req) 83 | So(resp.Code, ShouldEqual, http.StatusOK) 84 | So(resp.Header().Get("Expires"), ShouldBeBlank) 85 | So(resp.Body.String(), ShouldEqual, "Expected Content") 86 | }) 87 | 88 | Convey("Serve static files with head", t, func() { 89 | m := New() 90 | m.Use(Static(currentRoot)) 91 | 92 | resp := httptest.NewRecorder() 93 | resp.Body = new(bytes.Buffer) 94 | req, err := http.NewRequest("HEAD", "http://localhost:4000/macaron.go", nil) 95 | So(err, ShouldBeNil) 96 | m.ServeHTTP(resp, req) 97 | So(resp.Code, ShouldEqual, http.StatusOK) 98 | So(resp.Body.Len(), ShouldEqual, 0) 99 | }) 100 | 101 | Convey("Serve static files as post", t, func() { 102 | m := New() 103 | m.Use(Static(currentRoot)) 104 | 105 | resp := httptest.NewRecorder() 106 | req, err := http.NewRequest("POST", "http://localhost:4000/macaron.go", nil) 107 | So(err, ShouldBeNil) 108 | m.ServeHTTP(resp, req) 109 | So(resp.Code, ShouldEqual, http.StatusNotFound) 110 | }) 111 | 112 | Convey("Serve static files with bad directory", t, func() { 113 | m := Classic() 114 | resp := httptest.NewRecorder() 115 | req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) 116 | So(err, ShouldBeNil) 117 | m.ServeHTTP(resp, req) 118 | So(resp.Code, ShouldNotEqual, http.StatusOK) 119 | }) 120 | } 121 | 122 | func Test_Static_Options(t *testing.T) { 123 | Convey("Serve static files with options logging", t, func() { 124 | var buf bytes.Buffer 125 | m := NewWithLogger(&buf) 126 | opt := StaticOptions{} 127 | m.Use(Static(currentRoot, opt)) 128 | 129 | resp := httptest.NewRecorder() 130 | req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) 131 | So(err, ShouldBeNil) 132 | m.ServeHTTP(resp, req) 133 | 134 | So(resp.Code, ShouldEqual, http.StatusOK) 135 | So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n") 136 | 137 | // Not disable logging. 138 | m.Handlers() 139 | buf.Reset() 140 | opt.SkipLogging = true 141 | m.Use(Static(currentRoot, opt)) 142 | m.ServeHTTP(resp, req) 143 | 144 | So(resp.Code, ShouldEqual, http.StatusOK) 145 | So(buf.Len(), ShouldEqual, 0) 146 | }) 147 | 148 | Convey("Serve static files with options serve index", t, func() { 149 | var buf bytes.Buffer 150 | m := NewWithLogger(&buf) 151 | opt := StaticOptions{IndexFile: "macaron.go"} 152 | m.Use(Static(currentRoot, opt)) 153 | 154 | resp := httptest.NewRecorder() 155 | req, err := http.NewRequest("GET", "http://localhost:4000/", nil) 156 | So(err, ShouldBeNil) 157 | m.ServeHTTP(resp, req) 158 | 159 | So(resp.Code, ShouldEqual, http.StatusOK) 160 | So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n") 161 | }) 162 | 163 | Convey("Serve static files with options prefix", t, func() { 164 | var buf bytes.Buffer 165 | m := NewWithLogger(&buf) 166 | opt := StaticOptions{Prefix: "public"} 167 | m.Use(Static(currentRoot, opt)) 168 | 169 | resp := httptest.NewRecorder() 170 | req, err := http.NewRequest("GET", "http://localhost:4000/public/macaron.go", nil) 171 | So(err, ShouldBeNil) 172 | m.ServeHTTP(resp, req) 173 | 174 | So(resp.Code, ShouldEqual, http.StatusOK) 175 | So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n") 176 | }) 177 | 178 | Convey("Serve static files with options expires", t, func() { 179 | var buf bytes.Buffer 180 | m := NewWithLogger(&buf) 181 | opt := StaticOptions{Expires: func() string { return "46" }} 182 | m.Use(Static(currentRoot, opt)) 183 | 184 | resp := httptest.NewRecorder() 185 | req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) 186 | So(err, ShouldBeNil) 187 | m.ServeHTTP(resp, req) 188 | 189 | So(resp.Header().Get("Expires"), ShouldEqual, "46") 190 | }) 191 | } 192 | 193 | func Test_Static_Redirect(t *testing.T) { 194 | Convey("Serve static files with redirect", t, func() { 195 | m := New() 196 | m.Use(Static(currentRoot, StaticOptions{Prefix: "/public"})) 197 | 198 | resp := httptest.NewRecorder() 199 | req, err := http.NewRequest("GET", "http://localhost:4000/public", nil) 200 | So(err, ShouldBeNil) 201 | m.ServeHTTP(resp, req) 202 | 203 | So(resp.Code, ShouldEqual, http.StatusFound) 204 | So(resp.Header().Get("Location"), ShouldEqual, "/public/") 205 | }) 206 | } 207 | 208 | func Test_Statics(t *testing.T) { 209 | Convey("Serve multiple static routers", t, func() { 210 | Convey("Register empty directory", func() { 211 | defer func() { 212 | So(recover(), ShouldNotBeNil) 213 | }() 214 | 215 | m := New() 216 | m.Use(Statics(StaticOptions{})) 217 | 218 | resp := httptest.NewRecorder() 219 | req, err := http.NewRequest("GET", "http://localhost:4000/", nil) 220 | So(err, ShouldBeNil) 221 | m.ServeHTTP(resp, req) 222 | }) 223 | 224 | Convey("Serve normally", func() { 225 | var buf bytes.Buffer 226 | m := NewWithLogger(&buf) 227 | m.Use(Statics(StaticOptions{}, currentRoot, currentRoot+"/fixtures/basic")) 228 | 229 | resp := httptest.NewRecorder() 230 | req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) 231 | So(err, ShouldBeNil) 232 | m.ServeHTTP(resp, req) 233 | 234 | So(resp.Code, ShouldEqual, http.StatusOK) 235 | So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n") 236 | 237 | resp = httptest.NewRecorder() 238 | req, err = http.NewRequest("GET", "http://localhost:4000/admin/index.tmpl", nil) 239 | So(err, ShouldBeNil) 240 | m.ServeHTTP(resp, req) 241 | 242 | So(resp.Code, ShouldEqual, http.StatusOK) 243 | So(buf.String(), ShouldEndWith, "[Macaron] [Static] Serving /admin/index.tmpl\n") 244 | }) 245 | }) 246 | } 247 | -------------------------------------------------------------------------------- /macaron-1.1.8/tree.go: -------------------------------------------------------------------------------- 1 | package macaron 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "github.com/Unknwon/com" 8 | ) 9 | 10 | type patternType int8 11 | 12 | const ( 13 | _PATTERN_STATIC patternType = iota // /home 14 | _PATTERN_REGEXP // /:id([0-9]+) 15 | _PATTERN_PATH_EXT // /*.* 16 | _PATTERN_HOLDER // /:user 17 | _PATTERN_MATCH_ALL // /* 18 | ) 19 | 20 | 21 | // 关键模块: 22 | // gopkg.in/macaron.v1/router.go/NewRouteMap() 引用 23 | // 24 | // Leaf represents a leaf route information. 25 | type Leaf struct { 26 | parent *Tree 27 | 28 | typ patternType 29 | pattern string 30 | rawPattern string // Contains wildcard instead of regexp 31 | wildcards []string 32 | reg *regexp.Regexp 33 | optional bool 34 | 35 | handle Handle 36 | } 37 | 38 | var wildcardPattern = regexp.MustCompile(`:[a-zA-Z0-9]+`) 39 | 40 | func isSpecialRegexp(pattern, regStr string, pos []int) bool { 41 | return len(pattern) >= pos[1] + len(regStr) && pattern[pos[1]:pos[1] + len(regStr)] == regStr 42 | } 43 | 44 | // getNextWildcard tries to find next wildcard and update pattern with corresponding regexp. 45 | func getNextWildcard(pattern string) (wildcard, _ string) { 46 | pos := wildcardPattern.FindStringIndex(pattern) 47 | if pos == nil { 48 | return "", pattern 49 | } 50 | wildcard = pattern[pos[0]:pos[1]] 51 | 52 | // Reach last character or no regexp is given. 53 | if len(pattern) == pos[1] { 54 | return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1) 55 | } else if pattern[pos[1]] != '(' { 56 | switch { 57 | case isSpecialRegexp(pattern, ":int", pos): 58 | pattern = strings.Replace(pattern, ":int", "([0-9]+)", 1) 59 | case isSpecialRegexp(pattern, ":string", pos): 60 | pattern = strings.Replace(pattern, ":string", "([\\w]+)", 1) 61 | default: 62 | return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1) 63 | } 64 | } 65 | 66 | // Cut out placeholder directly. 67 | return wildcard, pattern[:pos[0]] + pattern[pos[1]:] 68 | } 69 | 70 | func getWildcards(pattern string) (string, []string) { 71 | wildcards := make([]string, 0, 2) 72 | 73 | // Keep getting next wildcard until nothing is left. 74 | var wildcard string 75 | for { 76 | wildcard, pattern = getNextWildcard(pattern) 77 | if len(wildcard) > 0 { 78 | wildcards = append(wildcards, wildcard) 79 | } else { 80 | break 81 | } 82 | } 83 | 84 | return pattern, wildcards 85 | } 86 | 87 | // getRawPattern removes all regexp but keeps wildcards for building URL path. 88 | func getRawPattern(rawPattern string) string { 89 | rawPattern = strings.Replace(rawPattern, ":int", "", -1) 90 | rawPattern = strings.Replace(rawPattern, ":string", "", -1) 91 | 92 | for { 93 | startIdx := strings.Index(rawPattern, "(") 94 | if startIdx == -1 { 95 | break 96 | } 97 | 98 | closeIdx := strings.Index(rawPattern, ")") 99 | if closeIdx > -1 { 100 | rawPattern = rawPattern[:startIdx] + rawPattern[closeIdx + 1:] 101 | } 102 | } 103 | return rawPattern 104 | } 105 | 106 | func checkPattern(pattern string) (typ patternType, rawPattern string, wildcards []string, reg *regexp.Regexp) { 107 | pattern = strings.TrimLeft(pattern, "?") 108 | rawPattern = getRawPattern(pattern) 109 | 110 | if pattern == "*" { 111 | typ = _PATTERN_MATCH_ALL 112 | } else if pattern == "*.*" { 113 | typ = _PATTERN_PATH_EXT 114 | } else if strings.Contains(pattern, ":") { 115 | typ = _PATTERN_REGEXP 116 | pattern, wildcards = getWildcards(pattern) 117 | if pattern == "(.+)" { 118 | typ = _PATTERN_HOLDER 119 | } else { 120 | reg = regexp.MustCompile(pattern) 121 | } 122 | } 123 | return typ, rawPattern, wildcards, reg 124 | } 125 | 126 | func NewLeaf(parent *Tree, pattern string, handle Handle) *Leaf { 127 | typ, rawPattern, wildcards, reg := checkPattern(pattern) 128 | optional := false 129 | if len(pattern) > 0 && pattern[0] == '?' { 130 | optional = true 131 | } 132 | return &Leaf{parent, typ, pattern, rawPattern, wildcards, reg, optional, handle} 133 | } 134 | 135 | // URLPath build path part of URL by given pair values. 136 | func (l *Leaf) URLPath(pairs ...string) string { 137 | if len(pairs) % 2 != 0 { 138 | panic("number of pairs does not match") 139 | } 140 | 141 | urlPath := l.rawPattern 142 | parent := l.parent 143 | for parent != nil { 144 | urlPath = parent.rawPattern + "/" + urlPath 145 | parent = parent.parent 146 | } 147 | for i := 0; i < len(pairs); i += 2 { 148 | if len(pairs[i]) == 0 { 149 | panic("pair value cannot be empty: " + com.ToStr(i)) 150 | } else if pairs[i][0] != ':' && pairs[i] != "*" && pairs[i] != "*.*" { 151 | pairs[i] = ":" + pairs[i] 152 | } 153 | urlPath = strings.Replace(urlPath, pairs[i], pairs[i + 1], 1) 154 | } 155 | return urlPath 156 | } 157 | 158 | // Tree represents a router tree in Macaron. 159 | type Tree struct { 160 | parent *Tree 161 | 162 | typ patternType 163 | pattern string 164 | rawPattern string 165 | wildcards []string 166 | reg *regexp.Regexp 167 | 168 | subtrees []*Tree 169 | leaves []*Leaf 170 | } 171 | 172 | func NewSubtree(parent *Tree, pattern string) *Tree { 173 | typ, rawPattern, wildcards, reg := checkPattern(pattern) 174 | return &Tree{parent, typ, pattern, rawPattern, wildcards, reg, make([]*Tree, 0, 5), make([]*Leaf, 0, 5)} 175 | } 176 | 177 | func NewTree() *Tree { 178 | return NewSubtree(nil, "") 179 | } 180 | 181 | func (t *Tree) addLeaf(pattern string, handle Handle) *Leaf { 182 | for i := 0; i < len(t.leaves); i++ { 183 | if t.leaves[i].pattern == pattern { 184 | return t.leaves[i] 185 | } 186 | } 187 | 188 | leaf := NewLeaf(t, pattern, handle) 189 | 190 | // Add exact same leaf to grandparent/parent level without optional. 191 | if leaf.optional { 192 | parent := leaf.parent 193 | if parent.parent != nil { 194 | parent.parent.addLeaf(parent.pattern, handle) 195 | } else { 196 | parent.addLeaf("", handle) // Root tree can add as empty pattern. 197 | } 198 | } 199 | 200 | i := 0 201 | for ; i < len(t.leaves); i++ { 202 | if leaf.typ < t.leaves[i].typ { 203 | break 204 | } 205 | } 206 | 207 | if i == len(t.leaves) { 208 | t.leaves = append(t.leaves, leaf) 209 | } else { 210 | t.leaves = append(t.leaves[:i], append([]*Leaf{leaf}, t.leaves[i:]...)...) 211 | } 212 | return leaf 213 | } 214 | 215 | func (t *Tree) addSubtree(segment, pattern string, handle Handle) *Leaf { 216 | for i := 0; i < len(t.subtrees); i++ { 217 | if t.subtrees[i].pattern == segment { 218 | return t.subtrees[i].addNextSegment(pattern, handle) 219 | } 220 | } 221 | 222 | subtree := NewSubtree(t, segment) 223 | i := 0 224 | for ; i < len(t.subtrees); i++ { 225 | if subtree.typ < t.subtrees[i].typ { 226 | break 227 | } 228 | } 229 | 230 | if i == len(t.subtrees) { 231 | t.subtrees = append(t.subtrees, subtree) 232 | } else { 233 | t.subtrees = append(t.subtrees[:i], append([]*Tree{subtree}, t.subtrees[i:]...)...) 234 | } 235 | return subtree.addNextSegment(pattern, handle) 236 | } 237 | 238 | func (t *Tree) addNextSegment(pattern string, handle Handle) *Leaf { 239 | pattern = strings.TrimPrefix(pattern, "/") 240 | 241 | i := strings.Index(pattern, "/") 242 | if i == -1 { 243 | return t.addLeaf(pattern, handle) 244 | } 245 | return t.addSubtree(pattern[:i], pattern[i + 1:], handle) 246 | } 247 | 248 | func (t *Tree) Add(pattern string, handle Handle) *Leaf { 249 | pattern = strings.TrimSuffix(pattern, "/") 250 | return t.addNextSegment(pattern, handle) 251 | } 252 | 253 | func (t *Tree) matchLeaf(globLevel int, url string, params Params) (Handle, bool) { 254 | for i := 0; i < len(t.leaves); i++ { 255 | switch t.leaves[i].typ { 256 | case _PATTERN_STATIC: 257 | if t.leaves[i].pattern == url { 258 | return t.leaves[i].handle, true 259 | } 260 | case _PATTERN_REGEXP: 261 | results := t.leaves[i].reg.FindStringSubmatch(url) 262 | // Number of results and wildcasrd should be exact same. 263 | if len(results) - 1 != len(t.leaves[i].wildcards) { 264 | break 265 | } 266 | 267 | for j := 0; j < len(t.leaves[i].wildcards); j++ { 268 | params[t.leaves[i].wildcards[j]] = results[j + 1] 269 | } 270 | return t.leaves[i].handle, true 271 | case _PATTERN_PATH_EXT: 272 | j := strings.LastIndex(url, ".") 273 | if j > -1 { 274 | params[":path"] = url[:j] 275 | params[":ext"] = url[j + 1:] 276 | } else { 277 | params[":path"] = url 278 | } 279 | return t.leaves[i].handle, true 280 | case _PATTERN_HOLDER: 281 | params[t.leaves[i].wildcards[0]] = url 282 | return t.leaves[i].handle, true 283 | case _PATTERN_MATCH_ALL: 284 | params["*"] = url 285 | params["*" + com.ToStr(globLevel)] = url 286 | return t.leaves[i].handle, true 287 | } 288 | } 289 | return nil, false 290 | } 291 | 292 | func (t *Tree) matchSubtree(globLevel int, segment, url string, params Params) (Handle, bool) { 293 | for i := 0; i < len(t.subtrees); i++ { 294 | switch t.subtrees[i].typ { 295 | case _PATTERN_STATIC: 296 | if t.subtrees[i].pattern == segment { 297 | if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok { 298 | return handle, true 299 | } 300 | } 301 | case _PATTERN_REGEXP: 302 | results := t.subtrees[i].reg.FindStringSubmatch(segment) 303 | if len(results) - 1 != len(t.subtrees[i].wildcards) { 304 | break 305 | } 306 | 307 | for j := 0; j < len(t.subtrees[i].wildcards); j++ { 308 | params[t.subtrees[i].wildcards[j]] = results[j + 1] 309 | } 310 | if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok { 311 | return handle, true 312 | } 313 | case _PATTERN_HOLDER: 314 | if handle, ok := t.subtrees[i].matchNextSegment(globLevel + 1, url, params); ok { 315 | params[t.subtrees[i].wildcards[0]] = segment 316 | return handle, true 317 | } 318 | case _PATTERN_MATCH_ALL: 319 | if handle, ok := t.subtrees[i].matchNextSegment(globLevel + 1, url, params); ok { 320 | params["*" + com.ToStr(globLevel)] = segment 321 | return handle, true 322 | } 323 | } 324 | } 325 | 326 | if len(t.leaves) > 0 { 327 | leaf := t.leaves[len(t.leaves) - 1] 328 | if leaf.typ == _PATTERN_PATH_EXT { 329 | url = segment + "/" + url 330 | j := strings.LastIndex(url, ".") 331 | if j > -1 { 332 | params[":path"] = url[:j] 333 | params[":ext"] = url[j + 1:] 334 | } else { 335 | params[":path"] = url 336 | } 337 | return leaf.handle, true 338 | } else if leaf.typ == _PATTERN_MATCH_ALL { 339 | params["*"] = segment + "/" + url 340 | params["*" + com.ToStr(globLevel)] = segment + "/" + url 341 | return leaf.handle, true 342 | } 343 | } 344 | return nil, false 345 | } 346 | 347 | func (t *Tree) matchNextSegment(globLevel int, url string, params Params) (Handle, bool) { 348 | i := strings.Index(url, "/") 349 | if i == -1 { 350 | return t.matchLeaf(globLevel, url, params) 351 | } 352 | return t.matchSubtree(globLevel, url[:i], url[i + 1:], params) 353 | } 354 | 355 | func (t *Tree) Match(url string) (Handle, Params, bool) { 356 | url = strings.TrimPrefix(url, "/") 357 | url = strings.TrimSuffix(url, "/") 358 | params := make(Params) 359 | handle, ok := t.matchNextSegment(0, url, params) 360 | return handle, params, ok 361 | } 362 | 363 | // MatchTest returns true if given URL is matched by given pattern. 364 | func MatchTest(pattern, url string) bool { 365 | t := NewTree() 366 | t.Add(pattern, nil) 367 | _, _, ok := t.Match(url) 368 | return ok 369 | } 370 | --------------------------------------------------------------------------------