├── logger_test.go ├── .gitignore ├── _fixture ├── favicon.ico ├── img │ └── baa.jpg ├── index1.html ├── index2.html └── cert │ ├── cert.pem │ └── key.pem ├── doc.go ├── go.mod ├── logger.go ├── .github └── workflows │ └── go.yml ├── .travis.yml ├── response_test.go ├── request.go ├── di.go ├── request_test.go ├── render.go ├── LICENSE ├── render_test.go ├── go.sum ├── di_test.go ├── static_test.go ├── router.go ├── static.go ├── response.go ├── baa_test.go ├── README_zh-CN.md ├── README.md ├── tree_test.go ├── baa.go ├── tree.go ├── context_test.go └── context.go /logger_test.go: -------------------------------------------------------------------------------- 1 | package baa 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | .idea 4 | .vscode 5 | .history 6 | -------------------------------------------------------------------------------- /_fixture/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-baa/baa/main/_fixture/favicon.ico -------------------------------------------------------------------------------- /_fixture/img/baa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-baa/baa/main/_fixture/img/baa.jpg -------------------------------------------------------------------------------- /_fixture/index1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |\n")
95 | var color, name string
96 | for _, v := range fl {
97 | name = v.Name()
98 | color = "#333333"
99 | if v.IsDir() {
100 | name += "/"
101 | color = "#3F89C8"
102 | }
103 | // name may contain '?' or '#', which must be escaped to remain
104 | // part of the URL path, and not indicate the start of a query
105 | // string or fragment.
106 | url := url.URL{Path: name}
107 | fmt.Fprintf(c.Resp, "%s\n", color, url.String(), template.HTMLEscapeString(name))
108 | }
109 | fmt.Fprintf(c.Resp, "\n")
110 | }
111 |
112 | func serveFile(file string, c *Context) error {
113 | f, err := os.Open(file)
114 | if err != nil {
115 | return err
116 | }
117 | defer f.Close()
118 | fs, err := f.Stat()
119 | if err != nil {
120 | return err
121 | }
122 | if fs.IsDir() {
123 | return fmt.Errorf("given path is dir, not file")
124 | }
125 | http.ServeContent(c.Resp, c.Req, f.Name(), fs.ModTime(), f)
126 | return nil
127 | }
128 |
--------------------------------------------------------------------------------
/response.go:
--------------------------------------------------------------------------------
1 | package baa
2 |
3 | import (
4 | "bufio"
5 | "errors"
6 | "io"
7 | "net"
8 | "net/http"
9 | )
10 |
11 | // Response implement ResponseWriter
12 | type Response struct {
13 | wroteHeader bool // reply header has been (logically) written
14 | written int64 // number of bytes written in body
15 | status int // status code passed to WriteHeader
16 | resp http.ResponseWriter
17 | writer io.Writer
18 | baa *Baa
19 | }
20 |
21 | // NewResponse ...
22 | func NewResponse(w http.ResponseWriter, b *Baa) *Response {
23 | r := new(Response)
24 | r.resp = w
25 | r.writer = w
26 | r.baa = b
27 | return r
28 | }
29 |
30 | // Header returns the header map that will be sent by
31 | // WriteHeader. Changing the header after a call to
32 | // WriteHeader (or Write) has no effect unless the modified
33 | // headers were declared as trailers by setting the
34 | // "Trailer" header before the call to WriteHeader (see example).
35 | // To suppress implicit response headers, set their value to nil.
36 | func (r *Response) Header() http.Header {
37 | return r.resp.Header()
38 | }
39 |
40 | // Write writes the data to the connection as part of an HTTP reply.
41 | // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)
42 | // before writing the data. If the Header does not contain a
43 | // Content-Type line, Write adds a Content-Type set to the result of passing
44 | // the initial 512 bytes of written data to DetectContentType.
45 | func (r *Response) Write(b []byte) (int, error) {
46 | if !r.wroteHeader {
47 | r.WriteHeader(http.StatusOK)
48 | }
49 | n, err := r.writer.Write(b)
50 | r.written += int64(n)
51 | return n, err
52 | }
53 |
54 | // WriteHeader sends an HTTP response header with status code.
55 | // If WriteHeader is not called explicitly, the first call to Write
56 | // will trigger an implicit WriteHeader(http.StatusOK).
57 | // Thus explicit calls to WriteHeader are mainly used to
58 | // send error codes.
59 | func (r *Response) WriteHeader(code int) {
60 | if r.wroteHeader {
61 | r.baa.Logger().Println("http: multiple response.WriteHeader calls")
62 | return
63 | }
64 | r.wroteHeader = true
65 | r.status = code
66 | r.resp.WriteHeader(code)
67 | }
68 |
69 | // Flush implements the http.Flusher interface to allow an HTTP handler to flush
70 | // buffered data to the client.
71 | // See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
72 | func (r *Response) Flush() {
73 | if v, ok := r.resp.(http.Flusher); ok {
74 | v.Flush()
75 | }
76 | }
77 |
78 | // Pusher is the interface implemented by ResponseWriters that support
79 | // HTTP/2 server push. For more background, see
80 | // https://tools.ietf.org/html/rfc7540#section-8.2.
81 | func (r *Response) Pusher() (http.Pusher, bool) {
82 | v, ok := r.resp.(http.Pusher)
83 | return v, ok
84 | }
85 |
86 | // Hijack implements the http.Hijacker interface to allow an HTTP handler to
87 | // take over the connection.
88 | // See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
89 | func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
90 | if v, ok := r.resp.(http.Hijacker); ok {
91 | return v.Hijack()
92 | }
93 | return nil, nil, errors.New("http.response denot implements the http.Hijacker")
94 | }
95 |
96 | // reset reuse response
97 | func (r *Response) reset(w http.ResponseWriter) {
98 | r.resp = w
99 | r.writer = w
100 | r.wroteHeader = false
101 | r.written = 0
102 | r.status = http.StatusOK
103 | }
104 |
105 | // Status returns status code
106 | func (r *Response) Status() int {
107 | return r.status
108 | }
109 |
110 | // Size returns body size
111 | func (r *Response) Size() int64 {
112 | return r.written
113 | }
114 |
115 | // Wrote returns if writes something
116 | func (r *Response) Wrote() bool {
117 | return r.wroteHeader
118 | }
119 |
120 | // GetWriter returns response io writer
121 | func (r *Response) GetWriter() io.Writer {
122 | return r.writer
123 | }
124 |
125 | // SetWriter set response io writer
126 | func (r *Response) SetWriter(w io.Writer) {
127 | r.writer = w
128 | }
129 |
--------------------------------------------------------------------------------
/baa_test.go:
--------------------------------------------------------------------------------
1 | package baa
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 |
10 | . "github.com/smartystreets/goconvey/convey"
11 | )
12 |
13 | var b = New()
14 | var r = b.Router()
15 | var c = NewContext(nil, nil, b)
16 | var f = func(c *Context) {}
17 |
18 | type newHandler struct{}
19 |
20 | func (t *newHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
21 | w.Header().Set("m3 http.Handler.ServeHTTP", "true")
22 | }
23 |
24 | func TestNew(t *testing.T) {
25 | Convey("new baa app", t, func() {
26 | b2 := New()
27 | So(b2, ShouldNotBeNil)
28 | })
29 | Convey("new instance app", t, func() {
30 | b2 := Default()
31 | b3 := Default()
32 | b4 := Instance("new")
33 | So(b2, ShouldEqual, b3)
34 | So(b2, ShouldNotEqual, b4)
35 | })
36 | }
37 |
38 | func TestRun(t *testing.T) {
39 | Convey("run baa app", t, func() {
40 | Convey("run baa app normal", func() {
41 | b3 := New()
42 | go b3.Run(":8011")
43 | go b3.RunServer(b3.Server(":8012"))
44 | // go run $GOROOT/src/crypto/tls/generate_cert.go --host localhost
45 | go b3.RunTLS(":8013", "_fixture/cert/cert.pem", "_fixture/cert/key.pem")
46 | go b3.RunTLSServer(b3.Server(":8014"), "_fixture/cert/cert.pem", "_fixture/cert/key.pem")
47 | })
48 | Convey("run baa app error", func() {
49 | b3 := New()
50 | defer func() {
51 | e := recover()
52 | So(e, ShouldNotBeNil)
53 | }()
54 | b3.run(b3.Server(":8015"), "")
55 | })
56 | })
57 | }
58 |
59 | func TestServeHTTP(t *testing.T) {
60 | Convey("ServeHTTP", t, func() {
61 | Convey("normal serve", func() {
62 | b.Get("/ok", func(c *Context) {
63 | c.String(200, "ok")
64 | })
65 | w := request("GET", "/ok")
66 | So(w.Code, ShouldEqual, http.StatusOK)
67 | })
68 | Convey("not found serve", func() {
69 | b.Get("/notfound", func(c *Context) {
70 | c.String(200, "ok")
71 | })
72 | w := request("GET", "/notfound2")
73 | So(w.Code, ShouldEqual, http.StatusNotFound)
74 | })
75 | Convey("error serve", func() {
76 | b.SetError(func(err error, c *Context) {
77 | c.Resp.WriteHeader(500)
78 | c.Resp.Write([]byte(err.Error()))
79 | })
80 | b.Get("/error", func(c *Context) {
81 | c.Error(fmt.Errorf("BOMB"))
82 | })
83 | w := request("GET", "/error")
84 | So(w.Code, ShouldEqual, http.StatusInternalServerError)
85 | })
86 | Convey("http error serve", func() {
87 | b2 := New()
88 | b2.errorHandler = nil
89 | b2.Get("/error", func(c *Context) {
90 | c.Error(fmt.Errorf("BOMB"))
91 | })
92 | req, _ := http.NewRequest("GET", "/error", nil)
93 | w := httptest.NewRecorder()
94 | b2.ServeHTTP(w, req)
95 | So(w.Code, ShouldEqual, http.StatusInternalServerError)
96 | })
97 | Convey("http error serve no debug", func() {
98 | b2 := New()
99 | b2.errorHandler = nil
100 | b2.SetDebug(false)
101 | b2.Get("/error", func(c *Context) {
102 | b2.Debug()
103 | c.Error(fmt.Errorf("BOMB"))
104 | })
105 | req, _ := http.NewRequest("GET", "/error", nil)
106 | w := httptest.NewRecorder()
107 | b2.ServeHTTP(w, req)
108 | So(w.Code, ShouldEqual, http.StatusInternalServerError)
109 | })
110 | Convey("Middleware", func() {
111 | b2 := New()
112 | b2.Use(func(c *Context) {
113 | c.Resp.Header().Set("m1", "true")
114 | c.Set("Middleware", "ok")
115 | c.Next()
116 | So(c.Get("Middleware").(string), ShouldEqual, "ok")
117 | })
118 | b2.Use(HandlerFunc(func(c *Context) {
119 | c.Resp.Header().Set("m2", "true")
120 | c.Next()
121 | }))
122 | b2.Use(new(newHandler))
123 | b2.Use(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124 | w.Header().Set("m4", "true")
125 | }))
126 | b2.Use(func(w http.ResponseWriter, r *http.Request) {
127 | w.Header().Set("m5", "true")
128 | })
129 |
130 | b2.Get("/ok", func(c *Context) {
131 | c.String(200, "ok")
132 | })
133 | req, _ := http.NewRequest("GET", "/ok", nil)
134 | w := httptest.NewRecorder()
135 | b2.ServeHTTP(w, req)
136 | So(w.Code, ShouldEqual, http.StatusOK)
137 | })
138 | Convey("Break middleware chain", func() {
139 | b2 := New()
140 | b2.Use(func(c *Context) {
141 | c.String(200, "ok1")
142 | c.Break()
143 | c.Next()
144 | })
145 | b2.Use(func(c *Context) {
146 | c.String(200, "ok2")
147 | c.Break()
148 | c.Next()
149 | })
150 | b2.Get("/ok", func(c *Context) {
151 | c.String(200, "ok")
152 | })
153 | req, _ := http.NewRequest("GET", "/ok", nil)
154 | w := httptest.NewRecorder()
155 | b2.ServeHTTP(w, req)
156 | So(w.Code, ShouldEqual, http.StatusOK)
157 | body, err := ioutil.ReadAll(w.Body)
158 | So(err, ShouldBeNil)
159 | So(string(body), ShouldEqual, "ok1")
160 | })
161 | Convey("Unknow Middleware", func() {
162 | b2 := New()
163 | defer func() {
164 | e := recover()
165 | So(e, ShouldNotBeNil)
166 | }()
167 | b2.Use(func() {})
168 | })
169 | })
170 | }
171 |
172 | func request(method, uri string) *httptest.ResponseRecorder {
173 | req, _ := http.NewRequest(method, uri, nil)
174 | w := httptest.NewRecorder()
175 | b.ServeHTTP(w, req)
176 | return w
177 | }
178 |
--------------------------------------------------------------------------------
/README_zh-CN.md:
--------------------------------------------------------------------------------
1 | # [Baa](https://github.com/go-baa/baa) [](http://godoc.org/github.com/go-baa/baa) [](https://raw.githubusercontent.com/go-baa/baa/master/LICENSE) [](https://travis-ci.com/github/go-baa/baa) [](https://coveralls.io/r/go-baa/baa)
2 |
3 | 一个简单高效的Go web开发框架。主要有路由、中间件,依赖注入和HTTP上下文构成。
4 |
5 | Baa 不使用 ``反射``和``正则``,没有魔法的实现。
6 |
7 | ## 文档
8 |
9 | * [简体中文](https://github.com/go-baa/doc/tree/master/zh-CN)
10 | * [English](https://github.com/go-baa/doc/tree/master/en-US)
11 | * [godoc](https://godoc.org/github.com/go-baa/baa)
12 |
13 | ## 快速上手
14 |
15 | 安装:
16 |
17 | ```
18 | go get -u github.com/go-baa/baa
19 | ```
20 |
21 | 示例:
22 |
23 | ```
24 | // baa.go
25 | package main
26 |
27 | import (
28 | "github.com/go-baa/baa"
29 | )
30 |
31 | func main() {
32 | app := baa.New()
33 | app.Get("/", func(c *baa.Context) {
34 | c.String(200, "Hello, 世界")
35 | })
36 | app.Run(":1323")
37 | }
38 | ```
39 |
40 | 运行:
41 |
42 | ```
43 | go run baa.go
44 | ```
45 |
46 | 浏览:
47 |
48 | ```
49 | http://127.0.0.1:1323/
50 | ```
51 |
52 | ## 特性
53 |
54 | * 支持静态路由、参数路由、组路由(前缀路由/命名空间)和路由命名
55 | * 路由支持链式操作
56 | * 路由支持文件/目录服务
57 | * 中间件支持链式操作
58 | * 支持依赖注入*
59 | * 支持JSON/JSONP/XML/HTML格式输出
60 | * 统一的HTTP错误处理
61 | * 统一的日志处理
62 | * 支持任意更换模板引擎(实现baa.Renderer接口即可)
63 |
64 | ## 示例
65 |
66 | https://github.com/go-baa/example
67 |
68 | * [blog](https://github.com/go-baa/example/tree/master/blog)
69 | * [api](https://github.com/go-baa/example/tree/master/api)
70 | * [websocket](https://github.com/go-baa/example/tree/master/websocket)
71 |
72 | ## 中间件
73 |
74 | * [gzip](https://github.com/baa-middleware/gzip)
75 | * [accesslog](https://github.com/baa-middleware/accesslog)
76 | * [recovery](https://github.com/baa-middleware/recovery)
77 | * [session](https://github.com/baa-middleware/session)
78 | * [static](https://github.com/baa-middleware/static)
79 | * [requestcache](https://github.com/baa-middleware/requestcache)
80 | * [nocache](https://github.com/baa-middleware/nocache)
81 | * [jwt](https://github.com/baa-middleware/jwt)
82 | * [cors](https://github.com/baa-middleware/cors)
83 | * [authz](https://github.com/baa-middleware/authz)
84 |
85 | ## 组件
86 |
87 | * [cache](https://github.com/go-baa/cache)
88 | * [render](https://github.com/go-baa/render)
89 | * [pongo2](https://github.com/go-baa/pongo2)
90 | * [router](https://github.com/go-baa/router)
91 | * [pool](https://github.com/go-baa/pool)
92 | * [bat](https://github.com/go-baa/bat)
93 | * [log](https://github.com/go-baa/log)
94 | * [setting](https://github.com/go-baa/setting)
95 |
96 | ## 性能测试
97 |
98 | ### 路由测试
99 |
100 | 使用 [go-http-routing-benchmark] (https://github.com/safeie/go-http-routing-benchmark) 测试, 2016-02-27 更新.
101 |
102 | ##### [GitHub API](http://developer.github.com/v3)
103 |
104 | > Baa的路由性能非常接近 Echo.
105 |
106 | ```
107 | BenchmarkBaa_GithubAll 30000 50984 ns/op 0 B/op 0 allocs/op
108 | BenchmarkBeego_GithubAll 3000 478556 ns/op 6496 B/op 203 allocs/op
109 | BenchmarkEcho_GithubAll 30000 47121 ns/op 0 B/op 0 allocs/op
110 | BenchmarkGin_GithubAll 30000 41004 ns/op 0 B/op 0 allocs/op
111 | BenchmarkGocraftWeb_GithubAll 3000 450709 ns/op 131656 B/op 1686 allocs/op
112 | BenchmarkGorillaMux_GithubAll 200 6591485 ns/op 154880 B/op 2469 allocs/op
113 | BenchmarkMacaron_GithubAll 2000 679559 ns/op 201140 B/op 1803 allocs/op
114 | BenchmarkMartini_GithubAll 300 5680389 ns/op 228216 B/op 2483 allocs/op
115 | BenchmarkRevel_GithubAll 1000 1413894 ns/op 337424 B/op 5512 allocs/op
116 | ```
117 |
118 | ### HTTP测试
119 |
120 | #### 代码
121 |
122 | Baa:
123 |
124 | ```
125 | package main
126 |
127 | import (
128 | "github.com/go-baa/baa"
129 | )
130 |
131 | func main() {
132 | app := baa.New()
133 | app.Get("/", func(c *baa.Context) {
134 | c.String(200, "Hello, 世界")
135 | })
136 | app.Run(":1323")
137 | }
138 | ```
139 |
140 | #### 测试结果:
141 |
142 | ```
143 | $ wrk -t 10 -c 100 -d 30 http://127.0.0.1:1323/
144 | Running 30s test @ http://127.0.0.1:1323/
145 | 10 threads and 100 connections
146 | Thread Stats Avg Stdev Max +/- Stdev
147 | Latency 1.64ms 299.23us 8.25ms 66.84%
148 | Req/Sec 6.11k 579.08 8.72k 68.74%
149 | 1827365 requests in 30.10s, 228.30MB read
150 | Requests/sec: 60704.90
151 | Transfer/sec: 7.58MB
152 | ```
153 |
154 | ## 案例
155 |
156 | 目前使用在 健康一线 的私有项目中。
157 |
158 | ## 贡献
159 |
160 | Baa的灵感来自 [beego](https://github.com/astaxie/beego) [echo](https://github.com/labstack/echo) [macaron](https://github.com/go-macaron/macaron)
161 |
162 | - [safeie](https://github.com/safeie)、[micate](https://github.com/micate) - Author
163 | - [betty](https://github.com/betty3039) - Language Consultant
164 | - [Contributors](https://github.com/go-baa/baa/graphs/contributors)
165 |
166 | ## License
167 |
168 | This project is under the MIT License (MIT) See the [LICENSE](https://raw.githubusercontent.com/go-baa/baa/master/LICENSE) file for the full license text.
169 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Baa](https://github.com/go-baa/baa) [](http://godoc.org/github.com/go-baa/baa) [](https://raw.githubusercontent.com/go-baa/baa/master/LICENSE) [](https://travis-ci.com/github/go-baa/baa) [](https://coveralls.io/r/go-baa/baa)
2 |
3 | an express Go web framework with routing, middleware, dependency injection, http context.
4 |
5 | Baa is ``no reflect``, ``no regexp``.
6 |
7 | ## document
8 |
9 | * [简体中文](https://github.com/go-baa/doc/tree/master/zh-CN)
10 | * [English](https://github.com/go-baa/doc/tree/master/en-US)
11 | * [godoc](https://godoc.org/github.com/go-baa/baa)
12 |
13 | ## Getting Started
14 |
15 | Install:
16 |
17 | ```
18 | go get -u github.com/go-baa/baa
19 | ```
20 |
21 | Example:
22 |
23 | ```
24 | // baa.go
25 | package main
26 |
27 | import (
28 | "github.com/go-baa/baa"
29 | )
30 |
31 | func main() {
32 | app := baa.New()
33 | app.Get("/", func(c *baa.Context) {
34 | c.String(200, "Hello, 世界")
35 | })
36 | app.Run(":1323")
37 | }
38 | ```
39 |
40 | Run:
41 |
42 | ```
43 | go run baa.go
44 | ```
45 |
46 | Explore:
47 |
48 | ```
49 | http://127.0.0.1:1323/
50 | ```
51 |
52 | ## Features
53 |
54 | * route support static, param, group
55 | * route support handler chain
56 | * route support static file serve
57 | * middleware supoort handle chain
58 | * dependency injection support*
59 | * context support JSON/JSONP/XML/HTML response
60 | * centralized HTTP error handling
61 | * centralized log handling
62 | * whichever template engine support(emplement baa.Renderer)
63 |
64 | ## Examples
65 |
66 | https://github.com/go-baa/example
67 |
68 | * [blog](https://github.com/go-baa/example/tree/master/blog)
69 | * [api](https://github.com/go-baa/example/tree/master/api)
70 | * [websocket](https://github.com/go-baa/example/tree/master/websocket)
71 |
72 | ## Middlewares
73 |
74 | * [gzip](https://github.com/baa-middleware/gzip)
75 | * [accesslog](https://github.com/baa-middleware/accesslog)
76 | * [recovery](https://github.com/baa-middleware/recovery)
77 | * [session](https://github.com/baa-middleware/session)
78 | * [static](https://github.com/baa-middleware/static)
79 | * [requestcache](https://github.com/baa-middleware/requestcache)
80 | * [nocache](https://github.com/baa-middleware/nocache)
81 | * [jwt](https://github.com/baa-middleware/jwt)
82 | * [cors](https://github.com/baa-middleware/cors)
83 | * [authz](https://github.com/baa-middleware/authz)
84 |
85 | ## Components
86 |
87 | * [cache](https://github.com/go-baa/cache)
88 | * [render](https://github.com/go-baa/render)
89 | * [pongo2](https://github.com/go-baa/pongo2)
90 | * [router](https://github.com/go-baa/router)
91 | * [pool](https://github.com/go-baa/pool)
92 | * [bat](https://github.com/go-baa/bat)
93 | * [log](https://github.com/go-baa/log)
94 | * [setting](https://github.com/go-baa/setting)
95 |
96 | ## Performance
97 |
98 | ### Route Test
99 |
100 | Based on [go-http-routing-benchmark] (https://github.com/safeie/go-http-routing-benchmark), Feb 27, 2016.
101 |
102 | ##### [GitHub API](http://developer.github.com/v3)
103 |
104 | > Baa route test is very close to Echo.
105 |
106 | ```
107 | BenchmarkBaa_GithubAll 30000 50984 ns/op 0 B/op 0 allocs/op
108 | BenchmarkBeego_GithubAll 3000 478556 ns/op 6496 B/op 203 allocs/op
109 | BenchmarkEcho_GithubAll 30000 47121 ns/op 0 B/op 0 allocs/op
110 | BenchmarkGin_GithubAll 30000 41004 ns/op 0 B/op 0 allocs/op
111 | BenchmarkGocraftWeb_GithubAll 3000 450709 ns/op 131656 B/op 1686 allocs/op
112 | BenchmarkGorillaMux_GithubAll 200 6591485 ns/op 154880 B/op 2469 allocs/op
113 | BenchmarkMacaron_GithubAll 2000 679559 ns/op 201140 B/op 1803 allocs/op
114 | BenchmarkMartini_GithubAll 300 5680389 ns/op 228216 B/op 2483 allocs/op
115 | BenchmarkRevel_GithubAll 1000 1413894 ns/op 337424 B/op 5512 allocs/op
116 | ```
117 |
118 | ### HTTP Test
119 |
120 | #### Code
121 |
122 | Baa:
123 |
124 | ```
125 | package main
126 |
127 | import (
128 | "github.com/go-baa/baa"
129 | )
130 |
131 | func main() {
132 | app := baa.New()
133 | app.Get("/", func(c *baa.Context) {
134 | c.String(200, "Hello, 世界")
135 | })
136 | app.Run(":1323")
137 | }
138 | ```
139 |
140 | #### Result:
141 |
142 | ```
143 | $ wrk -t 10 -c 100 -d 30 http://127.0.0.1:1323/
144 | Running 30s test @ http://127.0.0.1:1323/
145 | 10 threads and 100 connections
146 | Thread Stats Avg Stdev Max +/- Stdev
147 | Latency 1.64ms 299.23us 8.25ms 66.84%
148 | Req/Sec 6.11k 579.08 8.72k 68.74%
149 | 1827365 requests in 30.10s, 228.30MB read
150 | Requests/sec: 60704.90
151 | Transfer/sec: 7.58MB
152 | ```
153 |
154 | ## Use Cases
155 |
156 | vodjk private projects.
157 |
158 | ## Credits
159 |
160 | Get inspirations from [beego](https://github.com/astaxie/beego) [echo](https://github.com/labstack/echo) [macaron](https://github.com/go-macaron/macaron)
161 |
162 | - [safeie](https://github.com/safeie)、[micate](https://github.com/micate) - Author
163 | - [betty](https://github.com/betty3039) - Language Consultant
164 | - [Contributors](https://github.com/go-baa/baa/graphs/contributors)
165 |
166 | ## License
167 |
168 | This project is under the MIT License (MIT) See the [LICENSE](https://raw.githubusercontent.com/go-baa/baa/master/LICENSE) file for the full license text.
169 |
--------------------------------------------------------------------------------
/tree_test.go:
--------------------------------------------------------------------------------
1 | package baa
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | . "github.com/smartystreets/goconvey/convey"
10 | )
11 |
12 | // print the route map
13 | func (t *Tree) print(prefix string, root *leaf) {
14 | if root == nil {
15 | for m := range t.nodes {
16 | fmt.Println(m)
17 | t.print("", t.nodes[m])
18 | }
19 | return
20 | }
21 | prefix = fmt.Sprintf("%s -> %s", prefix, root.pattern)
22 | fmt.Println(prefix)
23 | root.children = append(root.children, root.paramChild)
24 | root.children = append(root.children, root.wideChild)
25 | for i := range root.children {
26 | if root.children[i] != nil {
27 | t.print(prefix, root.children[i])
28 | }
29 | }
30 | }
31 |
32 | func TestTreeRouteAdd1(t *testing.T) {
33 | Convey("add static route", t, func() {
34 | r.Add("GET", "/", []HandlerFunc{f})
35 | r.Add("GET", "/bcd", []HandlerFunc{f})
36 | r.Add("GET", "/abcd", []HandlerFunc{f})
37 | r.Add("GET", "/abc", []HandlerFunc{f})
38 | r.Add("GET", "/abd", []HandlerFunc{f})
39 | r.Add("GET", "/abcdef", []HandlerFunc{f})
40 | r.Add("GET", "/bcdefg", []HandlerFunc{f})
41 | r.Add("GET", "/abc/123", []HandlerFunc{f})
42 | r.Add("GET", "/abc/234", []HandlerFunc{f})
43 | r.Add("GET", "/abc/125", []HandlerFunc{f})
44 | r.Add("GET", "/abc/235", []HandlerFunc{f})
45 | r.Add("GET", "/cbd/123", []HandlerFunc{f})
46 | r.Add("GET", "/cbd/234", []HandlerFunc{f})
47 | r.Add("GET", "/cbd/345", []HandlerFunc{f})
48 | r.Add("GET", "/cbd/456", []HandlerFunc{f})
49 | r.Add("GET", "/cbd/346", []HandlerFunc{f})
50 | })
51 | }
52 |
53 | func TestTreeRouteAdd2(t *testing.T) {
54 | Convey("add param route", t, func() {
55 | r.Add("GET", "/a/:id/id", []HandlerFunc{f})
56 | r.Add("GET", "/a/:id/name", []HandlerFunc{f})
57 | r.Add("GET", "/a", []HandlerFunc{f})
58 | r.Add("GET", "/a/:id/", []HandlerFunc{f})
59 | r.Add("GET", "/a/", []HandlerFunc{f})
60 | r.Add("GET", "/a/*/xxx", []HandlerFunc{f})
61 | r.Add("GET", "/p/:project/file/:fileName", []HandlerFunc{f})
62 | r.Add("GET", "/cbd/:id", []HandlerFunc{f})
63 |
64 | defer func() {
65 | e := recover()
66 | So(e, ShouldNotBeNil)
67 | }()
68 | r.Add("GET", "/p/:/a", []HandlerFunc{f})
69 | })
70 | }
71 |
72 | func TestTreeRouteAdd3(t *testing.T) {
73 | Convey("add param route with two different param", t, func() {
74 | defer func() {
75 | e := recover()
76 | So(e, ShouldNotBeNil)
77 | }()
78 | r.Add("GET", "/a/:id", []HandlerFunc{f})
79 | r.Add("GET", "/a/:name", []HandlerFunc{f})
80 | })
81 | }
82 |
83 | func TestTreeRouteAdd4(t *testing.T) {
84 | Convey("add route by group", t, func() {
85 | b.Group("/user", func() {
86 | b.Get("/info", f)
87 | b.Get("/info2", f)
88 | b.Group("/group", func() {
89 | b.Get("/info", f)
90 | b.Get("/info2", f)
91 | })
92 | })
93 | b.Group("/user", func() {
94 | b.Get("/", f)
95 | b.Get("/pass", f)
96 | b.Get("/pass2", f)
97 | }, f)
98 | })
99 | }
100 |
101 | func TestTreeRouteAdd5(t *testing.T) {
102 | Convey("add route then set name, URLFor", t, func() {
103 | b.Get("/article/:id/show", f).Name("articleShow")
104 | b.Get("/article/:id/detail", f).Name("")
105 | url := b.URLFor("articleShow", 123)
106 | So(url, ShouldEqual, "/article/123/show")
107 | url = b.URLFor("", nil)
108 | So(url, ShouldEqual, "")
109 | url = b.URLFor("not exits", "no")
110 | So(url, ShouldEqual, "")
111 | ru, name := r.Match("GET", "/article/123/show", c)
112 | So(ru, ShouldNotBeNil)
113 | So(name, ShouldEqual, "articleShow")
114 | })
115 | }
116 |
117 | func TestTreeRouteAdd6(t *testing.T) {
118 | Convey("add route with not support method", t, func() {
119 | defer func() {
120 | e := recover()
121 | So(e, ShouldNotBeNil)
122 | }()
123 | r.Add("TRACE", "/", []HandlerFunc{f})
124 | })
125 | }
126 |
127 | func TestTreeRouteAdd7(t *testing.T) {
128 | Convey("add route with empty pattern", t, func() {
129 | defer func() {
130 | e := recover()
131 | So(e, ShouldNotBeNil)
132 | }()
133 | r.Add("GET", "", []HandlerFunc{f})
134 | })
135 | }
136 |
137 | func TestTreeRouteAdd8(t *testing.T) {
138 | Convey("add route with pattern that not begin with /", t, func() {
139 | defer func() {
140 | e := recover()
141 | So(e, ShouldNotBeNil)
142 | }()
143 | r.Add("GET", "abc", []HandlerFunc{f})
144 | })
145 | }
146 |
147 | func TestTreeRouteAdd9(t *testing.T) {
148 | Convey("other route method", t, func() {
149 | b2 := New()
150 | Convey("set auto head route", func() {
151 | b2.SetAutoHead(true)
152 | b2.Get("/head", func(c *Context) {
153 | So(c.Req.Method, ShouldEqual, "HEAD")
154 | })
155 | req, _ := http.NewRequest("HEAD", "/head", nil)
156 | w := httptest.NewRecorder()
157 | b2.ServeHTTP(w, req)
158 | So(w.Code, ShouldEqual, http.StatusOK)
159 | })
160 | Convey("set auto training slash", func() {
161 | b2.SetAutoTrailingSlash(true)
162 | b2.Get("/slash", func(c *Context) {})
163 | b2.Group("/slash2", func() {
164 | b2.Get("/", func(c *Context) {})
165 | b2.Get("/exist", func(c *Context) {})
166 | })
167 | req, _ := http.NewRequest("GET", "/slash", nil)
168 | w := httptest.NewRecorder()
169 | b2.ServeHTTP(w, req)
170 | So(w.Code, ShouldEqual, http.StatusOK)
171 | req, _ = http.NewRequest("GET", "/slash/", nil)
172 | w = httptest.NewRecorder()
173 | b2.ServeHTTP(w, req)
174 | So(w.Code, ShouldEqual, http.StatusOK)
175 | req, _ = http.NewRequest("GET", "/slash2", nil)
176 | w = httptest.NewRecorder()
177 | b2.ServeHTTP(w, req)
178 | So(w.Code, ShouldEqual, http.StatusOK)
179 | req, _ = http.NewRequest("GET", "/slash2/", nil)
180 | w = httptest.NewRecorder()
181 | b2.ServeHTTP(w, req)
182 | So(w.Code, ShouldEqual, http.StatusOK)
183 | req, _ = http.NewRequest("GET", "/slash2/exist/", nil)
184 | w = httptest.NewRecorder()
185 | b2.ServeHTTP(w, req)
186 | So(w.Code, ShouldEqual, http.StatusOK)
187 | })
188 | Convey("set multi method", func() {
189 | b2.Route("/mul1", "*", func(c *Context) {
190 | c.String(200, "mul")
191 | })
192 | b2.Route("/mul2", "GET,HEAD,POST", func(c *Context) {
193 | c.String(200, "mul")
194 | })
195 | req, _ := http.NewRequest("HEAD", "/mul2", nil)
196 | w := httptest.NewRecorder()
197 | b2.ServeHTTP(w, req)
198 | So(w.Code, ShouldEqual, http.StatusOK)
199 |
200 | req, _ = http.NewRequest("GET", "/mul2", nil)
201 | w = httptest.NewRecorder()
202 | b2.ServeHTTP(w, req)
203 | So(w.Code, ShouldEqual, http.StatusOK)
204 | req, _ = http.NewRequest("POST", "/mul2", nil)
205 | w = httptest.NewRecorder()
206 | b2.ServeHTTP(w, req)
207 | So(w.Code, ShouldEqual, http.StatusOK)
208 | })
209 | Convey("methods", func() {
210 | b2.Get("/methods", f)
211 | b2.Patch("/methods", f)
212 | b2.Post("/methods", f)
213 | b2.Put("/methods", f)
214 | b2.Delete("/methods", f)
215 | b2.Options("/methods", f)
216 | b2.Head("/methods", f)
217 | b2.Any("/any", f)
218 | b2.SetNotFound(func(c *Context) {
219 | c.String(404, "baa not found")
220 | })
221 | })
222 | })
223 | }
224 |
225 | func TestTreeRouteMatch1(t *testing.T) {
226 | Convey("match route", t, func() {
227 |
228 | ru, _ := r.Match("GET", "/", c)
229 | So(ru, ShouldNotBeNil)
230 |
231 | ru, _ = r.Match("GET", "/abc/1234", c)
232 | So(ru, ShouldBeNil)
233 |
234 | ru, _ = r.Match("GET", "xxx", c)
235 | So(ru, ShouldBeNil)
236 |
237 | ru, _ = r.Match("GET", "/a/123/id", c)
238 | So(ru, ShouldNotBeNil)
239 |
240 | ru, _ = r.Match("GET", "/p/yst/file/a.jpg", c)
241 | So(ru, ShouldNotBeNil)
242 |
243 | ru, _ = r.Match("GET", "/user/info", c)
244 | So(ru, ShouldNotBeNil)
245 |
246 | ru, _ = r.Match("GET", "/user/pass", c)
247 | So(ru, ShouldNotBeNil)
248 |
249 | ru, _ = r.Match("GET", "/user/pass32", c)
250 | So(ru, ShouldBeNil)
251 |
252 | ru, _ = r.Match("GET", "/user/xxx", c)
253 | So(ru, ShouldBeNil)
254 |
255 | ru, _ = r.Match("GET", "/xxxx", c)
256 | So(ru, ShouldBeNil)
257 |
258 | b.Get("/notifications/threads/:id", f)
259 | b.Get("/notifications/threads/:id/subscription", f)
260 | b.Get("/notifications/threads/:id/subc", f)
261 | b.Put("/notifications/threads/:id/subscription", f)
262 | b.Delete("/notifications/threads/:id/subscription", f)
263 | ru, _ = r.Match("GET", "/notifications/threads/:id", c)
264 | So(ru, ShouldNotBeNil)
265 | ru, _ = r.Match("GET", "/notifications/threads/:id/sub", c)
266 | So(ru, ShouldBeNil)
267 | })
268 | }
269 |
270 | func TestTreeRouteMatch2(t *testing.T) {
271 | Convey("match route with params", t, func() {
272 | b.Get("/withparams/:id", f)
273 | ru, _ := r.Match("GET", "/withparams/:id", c)
274 | So(ru, ShouldNotBeNil)
275 | ru, _ = r.Match("GET", "/withparams", c)
276 | So(ru, ShouldBeNil)
277 | ru, _ = r.Match("GET", "/withparams/", c)
278 | So(ru, ShouldBeNil)
279 | })
280 | }
281 |
282 | func _TestTreeRoutePrint1(t *testing.T) {
283 | Convey("print route table", t, func() {
284 | r.(*Tree).print("", nil)
285 | })
286 | }
287 |
288 | func _TestTreeRoutePrint2(t *testing.T) {
289 | Convey("print routes", t, func() {
290 | fmt.Println("")
291 | for method, routes := range r.Routes() {
292 | fmt.Println("Method: ", method)
293 | for i, route := range routes {
294 | fmt.Printf(" %3d %s\n", i, route)
295 | }
296 | }
297 | })
298 | }
299 |
300 | func _TestTreeRoutePrint3(t *testing.T) {
301 | Convey("print named routes", t, func() {
302 | fmt.Println("")
303 | for name, route := range r.NamedRoutes() {
304 | fmt.Printf("%20s \t %s\n", name, route)
305 | }
306 | })
307 | }
308 |
--------------------------------------------------------------------------------
/baa.go:
--------------------------------------------------------------------------------
1 | package baa
2 |
3 | import (
4 | "errors"
5 | "log"
6 | "net/http"
7 | "os"
8 | "strings"
9 | "sync"
10 |
11 | "github.com/gorilla/websocket"
12 | )
13 |
14 | const (
15 | // DEV mode
16 | DEV = "development"
17 | // PROD mode
18 | PROD = "production"
19 | // TEST mode
20 | TEST = "test"
21 | )
22 |
23 | // Env default application runtime environment
24 | var Env string
25 |
26 | // Baa provlider an application
27 | type Baa struct {
28 | debug bool
29 | name string
30 | di DIer
31 | router Router
32 | pool sync.Pool
33 | errorHandler ErrorHandleFunc
34 | notFoundHandler HandlerFunc
35 | middleware []HandlerFunc
36 | }
37 |
38 | // Middleware middleware handler
39 | type Middleware interface{}
40 |
41 | // HandlerFunc context handler func
42 | type HandlerFunc func(*Context)
43 |
44 | // ErrorHandleFunc HTTP error handleFunc
45 | type ErrorHandleFunc func(error, *Context)
46 |
47 | // appInstances storage application instances
48 | var appInstances map[string]*Baa
49 |
50 | // defaultAppName default application name
51 | const defaultAppName = "_default_"
52 |
53 | // New create a baa application without any config.
54 | func New() *Baa {
55 | b := new(Baa)
56 | b.middleware = make([]HandlerFunc, 0)
57 | b.pool = sync.Pool{
58 | New: func() interface{} {
59 | return NewContext(nil, nil, b)
60 | },
61 | }
62 | if Env != PROD {
63 | b.debug = true
64 | }
65 | b.SetDIer(NewDI())
66 | b.SetDI("router", NewTree(b))
67 | b.SetDI("logger", log.New(os.Stderr, "[Baa] ", log.LstdFlags))
68 | b.SetDI("render", newRender())
69 | b.SetNotFound(b.DefaultNotFoundHandler)
70 | return b
71 | }
72 |
73 | // Instance register or returns named application
74 | func Instance(name string) *Baa {
75 | if name == "" {
76 | name = defaultAppName
77 | }
78 | if appInstances[name] == nil {
79 | appInstances[name] = New()
80 | appInstances[name].name = name
81 | }
82 | return appInstances[name]
83 | }
84 |
85 | // Default initial a default app then returns
86 | func Default() *Baa {
87 | return Instance(defaultAppName)
88 | }
89 |
90 | // Server returns the internal *http.Server.
91 | func (b *Baa) Server(addr string) *http.Server {
92 | s := &http.Server{Addr: addr}
93 | return s
94 | }
95 |
96 | // Run runs a server.
97 | func (b *Baa) Run(addr string) {
98 | b.run(b.Server(addr))
99 | }
100 |
101 | // RunTLS runs a server with TLS configuration.
102 | func (b *Baa) RunTLS(addr, certfile, keyfile string) {
103 | b.run(b.Server(addr), certfile, keyfile)
104 | }
105 |
106 | // RunServer runs a custom server.
107 | func (b *Baa) RunServer(s *http.Server) {
108 | b.run(s)
109 | }
110 |
111 | // RunTLSServer runs a custom server with TLS configuration.
112 | func (b *Baa) RunTLSServer(s *http.Server, crtFile, keyFile string) {
113 | b.run(s, crtFile, keyFile)
114 | }
115 |
116 | func (b *Baa) run(s *http.Server, files ...string) {
117 | s.Handler = b
118 | b.Logger().Printf("Run mode: %s", Env)
119 | if len(files) == 0 {
120 | b.Logger().Printf("Listen %s", s.Addr)
121 | b.Logger().Fatal(s.ListenAndServe())
122 | } else if len(files) == 2 {
123 | b.Logger().Printf("Listen %s with TLS", s.Addr)
124 | b.Logger().Fatal(s.ListenAndServeTLS(files[0], files[1]))
125 | } else {
126 | panic("invalid TLS configuration")
127 | }
128 | }
129 |
130 | func (b *Baa) ServeHTTP(w http.ResponseWriter, r *http.Request) {
131 | c := b.pool.Get().(*Context)
132 | c.Reset(w, r)
133 |
134 | // build handler chain
135 | path := strings.Replace(r.URL.Path, "//", "/", -1)
136 | h, name := b.Router().Match(r.Method, path, c)
137 | c.routeName = name
138 |
139 | // notFound
140 | if h == nil {
141 | c.handlers = append(c.handlers, b.notFoundHandler)
142 | } else {
143 | c.handlers = append(c.handlers, h...)
144 | }
145 |
146 | c.Next()
147 |
148 | b.pool.Put(c)
149 | }
150 |
151 | // SetDIer set baa di
152 | func (b *Baa) SetDIer(v DIer) {
153 | b.di = v
154 | }
155 |
156 | // SetDebug set baa debug
157 | func (b *Baa) SetDebug(v bool) {
158 | b.debug = v
159 | }
160 |
161 | // Debug returns baa debug state
162 | func (b *Baa) Debug() bool {
163 | return b.debug
164 | }
165 |
166 | // Logger return baa logger
167 | func (b *Baa) Logger() Logger {
168 | return b.GetDI("logger").(Logger)
169 | }
170 |
171 | // Render return baa render
172 | func (b *Baa) Render() Renderer {
173 | return b.GetDI("render").(Renderer)
174 | }
175 |
176 | // Router return baa router
177 | func (b *Baa) Router() Router {
178 | if b.router == nil {
179 | b.router = b.GetDI("router").(Router)
180 | }
181 | return b.router
182 | }
183 |
184 | // Use registers a middleware
185 | func (b *Baa) Use(m ...Middleware) {
186 | for i := range m {
187 | if m[i] != nil {
188 | b.middleware = append(b.middleware, wrapMiddleware(m[i]))
189 | }
190 | }
191 | }
192 |
193 | // SetDI registers a dependency injection
194 | func (b *Baa) SetDI(name string, h interface{}) {
195 | switch name {
196 | case "logger":
197 | if _, ok := h.(Logger); !ok {
198 | panic("DI logger must be implement interface baa.Logger")
199 | }
200 | case "render":
201 | if _, ok := h.(Renderer); !ok {
202 | panic("DI render must be implement interface baa.Renderer")
203 | }
204 | case "router":
205 | if _, ok := h.(Router); !ok {
206 | panic("DI router must be implement interface baa.Router")
207 | }
208 | }
209 | b.di.Set(name, h)
210 | }
211 |
212 | // GetDI fetch a registered dependency injection
213 | func (b *Baa) GetDI(name string) interface{} {
214 | return b.di.Get(name)
215 | }
216 |
217 | // Static set static file route
218 | // h used for set Expries ...
219 | func (b *Baa) Static(prefix string, dir string, index bool, h HandlerFunc) {
220 | if prefix == "" {
221 | panic("baa.Static prefix can not be empty")
222 | }
223 | if dir == "" {
224 | panic("baa.Static dir can not be empty")
225 | }
226 | b.Get(prefix+"*", newStatic(prefix, dir, index, h))
227 | }
228 |
229 | // StaticFile shortcut for serve file
230 | func (b *Baa) StaticFile(pattern string, path string) RouteNode {
231 | return b.Get(pattern, func(c *Context) {
232 | if err := serveFile(path, c); err != nil {
233 | c.Error(err)
234 | }
235 | })
236 | }
237 |
238 | // SetAutoHead sets the value who determines whether add HEAD method automatically
239 | // when GET method is added. Combo router will not be affected by this value.
240 | func (b *Baa) SetAutoHead(v bool) {
241 | b.Router().SetAutoHead(v)
242 | }
243 |
244 | // SetAutoTrailingSlash optional trailing slash.
245 | func (b *Baa) SetAutoTrailingSlash(v bool) {
246 | b.Router().SetAutoTrailingSlash(v)
247 | }
248 |
249 | // Route is a shortcut for same handlers but different HTTP methods.
250 | //
251 | // Example:
252 | //
253 | // baa.Route("/", "GET,POST", h)
254 | func (b *Baa) Route(pattern, methods string, h ...HandlerFunc) RouteNode {
255 | var ru RouteNode
256 | var ms []string
257 | if methods == "*" {
258 | for m := range RouterMethods {
259 | ms = append(ms, m)
260 | }
261 | } else {
262 | ms = strings.Split(methods, ",")
263 | }
264 | for _, m := range ms {
265 | ru = b.Router().Add(strings.TrimSpace(m), pattern, h)
266 | }
267 | return ru
268 | }
269 |
270 | // Group registers a list of same prefix route
271 | func (b *Baa) Group(pattern string, f func(), h ...HandlerFunc) {
272 | b.Router().GroupAdd(pattern, f, h)
273 | }
274 |
275 | // Any is a shortcut for b.Router().handle("*", pattern, handlers)
276 | func (b *Baa) Any(pattern string, h ...HandlerFunc) RouteNode {
277 | var ru RouteNode
278 | for m := range RouterMethods {
279 | ru = b.Router().Add(m, pattern, h)
280 | }
281 | return ru
282 | }
283 |
284 | // Delete is a shortcut for b.Route(pattern, "DELETE", handlers)
285 | func (b *Baa) Delete(pattern string, h ...HandlerFunc) RouteNode {
286 | return b.Router().Add("DELETE", pattern, h)
287 | }
288 |
289 | // Get is a shortcut for b.Route(pattern, "GET", handlers)
290 | func (b *Baa) Get(pattern string, h ...HandlerFunc) RouteNode {
291 | return b.Router().Add("GET", pattern, h)
292 | }
293 |
294 | // Head is a shortcut forb.Route(pattern, "Head", handlers)
295 | func (b *Baa) Head(pattern string, h ...HandlerFunc) RouteNode {
296 | return b.Router().Add("HEAD", pattern, h)
297 | }
298 |
299 | // Options is a shortcut for b.Route(pattern, "Options", handlers)
300 | func (b *Baa) Options(pattern string, h ...HandlerFunc) RouteNode {
301 | return b.Router().Add("OPTIONS", pattern, h)
302 | }
303 |
304 | // Patch is a shortcut for b.Route(pattern, "PATCH", handlers)
305 | func (b *Baa) Patch(pattern string, h ...HandlerFunc) RouteNode {
306 | return b.Router().Add("PATCH", pattern, h)
307 | }
308 |
309 | // Post is a shortcut for b.Route(pattern, "POST", handlers)
310 | func (b *Baa) Post(pattern string, h ...HandlerFunc) RouteNode {
311 | return b.Router().Add("POST", pattern, h)
312 | }
313 |
314 | // Put is a shortcut for b.Route(pattern, "Put", handlers)
315 | func (b *Baa) Put(pattern string, h ...HandlerFunc) RouteNode {
316 | return b.Router().Add("PUT", pattern, h)
317 | }
318 |
319 | // Websocket register a websocket router handler
320 | func (b *Baa) Websocket(pattern string, h func(*websocket.Conn)) RouteNode {
321 | var upgrader = websocket.Upgrader{
322 | ReadBufferSize: 4096,
323 | WriteBufferSize: 4096,
324 | EnableCompression: true,
325 | CheckOrigin: func(r *http.Request) bool {
326 | return true
327 | },
328 | }
329 |
330 | return b.Route(pattern, "GET,POST", func(c *Context) {
331 | conn, err := upgrader.Upgrade(c.Resp, c.Req, nil)
332 | if err != nil {
333 | b.Logger().Printf("websocket upgrade connection error: %v", err)
334 | return
335 | }
336 | h(conn)
337 | })
338 | }
339 |
340 | // SetNotFound set not found route handler
341 | func (b *Baa) SetNotFound(h HandlerFunc) {
342 | b.notFoundHandler = h
343 | }
344 |
345 | // NotFound execute not found handler
346 | func (b *Baa) NotFound(c *Context) {
347 | if b.notFoundHandler != nil {
348 | b.notFoundHandler(c)
349 | return
350 | }
351 | http.NotFound(c.Resp, c.Req)
352 | }
353 |
354 | // SetError set error handler
355 | func (b *Baa) SetError(h ErrorHandleFunc) {
356 | b.errorHandler = h
357 | }
358 |
359 | // Error execute internal error handler
360 | func (b *Baa) Error(err error, c *Context) {
361 | if err == nil {
362 | err = errors.New("internal server error")
363 | }
364 | if b.errorHandler != nil {
365 | b.errorHandler(err, c)
366 | return
367 | }
368 | code := http.StatusInternalServerError
369 | msg := http.StatusText(code)
370 | if b.debug {
371 | msg = err.Error()
372 | }
373 | b.Logger().Println(err)
374 | http.Error(c.Resp, msg, code)
375 | }
376 |
377 | // DefaultNotFoundHandler invokes the default HTTP error handler.
378 | func (b *Baa) DefaultNotFoundHandler(c *Context) {
379 | code := http.StatusNotFound
380 | msg := http.StatusText(code)
381 | http.Error(c.Resp, msg, code)
382 | }
383 |
384 | // URLFor use named route return format url
385 | func (b *Baa) URLFor(name string, args ...interface{}) string {
386 | return b.Router().URLFor(name, args...)
387 | }
388 |
389 | // wrapMiddleware wraps middleware.
390 | func wrapMiddleware(m Middleware) HandlerFunc {
391 | switch m := m.(type) {
392 | case HandlerFunc:
393 | return m
394 | case func(*Context):
395 | return m
396 | case http.Handler, http.HandlerFunc:
397 | return WrapHandlerFunc(func(c *Context) {
398 | m.(http.Handler).ServeHTTP(c.Resp, c.Req)
399 | })
400 | case func(http.ResponseWriter, *http.Request):
401 | return WrapHandlerFunc(func(c *Context) {
402 | m(c.Resp, c.Req)
403 | })
404 | default:
405 | panic("unknown middleware")
406 | }
407 | }
408 |
409 | // WrapHandlerFunc wrap for context handler chain
410 | func WrapHandlerFunc(h HandlerFunc) HandlerFunc {
411 | return func(c *Context) {
412 | h(c)
413 | c.Next()
414 | }
415 | }
416 |
417 | func init() {
418 | appInstances = make(map[string]*Baa)
419 | Env = os.Getenv("BAA_ENV")
420 | if Env == "" {
421 | Env = DEV
422 | }
423 | }
424 |
--------------------------------------------------------------------------------
/tree.go:
--------------------------------------------------------------------------------
1 | package baa
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | const (
9 | leafKindStatic uint = iota
10 | leafKindParam
11 | leafKindWide
12 | )
13 |
14 | // Tree provlider router for baa with radix tree
15 | type Tree struct {
16 | autoHead bool
17 | autoTrailingSlash bool
18 | mu sync.RWMutex
19 | groups []*group
20 | nodes [RouteLength]*leaf
21 | baa *Baa
22 | nameNodes map[string]*Node
23 | }
24 |
25 | // Node is struct for named route
26 | type Node struct {
27 | paramNum int
28 | pattern string
29 | format string
30 | name string
31 | root *Tree
32 | }
33 |
34 | // Leaf is a tree node
35 | type leaf struct {
36 | kind uint
37 | pattern string
38 | param string
39 | handlers []HandlerFunc
40 | children []*leaf
41 | childrenNum uint
42 | paramChild *leaf
43 | wideChild *leaf
44 | root *Tree
45 | nameNode *Node
46 | }
47 |
48 | // group route
49 | type group struct {
50 | pattern string
51 | handlers []HandlerFunc
52 | }
53 |
54 | // NewTree create a router instance
55 | func NewTree(b *Baa) Router {
56 | t := new(Tree)
57 | for i := 0; i < len(t.nodes); i++ {
58 | t.nodes[i] = newLeaf("/", nil, t)
59 | }
60 | t.nameNodes = make(map[string]*Node)
61 | t.groups = make([]*group, 0)
62 | t.baa = b
63 | return t
64 | }
65 |
66 | // NewNode create a route node
67 | func NewNode(pattern string, root *Tree) *Node {
68 | return &Node{
69 | pattern: pattern,
70 | root: root,
71 | }
72 | }
73 |
74 | // newLeaf create a tree leaf
75 | func newLeaf(pattern string, handlers []HandlerFunc, root *Tree) *leaf {
76 | l := new(leaf)
77 | l.pattern = pattern
78 | l.handlers = handlers
79 | l.root = root
80 | l.kind = leafKindStatic
81 | l.children = make([]*leaf, 128)
82 | return l
83 | }
84 |
85 | // newGroup create a group router
86 | func newGroup() *group {
87 | g := new(group)
88 | g.handlers = make([]HandlerFunc, 0)
89 | return g
90 | }
91 |
92 | // SetAutoHead sets the value who determines whether add HEAD method automatically
93 | // when GET method is added. Combo router will not be affected by this value.
94 | func (t *Tree) SetAutoHead(v bool) {
95 | t.autoHead = v
96 | }
97 |
98 | // SetAutoTrailingSlash optional trailing slash.
99 | func (t *Tree) SetAutoTrailingSlash(v bool) {
100 | t.autoTrailingSlash = v
101 | }
102 |
103 | // Match find matched route then returns handlers and name
104 | func (t *Tree) Match(method, pattern string, c *Context) ([]HandlerFunc, string) {
105 | var i, l int
106 | var root, nl *leaf
107 | root = t.nodes[RouterMethods[method]]
108 | current := root
109 |
110 | for {
111 | switch current.kind {
112 | case leafKindStatic:
113 | // static route
114 | l = len(current.pattern)
115 | if l > len(pattern) {
116 | break
117 | }
118 | i := l - 1
119 | for ; i >= 0; i-- {
120 | if current.pattern[i] != pattern[i] {
121 | break
122 | }
123 | }
124 | if i >= 0 {
125 | break
126 | }
127 | if len(pattern) == l || current.children[pattern[l]] != nil ||
128 | current.paramChild != nil ||
129 | current.wideChild != nil {
130 | pattern = pattern[l:]
131 | root = current
132 | }
133 | case leafKindParam:
134 | // params route
135 | l = len(pattern)
136 | for i = 0; i < l && pattern[i] != '/'; i++ {
137 | }
138 | if len(pattern[:i]) == 0 {
139 | return nil, "" // param is empty
140 | }
141 | c.SetParam(current.param, pattern[:i])
142 | pattern = pattern[i:]
143 | root = current
144 | case leafKindWide:
145 | // wide route
146 | c.SetParam(current.param, pattern)
147 | pattern = pattern[:0]
148 | default:
149 | }
150 |
151 | if len(pattern) == 0 {
152 | if current.handlers != nil {
153 | if current.nameNode != nil {
154 | return current.handlers, current.nameNode.name
155 | }
156 | return current.handlers, ""
157 | }
158 | if root.paramChild == nil && root.wideChild == nil {
159 | return nil, ""
160 | }
161 | } else {
162 | // children static route
163 | if current == root {
164 | if nl = root.children[pattern[0]]; nl != nil {
165 | current = nl
166 | continue
167 | }
168 | }
169 | }
170 |
171 | // param route
172 | if root.paramChild != nil {
173 | current = root.paramChild
174 | continue
175 | }
176 |
177 | // wide route
178 | if root.wideChild != nil {
179 | current = root.wideChild
180 | continue
181 | }
182 |
183 | break
184 | }
185 |
186 | return nil, ""
187 | }
188 |
189 | // URLFor use named route return format url
190 | func (t *Tree) URLFor(name string, args ...interface{}) string {
191 | if name == "" {
192 | return ""
193 | }
194 | node := t.nameNodes[name]
195 | if node == nil || len(node.format) == 0 {
196 | return ""
197 | }
198 | format := make([]byte, len(node.format))
199 | copy(format, node.format)
200 | for i := node.paramNum + 1; i <= len(args); i++ {
201 | format = append(format, "%v"...)
202 | }
203 | return fmt.Sprintf(string(format), args...)
204 | }
205 |
206 | // Routes returns registered route uri in a string slice
207 | func (t *Tree) Routes() map[string][]string {
208 | routes := make(map[string][]string)
209 | for _, method := range RouterMethodName {
210 | routes[method] = make([]string, 0)
211 | }
212 | for k := range t.nodes {
213 | routes[RouterMethodName[k]] = t.routes(t.nodes[k])
214 | }
215 |
216 | return routes
217 | }
218 |
219 | // routes print the route table
220 | func (t *Tree) routes(l *leaf) []string {
221 | if l == nil {
222 | return nil
223 | }
224 | var data []string
225 | if l.handlers != nil {
226 | data = append(data, l.String())
227 | }
228 | l.children = append(l.children, l.paramChild)
229 | l.children = append(l.children, l.wideChild)
230 | for i := range l.children {
231 | if l.children[i] != nil {
232 | cdata := t.routes(l.children[i])
233 | for i := range cdata {
234 | data = append(data, l.String()+cdata[i])
235 | }
236 | }
237 | }
238 |
239 | return data
240 | }
241 |
242 | // NamedRoutes returns named route uri in a string slice
243 | func (t *Tree) NamedRoutes() map[string]string {
244 | routes := make(map[string]string)
245 | for k, v := range t.nameNodes {
246 | routes[k] = v.pattern
247 | }
248 | return routes
249 | }
250 |
251 | // Add registers a new handle with the given method, pattern and handlers.
252 | // add check training slash option.
253 | func (t *Tree) Add(method, pattern string, handlers []HandlerFunc) RouteNode {
254 | if method == "GET" && t.autoHead {
255 | t.add("HEAD", pattern, handlers)
256 | }
257 | if t.autoTrailingSlash && (len(pattern) > 1 || len(t.groups) > 0) {
258 | var index byte
259 | if len(pattern) > 0 {
260 | index = pattern[len(pattern)-1]
261 | }
262 | if index == '/' {
263 | t.add(method, pattern[:len(pattern)-1], handlers)
264 | } else if index == '*' {
265 | // wideChild not need trail slash
266 | } else {
267 | t.add(method, pattern+"/", handlers)
268 | }
269 | }
270 | return t.add(method, pattern, handlers)
271 | }
272 |
273 | // GroupAdd add a group route has same prefix and handle chain
274 | func (t *Tree) GroupAdd(pattern string, f func(), handlers []HandlerFunc) {
275 | g := newGroup()
276 | g.pattern = pattern
277 | g.handlers = handlers
278 | t.groups = append(t.groups, g)
279 |
280 | f()
281 |
282 | t.groups = t.groups[:len(t.groups)-1]
283 | }
284 |
285 | // add registers a new request handle with the given method, pattern and handlers.
286 | func (t *Tree) add(method, pattern string, handlers []HandlerFunc) RouteNode {
287 | if _, ok := RouterMethods[method]; !ok {
288 | panic("unsupport http method [" + method + "]")
289 | }
290 |
291 | t.mu.Lock()
292 | defer t.mu.Unlock()
293 |
294 | // check group set
295 | if len(t.groups) > 0 {
296 | var gpattern string
297 | var ghandlers []HandlerFunc
298 | for i := range t.groups {
299 | gpattern += t.groups[i].pattern
300 | if len(t.groups[i].handlers) > 0 {
301 | ghandlers = append(ghandlers, t.groups[i].handlers...)
302 | }
303 | }
304 | pattern = gpattern + pattern
305 | ghandlers = append(ghandlers, handlers...)
306 | handlers = ghandlers
307 | }
308 |
309 | // check pattern (for training slash move behind group check)
310 | if pattern == "" {
311 | panic("route pattern can not be emtpy!")
312 | }
313 | if pattern[0] != '/' {
314 | panic("route pattern must begin /")
315 | }
316 |
317 | for i := 0; i < len(handlers); i++ {
318 | handlers[i] = WrapHandlerFunc(handlers[i])
319 | }
320 |
321 | root := t.nodes[RouterMethods[method]]
322 | origPattern := pattern
323 | nameNode := NewNode(origPattern, t)
324 |
325 | // specialy route = /
326 | if len(pattern) == 1 {
327 | root.handlers = handlers
328 | root.nameNode = nameNode
329 | return nameNode
330 | }
331 |
332 | // left trim slash, because root is slash /
333 | pattern = pattern[1:]
334 |
335 | var radix []byte
336 | var param []byte
337 | var i, k int
338 | var tl *leaf
339 | for i = 0; i < len(pattern); i++ {
340 | // wide route
341 | if pattern[i] == '*' {
342 | // clear static route
343 | if len(radix) > 0 {
344 | root = root.insertChild(newLeaf(string(radix), nil, t))
345 | radix = radix[:0]
346 | }
347 | tl = newLeaf("*", handlers, t)
348 | tl.kind = leafKindWide
349 | tl.nameNode = nameNode
350 | root.insertChild(tl)
351 | break
352 | }
353 |
354 | // param route
355 | if pattern[i] == ':' {
356 | // clear static route
357 | if len(radix) > 0 {
358 | root = root.insertChild(newLeaf(string(radix), nil, t))
359 | radix = radix[:0]
360 | }
361 | // set param route
362 | param = param[:0]
363 | k = 0
364 | for i = i + 1; i < len(pattern); i++ {
365 | if pattern[i] == '/' {
366 | i--
367 | break
368 | }
369 | param = append(param, pattern[i])
370 | k++
371 | }
372 | if k == 0 {
373 | panic("route pattern param is empty")
374 | }
375 | // check last character
376 | if i == len(pattern) {
377 | tl = newLeaf(":", handlers, t)
378 | tl.nameNode = nameNode
379 | } else {
380 | tl = newLeaf(":", nil, t)
381 | }
382 | tl.param = string(param[:k])
383 | tl.kind = leafKindParam
384 | root = root.insertChild(tl)
385 | continue
386 | }
387 | radix = append(radix, pattern[i])
388 | }
389 |
390 | // static route
391 | if len(radix) > 0 {
392 | tl = newLeaf(string(radix), handlers, t)
393 | tl.nameNode = nameNode
394 | root.insertChild(tl)
395 | }
396 |
397 | return nameNode
398 | }
399 |
400 | // insertChild insert child into root route, and returns the child route
401 | func (l *leaf) insertChild(node *leaf) *leaf {
402 | // wide route
403 | if node.kind == leafKindWide {
404 | if l.wideChild != nil {
405 | panic("Router Tree.insert error: cannot set two wide route with same prefix!")
406 | }
407 | l.wideChild = node
408 | return node
409 | }
410 |
411 | // param route
412 | if node.kind == leafKindParam {
413 | if l.paramChild == nil {
414 | l.paramChild = node
415 | return l.paramChild
416 | }
417 | if l.paramChild.param != node.param {
418 | panic("Router Tree.insert error cannot use two param [:" + l.paramChild.param + ", :" + node.param + "] with same prefix!")
419 | }
420 | if node.handlers != nil {
421 | if l.paramChild.handlers != nil {
422 | panic("Router Tree.insert error: cannot twice set handler for same route")
423 | }
424 | l.paramChild.handlers = node.handlers
425 | l.paramChild.nameNode = node.nameNode
426 | }
427 | return l.paramChild
428 | }
429 |
430 | // static route
431 | child := l.children[node.pattern[0]]
432 | if child == nil {
433 | // new child
434 | l.children[node.pattern[0]] = node
435 | l.childrenNum++
436 | return node
437 | }
438 |
439 | pos := child.hasPrefixString(node.pattern)
440 | pre := node.pattern[:pos]
441 | if pos == len(child.pattern) {
442 | // same route
443 | if pos == len(node.pattern) {
444 | if node.handlers != nil {
445 | if child.handlers != nil {
446 | panic("Router Tree.insert error: cannot twice set handler for same route")
447 | }
448 | child.handlers = node.handlers
449 | child.nameNode = node.nameNode
450 | }
451 | return child
452 | }
453 |
454 | // child is prefix or node
455 | node.pattern = node.pattern[pos:]
456 | return child.insertChild(node)
457 | }
458 |
459 | newChild := newLeaf(child.pattern[pos:], child.handlers, child.root)
460 | newChild.nameNode = child.nameNode
461 | newChild.children = child.children
462 | newChild.childrenNum = child.childrenNum
463 | newChild.paramChild = child.paramChild
464 | newChild.wideChild = child.wideChild
465 |
466 | // node is prefix of child
467 | if pos == len(node.pattern) {
468 | child.reset(node.pattern, node.handlers)
469 | child.nameNode = node.nameNode
470 | child.children[newChild.pattern[0]] = newChild
471 | child.childrenNum++
472 | return child
473 | }
474 |
475 | // child and node has same prefix
476 | child.reset(pre, nil)
477 | child.children[newChild.pattern[0]] = newChild
478 | child.childrenNum++
479 | node.pattern = node.pattern[pos:]
480 | child.children[node.pattern[0]] = node
481 | child.childrenNum++
482 | return node
483 | }
484 |
485 | // resetPattern reset route pattern and alpha
486 | func (l *leaf) reset(pattern string, handlers []HandlerFunc) {
487 | l.pattern = pattern
488 | l.children = make([]*leaf, 128)
489 | l.childrenNum = 0
490 | l.paramChild = nil
491 | l.wideChild = nil
492 | l.nameNode = nil
493 | l.param = ""
494 | l.handlers = handlers
495 | }
496 |
497 | // hasPrefixString returns the same prefix position, if none return 0
498 | func (l *leaf) hasPrefixString(s string) int {
499 | var i, j int
500 | j = len(l.pattern)
501 | if len(s) < j {
502 | j = len(s)
503 | }
504 | for i = 0; i < j && s[i] == l.pattern[i]; i++ {
505 | }
506 | return i
507 | }
508 |
509 | // String returns pattern of leaf
510 | func (l *leaf) String() string {
511 | s := l.pattern
512 | if l.kind == leafKindParam {
513 | s += l.param
514 | }
515 | return s
516 | }
517 |
518 | // Name set name of route
519 | func (n *Node) Name(name string) {
520 | if name == "" {
521 | return
522 | }
523 | p := 0
524 | f := make([]byte, 0, len(n.pattern))
525 | for i := 0; i < len(n.pattern); i++ {
526 | if n.pattern[i] != ':' {
527 | f = append(f, n.pattern[i])
528 | continue
529 | }
530 | f = append(f, '%')
531 | f = append(f, 'v')
532 | p++
533 | for i = i + 1; i < len(n.pattern); i++ {
534 | if n.pattern[i] == '/' {
535 | i--
536 | break
537 | }
538 | }
539 | }
540 | n.format = string(f)
541 | n.paramNum = p
542 | n.name = name
543 | n.root.nameNodes[name] = n
544 | }
545 |
--------------------------------------------------------------------------------
/context_test.go:
--------------------------------------------------------------------------------
1 | package baa
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "encoding/xml"
8 | "fmt"
9 | "io"
10 | "mime/multipart"
11 | "net/http"
12 | "net/http/httptest"
13 | "net/url"
14 | "os"
15 | "path/filepath"
16 | "strings"
17 | "testing"
18 |
19 | . "github.com/smartystreets/goconvey/convey"
20 | )
21 |
22 | var _ context.Context = &Context{}
23 |
24 | func TestContextStore(t *testing.T) {
25 | Convey("context store", t, func() {
26 | b.Get("/context", func(c *Context) {
27 | c.Get("name")
28 | c.Gets()
29 | c.Set("name", "Baa")
30 | c.Get("name")
31 | c.Gets()
32 | })
33 |
34 | w := request("GET", "/context")
35 | So(w.Code, ShouldEqual, http.StatusOK)
36 | })
37 | }
38 |
39 | func TestContextParam(t *testing.T) {
40 | Convey("context route param", t, func() {
41 | Convey("param", func() {
42 | b.Get("/context/p1/:id", func(c *Context) {
43 | id := c.Param("id")
44 | So(id, ShouldEqual, "123")
45 | })
46 |
47 | w := request("GET", "/context/p1/123")
48 | So(w.Code, ShouldEqual, http.StatusOK)
49 | })
50 |
51 | Convey("param int", func() {
52 | b.Get("/context/p2/:id", func(c *Context) {
53 | id := c.ParamInt("id")
54 | So(id, ShouldEqual, 123)
55 | })
56 |
57 | w := request("GET", "/context/p2/123")
58 | So(w.Code, ShouldEqual, http.StatusOK)
59 | })
60 |
61 | Convey("param int32", func() {
62 | b.Get("/context/p3/:id", func(c *Context) {
63 | id := c.ParamInt32("id")
64 | So(id, ShouldEqual, 123)
65 | })
66 |
67 | w := request("GET", "/context/p3/123")
68 | So(w.Code, ShouldEqual, http.StatusOK)
69 | })
70 |
71 | Convey("param int64", func() {
72 | b.Get("/context/p4/:id", func(c *Context) {
73 | id := c.ParamInt64("id")
74 | So(id, ShouldEqual, 123)
75 | })
76 |
77 | w := request("GET", "/context/p4/123")
78 | So(w.Code, ShouldEqual, http.StatusOK)
79 | })
80 |
81 | Convey("param float", func() {
82 | b.Get("/context/p5/:id", func(c *Context) {
83 | id := c.ParamFloat("id")
84 | So(id, ShouldEqual, 123.4)
85 | })
86 |
87 | w := request("GET", "/context/p5/123.4")
88 | So(w.Code, ShouldEqual, http.StatusOK)
89 | })
90 |
91 | Convey("param bool", func() {
92 | b.Get("/context/p6/:id", func(c *Context) {
93 | id := c.ParamBool("id")
94 | So(id, ShouldEqual, true)
95 | })
96 |
97 | w := request("GET", "/context/p6/1")
98 | So(w.Code, ShouldEqual, http.StatusOK)
99 | })
100 | })
101 | }
102 |
103 | func TestContextQuery(t *testing.T) {
104 | Convey("context query param", t, func() {
105 | Convey("query string param", func() {
106 | b.Get("/context/1/:id", func(c *Context) {
107 | id := c.Query("p")
108 | So(id, ShouldEqual, "123")
109 | })
110 |
111 | w := request("GET", "/context/1/1?p=123")
112 | So(w.Code, ShouldEqual, http.StatusOK)
113 | })
114 |
115 | Convey("form string param", func() {
116 | b.Post("/context/2/:id", func(c *Context) {
117 | id := c.Query("p")
118 | So(id, ShouldEqual, "123")
119 | })
120 | data := url.Values{}
121 | data.Add("p", "123")
122 | req, _ := http.NewRequest("POST", "/context/2/1", strings.NewReader(data.Encode()))
123 | req.Header.Set("Content-Type", ApplicationForm)
124 | w := httptest.NewRecorder()
125 | b.ServeHTTP(w, req)
126 | So(w.Code, ShouldEqual, http.StatusOK)
127 | })
128 |
129 | Convey("type param", func() {
130 | b.Post("/context/3/:id", func(c *Context) {
131 | var p interface{}
132 | p = c.QueryInt("int")
133 | So(p, ShouldEqual, 123)
134 |
135 | p = c.QueryInt32("int32")
136 | So(p, ShouldEqual, 123)
137 |
138 | p = c.QueryInt64("int64")
139 | So(p, ShouldEqual, 123)
140 |
141 | p = c.QueryFloat("float")
142 | So(p, ShouldEqual, 123.4)
143 |
144 | p = c.QueryBool("bool")
145 | So(p, ShouldEqual, true)
146 |
147 | p = c.QueryBool("bool2")
148 | So(p, ShouldEqual, false)
149 |
150 | p = c.QueryTrim("trim")
151 | So(p, ShouldEqual, "abc")
152 |
153 | p = c.QueryStrings("strings")
154 | So(fmt.Sprintf("%s", p.([]string)), ShouldEqual, "[abc1 abc2]")
155 |
156 | p = c.QueryStrings("strings2")
157 | So(fmt.Sprintf("%s", p.([]string)), ShouldEqual, "[]")
158 |
159 | p = c.QueryEscape("escape")
160 | So(p, ShouldEqual, "<a href>string</a>")
161 | })
162 | data := url.Values{}
163 | data.Add("int", "123")
164 | data.Add("int32", "123")
165 | data.Add("int64", "123")
166 | data.Add("float", "123.4")
167 | data.Add("bool", "1")
168 | data.Add("bool2", "0")
169 | data.Add("trim", "abc ")
170 | data.Add("strings", "abc1")
171 | data.Add("strings", "abc2")
172 | data.Add("escape", "string")
173 | req, _ := http.NewRequest("POST", "/context/3/1", strings.NewReader(data.Encode()))
174 | req.Header.Set("Content-Type", ApplicationForm)
175 | w := httptest.NewRecorder()
176 | b.ServeHTTP(w, req)
177 | So(w.Code, ShouldEqual, http.StatusOK)
178 | })
179 |
180 | Convey("querys/gets, not contains form data", func() {
181 | b.Post("/context/4/:id", func(c *Context) {
182 | querys := c.Querys()
183 | So(querys, ShouldNotBeNil)
184 | p := querys["a"].(string)
185 | So(p, ShouldEqual, "1")
186 | p = querys["b"].(string)
187 | So(p, ShouldEqual, "1")
188 | ps := querys["d"].([]string)
189 | So(fmt.Sprintf("%s", ps), ShouldEqual, "[1 2]")
190 | })
191 | data := url.Values{}
192 | data.Add("a", "2")
193 | data.Add("b", "2")
194 | data.Add("d", "2")
195 | req, _ := http.NewRequest("POST", "/context/4/1?a=1&b=1&d=1&d=2", strings.NewReader(data.Encode()))
196 | req.Header.Set("Content-Type", ApplicationForm)
197 | w := httptest.NewRecorder()
198 | b.ServeHTTP(w, req)
199 | So(w.Code, ShouldEqual, http.StatusOK)
200 | })
201 |
202 | Convey("posts, not contains get params", func() {
203 | b.Post("/contextp/:id", func(c *Context) {
204 | querys := c.Posts()
205 | So(querys, ShouldNotBeNil)
206 | p := querys["a"].(string)
207 | So(p, ShouldEqual, "2")
208 | p = querys["b"].(string)
209 | So(p, ShouldEqual, "2")
210 | ps := querys["d"].([]string)
211 | So(fmt.Sprintf("%s", ps), ShouldEqual, "[2 3]")
212 | })
213 | data := url.Values{}
214 | data.Add("a", "2")
215 | data.Add("b", "2")
216 | data.Add("d", "2")
217 | data.Add("d", "3")
218 | req, _ := http.NewRequest("POST", "/contextp/1?a=1&b=1&d=1", strings.NewReader(data.Encode()))
219 | req.Header.Set("Content-Type", ApplicationForm)
220 | w := httptest.NewRecorder()
221 | b.ServeHTTP(w, req)
222 | So(w.Code, ShouldEqual, http.StatusOK)
223 | })
224 |
225 | Convey("body json param", func() {
226 | dataSource := map[string]interface{}{"test": "json"}
227 | b.Post("/context/json", func(c *Context) {
228 | var dataFromBody map[string]interface{}
229 | c.QueryJSON(&dataFromBody)
230 | So(dataFromBody["test"], ShouldEqual, dataSource["test"])
231 | })
232 | body, _ := json.Marshal(dataSource)
233 | req, _ := http.NewRequest("POST", "/context/json", bytes.NewReader(body))
234 | req.Header.Set("Content-Type", ApplicationJSON)
235 | w := httptest.NewRecorder()
236 | b.ServeHTTP(w, req)
237 | So(w.Code, ShouldEqual, http.StatusOK)
238 | })
239 |
240 | Convey("body json param nil", func() {
241 | dataSource := map[string]interface{}{"test": "json"}
242 | b.Post("/context/json2", func(c *Context) {
243 | var dataFromBody interface{}
244 | c.QueryJSON(dataFromBody)
245 | So(dataFromBody, ShouldBeNil)
246 | })
247 | body, _ := json.Marshal(dataSource)
248 | req, _ := http.NewRequest("POST", "/context/json2", bytes.NewReader(body))
249 | req.Header.Set("Content-Type", ApplicationJSON)
250 | w := httptest.NewRecorder()
251 | b.ServeHTTP(w, req)
252 | So(w.Code, ShouldEqual, http.StatusOK)
253 | })
254 |
255 | Convey("body json param diffrent struct", func() {
256 | dataSource := map[string]interface{}{"test": "json"}
257 | b.Post("/context/json3", func(c *Context) {
258 | var dataFromBody struct {
259 | Test string
260 | }
261 | c.QueryJSON(&dataFromBody)
262 | So(dataFromBody.Test, ShouldEqual, dataSource["test"])
263 | })
264 | body, _ := json.Marshal(dataSource)
265 | req, _ := http.NewRequest("POST", "/context/json3", bytes.NewReader(body))
266 | req.Header.Set("Content-Type", ApplicationJSON)
267 | w := httptest.NewRecorder()
268 | b.ServeHTTP(w, req)
269 | So(w.Code, ShouldEqual, http.StatusOK)
270 | })
271 |
272 | Convey("body json param is empty", func() {
273 | b.Post("/context/json/empty", func(c *Context) {
274 | var dataFromBody map[string]interface{}
275 | err := c.QueryJSON(&dataFromBody)
276 | So(err, ShouldBeError, ErrJSONPayloadEmpty)
277 | })
278 | req, _ := http.NewRequest("POST", "/context/json/empty", strings.NewReader(""))
279 | req.Header.Set("Content-Type", ApplicationJSON)
280 | w := httptest.NewRecorder()
281 | b.ServeHTTP(w, req)
282 | So(w.Code, ShouldEqual, http.StatusOK)
283 | })
284 |
285 | Convey("body xml param", func() {
286 | type XML struct {
287 | Test string `xml:"test"`
288 | }
289 | dataSource := XML{Test: "xml"}
290 | b.Post("/context/xml", func(c *Context) {
291 | var dataFromBody XML
292 | c.QueryXML(&dataFromBody)
293 | So(dataFromBody.Test, ShouldEqual, dataSource.Test)
294 | })
295 | body, _ := xml.Marshal(dataSource)
296 | req, _ := http.NewRequest("POST", "/context/xml", bytes.NewReader(body))
297 | req.Header.Set("Content-Type", ApplicationXML)
298 | w := httptest.NewRecorder()
299 | b.ServeHTTP(w, req)
300 | So(w.Code, ShouldEqual, http.StatusOK)
301 | })
302 |
303 | Convey("body xml param is empty", func() {
304 | b.Post("/context/xml/empty", func(c *Context) {
305 | var dataFromBody map[string]interface{}
306 | err := c.QueryXML(&dataFromBody)
307 | So(err, ShouldBeError, ErrXMLPayloadEmpty)
308 | })
309 | req, _ := http.NewRequest("POST", "/context/xml/empty", strings.NewReader(""))
310 | req.Header.Set("Content-Type", ApplicationXML)
311 | w := httptest.NewRecorder()
312 | b.ServeHTTP(w, req)
313 | So(w.Code, ShouldEqual, http.StatusOK)
314 | })
315 | })
316 | }
317 |
318 | func TestContextFile(t *testing.T) {
319 | Convey("context file", t, func() {
320 | b.Post("/file", func(c *Context) {
321 | c.Posts()
322 | c.GetFile("file1")
323 | c.SaveToFile("file1", "/tmp/baa.jpg")
324 | c.SaveToFile("file1", "/tmpx/baa.jpg")
325 | c.SaveToFile("file2", "/tmpx/baa.jpg")
326 | })
327 | data := make(map[string]string)
328 | data["a"] = "1"
329 | req, _ := newfileUploadRequest("/file", data, "file1", "./_fixture/img/baa.jpg")
330 | w := httptest.NewRecorder()
331 | b.ServeHTTP(w, req)
332 | So(w.Code, ShouldEqual, http.StatusOK)
333 | })
334 | }
335 |
336 | func TestContextCookie(t *testing.T) {
337 | Convey("context cookie", t, func() {
338 | Convey("cookie get", func() {
339 | b.Get("/cookie/get", func(c *Context) {
340 | var p interface{}
341 | p = c.GetCookie("s")
342 | So(p, ShouldEqual, "123")
343 | p = c.GetCookieInt("int")
344 | So(p, ShouldEqual, 123)
345 | p = c.GetCookieInt32("int32")
346 | So(p, ShouldEqual, 123)
347 | p = c.GetCookieInt64("int64")
348 | So(p, ShouldEqual, 123)
349 | p = c.GetCookieFloat64("float")
350 | So(p, ShouldEqual, 123.4)
351 | p = c.GetCookieBool("bool")
352 | So(p, ShouldEqual, true)
353 | p = c.GetCookieBool("bool2")
354 | So(p, ShouldEqual, false)
355 | p = c.GetCookie("not")
356 | So(p, ShouldEqual, "")
357 | })
358 | req, _ := http.NewRequest("GET", "/cookie/get", nil)
359 | req.Header.Set("Cookie", "s=123; int=123; int32=123; int64=123; float=123.4; bool=1; boo2=0;")
360 | w := httptest.NewRecorder()
361 | b.ServeHTTP(w, req)
362 |
363 | So(w.Code, ShouldEqual, http.StatusOK)
364 | })
365 | Convey("cookie set", func() {
366 | b.Get("/cookie/set", func(c *Context) {
367 | c.SetCookie("name", "baa")
368 | c.SetCookie("name", "baa", 10)
369 | c.SetCookie("name", "baa", int32(10))
370 | c.SetCookie("name", "baa", int64(10))
371 | c.SetCookie("name", "baa", 10, "/")
372 | c.SetCookie("name", "baa", 10, "/", "localhost")
373 | c.SetCookie("name", "baa", 10, "/", "localhost", "1")
374 | c.SetCookie("name", "baa", 10, "/", "localhost", true, true)
375 | })
376 | w := request("GET", "/cookie/set")
377 | So(w.Code, ShouldEqual, http.StatusOK)
378 | So(w.Header().Get("set-cookie"), ShouldContainSubstring, "name=baa;")
379 | })
380 | })
381 | }
382 |
383 | func TestContextWriter(t *testing.T) {
384 | Convey("context writer", t, func() {
385 | b.SetDebug(true)
386 | Convey("write string", func() {
387 | b.Get("/writer/string", func(c *Context) {
388 | c.String(200, "abc\n")
389 | })
390 | w := request("GET", "/writer/string")
391 | So(w.Code, ShouldEqual, http.StatusOK)
392 | })
393 | Convey("write byte", func() {
394 | b.Get("/writer/byte", func(c *Context) {
395 | c.Text(200, []byte("abc\n"))
396 | })
397 | w := request("GET", "/writer/byte")
398 | So(w.Code, ShouldEqual, http.StatusOK)
399 | })
400 | Convey("write JSON", func() {
401 | b.Get("/writer/json", func(c *Context) {
402 | data := map[string]interface{}{"a": "1"}
403 | c.JSON(200, data)
404 | })
405 | w := request("GET", "/writer/json")
406 | So(w.Code, ShouldEqual, http.StatusOK)
407 | })
408 | Convey("write JSON error", func() {
409 | b.Get("/writer/json/error", func(c *Context) {
410 | data := f
411 | c.JSON(200, data)
412 | })
413 | w := request("GET", "/writer/json/error")
414 | So(w.Code, ShouldEqual, http.StatusInternalServerError)
415 | fmt.Println(w.Body)
416 | })
417 | Convey("write JSONString", func() {
418 | b.Get("/writer/jsonstring", func(c *Context) {
419 | data := map[string]interface{}{"a": "1"}
420 | str, _ := c.JSONString(data)
421 | c.String(200, str)
422 | })
423 | w := request("GET", "/writer/jsonstring")
424 | So(w.Code, ShouldEqual, http.StatusOK)
425 | })
426 | Convey("write JSONString error", func() {
427 | b.Get("/writer/jsonstring/error", func(c *Context) {
428 | data := f
429 | str, _ := c.JSONString(data)
430 | c.String(200, str)
431 | })
432 | w := request("GET", "/writer/jsonstring/error")
433 | So(w.Code, ShouldEqual, http.StatusOK)
434 | fmt.Println(w.Body)
435 | })
436 | Convey("write JSONP", func() {
437 | b.Get("/writer/jsonp", func(c *Context) {
438 | data := map[string]interface{}{"a": "1"}
439 | callback := c.Query("callback")
440 | c.JSONP(200, callback, data)
441 | })
442 | w := request("GET", "/writer/jsonp?callback=callback")
443 | So(w.Code, ShouldEqual, http.StatusOK)
444 | })
445 | Convey("write JSONP error", func() {
446 | b.Get("/writer/jsonp/error", func(c *Context) {
447 | data := f
448 | callback := c.Query("callback")
449 | c.JSONP(200, callback, data)
450 | })
451 | w := request("GET", "/writer/jsonp/error?callback=callback")
452 | So(w.Code, ShouldEqual, http.StatusInternalServerError)
453 | fmt.Println(w.Body)
454 | })
455 | Convey("write XML", func() {
456 | b.Get("/writer/xml", func(c *Context) {
457 | type XMLNode struct {
458 | XMLName xml.Name `xml:"item"`
459 | Name string `xml:"name"`
460 | ID int `xml:"id,attr"`
461 | Addr string `xml:"adr"`
462 | }
463 | data := &XMLNode{Name: "baa", ID: 1, Addr: "beijing"}
464 | c.XML(200, data)
465 | })
466 | w := request("GET", "/writer/xml")
467 | So(w.Code, ShouldEqual, http.StatusOK)
468 | })
469 | Convey("write XML error", func() {
470 | b.Get("/writer/xml/error", func(c *Context) {
471 | data := map[string]interface{}{"name": "123"}
472 | c.XML(200, data)
473 | })
474 | w := request("GET", "/writer/xml/error")
475 | So(w.Code, ShouldEqual, http.StatusInternalServerError)
476 | })
477 | })
478 | }
479 |
480 | func TestContextWrite_WithoutDebug(t *testing.T) {
481 | Convey("context writer without debug mode", t, func() {
482 | b.SetDebug(false)
483 | Convey("write JSON", func() {
484 | w := request("GET", "/writer/json")
485 | So(w.Code, ShouldEqual, http.StatusOK)
486 | })
487 | Convey("write JSONString", func() {
488 | w := request("GET", "/writer/jsonstring")
489 | So(w.Code, ShouldEqual, http.StatusOK)
490 | })
491 | Convey("write JSONP", func() {
492 | w := request("GET", "/writer/jsonp?callback=callback")
493 | So(w.Code, ShouldEqual, http.StatusOK)
494 | })
495 | Convey("write XML", func() {
496 | w := request("GET", "/writer/xml")
497 | So(w.Code, ShouldEqual, http.StatusOK)
498 | })
499 | })
500 | b.SetDebug(true)
501 | }
502 |
503 | func TestConextRedirect(t *testing.T) {
504 | Convey("redirect", t, func() {
505 | Convey("redirect normal", func() {
506 | b.Get("/redirect/1", func(c *Context) {
507 | c.Redirect(301, "/")
508 | })
509 | w := request("GET", "/redirect/1")
510 | So(w.Code, ShouldEqual, http.StatusMovedPermanently)
511 | })
512 | Convey("redirect error", func() {
513 | b.Get("/redirect/2", func(c *Context) {
514 | c.Redirect(500, "/")
515 | })
516 | w := request("GET", "/redirect/2")
517 | So(w.Code, ShouldEqual, http.StatusOK)
518 | })
519 | })
520 | }
521 |
522 | func TestContextIP(t *testing.T) {
523 | Convey("get remote addr", t, func() {
524 | b.Get("/ip", func(c *Context) {
525 | _ = c.RemoteAddr()
526 | _ = c.RemoteAddr()
527 | ip := c.RemoteAddr()
528 | So(ip, ShouldEqual, "10.1.2.1")
529 | })
530 | b.Get("/ip2", func(c *Context) {
531 | ip := c.RemoteAddr()
532 | So(ip, ShouldBeEmpty)
533 | })
534 | req, _ := http.NewRequest("GET", "/ip", nil)
535 | req.Header.Set("X-Forwarded-For", "10.1.2.1, 10.1.2.2")
536 | w := httptest.NewRecorder()
537 | b.ServeHTTP(w, req)
538 | So(w.Code, ShouldEqual, http.StatusOK)
539 |
540 | req, _ = http.NewRequest("GET", "/ip2", nil)
541 | w = httptest.NewRecorder()
542 | b.ServeHTTP(w, req)
543 | So(w.Code, ShouldEqual, http.StatusOK)
544 | })
545 | }
546 |
547 | func TestContextUnits(t *testing.T) {
548 | Convey("request methods", t, func() {
549 | Convey("Referer, UserAgent, IsMobile", func() {
550 | b.Get("/req", func(c *Context) {
551 | c.Referer()
552 | c.UserAgent()
553 | isMobile := c.IsMobile()
554 | So(isMobile, ShouldBeTrue)
555 | })
556 | req, _ := http.NewRequest("GET", "/req", nil)
557 | req.Header.Set("User-Agent", "Mozilla/5.0 (iPod; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1")
558 | w := httptest.NewRecorder()
559 | b.ServeHTTP(w, req)
560 | So(w.Code, ShouldEqual, http.StatusOK)
561 | })
562 | Convey("IsMobile false", func() {
563 | b.Get("/req2", func(c *Context) {
564 | isMobile := c.IsMobile()
565 | So(isMobile, ShouldBeFalse)
566 | })
567 | req, _ := http.NewRequest("GET", "/req2", nil)
568 | req.Header.Set("User-Agent", "Mozilla/5.0 Version/9.0 Mobile/13B143 Safari/601.1")
569 | w := httptest.NewRecorder()
570 | b.ServeHTTP(w, req)
571 | So(w.Code, ShouldEqual, http.StatusOK)
572 | })
573 | Convey("IsAJAX", func() {
574 | b.Get("/req/ajax", func(c *Context) {
575 | isAJAX := c.IsAJAX()
576 | So(isAJAX, ShouldBeTrue)
577 | })
578 | req, _ := http.NewRequest("GET", "/req/ajax", nil)
579 | req.Header.Set("X-Requested-With", "XMLHttpRequest")
580 | w := httptest.NewRecorder()
581 | b.ServeHTTP(w, req)
582 | So(w.Code, ShouldEqual, http.StatusOK)
583 | })
584 | Convey("Get URL", func() {
585 | b.Get("/url", func(c *Context) {
586 | So(c.URL(false), ShouldEqual, "/url")
587 | So(c.URL(true), ShouldEqual, "/url?id=xx&ib=yy")
588 | })
589 | w := request("GET", "/url?id=xx&ib=yy")
590 | So(w.Code, ShouldEqual, http.StatusOK)
591 | })
592 | Convey("occur error", func() {
593 | b.Get("/error2", func(c *Context) {
594 | c.Error(nil)
595 | })
596 | b.Get("/notfound3", func(c *Context) {
597 | c.NotFound()
598 | })
599 | w := request("GET", "/error2")
600 | So(w.Code, ShouldEqual, http.StatusInternalServerError)
601 | w = request("GET", "/notfound3")
602 | So(w.Code, ShouldEqual, http.StatusNotFound)
603 | })
604 | })
605 | }
606 |
607 | func TestContextContext(t *testing.T) {
608 | Convey("context cancel", t, func() {
609 | c.Req, _ = http.NewRequest("GET", "/context", nil)
610 | ctx, cancel := context.WithCancel(c)
611 | cancel()
612 | ctx.Done()
613 | So(ctx.Err(), ShouldEqual, context.Canceled)
614 | })
615 | }
616 |
617 | func TestContextBaa(t *testing.T) {
618 | Convey("get baa", t, func() {
619 | Convey("get baa", func() {
620 | So(c.Baa(), ShouldNotBeNil)
621 | })
622 | Convey("get di", func() {
623 | logger := c.DI("logger")
624 | _, ok := logger.(Logger)
625 | So(ok, ShouldBeTrue)
626 | })
627 | })
628 | }
629 |
630 | // newfileUploadRequest Creates a new file upload http request with optional extra params
631 | func newfileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
632 | file, err := os.Open(path)
633 | if err != nil {
634 | return nil, err
635 | }
636 | defer file.Close()
637 |
638 | body := &bytes.Buffer{}
639 | writer := multipart.NewWriter(body)
640 | part, err := writer.CreateFormFile(paramName, filepath.Base(path))
641 | if err != nil {
642 | return nil, err
643 | }
644 | _, _ = io.Copy(part, file)
645 |
646 | for key, val := range params {
647 | _ = writer.WriteField(key, val)
648 | }
649 | err = writer.Close()
650 | if err != nil {
651 | return nil, err
652 | }
653 |
654 | req, err := http.NewRequest("POST", uri, body)
655 | req.Header.Add("Content-Type", writer.FormDataContentType())
656 | return req, err
657 | }
658 |
--------------------------------------------------------------------------------
/context.go:
--------------------------------------------------------------------------------
1 | package baa
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "encoding/json"
7 | "encoding/xml"
8 | "errors"
9 | "fmt"
10 | "html/template"
11 | "io"
12 | "mime/multipart"
13 | "net"
14 | "net/http"
15 | "net/url"
16 | "os"
17 | "strconv"
18 | "strings"
19 | "sync"
20 | "time"
21 | )
22 |
23 | var (
24 | // ErrJSONPayloadEmpty is returned when the JSON payload is empty.
25 | ErrJSONPayloadEmpty = errors.New("JSON payload is empty")
26 |
27 | // ErrXMLPayloadEmpty is returned when the XML payload is empty.
28 | ErrXMLPayloadEmpty = errors.New("XML payload is empty")
29 | )
30 |
31 | const (
32 | // defaultMaxMemory Maximum amount of memory to use when parsing a multipart form.
33 | // Set this to whatever value you prefer; default is 32 MB.
34 | defaultMaxMemory = 32 << 20 // 32 MB
35 |
36 | // CharsetUTF8 ...
37 | CharsetUTF8 = "charset=utf-8"
38 |
39 | // MediaTypes
40 | ApplicationJSON = "application/json"
41 | ApplicationJSONCharsetUTF8 = ApplicationJSON + "; " + CharsetUTF8
42 | ApplicationJavaScript = "application/javascript"
43 | ApplicationJavaScriptCharsetUTF8 = ApplicationJavaScript + "; " + CharsetUTF8
44 | ApplicationXML = "application/xml"
45 | ApplicationXMLCharsetUTF8 = ApplicationXML + "; " + CharsetUTF8
46 | ApplicationForm = "application/x-www-form-urlencoded"
47 | ApplicationProtobuf = "application/protobuf"
48 | TextHTML = "text/html"
49 | TextHTMLCharsetUTF8 = TextHTML + "; " + CharsetUTF8
50 | TextPlain = "text/plain"
51 | TextPlainCharsetUTF8 = TextPlain + "; " + CharsetUTF8
52 | MultipartForm = "multipart/form-data"
53 | )
54 |
55 | // Context provlider a HTTP context for baa
56 | // context contains reqest, response, header, cookie and some content type.
57 | type Context struct {
58 | Req *http.Request
59 | Resp *Response
60 | baa *Baa
61 | store map[string]interface{}
62 | storeMutex sync.RWMutex // store rw lock
63 | routeName string // route name
64 | pNames []string // route params names
65 | pValues []string // route params values
66 | handlers []HandlerFunc // middleware handler and route match handler
67 | hi int // handlers execute position
68 | }
69 |
70 | // NewContext create a http context
71 | func NewContext(w http.ResponseWriter, r *http.Request, b *Baa) *Context {
72 | c := new(Context)
73 | c.Resp = NewResponse(w, b)
74 | c.baa = b
75 | c.pNames = make([]string, 0, 16)
76 | c.pValues = make([]string, 0, 16)
77 | c.handlers = make([]HandlerFunc, len(b.middleware), len(b.middleware)+3)
78 | copy(c.handlers, b.middleware)
79 | c.Reset(w, r)
80 | return c
81 | }
82 |
83 | // RouteName return context matched route name
84 | func (c *Context) RouteName() string {
85 | return c.routeName
86 | }
87 |
88 | // Reset ...
89 | func (c *Context) Reset(w http.ResponseWriter, r *http.Request) {
90 | c.Resp.reset(w)
91 | c.Req = r
92 | c.hi = 0
93 | c.handlers = c.handlers[:len(c.baa.middleware)]
94 | c.routeName = ""
95 | c.pNames = c.pNames[:0]
96 | c.pValues = c.pValues[:0]
97 | c.storeMutex.Lock()
98 | c.store = nil
99 | c.storeMutex.Unlock()
100 | }
101 |
102 | // Set store data in context
103 | func (c *Context) Set(key string, v interface{}) {
104 | c.storeMutex.Lock()
105 | if c.store == nil {
106 | c.store = make(map[string]interface{})
107 | }
108 | c.store[key] = v
109 | c.storeMutex.Unlock()
110 | }
111 |
112 | // Get returns data from context
113 | func (c *Context) Get(key string) interface{} {
114 | c.storeMutex.RLock()
115 | defer c.storeMutex.RUnlock()
116 | if c.store == nil {
117 | return nil
118 | }
119 | return c.store[key]
120 | }
121 |
122 | // Gets returns data map from content store
123 | func (c *Context) Gets() map[string]interface{} {
124 | c.storeMutex.RLock()
125 | vals := make(map[string]interface{})
126 | for k, v := range c.store {
127 | vals[k] = v
128 | }
129 | c.storeMutex.RUnlock()
130 | return vals
131 | }
132 |
133 | // SetParam read route param value from uri
134 | func (c *Context) SetParam(name, value string) {
135 | c.pNames = append(c.pNames, name)
136 | c.pValues = append(c.pValues, value)
137 | }
138 |
139 | // Param get route param from context
140 | func (c *Context) Param(name string) string {
141 | for i := len(c.pNames) - 1; i >= 0; i-- {
142 | if c.pNames[i] == name {
143 | return c.pValues[i]
144 | }
145 | }
146 | return ""
147 | }
148 |
149 | // Params returns route params from context
150 | func (c *Context) Params() map[string]string {
151 | m := make(map[string]string)
152 | for i := 0; i < len(c.pNames); i++ {
153 | m[c.pNames[i]] = c.pValues[i]
154 | }
155 | return m
156 | }
157 |
158 | // ParamInt get route param from context and format to int
159 | func (c *Context) ParamInt(name string) int {
160 | v, _ := strconv.Atoi(c.Param(name))
161 | return v
162 | }
163 |
164 | // ParamInt32 get route param from context and format to int32
165 | func (c *Context) ParamInt32(name string) int32 {
166 | return int32(c.ParamInt64(name))
167 | }
168 |
169 | // ParamInt64 get route param from context and format to int64
170 | func (c *Context) ParamInt64(name string) int64 {
171 | v, _ := strconv.ParseInt(c.Param(name), 10, 64)
172 | return v
173 | }
174 |
175 | // ParamFloat get route param from context and format to float64
176 | func (c *Context) ParamFloat(name string) float64 {
177 | v, _ := strconv.ParseFloat(c.Param(name), 64)
178 | return v
179 | }
180 |
181 | // ParamBool get route param from context and format to bool
182 | func (c *Context) ParamBool(name string) bool {
183 | v, _ := strconv.ParseBool(c.Param(name))
184 | return v
185 | }
186 |
187 | // Query get a param from http.Request.Form
188 | func (c *Context) Query(name string) string {
189 | c.ParseForm(0)
190 | return c.Req.Form.Get(name)
191 | }
192 |
193 | // QueryTrim querys and trims spaces form parameter.
194 | func (c *Context) QueryTrim(name string) string {
195 | c.ParseForm(0)
196 | return strings.TrimSpace(c.Req.Form.Get(name))
197 | }
198 |
199 | // QueryStrings get a group param from http.Request.Form and format to string slice
200 | func (c *Context) QueryStrings(name string) []string {
201 | c.ParseForm(0)
202 | if v, ok := c.Req.Form[name]; ok {
203 | return v
204 | }
205 | return []string{}
206 | }
207 |
208 | // QueryEscape returns escapred query result.
209 | func (c *Context) QueryEscape(name string) string {
210 | c.ParseForm(0)
211 | return template.HTMLEscapeString(c.Req.Form.Get(name))
212 | }
213 |
214 | // QueryInt get a param from http.Request.Form and format to int
215 | func (c *Context) QueryInt(name string) int {
216 | c.ParseForm(0)
217 | v, _ := strconv.Atoi(c.Req.Form.Get(name))
218 | return v
219 | }
220 |
221 | // QueryInt32 get a param from http.Request.Form and format to int32
222 | func (c *Context) QueryInt32(name string) int32 {
223 | return int32(c.QueryInt64(name))
224 | }
225 |
226 | // QueryInt64 get a param from http.Request.Form and format to int64
227 | func (c *Context) QueryInt64(name string) int64 {
228 | c.ParseForm(0)
229 | v, _ := strconv.ParseInt(c.Req.Form.Get(name), 10, 64)
230 | return v
231 | }
232 |
233 | // QueryFloat get a param from http.Request.Form and format to float64
234 | func (c *Context) QueryFloat(name string) float64 {
235 | c.ParseForm(0)
236 | v, _ := strconv.ParseFloat(c.Req.Form.Get(name), 64)
237 | return v
238 | }
239 |
240 | // QueryBool get a param from http.Request.Form and format to bool
241 | func (c *Context) QueryBool(name string) bool {
242 | c.ParseForm(0)
243 | v, _ := strconv.ParseBool(c.Req.Form.Get(name))
244 | return v
245 | }
246 |
247 | // Querys return http.Request.URL queryString data
248 | func (c *Context) Querys() map[string]interface{} {
249 | params := make(map[string]interface{})
250 | var newValues url.Values
251 | if c.Req.URL != nil {
252 | newValues, _ = url.ParseQuery(c.Req.URL.RawQuery)
253 | }
254 | for k, v := range newValues {
255 | if len(v) > 1 {
256 | params[k] = v
257 | } else {
258 | params[k] = v[0]
259 | }
260 | }
261 | return params
262 | }
263 |
264 | // Posts return http.Request form data
265 | func (c *Context) Posts() map[string]interface{} {
266 | if err := c.ParseForm(0); err != nil {
267 | return nil
268 | }
269 | params := make(map[string]interface{})
270 | data := c.Req.PostForm
271 | if len(data) == 0 && len(c.Req.Form) > 0 {
272 | data = c.Req.Form
273 | }
274 | for k, v := range data {
275 | if len(v) > 1 {
276 | params[k] = v
277 | } else {
278 | params[k] = v[0]
279 | }
280 | }
281 | return params
282 | }
283 |
284 | // QueryJSON decode json from http.Request.Body
285 | func (c *Context) QueryJSON(v interface{}) error {
286 | content, err := c.Body().Bytes()
287 | if err != nil {
288 | return err
289 | }
290 | if len(content) == 0 {
291 | return ErrJSONPayloadEmpty
292 | }
293 | return json.Unmarshal(content, v)
294 | }
295 |
296 | // QueryXML decode xml from http.Request.Body
297 | func (c *Context) QueryXML(v interface{}) error {
298 | content, err := c.Body().Bytes()
299 | if err != nil {
300 | return err
301 | }
302 | if len(content) == 0 {
303 | return ErrXMLPayloadEmpty
304 | }
305 | return xml.Unmarshal(content, v)
306 | }
307 |
308 | // GetFile returns information about user upload file by given form field name.
309 | func (c *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) {
310 | if err := c.ParseForm(0); err != nil {
311 | return nil, nil, err
312 | }
313 | return c.Req.FormFile(name)
314 | }
315 |
316 | // SaveToFile reads a file from request by field name and saves to given path.
317 | func (c *Context) SaveToFile(name, savePath string) error {
318 | fr, _, err := c.GetFile(name)
319 | if err != nil {
320 | return err
321 | }
322 | defer fr.Close()
323 |
324 | fw, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
325 | if err != nil {
326 | return err
327 | }
328 | defer fw.Close()
329 |
330 | _, err = io.Copy(fw, fr)
331 | return err
332 | }
333 |
334 | // Body get raw request body and return RequestBody
335 | func (c *Context) Body() *RequestBody {
336 | return NewRequestBody(c.Req.Body)
337 | }
338 |
339 | // SetCookie sets given cookie value to response header.
340 | // full params example:
341 | // SetCookie(