├── .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 [](https://travis-ci.org/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 | - [](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, "headHello 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, "headjeremy
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 headjeremy
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 headjeremy
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 |
--------------------------------------------------------------------------------