├── .gitignore
├── public
├── js
│ └── my.js
├── test.html
├── index.html
├── cert.pem
└── key.pem
├── logo.png
├── go.mod
├── go.sum
├── prefix.go
├── RELEASE.md
├── .circleci
└── config.yml
├── file.go
├── handler_test.go
├── recovery_test.go
├── pool.go
├── pool_test.go
├── LICENSE
├── recovery.go
├── prefix_test.go
├── doc.go
├── response.go
├── file_test.go
├── error.go
├── tan_test.go
├── logger_test.go
├── compress_test.go
├── logger.go
├── group.go
├── compress.go
├── static.go
├── error_test.go
├── return.go
├── static_test.go
├── tan.go
├── form.go
├── README_CN.md
├── README.md
├── query.go
├── return_test.go
├── param.go
├── context_test.go
├── group_test.go
├── context.go
├── router.go
└── form_test.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
--------------------------------------------------------------------------------
/public/js/my.js:
--------------------------------------------------------------------------------
1 | var test
--------------------------------------------------------------------------------
/public/test.html:
--------------------------------------------------------------------------------
1 | hello tango
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 | this is index.html
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lunny/tango/HEAD/logo.png
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/lunny/tango
2 |
3 | require gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e
4 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk=
2 | gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
3 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
4 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
5 |
--------------------------------------------------------------------------------
/prefix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "strings"
9 | )
10 |
11 | // Prefix provides a middleware to wrap another middleware with a prefix URL
12 | // TODO: regex prefix
13 | func Prefix(prefix string, handler Handler) HandlerFunc {
14 | return func(ctx *Context) {
15 | if strings.HasPrefix(ctx.Req().URL.Path, prefix) {
16 | handler.Handle(ctx)
17 | return
18 | }
19 |
20 | ctx.Next()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release Log
2 |
3 | ## v0.2.9
4 |
5 | * Add `Prefix` handler to wrap a handler filtered by request url prefix
6 | * Add `Use` method for `Group` for supporting apply middleware with group and resolved #2
7 |
8 | ## v0.2.8
9 |
10 | New Features:
11 | * cookie support
12 | * custom error handler
13 | * custom routes' method
14 |
15 | Improvements on internal middlewares:
16 | * return handler
17 | * static handler added more options
18 |
19 | Added external middlewares
20 | * events
21 | * debug
22 | * renders
23 |
24 | Added a new example [dbweb](github.com/go-xorm/dbweb)
25 |
26 | ## v0.1
27 |
28 | First version
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Golang CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-go/ for more details
4 | version: 2
5 | jobs:
6 | build:
7 | docker:
8 | # specify the version
9 | - image: circleci/golang:1.10
10 |
11 | working_directory: /go/src/github.com/lunny/tango
12 | steps:
13 | - checkout
14 |
15 | - run: go get -t -d -v ./...
16 | - run: GO111MODULE=off go build -v
17 | - run: GO111MODULE=on go build -v
18 | - run: go test -v -race -coverprofile=coverage.txt -covermode=atomic
19 |
20 | - run: bash <(curl -s https://codecov.io/bash)
--------------------------------------------------------------------------------
/file.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import "path/filepath"
8 |
9 | // File returns a handle to serve a file
10 | func File(path string) func(ctx *Context) {
11 | return func(ctx *Context) {
12 | ctx.ServeFile(path)
13 | }
14 | }
15 |
16 | // Dir returns a handle to serve a directory
17 | func Dir(dir string) func(ctx *Context) {
18 | return func(ctx *Context) {
19 | params := ctx.Params()
20 | if len(*params) <= 0 {
21 | ctx.Result = NotFound()
22 | ctx.HandleError()
23 | return
24 | }
25 | ctx.ServeFile(filepath.Join(dir, (*params)[0].Value))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/handler_test.go:
--------------------------------------------------------------------------------
1 | package tango
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 | )
10 |
11 | func TestGroupGroup(t *testing.T) {
12 | var a int
13 | tg := Classic()
14 | tg.Group("/api", func(api *Group) {
15 | api.Group("/v1", func(v1 *Group) {
16 | v1.Use(HandlerFunc(func(ctx *Context) {
17 | a = 1
18 | ctx.Next()
19 | }))
20 |
21 | v1.Get("/", func(ctx *Context) {
22 | fmt.Println("context")
23 | })
24 |
25 | })
26 | })
27 |
28 | buff := bytes.NewBufferString("")
29 | recorder := httptest.NewRecorder()
30 | recorder.Body = buff
31 |
32 | req, err := http.NewRequest("GET", "http://localhost:8000/api/v1", nil)
33 | if err != nil {
34 | t.Error(err)
35 | }
36 | tg.ServeHTTP(recorder, req)
37 | expect(t, recorder.Code, http.StatusOK)
38 | expect(t, buff.String(), "")
39 | expect(t, 1, a)
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/recovery_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "net/http"
10 | "net/http/httptest"
11 | "os"
12 | "testing"
13 | )
14 |
15 | func TestRecovery(t *testing.T) {
16 | buff := bytes.NewBufferString("")
17 | recorder := httptest.NewRecorder()
18 | recorder.Body = buff
19 |
20 | n := NewWithLog(NewLogger(os.Stdout))
21 | n.Use(Recovery(true))
22 | n.UseHandler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
23 | panic("here is a panic!")
24 | }))
25 |
26 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
27 | if err != nil {
28 | t.Error(err)
29 | }
30 |
31 | n.ServeHTTP(recorder, req)
32 | expect(t, recorder.Code, http.StatusInternalServerError)
33 | refute(t, recorder.Body.Len(), 0)
34 | refute(t, len(buff.String()), 0)
35 | }
36 |
--------------------------------------------------------------------------------
/pool.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "reflect"
9 | "sync"
10 | )
11 |
12 | type pool struct {
13 | size int
14 | tp reflect.Type
15 | stp reflect.Type
16 | pool reflect.Value
17 | cur int
18 | lock sync.Mutex
19 | }
20 |
21 | func newPool(size int, tp reflect.Type) *pool {
22 | return &pool{
23 | size: size,
24 | cur: 0,
25 | pool: reflect.MakeSlice(reflect.SliceOf(tp), 0, 0), // init for don't allocate memory
26 | tp: reflect.SliceOf(tp),
27 | stp: tp,
28 | }
29 | }
30 |
31 | func (p *pool) New() reflect.Value {
32 | if p.size == 0 {
33 | return reflect.New(p.stp)
34 | }
35 |
36 | p.lock.Lock()
37 | if p.cur == p.pool.Len() {
38 | p.pool = reflect.MakeSlice(p.tp, p.size, p.size)
39 | p.cur = 0
40 | }
41 | res := p.pool.Index(p.cur).Addr()
42 | p.cur++
43 | p.lock.Unlock()
44 | return res
45 | }
46 |
--------------------------------------------------------------------------------
/pool_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "net/http"
10 | "net/http/httptest"
11 | "sync"
12 | "testing"
13 | )
14 |
15 | type PoolAction struct {
16 | }
17 |
18 | func (PoolAction) Get() string {
19 | return "pool"
20 | }
21 |
22 | func TestPool(t *testing.T) {
23 | o := Classic()
24 | o.Get("/", new(PoolAction))
25 |
26 | var wg sync.WaitGroup
27 | // default pool size is 800
28 | for i := 0; i < 1000; i++ {
29 | wg.Add(1)
30 | go func() {
31 | buff := bytes.NewBufferString("")
32 | recorder := httptest.NewRecorder()
33 | recorder.Body = buff
34 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
35 | if err != nil {
36 | t.Error(err)
37 | }
38 |
39 | o.ServeHTTP(recorder, req)
40 | expect(t, recorder.Code, http.StatusOK)
41 | refute(t, len(buff.String()), 0)
42 | expect(t, buff.String(), "pool")
43 | wg.Done()
44 | }()
45 | }
46 | wg.Wait()
47 | }
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 - 2015 The Tango Authors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/public/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC8TCCAdugAwIBAgIRAIQ6ko7wQnT7LktA0f0iotYwCwYJKoZIhvcNAQELMBIx
3 | EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTUwNDE2MTUxNzQyWhcNMTYwNDE1MTUxNzQy
4 | WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
5 | CgKCAQEAn+nHPLXpiibd8vdizD3DmaRZdicUJ3Ftrjq8UvLWgGUsmYk05bESWT/I
6 | vGRcOpQqhc9ugctZraFK7ihn6c+A7fGOYdXlY/wCNt12evAY8P9uxJQ4lQPY9qUs
7 | hegJyCGD/HtUr/AdbGBzBLtmU5Z7ZOe73TzO6zmiVnLwasO2AoVvUCRCFXQkZkIk
8 | HO/evHq03lo/z0PyBWbT6YKSM9mLFTYKUdAkC/gr5FKcGCqyMsMmewiupPtRSnln
9 | ZrDpzp9+oGl92lkj885714BkROfP2KGTbTSlpbVfmtvcb6TQK8mfithnQzs/9jDw
10 | LGSWBWQEdomTzPmKw07zNHYkXhcpqwIDAQABo0YwRDAOBgNVHQ8BAf8EBAMCAKAw
11 | EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAPBgNVHREECDAGhwR/
12 | AAABMAsGCSqGSIb3DQEBCwOCAQEAQSVmRqDi/UXXhF+qD5JxrUNG4ssBvFl91b0n
13 | Po8W4InE4WaNqTfAxnsDwgglNMGijGp30cpp5ZdbpdHnpVEjPG9wTrSJBCoMwcnH
14 | rnDau97FuVLff+UROghwtXHhMt1bdLMdle2rdHpyd69awNBuI4wY+Dg/BmVZq+JM
15 | DTfGd4X5FND72JhOxx62+p2S23jmcb+0vnNvctT88ekaWVOicF8bs1XY0Pm1KzA/
16 | l5mIUiSl2zR6Met0b+LK3F7d4H+uhuZql0syPZ8HSAJweUAMX3L5KASB5QJFtXwO
17 | DMWBDzqT/nEsT77Cb16tKNhVVeb6U9h2LezxB0M8+NfLL8alxw==
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/recovery.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "net/http"
11 | "runtime"
12 | )
13 |
14 | // Recovery returns a middleware which catch panics and log them
15 | func Recovery(debug bool) HandlerFunc {
16 | return func(ctx *Context) {
17 | defer func() {
18 | if e := recover(); e != nil {
19 | var buf bytes.Buffer
20 | fmt.Fprintf(&buf, "Handler crashed with error: %v", e)
21 |
22 | for i := 1; ; i++ {
23 | _, file, line, ok := runtime.Caller(i)
24 | if !ok {
25 | break
26 | } else {
27 | fmt.Fprintf(&buf, "\n")
28 | }
29 | fmt.Fprintf(&buf, "%v:%v", file, line)
30 | }
31 |
32 | var content = buf.String()
33 | ctx.Logger.Error(content)
34 |
35 | if !ctx.Written() {
36 | if !debug {
37 | ctx.Result = InternalServerError(http.StatusText(http.StatusInternalServerError))
38 | } else {
39 | ctx.Result = InternalServerError(content)
40 | }
41 | }
42 | }
43 | }()
44 |
45 | ctx.Next()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/prefix_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "net/http"
10 | "net/http/httptest"
11 | "testing"
12 | )
13 |
14 | type PrefixAction struct {
15 | }
16 |
17 | func (PrefixAction) Get() string {
18 | return "Prefix"
19 | }
20 |
21 | type NoPrefixAction struct {
22 | }
23 |
24 | func (NoPrefixAction) Get() string {
25 | return "NoPrefix"
26 | }
27 |
28 | func TestPrefix(t *testing.T) {
29 | buff := bytes.NewBufferString("")
30 | recorder := httptest.NewRecorder()
31 | recorder.Body = buff
32 |
33 | var isPrefix bool
34 |
35 | o := Classic()
36 | o.Use(Prefix("/prefix", HandlerFunc(func(ctx *Context) {
37 | isPrefix = true
38 | ctx.Next()
39 | })))
40 | o.Get("/prefix/t", new(PrefixAction))
41 | o.Get("/t", new(NoPrefixAction))
42 |
43 | req, err := http.NewRequest("GET", "http://localhost:8000/prefix/t", nil)
44 | if err != nil {
45 | t.Error(err)
46 | }
47 |
48 | o.ServeHTTP(recorder, req)
49 | expect(t, recorder.Code, http.StatusOK)
50 | refute(t, len(buff.String()), 0)
51 | expect(t, buff.String(), "Prefix")
52 | expect(t, isPrefix, true)
53 |
54 | isPrefix = false
55 | buff.Reset()
56 |
57 | req, err = http.NewRequest("GET", "http://localhost:8000/t", nil)
58 | if err != nil {
59 | t.Error(err)
60 | }
61 |
62 | o.ServeHTTP(recorder, req)
63 | expect(t, recorder.Code, http.StatusOK)
64 | refute(t, len(buff.String()), 0)
65 | expect(t, buff.String(), "NoPrefix")
66 | expect(t, isPrefix, false)
67 | }
68 |
--------------------------------------------------------------------------------
/public/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEogIBAAKCAQEAn+nHPLXpiibd8vdizD3DmaRZdicUJ3Ftrjq8UvLWgGUsmYk0
3 | 5bESWT/IvGRcOpQqhc9ugctZraFK7ihn6c+A7fGOYdXlY/wCNt12evAY8P9uxJQ4
4 | lQPY9qUshegJyCGD/HtUr/AdbGBzBLtmU5Z7ZOe73TzO6zmiVnLwasO2AoVvUCRC
5 | FXQkZkIkHO/evHq03lo/z0PyBWbT6YKSM9mLFTYKUdAkC/gr5FKcGCqyMsMmewiu
6 | pPtRSnlnZrDpzp9+oGl92lkj885714BkROfP2KGTbTSlpbVfmtvcb6TQK8mfithn
7 | Qzs/9jDwLGSWBWQEdomTzPmKw07zNHYkXhcpqwIDAQABAoIBAFWkA7m1yr7cFd17
8 | M4QiR9DOvcKTJy4AhzbZ6eWae9oDVSFc4+FnNWZqzHxoWyRcGXHUJ2CHoR1l1hU5
9 | unzzTh8gUJqAzPsBCcaMUFmCoDjg81d/8dWMW/Orfe6w2BxAJsle23nl5DwYY0DT
10 | g/ecDbV6jZfsavx6v0ABClSDP8SVDMwpgqLzLgqaKdcLzx1TKNcJXfMAGO8knXcG
11 | YHTmQuh/SVVbNd1yjsFUMfL/wrq2Wd18vWlUV8BmicARw2BxkariUoTOs1Ze1tPj
12 | CvBPJ51dmdz8TEbP+VQpetfw8Sj70GHdUhvysVRf5gaI5BtDgbjTpfBfbaPsVJu4
13 | biGkqWECgYEAzWKLtY/rVcnIB0M2A5bTjD/97Sfq5Hf7qAbYnBpnw/D2utSdYow3
14 | 2Qo6Ng6TXCGEDhpyWfUy7jjXmBIhurO5GCNdK650JNoRWELE+TWEKg3qfQigAxBI
15 | IRQRiARKWJ0e2we9Bxqe09EMZaA70GClQYB7hWaXZhmf4RjNBhwc07kCgYEAx1J7
16 | e/2IzMKbmlcCCPG7tilwQdEY+++cpgfzh/Arp/ZLZ6u6qSlye2gZkiRcm6W6H7dS
17 | SX9S+iJKBRTm6ysk0gagAu/x1ScsPrV3yJJ5T5qRG03/ZLlAmtJzc9h04Jqi8ErQ
18 | qFCazvgZ+g2Ls4n+fPeA8Y9SNpxHKJKhar2aYoMCgYABwUXQV1p7cS30Ye6kOTW1
19 | jRZuYFjxetT7qpNPQiqA0h5Jmmd94BTaFexJafZ4YxDtzewMOLwmrPWqpv0Cy2ZZ
20 | fnPdW7BCYFqllmx4dKycb2IBj4FOhWUYY0ODFgZMm4sX9Aj5dpDE3pRsieH49dpz
21 | pNVpXmcMyEtFcSDPXI4igQKBgDaffAe2q06x5kKdpYkd9fstz/25d8dTGvLFKxAN
22 | 2WjmLjPy8+x310/Kb3eFT3u4JxGaA4rwwaSa0P4jhETeRfDor+EeMH/hhFaLFJB6
23 | 05PlH+8DqQHJYtMK6WjN4PnMZurDFfuKW2Jsy3GjVK2XG47TpRqN1FHy8e1Egcfm
24 | vfBRAoGAaMKW2EYGI98m/0UhnaBf5mCqcewpU3Cusht3+9orC2PFtiQwF2JkBfF/
25 | zcSgoO9VsTqpVWVmUI5LaVWG/1CscSIdl4tAVxkXiUbSniufYZko9xUXPDdB3a6/
26 | H4vpV8K07EtKToJnA+qZC/szyTwFZx3yuqtXAEtyW5hhZ873dbI=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Tango is a micro & pluggable web framework for Go language.
6 |
7 | // package main
8 |
9 | // import "github.com/lunny/tango"
10 |
11 | // type Action struct {
12 | // }
13 |
14 | // func (Action) Get() string {
15 | // return "Hello tango!"
16 | // }
17 |
18 | // func main() {
19 | // t := tango.Classic()
20 | // t.Get("/", new(Action))
21 | // t.Run()
22 | // }
23 |
24 | // Middlewares allow you easily plugin/unplugin features for your Tango applications.
25 |
26 | // There are already many [middlewares](https://github.com/tango-contrib) to simplify your work:
27 |
28 | // - recovery - recover after panic
29 | // - compress - Gzip & Deflate compression
30 | // - static - Serves static files
31 | // - logger - Log the request & inject Logger to action struct
32 | // - param - get the router parameters
33 | // - return - Handle the returned value smartlly
34 | // - ctx - Inject context to action struct
35 |
36 | // - [session](https://github.com/tango-contrib/session) - Session manager, with stores support:
37 | // * Memory - memory as a session store
38 | // * [Redis](https://github.com/tango-contrib/session-redis) - redis server as a session store
39 | // * [nodb](https://github.com/tango-contrib/session-nodb) - nodb as a session store
40 | // * [ledis](https://github.com/tango-contrib/session-ledis) - ledis server as a session store)
41 | // - [xsrf](https://github.com/tango-contrib/xsrf) - Generates and validates csrf tokens
42 | // - [binding](https://github.com/tango-contrib/binding) - Bind and validates forms
43 | // - [renders](https://github.com/tango-contrib/renders) - Go template engine
44 | // - [dispatch](https://github.com/tango-contrib/dispatch) - Multiple Application support on one server
45 | // - [tpongo2](https://github.com/tango-contrib/tpongo2) - Pongo2 teamplte engine support
46 | // - [captcha](https://github.com/tango-contrib/captcha) - Captcha
47 | // - [events](https://github.com/tango-contrib/events) - Before and After
48 | // - [flash](https://github.com/tango-contrib/flash) - Share data between requests
49 | // - [debug](https://github.com/tango-contrib/debug) - Show detail debug infomaton on log
50 |
51 | package tango
52 |
--------------------------------------------------------------------------------
/response.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bufio"
9 | "fmt"
10 | "net"
11 | "net/http"
12 | )
13 |
14 | // ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about
15 | // the response. It is recommended that middleware handlers use this construct to wrap a responsewriter
16 | // if the functionality calls for it.
17 | type ResponseWriter interface {
18 | http.ResponseWriter
19 | http.Flusher
20 | http.Hijacker
21 | // Status returns the status code of the response or 0 if the response has not been written.
22 | Status() int
23 | // Written returns whether or not the ResponseWriter has been written.
24 | Written() bool
25 | // Size returns the size of the response body.
26 | Size() int
27 | }
28 |
29 | type responseWriter struct {
30 | http.ResponseWriter
31 | status int
32 | size int
33 | }
34 |
35 | func (rw *responseWriter) reset(w http.ResponseWriter) {
36 | rw.ResponseWriter = w
37 | rw.status = 0
38 | rw.size = 0
39 | }
40 |
41 | func (rw *responseWriter) WriteHeader(s int) {
42 | rw.status = s
43 | rw.ResponseWriter.WriteHeader(s)
44 | }
45 |
46 | func (rw *responseWriter) Write(b []byte) (int, error) {
47 | if !rw.Written() {
48 | // The status will be StatusOK if WriteHeader has not been called yet
49 | rw.WriteHeader(http.StatusOK)
50 | }
51 | size, err := rw.ResponseWriter.Write(b)
52 | rw.size += size
53 | return size, err
54 | }
55 |
56 | func (rw *responseWriter) Status() int {
57 | return rw.status
58 | }
59 |
60 | func (rw *responseWriter) Size() int {
61 | return rw.size
62 | }
63 |
64 | func (rw *responseWriter) Written() bool {
65 | return rw.status != 0
66 | }
67 |
68 | func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
69 | hijacker, ok := rw.ResponseWriter.(http.Hijacker)
70 | if !ok {
71 | return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
72 | }
73 | return hijacker.Hijack()
74 | }
75 |
76 | func (rw *responseWriter) CloseNotify() <-chan bool {
77 | return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
78 | }
79 |
80 | func (rw *responseWriter) Flush() {
81 | flusher, ok := rw.ResponseWriter.(http.Flusher)
82 | if ok {
83 | flusher.Flush()
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/file_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "net/http"
10 | "net/http/httptest"
11 | "testing"
12 | )
13 |
14 | func TestDir1(t *testing.T) {
15 | buff := bytes.NewBufferString("")
16 | recorder := httptest.NewRecorder()
17 | recorder.Body = buff
18 |
19 | tg := New()
20 | tg.Get("/:name", Dir("./public"))
21 |
22 | req, err := http.NewRequest("GET", "http://localhost:8000/test.html", nil)
23 | if err != nil {
24 | t.Error(err)
25 | }
26 |
27 | tg.ServeHTTP(recorder, req)
28 | expect(t, recorder.Code, http.StatusOK)
29 | refute(t, len(buff.String()), 0)
30 | expect(t, buff.String(), "hello tango")
31 | }
32 |
33 | func TestDir2(t *testing.T) {
34 | buff := bytes.NewBufferString("")
35 | recorder := httptest.NewRecorder()
36 | recorder.Body = buff
37 |
38 | tg := New()
39 | tg.Get("/", Dir("./public"))
40 |
41 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
42 | if err != nil {
43 | t.Error(err)
44 | }
45 |
46 | tg.ServeHTTP(recorder, req)
47 | expect(t, recorder.Code, http.StatusNotFound)
48 | refute(t, len(buff.String()), 0)
49 | expect(t, buff.String(), http.StatusText(http.StatusNotFound))
50 | }
51 |
52 | func TestDir3(t *testing.T) {
53 | buff := bytes.NewBufferString("")
54 | recorder := httptest.NewRecorder()
55 | recorder.Body = buff
56 |
57 | tg := New()
58 | tg.Get("/*name", Dir("./public"))
59 |
60 | req, err := http.NewRequest("GET", "http://localhost:8000/js/my.js", nil)
61 | if err != nil {
62 | t.Error(err)
63 | }
64 |
65 | var res = "var test"
66 | tg.ServeHTTP(recorder, req)
67 | expect(t, recorder.Code, http.StatusOK)
68 | expect(t, len(buff.String()), len(res))
69 | expect(t, buff.String(), res)
70 | }
71 |
72 | func TestFile1(t *testing.T) {
73 | buff := bytes.NewBufferString("")
74 | recorder := httptest.NewRecorder()
75 | recorder.Body = buff
76 |
77 | tg := New()
78 | tg.Get("/test.html", File("./public/test.html"))
79 |
80 | req, err := http.NewRequest("GET", "http://localhost:8000/test.html", nil)
81 | if err != nil {
82 | t.Error(err)
83 | }
84 |
85 | tg.ServeHTTP(recorder, req)
86 | expect(t, recorder.Code, http.StatusOK)
87 | refute(t, len(buff.String()), 0)
88 | expect(t, buff.String(), "hello tango")
89 | }
90 |
--------------------------------------------------------------------------------
/error.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "fmt"
9 | "net/http"
10 | )
11 |
12 | // AbortError defines an interface to describe HTTP error
13 | type AbortError interface {
14 | error
15 | Code() int
16 | }
17 |
18 | type abortError struct {
19 | code int
20 | content string
21 | }
22 |
23 | func (a *abortError) Code() int {
24 | return a.code
25 | }
26 |
27 | func (a *abortError) Error() string {
28 | return fmt.Sprintf("%v", a.content)
29 | }
30 |
31 | // Abort returns an AbortError
32 | func Abort(code int, content ...string) AbortError {
33 | if len(content) >= 1 {
34 | return &abortError{code, content[0]}
35 | }
36 | return &abortError{code, http.StatusText(code)}
37 | }
38 |
39 | // NotFound returns not found HTTP error
40 | func NotFound(content ...string) AbortError {
41 | return Abort(http.StatusNotFound, content...)
42 | }
43 |
44 | // NotSupported returns not supported HTTP error
45 | func NotSupported(content ...string) AbortError {
46 | return Abort(http.StatusMethodNotAllowed, content...)
47 | }
48 |
49 | // InternalServerError returns internal server HTTP error
50 | func InternalServerError(content ...string) AbortError {
51 | return Abort(http.StatusInternalServerError, content...)
52 | }
53 |
54 | // Forbidden returns forbidden HTTP error
55 | func Forbidden(content ...string) AbortError {
56 | return Abort(http.StatusForbidden, content...)
57 | }
58 |
59 | // Unauthorized returns unauthorized HTTP error
60 | func Unauthorized(content ...string) AbortError {
61 | return Abort(http.StatusUnauthorized, content...)
62 | }
63 |
64 | // Errors returns default errorhandler, you can use your self handler
65 | func Errors() HandlerFunc {
66 | return func(ctx *Context) {
67 | switch res := ctx.Result.(type) {
68 | case AbortError:
69 | ctx.WriteHeader(res.Code())
70 | ctx.WriteString(res.Error())
71 | case error:
72 | ctx.WriteHeader(http.StatusInternalServerError)
73 | ctx.WriteString(res.Error())
74 | default:
75 | ctx.WriteHeader(http.StatusInternalServerError)
76 | ctx.WriteString(http.StatusText(http.StatusInternalServerError))
77 | }
78 | }
79 | }
80 |
81 | // ErrorWithCode descripts an error that with error code
82 | type ErrorWithCode interface {
83 | error
84 | ErrorCode() int
85 | }
86 |
87 | type ErrorKeyIsNotExist struct {
88 | Key string
89 | }
90 |
91 | func (e ErrorKeyIsNotExist) Error() string {
92 | return fmt.Sprintf("Key %s is not exist", e.Key)
93 | }
94 |
--------------------------------------------------------------------------------
/tan_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "io/ioutil"
10 | "net/http"
11 | "net/http/httptest"
12 | "os"
13 | "reflect"
14 | "testing"
15 | "time"
16 | )
17 |
18 | func TestTan1(t *testing.T) {
19 | buff := bytes.NewBufferString("")
20 | recorder := httptest.NewRecorder()
21 | recorder.Body = buff
22 |
23 | l := NewLogger(os.Stdout)
24 | o := Classic(l)
25 | o.Get("/", func() string {
26 | return Version()
27 | })
28 | o.Logger().Debug("it's ok")
29 |
30 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
31 | if err != nil {
32 | t.Error(err)
33 | }
34 |
35 | o.ServeHTTP(recorder, req)
36 | expect(t, recorder.Code, http.StatusOK)
37 | refute(t, len(buff.String()), 0)
38 | expect(t, buff.String(), Version())
39 | }
40 |
41 | func TestTan2(t *testing.T) {
42 | o := Classic()
43 | o.Get("/", func() string {
44 | return Version()
45 | })
46 | go o.Run()
47 |
48 | time.Sleep(100 * time.Millisecond)
49 |
50 | resp, err := http.Get("http://localhost:8000/")
51 | if err != nil {
52 | t.Error(err)
53 | }
54 | bs, err := ioutil.ReadAll(resp.Body)
55 | if err != nil {
56 | t.Error(err)
57 | }
58 |
59 | expect(t, resp.StatusCode, http.StatusOK)
60 | expect(t, string(bs), Version())
61 | }
62 |
63 | func TestTan3(t *testing.T) {
64 | o := Classic()
65 | o.Get("/", func() string {
66 | return Version()
67 | })
68 | go o.Run(":4040")
69 |
70 | time.Sleep(100 * time.Millisecond)
71 |
72 | resp, err := http.Get("http://localhost:4040/")
73 | if err != nil {
74 | t.Error(err)
75 | }
76 | bs, err := ioutil.ReadAll(resp.Body)
77 | if err != nil {
78 | t.Error(err)
79 | }
80 |
81 | expect(t, resp.StatusCode, http.StatusOK)
82 | expect(t, string(bs), Version())
83 | }
84 |
85 | /*
86 | func TestTan4(t *testing.T) {
87 | o := Classic()
88 | o.Get("/", func() string {
89 | return Version()
90 | })
91 | go o.RunTLS("./public/cert.pem", "./public/key.pem", ":5050")
92 |
93 | time.Sleep(100 * time.Millisecond)
94 |
95 | resp, err := http.Get("https://localhost:5050/")
96 | if err != nil {
97 | t.Error(err)
98 | }
99 | bs, err := ioutil.ReadAll(resp.Body)
100 | if err != nil {
101 | t.Error(err)
102 | }
103 |
104 | expect(t, resp.StatusCode, http.StatusOK)
105 | expect(t, string(bs), Version())
106 | }*/
107 |
108 | /* Test Helpers */
109 | func expect(t *testing.T, a interface{}, b interface{}) {
110 | if a != b {
111 | t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
112 | }
113 | }
114 |
115 | func refute(t *testing.T, a interface{}, b interface{}) {
116 | if a == b {
117 | t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/logger_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "net/http"
10 | "net/http/httptest"
11 | "testing"
12 |
13 | "gitea.com/lunny/log"
14 | )
15 |
16 | func TestLogger1(t *testing.T) {
17 | buff := bytes.NewBufferString("")
18 | recorder := httptest.NewRecorder()
19 |
20 | n := NewWithLog(log.New(buff, "[tango] ", 0))
21 | n.Use(Logging())
22 | n.UseHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
23 | rw.WriteHeader(http.StatusNotFound)
24 | }))
25 |
26 | req, err := http.NewRequest("GET", "http://localhost:3000/foobar", nil)
27 | if err != nil {
28 | t.Error(err)
29 | }
30 |
31 | n.ServeHTTP(recorder, req)
32 | expect(t, recorder.Code, http.StatusNotFound)
33 | refute(t, len(buff.String()), 0)
34 | }
35 |
36 | type LoggerAction struct {
37 | Log
38 | }
39 |
40 | func (l *LoggerAction) Get() string {
41 | l.Warn("this is a warn")
42 | l.Warnf("This is a %s", "warnf")
43 | l.Error("this is an error")
44 | l.Errorf("This is a %s", "errorf")
45 | l.Infof("This is a %s", "infof")
46 | l.Debugf("This is a %s", "debuf")
47 | return "log"
48 | }
49 |
50 | func TestLogger2(t *testing.T) {
51 | buff := bytes.NewBufferString("")
52 | recorder := httptest.NewRecorder()
53 | recorder.Body = buff
54 |
55 | n := Classic()
56 | n.Get("/", new(LoggerAction))
57 |
58 | req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
59 | if err != nil {
60 | t.Error(err)
61 | }
62 |
63 | n.ServeHTTP(recorder, req)
64 | expect(t, recorder.Code, http.StatusOK)
65 | refute(t, len(buff.String()), 0)
66 | expect(t, buff.String(), "log")
67 | }
68 |
69 | func TestLogger3(t *testing.T) {
70 | buff := bytes.NewBufferString("")
71 | recorder := httptest.NewRecorder()
72 | recorder.Body = buff
73 |
74 | logger := NewCompositeLogger(log.Std, log.New(log.NewFileWriter(log.FileOptions{
75 | Dir: "./",
76 | ByType: log.ByDay,
77 | }), "file", log.Ldefault()))
78 |
79 | n := Classic(logger)
80 | n.Get("/", new(LoggerAction))
81 |
82 | req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
83 | if err != nil {
84 | t.Error(err)
85 | }
86 |
87 | n.ServeHTTP(recorder, req)
88 | expect(t, recorder.Code, http.StatusOK)
89 | refute(t, len(buff.String()), 0)
90 | expect(t, buff.String(), "log")
91 | }
92 |
93 | type Logger4Action struct {
94 | }
95 |
96 | func (l *Logger4Action) Get() {
97 | }
98 |
99 | func TestLogger4(t *testing.T) {
100 | buff := bytes.NewBufferString("")
101 | recorder := httptest.NewRecorder()
102 | recorder.Body = buff
103 |
104 | n := Classic()
105 | n.Get("/", new(Logger4Action))
106 |
107 | req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
108 | if err != nil {
109 | t.Error(err)
110 | }
111 |
112 | n.ServeHTTP(recorder, req)
113 | expect(t, recorder.Code, http.StatusOK)
114 | }
115 |
--------------------------------------------------------------------------------
/compress_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "compress/flate"
10 | "compress/gzip"
11 | "fmt"
12 | "io/ioutil"
13 | "net/http"
14 | "net/http/httptest"
15 | "testing"
16 | )
17 |
18 | type CompressExample struct {
19 | Compress // add this for ask compress according request accept-encoding
20 | }
21 |
22 | func (CompressExample) Get() string {
23 | return fmt.Sprintf("This is a auto compress text")
24 | }
25 |
26 | type GZipExample struct {
27 | GZip // add this for ask compress to GZip
28 | }
29 |
30 | func (GZipExample) Get() string {
31 | return fmt.Sprintf("This is a gzip compress text")
32 | }
33 |
34 | type DeflateExample struct {
35 | Deflate // add this for ask compress to Deflate, if not support then not compress
36 | }
37 |
38 | func (DeflateExample) Get() string {
39 | return fmt.Sprintf("This is a deflate compress text")
40 | }
41 |
42 | type NoCompress struct {
43 | }
44 |
45 | func (NoCompress) Get() string {
46 | return fmt.Sprintf("This is a non-compress text")
47 | }
48 |
49 | func TestCompressAuto(t *testing.T) {
50 | o := Classic()
51 | o.Get("/", new(CompressExample))
52 | testCompress(t, o, "http://localhost:8000/",
53 | "This is a auto compress text", "gzip")
54 | }
55 |
56 | func TestCompressGzip(t *testing.T) {
57 | o := Classic()
58 | o.Get("/", new(GZipExample))
59 | testCompress(t, o, "http://localhost:8000/",
60 | "This is a gzip compress text", "gzip")
61 | }
62 |
63 | func TestCompressDeflate(t *testing.T) {
64 | o := Classic()
65 | o.Get("/", new(DeflateExample))
66 | testCompress(t, o, "http://localhost:8000/",
67 | "This is a deflate compress text", "deflate")
68 | }
69 |
70 | func TestCompressNon(t *testing.T) {
71 | o := Classic()
72 | o.Get("/", new(NoCompress))
73 | testCompress(t, o, "http://localhost:8000/",
74 | "This is a non-compress text", "")
75 | }
76 |
77 | func TestCompressStatic(t *testing.T) {
78 | o := New()
79 | o.Use(Compresses([]string{".html"}))
80 | o.Use(ClassicHandlers...)
81 | testCompress(t, o, "http://localhost:8000/public/test.html",
82 | "hello tango", "gzip")
83 | }
84 |
85 | func testCompress(t *testing.T, o *Tango, url, content, enc string) {
86 | buff := bytes.NewBufferString("")
87 | recorder := httptest.NewRecorder()
88 | recorder.Body = buff
89 |
90 | req, err := http.NewRequest("GET", url, nil)
91 | if err != nil {
92 | t.Error(err)
93 | }
94 | req.Header.Add("Accept-Encoding", "gzip, deflate")
95 |
96 | o.ServeHTTP(recorder, req)
97 | expect(t, recorder.Code, http.StatusOK)
98 | refute(t, len(buff.String()), 0)
99 |
100 | ce := recorder.Header().Get("Content-Encoding")
101 | if ce == "gzip" {
102 | r, err := gzip.NewReader(buff)
103 | if err != nil {
104 | t.Error(err)
105 | }
106 | defer r.Close()
107 |
108 | bs, err := ioutil.ReadAll(r)
109 | if err != nil {
110 | t.Error(err)
111 | }
112 | expect(t, string(bs), content)
113 | } else if ce == "deflate" {
114 | r := flate.NewReader(buff)
115 | defer r.Close()
116 |
117 | bs, err := ioutil.ReadAll(r)
118 | if err != nil {
119 | t.Error(err)
120 | }
121 | expect(t, string(bs), content)
122 | } else {
123 | expect(t, buff.String(), content)
124 | }
125 | expect(t, enc, ce)
126 | }
127 |
--------------------------------------------------------------------------------
/logger.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "io"
9 | "time"
10 |
11 | "gitea.com/lunny/log"
12 | )
13 |
14 | // Logger defines the logger interface for tango use
15 | type Logger interface {
16 | Debugf(format string, v ...interface{})
17 | Debug(v ...interface{})
18 | Infof(format string, v ...interface{})
19 | Info(v ...interface{})
20 | Warnf(format string, v ...interface{})
21 | Warn(v ...interface{})
22 | Errorf(format string, v ...interface{})
23 | Error(v ...interface{})
24 | }
25 |
26 | // CompositeLogger defines a composite loggers
27 | type CompositeLogger struct {
28 | loggers []Logger
29 | }
30 |
31 | // NewCompositeLogger creates a composite loggers
32 | func NewCompositeLogger(logs ...Logger) Logger {
33 | return &CompositeLogger{loggers: logs}
34 | }
35 |
36 | // Debugf implementes Logger interface
37 | func (l *CompositeLogger) Debugf(format string, v ...interface{}) {
38 | for _, log := range l.loggers {
39 | log.Debugf(format, v...)
40 | }
41 | }
42 |
43 | // Debug implementes Logger interface
44 | func (l *CompositeLogger) Debug(v ...interface{}) {
45 | for _, log := range l.loggers {
46 | log.Debug(v...)
47 | }
48 | }
49 |
50 | // Infof implementes Logger interface
51 | func (l *CompositeLogger) Infof(format string, v ...interface{}) {
52 | for _, log := range l.loggers {
53 | log.Infof(format, v...)
54 | }
55 | }
56 |
57 | // Info implementes Logger interface
58 | func (l *CompositeLogger) Info(v ...interface{}) {
59 | for _, log := range l.loggers {
60 | log.Info(v...)
61 | }
62 | }
63 |
64 | // Warnf implementes Logger interface
65 | func (l *CompositeLogger) Warnf(format string, v ...interface{}) {
66 | for _, log := range l.loggers {
67 | log.Warnf(format, v...)
68 | }
69 | }
70 |
71 | // Warn implementes Logger interface
72 | func (l *CompositeLogger) Warn(v ...interface{}) {
73 | for _, log := range l.loggers {
74 | log.Warn(v...)
75 | }
76 | }
77 |
78 | // Errorf implementes Logger interface
79 | func (l *CompositeLogger) Errorf(format string, v ...interface{}) {
80 | for _, log := range l.loggers {
81 | log.Errorf(format, v...)
82 | }
83 | }
84 |
85 | // Error implementes Logger interface
86 | func (l *CompositeLogger) Error(v ...interface{}) {
87 | for _, log := range l.loggers {
88 | log.Error(v...)
89 | }
90 | }
91 |
92 | // NewLogger use the default logger with special writer
93 | func NewLogger(out io.Writer) Logger {
94 | l := log.New(out, "[tango] ", log.Ldefault())
95 | l.SetOutputLevel(log.Ldebug)
96 | return l
97 | }
98 |
99 | // LogInterface defines logger interface to inject logger to struct
100 | type LogInterface interface {
101 | SetLogger(Logger)
102 | }
103 |
104 | // Log implementes LogInterface
105 | type Log struct {
106 | Logger
107 | }
108 |
109 | // SetLogger implementes LogInterface
110 | func (l *Log) SetLogger(log Logger) {
111 | l.Logger = log
112 | }
113 |
114 | // Logging returns handler to log informations
115 | func Logging() HandlerFunc {
116 | return func(ctx *Context) {
117 | start := time.Now()
118 | p := ctx.Req().URL.Path
119 | if len(ctx.Req().URL.RawQuery) > 0 {
120 | p = p + "?" + ctx.Req().URL.RawQuery
121 | }
122 |
123 | ctx.Debug("Started", ctx.Req().Method, p, "for", ctx.IP())
124 |
125 | if action := ctx.Action(); action != nil {
126 | if l, ok := action.(LogInterface); ok {
127 | l.SetLogger(ctx.Logger)
128 | }
129 | }
130 |
131 | ctx.Next()
132 |
133 | if !ctx.Written() {
134 | if ctx.Result == nil {
135 | ctx.Result = NotFound()
136 | }
137 | ctx.HandleError()
138 | }
139 |
140 | statusCode := ctx.Status()
141 |
142 | if statusCode >= 200 && statusCode < 400 {
143 | ctx.Info(ctx.Req().Method, statusCode, time.Since(start), p)
144 | } else {
145 | ctx.Error(ctx.Req().Method, statusCode, time.Since(start), p, ctx.Result)
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/group.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | type groupRouter struct {
8 | methods interface{}
9 | url string
10 | c interface{}
11 | handlers []Handler
12 | }
13 |
14 | // Group defines a route group
15 | type Group struct {
16 | routers []groupRouter
17 | handlers []Handler
18 | }
19 |
20 | // NewGroup creates a route group
21 | func NewGroup() *Group {
22 | return &Group{
23 | routers: make([]groupRouter, 0),
24 | handlers: make([]Handler, 0),
25 | }
26 | }
27 |
28 | // Use set the middlewares to apply to this group's routes
29 | func (g *Group) Use(handlers ...Handler) {
30 | g.handlers = append(g.handlers, handlers...)
31 | }
32 |
33 | // Get addes a GET route to this group
34 | func (g *Group) Get(url string, c interface{}, middlewares ...Handler) {
35 | g.Route([]string{"GET", "HEAD:Get"}, url, c, middlewares...)
36 | }
37 |
38 | // Post addes a POST route to this group
39 | func (g *Group) Post(url string, c interface{}, middlewares ...Handler) {
40 | g.Route([]string{"POST"}, url, c, middlewares...)
41 | }
42 |
43 | // Head addes a HEAD route to this group
44 | func (g *Group) Head(url string, c interface{}, middlewares ...Handler) {
45 | g.Route([]string{"HEAD"}, url, c, middlewares...)
46 | }
47 |
48 | // Options addes a OPTIONS route to this group
49 | func (g *Group) Options(url string, c interface{}, middlewares ...Handler) {
50 | g.Route([]string{"OPTIONS"}, url, c, middlewares...)
51 | }
52 |
53 | // Trace addes a TRACE route to this group
54 | func (g *Group) Trace(url string, c interface{}, middlewares ...Handler) {
55 | g.Route([]string{"TRACE"}, url, c, middlewares...)
56 | }
57 |
58 | // Patch addes a PATCH route to this group
59 | func (g *Group) Patch(url string, c interface{}, middlewares ...Handler) {
60 | g.Route([]string{"PATCH"}, url, c, middlewares...)
61 | }
62 |
63 | // Delete addes a DELETE route to this group
64 | func (g *Group) Delete(url string, c interface{}, middlewares ...Handler) {
65 | g.Route([]string{"DELETE"}, url, c, middlewares...)
66 | }
67 |
68 | // Put addes a PUT route to this group
69 | func (g *Group) Put(url string, c interface{}, middlewares ...Handler) {
70 | g.Route([]string{"PUT"}, url, c, middlewares...)
71 | }
72 |
73 | // Any addes the default mehtods route to this group
74 | func (g *Group) Any(url string, c interface{}, middlewares ...Handler) {
75 | g.Route(SupportMethods, url, c, middlewares...)
76 | g.Route([]string{"HEAD:Get"}, url, c, middlewares...)
77 | }
78 |
79 | // Route defines a customerize route to this group
80 | func (g *Group) Route(methods interface{}, url string, c interface{}, middlewares ...Handler) {
81 | g.routers = append(g.routers, groupRouter{methods, url, c, middlewares})
82 | }
83 |
84 | // Group defines group's child group
85 | func (g *Group) Group(p string, o interface{}, handlers ...Handler) {
86 | gr := getGroup(o)
87 | if len(handlers) > 0 {
88 | gr.handlers = append(handlers, gr.handlers...)
89 | }
90 | for _, gchild := range gr.routers {
91 | g.Route(gchild.methods, joinRoute(p, gchild.url), gchild.c, append(gr.handlers, gchild.handlers...)...)
92 | }
93 | }
94 |
95 | func getGroup(o interface{}) *Group {
96 | var g *Group
97 | var gf func(*Group)
98 | var ok bool
99 | if g, ok = o.(*Group); ok {
100 | } else if gf, ok = o.(func(*Group)); ok {
101 | g = NewGroup()
102 | gf(g)
103 | } else {
104 | panic("not allowed group parameter")
105 | }
106 | return g
107 | }
108 |
109 | func joinRoute(p, url string) string {
110 | if len(p) == 0 || p == "/" {
111 | return url
112 | }
113 | return p + url
114 | }
115 |
116 | func (t *Tango) addGroup(p string, g *Group) {
117 | for _, r := range g.routers {
118 | t.Route(r.methods, joinRoute(p, r.url), r.c, append(g.handlers, r.handlers...)...)
119 | }
120 | }
121 |
122 | // Group adds routines groups
123 | func (t *Tango) Group(p string, o interface{}, handlers ...Handler) {
124 | g := getGroup(o)
125 | g.handlers = append(handlers, g.handlers...)
126 | t.addGroup(p, g)
127 | }
128 |
--------------------------------------------------------------------------------
/compress.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bufio"
9 | "compress/flate"
10 | "compress/gzip"
11 | "fmt"
12 | "io"
13 | "net"
14 | "net/http"
15 | "path"
16 | "strings"
17 | )
18 |
19 | // some http headers
20 | const (
21 | HeaderAcceptEncoding = "Accept-Encoding"
22 | HeaderContentEncoding = "Content-Encoding"
23 | HeaderContentLength = "Content-Length"
24 | HeaderContentType = "Content-Type"
25 | HeaderVary = "Vary"
26 | )
27 |
28 | // Compresser defines the interface return compress type
29 | type Compresser interface {
30 | CompressType() string
31 | }
32 |
33 | // GZip implements gzip Compresser
34 | type GZip struct{}
35 |
36 | // CompressType returns compress type
37 | func (GZip) CompressType() string {
38 | return "gzip"
39 | }
40 |
41 | // Deflate implements deflate Compresser
42 | type Deflate struct{}
43 |
44 | // CompressType returns compress type
45 | func (Deflate) CompressType() string {
46 | return "deflate"
47 | }
48 |
49 | // Compress implements auto Compresser
50 | type Compress struct{}
51 |
52 | // CompressType returns compress type
53 | func (Compress) CompressType() string {
54 | return "auto"
55 | }
56 |
57 | // Compresses defines a middleware to compress HTTP response
58 | func Compresses(exts []string) HandlerFunc {
59 | extsmap := make(map[string]bool)
60 | for _, ext := range exts {
61 | extsmap[strings.ToLower(ext)] = true
62 | }
63 |
64 | return func(ctx *Context) {
65 | ae := ctx.Req().Header.Get("Accept-Encoding")
66 | if ae == "" {
67 | ctx.Next()
68 | return
69 | }
70 |
71 | if len(extsmap) > 0 {
72 | ext := strings.ToLower(path.Ext(ctx.Req().URL.Path))
73 | if _, ok := extsmap[ext]; ok {
74 | compress(ctx, "auto")
75 | return
76 | }
77 | }
78 |
79 | if action := ctx.Action(); action != nil {
80 | if c, ok := action.(Compresser); ok {
81 | compress(ctx, c.CompressType())
82 | return
83 | }
84 | }
85 |
86 | // if blank, then no compress
87 | ctx.Next()
88 | }
89 | }
90 |
91 | func compress(ctx *Context, compressType string) {
92 | ae := ctx.Req().Header.Get("Accept-Encoding")
93 | acceptCompress := strings.SplitN(ae, ",", -1)
94 | var writer io.Writer
95 | var val string
96 |
97 | for _, val = range acceptCompress {
98 | val = strings.TrimSpace(val)
99 | if compressType == "auto" || val == compressType {
100 | if val == "gzip" {
101 | ctx.Header().Set("Content-Encoding", "gzip")
102 | writer = gzip.NewWriter(ctx.ResponseWriter)
103 | break
104 | } else if val == "deflate" {
105 | ctx.Header().Set("Content-Encoding", "deflate")
106 | writer, _ = flate.NewWriter(ctx.ResponseWriter, flate.BestSpeed)
107 | break
108 | }
109 | }
110 | }
111 |
112 | // not supported compress method, then ignore
113 | if writer == nil {
114 | ctx.Next()
115 | return
116 | }
117 |
118 | // for cache server
119 | ctx.Header().Add(HeaderVary, "Accept-Encoding")
120 |
121 | gzw := &compressWriter{writer, ctx.ResponseWriter}
122 | ctx.ResponseWriter = gzw
123 |
124 | ctx.Next()
125 |
126 | // delete content length after we know we have been written to
127 | gzw.Header().Del(HeaderContentLength)
128 | ctx.ResponseWriter = gzw.ResponseWriter
129 |
130 | switch writer.(type) {
131 | case *gzip.Writer:
132 | writer.(*gzip.Writer).Close()
133 | case *flate.Writer:
134 | writer.(*flate.Writer).Close()
135 | }
136 | }
137 |
138 | type compressWriter struct {
139 | w io.Writer
140 | ResponseWriter
141 | }
142 |
143 | func (grw *compressWriter) Write(p []byte) (int, error) {
144 | if len(grw.Header().Get(HeaderContentType)) == 0 {
145 | grw.Header().Set(HeaderContentType, http.DetectContentType(p))
146 | }
147 | return grw.w.Write(p)
148 | }
149 |
150 | func (grw *compressWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
151 | hijacker, ok := grw.ResponseWriter.(http.Hijacker)
152 | if !ok {
153 | return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
154 | }
155 | return hijacker.Hijack()
156 | }
157 |
--------------------------------------------------------------------------------
/static.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "net/http"
9 | "os"
10 | "path"
11 | "path/filepath"
12 | "strings"
13 | )
14 |
15 | // StaticOptions defines Static middleware's options
16 | type StaticOptions struct {
17 | RootPath string
18 | Prefix string
19 | IndexFiles []string
20 | ListDir bool
21 | FilterExts []string
22 | // FileSystem is the interface for supporting any implmentation of file system.
23 | FileSystem http.FileSystem
24 | }
25 |
26 | // IsFilterExt decribes if rPath's ext match filter ext
27 | func (s *StaticOptions) IsFilterExt(rPath string) bool {
28 | rext := path.Ext(rPath)
29 | for _, ext := range s.FilterExts {
30 | if rext == ext {
31 | return true
32 | }
33 | }
34 | return false
35 | }
36 |
37 | func prepareStaticOptions(options []StaticOptions) StaticOptions {
38 | var opt StaticOptions
39 | if len(options) > 0 {
40 | opt = options[0]
41 | }
42 |
43 | // Defaults
44 | if len(opt.RootPath) == 0 {
45 | opt.RootPath = "./public"
46 | }
47 |
48 | if len(opt.Prefix) > 0 {
49 | if opt.Prefix[0] != '/' {
50 | opt.Prefix = "/" + opt.Prefix
51 | }
52 | }
53 |
54 | if len(opt.IndexFiles) == 0 {
55 | opt.IndexFiles = []string{"index.html", "index.htm"}
56 | }
57 |
58 | if opt.FileSystem == nil {
59 | ps, _ := filepath.Abs(opt.RootPath)
60 | opt.FileSystem = http.Dir(ps)
61 | }
62 |
63 | return opt
64 | }
65 |
66 | // Static return a middleware for serving static files
67 | func Static(opts ...StaticOptions) HandlerFunc {
68 | return func(ctx *Context) {
69 | if ctx.Req().Method != "GET" && ctx.Req().Method != "HEAD" {
70 | ctx.Next()
71 | return
72 | }
73 |
74 | opt := prepareStaticOptions(opts)
75 |
76 | var rPath = ctx.Req().URL.Path
77 | // if defined prefix, then only check prefix
78 | if opt.Prefix != "" {
79 | if !strings.HasPrefix(ctx.Req().URL.Path, opt.Prefix) {
80 | ctx.Next()
81 | return
82 | }
83 |
84 | if len(opt.Prefix) == len(ctx.Req().URL.Path) {
85 | rPath = ""
86 | } else {
87 | rPath = ctx.Req().URL.Path[len(opt.Prefix):]
88 | }
89 | }
90 |
91 | f, err := opt.FileSystem.Open(strings.TrimLeft(rPath, "/"))
92 | if err != nil {
93 | if os.IsNotExist(err) {
94 | if opt.Prefix != "" {
95 | ctx.Result = NotFound()
96 | } else {
97 | ctx.Next()
98 | return
99 | }
100 | } else {
101 | ctx.Result = InternalServerError(err.Error())
102 | }
103 | ctx.HandleError()
104 | return
105 | }
106 | defer f.Close()
107 |
108 | finfo, err := f.Stat()
109 | if err != nil {
110 | ctx.Result = InternalServerError(err.Error())
111 | ctx.HandleError()
112 | return
113 | }
114 |
115 | if !finfo.IsDir() {
116 | if len(opt.FilterExts) > 0 && !opt.IsFilterExt(rPath) {
117 | ctx.Next()
118 | return
119 | }
120 |
121 | http.ServeContent(ctx, ctx.Req(), finfo.Name(), finfo.ModTime(), f)
122 | return
123 | }
124 |
125 | // try serving index.html or index.htm
126 | if len(opt.IndexFiles) > 0 {
127 | for _, index := range opt.IndexFiles {
128 | fi, err := opt.FileSystem.Open(strings.TrimLeft(path.Join(rPath, index), "/"))
129 | if err != nil {
130 | if !os.IsNotExist(err) {
131 | ctx.Result = InternalServerError(err.Error())
132 | ctx.HandleError()
133 | return
134 | }
135 | } else {
136 | finfo, err = fi.Stat()
137 | if err != nil {
138 | fi.Close()
139 | ctx.Result = InternalServerError(err.Error())
140 | ctx.HandleError()
141 | return
142 | }
143 | if !finfo.IsDir() {
144 | http.ServeContent(ctx, ctx.Req(), finfo.Name(), finfo.ModTime(), fi)
145 | fi.Close()
146 | return
147 | }
148 | fi.Close()
149 | }
150 | }
151 | }
152 |
153 | // list dir files
154 | if opt.ListDir {
155 | ctx.Header().Set("Content-Type", "text/html; charset=UTF-8")
156 | ctx.WriteString(`
`)
157 | if rPath != "/" {
158 | ctx.WriteString(`- ..
`)
159 | }
160 |
161 | fs, err := f.Readdir(0)
162 | if err != nil {
163 | ctx.Result = InternalServerError(err.Error())
164 | ctx.HandleError()
165 | return
166 | }
167 |
168 | for _, fi := range fs {
169 | if fi.IsDir() {
170 | ctx.WriteString(`- ┖ ` + path.Base(fi.Name()) + `
`)
171 | } else {
172 | if len(opt.FilterExts) > 0 && !opt.IsFilterExt(fi.Name()) {
173 | continue
174 | }
175 |
176 | ctx.WriteString(`- ` + filepath.Base(fi.Name()) + `
`)
177 | }
178 | }
179 | ctx.WriteString("
")
180 | return
181 | }
182 |
183 | ctx.Next()
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/error_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "net/http"
11 | "net/http/httptest"
12 | "os"
13 | "testing"
14 | )
15 |
16 | func TestError1(t *testing.T) {
17 | buff := bytes.NewBufferString("")
18 | recorder := httptest.NewRecorder()
19 | recorder.Body = buff
20 |
21 | l := NewLogger(os.Stdout)
22 | o := Classic(l)
23 | o.Get("/", func(ctx *Context) error {
24 | return NotFound()
25 | })
26 |
27 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
28 | if err != nil {
29 | t.Error(err)
30 | }
31 |
32 | o.ServeHTTP(recorder, req)
33 | expect(t, recorder.Code, http.StatusNotFound)
34 | refute(t, len(buff.String()), 0)
35 | }
36 |
37 | func TestError2(t *testing.T) {
38 | buff := bytes.NewBufferString("")
39 | recorder := httptest.NewRecorder()
40 | recorder.Body = buff
41 |
42 | o := Classic()
43 | o.Get("/", func(ctx *Context) error {
44 | return NotSupported()
45 | })
46 |
47 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
48 | if err != nil {
49 | t.Error(err)
50 | }
51 |
52 | o.ServeHTTP(recorder, req)
53 | expect(t, recorder.Code, http.StatusMethodNotAllowed)
54 | refute(t, len(buff.String()), 0)
55 | }
56 |
57 | func TestError3(t *testing.T) {
58 | buff := bytes.NewBufferString("")
59 | recorder := httptest.NewRecorder()
60 | recorder.Body = buff
61 |
62 | o := Classic()
63 | o.Get("/", func(ctx *Context) error {
64 | return InternalServerError()
65 | })
66 |
67 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
68 | if err != nil {
69 | t.Error(err)
70 | }
71 |
72 | o.ServeHTTP(recorder, req)
73 | expect(t, recorder.Code, http.StatusInternalServerError)
74 | refute(t, len(buff.String()), 0)
75 | }
76 |
77 | func TestError4(t *testing.T) {
78 | buff := bytes.NewBufferString("")
79 | recorder := httptest.NewRecorder()
80 | recorder.Body = buff
81 |
82 | o := Classic()
83 | o.Patch("/", func(ctx *Context) error {
84 | return Forbidden()
85 | })
86 |
87 | req, err := http.NewRequest("PATCH", "http://localhost:8000/", nil)
88 | if err != nil {
89 | t.Error(err)
90 | }
91 |
92 | o.ServeHTTP(recorder, req)
93 | expect(t, recorder.Code, http.StatusForbidden)
94 | refute(t, len(buff.String()), 0)
95 | }
96 |
97 | func TestError5(t *testing.T) {
98 | buff := bytes.NewBufferString("")
99 | recorder := httptest.NewRecorder()
100 | recorder.Body = buff
101 |
102 | o := Classic()
103 | o.Trace("/", func(ctx *Context) error {
104 | return Unauthorized()
105 | })
106 |
107 | req, err := http.NewRequest("TRACE", "http://localhost:8000/", nil)
108 | if err != nil {
109 | t.Error(err)
110 | }
111 |
112 | o.ServeHTTP(recorder, req)
113 | expect(t, recorder.Code, http.StatusUnauthorized)
114 | refute(t, len(buff.String()), 0)
115 | }
116 |
117 | var err500 = Abort(500, "error")
118 |
119 | func TestError6(t *testing.T) {
120 | buff := bytes.NewBufferString("")
121 | recorder := httptest.NewRecorder()
122 | recorder.Body = buff
123 |
124 | o := Classic()
125 | o.Head("/", func(ctx *Context) error {
126 | return err500
127 | })
128 |
129 | req, err := http.NewRequest("HEAD", "http://localhost:8000/", nil)
130 | if err != nil {
131 | t.Error(err)
132 | }
133 |
134 | o.ServeHTTP(recorder, req)
135 | expect(t, recorder.Code, err500.Code())
136 | refute(t, len(buff.String()), 0)
137 | expect(t, buff.String(), err500.Error())
138 | }
139 |
140 | func TestError7(t *testing.T) {
141 | buff := bytes.NewBufferString("")
142 | recorder := httptest.NewRecorder()
143 | recorder.Body = buff
144 |
145 | o := Classic()
146 | o.Head("/", func(ctx *Context) {
147 | return
148 | })
149 |
150 | req, err := http.NewRequest("HEAD", "http://localhost:8000/11?==", nil)
151 | if err != nil {
152 | t.Error(err)
153 | }
154 |
155 | o.ServeHTTP(recorder, req)
156 | expect(t, recorder.Code, http.StatusNotFound)
157 | refute(t, len(buff.String()), 0)
158 | }
159 |
160 | var (
161 | prefix = "tango"
162 | suffix = fmt.Sprintf("
version: %s
", Version())
163 | )
164 |
165 | func TestError8(t *testing.T) {
166 | buff := bytes.NewBufferString("")
167 | recorder := httptest.NewRecorder()
168 | recorder.Body = buff
169 |
170 | o := Classic()
171 | o.ErrHandler = HandlerFunc(func(ctx *Context) {
172 | switch res := ctx.Result.(type) {
173 | case AbortError:
174 | ctx.WriteHeader(res.Code())
175 | ctx.Write([]byte(prefix))
176 | ctx.Write([]byte(res.Error()))
177 | case error:
178 | ctx.WriteHeader(http.StatusInternalServerError)
179 | ctx.Write([]byte(prefix))
180 | ctx.Write([]byte(res.Error()))
181 | default:
182 | ctx.WriteHeader(http.StatusInternalServerError)
183 | ctx.Write([]byte(prefix))
184 | ctx.Write([]byte(http.StatusText(http.StatusInternalServerError)))
185 | }
186 | ctx.Write([]byte(suffix))
187 | })
188 |
189 | o.Get("/", func() error {
190 | return NotFound()
191 | })
192 |
193 | req, err := http.NewRequest("HEAD", "http://localhost:8000/", nil)
194 | if err != nil {
195 | t.Error(err)
196 | }
197 |
198 | o.ServeHTTP(recorder, req)
199 | expect(t, recorder.Code, http.StatusNotFound)
200 | refute(t, len(buff.String()), 0)
201 | expect(t, buff.String(), prefix+"Not Found"+suffix)
202 | }
203 |
--------------------------------------------------------------------------------
/return.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "encoding/json"
9 | "encoding/xml"
10 | "net/http"
11 | "reflect"
12 | )
13 |
14 | // StatusResult describes http response
15 | type StatusResult struct {
16 | Code int
17 | Result interface{}
18 | }
19 |
20 | // enumerate all the return response types
21 | const (
22 | autoResponse = iota
23 | jsonResponse
24 | xmlResponse
25 | )
26 |
27 | // ResponseTyper describes reponse type
28 | type ResponseTyper interface {
29 | ResponseType() int
30 | }
31 |
32 | // Json describes return JSON type
33 | // Deprecated: use JSON instead
34 | type Json struct{}
35 |
36 | // ResponseType implementes ResponseTyper
37 | func (Json) ResponseType() int {
38 | return jsonResponse
39 | }
40 |
41 | // JSON describes return JSON type
42 | type JSON struct{}
43 |
44 | // ResponseType implementes ResponseTyper
45 | func (JSON) ResponseType() int {
46 | return jsonResponse
47 | }
48 |
49 | // Xml descirbes return XML type
50 | // Deprecated: use XML instead
51 | type Xml struct{}
52 |
53 | // ResponseType implementes ResponseTyper
54 | func (Xml) ResponseType() int {
55 | return xmlResponse
56 | }
57 |
58 | // XML descirbes return XML type
59 | type XML struct{}
60 |
61 | // ResponseType implementes ResponseTyper
62 | func (XML) ResponseType() int {
63 | return xmlResponse
64 | }
65 |
66 | func isNil(a interface{}) bool {
67 | if a == nil {
68 | return true
69 | }
70 | aa := reflect.ValueOf(a)
71 | return !aa.IsValid() || (aa.Type().Kind() == reflect.Ptr && aa.IsNil())
72 | }
73 |
74 | // XMLError describes return xml error
75 | type XMLError struct {
76 | XMLName xml.Name `xml:"err"`
77 | Content string `xml:"content"`
78 | }
79 |
80 | // XMLString describes return xml string
81 | type XMLString struct {
82 | XMLName xml.Name `xml:"string"`
83 | Content string `xml:"content"`
84 | }
85 |
86 | // Return returns a tango middleware to handler return values
87 | func Return() HandlerFunc {
88 | return func(ctx *Context) {
89 | var rt int
90 | action := ctx.Action()
91 | if action != nil {
92 | if i, ok := action.(ResponseTyper); ok {
93 | rt = i.ResponseType()
94 | }
95 | }
96 |
97 | ctx.Next()
98 |
99 | // if no route match or has been write, then return
100 | if action == nil || ctx.Written() {
101 | return
102 | }
103 |
104 | // if there is no return value or return nil
105 | if isNil(ctx.Result) {
106 | // then we return blank page
107 | ctx.Result = ""
108 | }
109 |
110 | var result = ctx.Result
111 | var statusCode = 0
112 | if res, ok := ctx.Result.(*StatusResult); ok {
113 | statusCode = res.Code
114 | result = res.Result
115 | }
116 |
117 | if rt == jsonResponse {
118 | encoder := json.NewEncoder(ctx)
119 | if len(ctx.Header().Get("Content-Type")) <= 0 {
120 | ctx.Header().Set("Content-Type", "application/json; charset=UTF-8")
121 | }
122 |
123 | switch res := result.(type) {
124 | case AbortError:
125 | if statusCode == 0 {
126 | statusCode = res.Code()
127 | }
128 | ctx.WriteHeader(statusCode)
129 | encoder.Encode(map[string]string{
130 | "err": res.Error(),
131 | })
132 | case ErrorWithCode:
133 | if statusCode == 0 {
134 | statusCode = http.StatusOK
135 | }
136 | ctx.WriteHeader(statusCode)
137 | encoder.Encode(map[string]interface{}{
138 | "err": res.Error(),
139 | "err_code": res.ErrorCode(),
140 | })
141 | case error:
142 | if statusCode == 0 {
143 | statusCode = http.StatusOK
144 | }
145 | ctx.WriteHeader(statusCode)
146 | encoder.Encode(map[string]string{
147 | "err": res.Error(),
148 | })
149 | case string:
150 | if statusCode == 0 {
151 | statusCode = http.StatusOK
152 | }
153 | ctx.WriteHeader(statusCode)
154 | encoder.Encode(map[string]string{
155 | "content": res,
156 | })
157 | case []byte:
158 | if statusCode == 0 {
159 | statusCode = http.StatusOK
160 | }
161 | ctx.WriteHeader(statusCode)
162 | encoder.Encode(map[string]string{
163 | "content": string(res),
164 | })
165 | default:
166 | if statusCode == 0 {
167 | statusCode = http.StatusOK
168 | }
169 | ctx.WriteHeader(statusCode)
170 | err := encoder.Encode(result)
171 | if err != nil {
172 | ctx.Result = err
173 | encoder.Encode(map[string]string{
174 | "err": err.Error(),
175 | })
176 | }
177 | }
178 |
179 | return
180 | } else if rt == xmlResponse {
181 | encoder := xml.NewEncoder(ctx)
182 | if len(ctx.Header().Get("Content-Type")) <= 0 {
183 | ctx.Header().Set("Content-Type", "application/xml; charset=UTF-8")
184 | }
185 | switch res := result.(type) {
186 | case AbortError:
187 | if statusCode == 0 {
188 | statusCode = res.Code()
189 | }
190 | ctx.WriteHeader(statusCode)
191 | encoder.Encode(XMLError{
192 | Content: res.Error(),
193 | })
194 | case error:
195 | if statusCode == 0 {
196 | statusCode = http.StatusOK
197 | }
198 | ctx.WriteHeader(statusCode)
199 | encoder.Encode(XMLError{
200 | Content: res.Error(),
201 | })
202 | case string:
203 | if statusCode == 0 {
204 | statusCode = http.StatusOK
205 | }
206 | ctx.WriteHeader(statusCode)
207 | encoder.Encode(XMLString{
208 | Content: res,
209 | })
210 | case []byte:
211 | if statusCode == 0 {
212 | statusCode = http.StatusOK
213 | }
214 | ctx.WriteHeader(statusCode)
215 | encoder.Encode(XMLString{
216 | Content: string(res),
217 | })
218 | default:
219 | if statusCode == 0 {
220 | statusCode = http.StatusOK
221 | }
222 | ctx.WriteHeader(statusCode)
223 | err := encoder.Encode(result)
224 | if err != nil {
225 | ctx.Result = err
226 | encoder.Encode(XMLError{
227 | Content: err.Error(),
228 | })
229 | }
230 | }
231 | return
232 | }
233 |
234 | switch res := result.(type) {
235 | case AbortError, error:
236 | ctx.HandleError()
237 | case []byte:
238 | if statusCode == 0 {
239 | statusCode = http.StatusOK
240 | }
241 | ctx.WriteHeader(statusCode)
242 | ctx.Write(res)
243 | case string:
244 | if statusCode == 0 {
245 | statusCode = http.StatusOK
246 | }
247 | ctx.WriteHeader(statusCode)
248 | ctx.WriteString(res)
249 | }
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/static_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "encoding/json"
10 | "net/http"
11 | "net/http/httptest"
12 | "os"
13 | "testing"
14 | "time"
15 | )
16 |
17 | func TestStatic(t *testing.T) {
18 | buff := bytes.NewBufferString("")
19 | recorder := httptest.NewRecorder()
20 | recorder.Body = buff
21 |
22 | tg := New()
23 | tg.Use(Static())
24 |
25 | req, err := http.NewRequest("GET", "http://localhost:8000/test.html", nil)
26 | if err != nil {
27 | t.Error(err)
28 | }
29 |
30 | tg.ServeHTTP(recorder, req)
31 | expect(t, recorder.Code, http.StatusOK)
32 | refute(t, len(buff.String()), 0)
33 | expect(t, buff.String(), "hello tango")
34 |
35 | buff.Reset()
36 |
37 | req, err = http.NewRequest("GET", "http://localhost:8000/", nil)
38 | if err != nil {
39 | t.Error(err)
40 | }
41 |
42 | tg.ServeHTTP(recorder, req)
43 | expect(t, recorder.Code, http.StatusOK)
44 | refute(t, len(buff.String()), 0)
45 | expect(t, buff.String(), "this is index.html")
46 | }
47 |
48 | func TestStatic2(t *testing.T) {
49 | buff := bytes.NewBufferString("")
50 | recorder := httptest.NewRecorder()
51 | recorder.Body = buff
52 |
53 | tg := New()
54 | tg.Use(Static())
55 |
56 | req, err := http.NewRequest("GET", "http://localhost:8000/test.png", nil)
57 | if err != nil {
58 | t.Error(err)
59 | }
60 |
61 | tg.ServeHTTP(recorder, req)
62 | expect(t, recorder.Code, http.StatusNotFound)
63 | }
64 |
65 | func TestStatic3(t *testing.T) {
66 | buff := bytes.NewBufferString("")
67 | recorder := httptest.NewRecorder()
68 | recorder.Body = buff
69 |
70 | tg := New()
71 | tg.Use(Static(StaticOptions{
72 | Prefix: "/public",
73 | RootPath: "./public",
74 | }))
75 |
76 | req, err := http.NewRequest("GET", "http://localhost:8000/public/test.html", nil)
77 | if err != nil {
78 | t.Error(err)
79 | }
80 |
81 | tg.ServeHTTP(recorder, req)
82 | expect(t, recorder.Code, http.StatusOK)
83 | expect(t, buff.String(), "hello tango")
84 | }
85 |
86 | func TestStatic4(t *testing.T) {
87 | buff := bytes.NewBufferString("")
88 | recorder := httptest.NewRecorder()
89 | recorder.Body = buff
90 |
91 | tg := New()
92 | tg.Use(Static(StaticOptions{
93 | Prefix: "/public",
94 | RootPath: "./public",
95 | }))
96 |
97 | req, err := http.NewRequest("GET", "http://localhost:8000/public/t.html", nil)
98 | if err != nil {
99 | t.Error(err)
100 | }
101 |
102 | tg.ServeHTTP(recorder, req)
103 | expect(t, recorder.Code, http.StatusNotFound)
104 | expect(t, buff.String(), NotFound().Error())
105 | }
106 |
107 | func TestStatic5(t *testing.T) {
108 | buff := bytes.NewBufferString("")
109 | recorder := httptest.NewRecorder()
110 | recorder.Body = buff
111 |
112 | tg := New()
113 | tg.Use(Static(StaticOptions{
114 | Prefix: "/public",
115 | RootPath: "./public",
116 | ListDir: true,
117 | IndexFiles: []string{"a.html"},
118 | }))
119 |
120 | req, err := http.NewRequest("GET", "http://localhost:8000/public/", nil)
121 | if err != nil {
122 | t.Error(err)
123 | }
124 |
125 | tg.ServeHTTP(recorder, req)
126 | expect(t, recorder.Code, http.StatusOK)
127 | }
128 |
129 | type MemoryFileSystem map[string][]byte
130 |
131 | type MemoryFile struct {
132 | Name string
133 | isDir bool
134 | *bytes.Reader
135 | }
136 |
137 | func (m *MemoryFile) Close() error {
138 | return nil
139 | }
140 |
141 | func (m *MemoryFile) Readdir(count int) ([]os.FileInfo, error) {
142 | var infos []os.FileInfo
143 | err := json.NewDecoder(m.Reader).Decode(&infos)
144 | if err != nil {
145 | return nil, err
146 | }
147 | if count > 0 && count < len(infos) {
148 | return infos[:count], nil
149 | }
150 | return infos, nil
151 | }
152 |
153 | type MemoryFileInfo struct {
154 | name string
155 | size int64
156 | time.Time
157 | isDir bool
158 | }
159 |
160 | func (m *MemoryFileInfo) Name() string {
161 | return m.name
162 | }
163 |
164 | func (m *MemoryFileInfo) Size() int64 {
165 | return m.size
166 | }
167 |
168 | func (m *MemoryFileInfo) Mode() os.FileMode {
169 | return os.ModePerm
170 | }
171 |
172 | func (m *MemoryFileInfo) ModTime() time.Time {
173 | return m.Time
174 | }
175 |
176 | func (m *MemoryFileInfo) IsDir() bool {
177 | return m.isDir
178 | }
179 |
180 | func (m *MemoryFileInfo) Sys() interface{} {
181 | return nil
182 | }
183 |
184 | func (m *MemoryFile) Stat() (os.FileInfo, error) {
185 | return &MemoryFileInfo{
186 | name: m.Name,
187 | size: int64(m.Len()),
188 | Time: time.Now(),
189 | isDir: m.isDir,
190 | }, nil
191 | }
192 |
193 | var (
194 | _ http.FileSystem = &MemoryFileSystem{}
195 | _ http.File = &MemoryFile{}
196 | )
197 |
198 | func (m MemoryFileSystem) Open(name string) (http.File, error) {
199 | if name == "/" || name == "" {
200 | var finfos []os.FileInfo
201 | for k, v := range m {
202 | finfos = append(finfos, &MemoryFileInfo{
203 | name: k,
204 | size: int64(len(v)),
205 | Time: time.Now(),
206 | isDir: v[0] == '[',
207 | })
208 | }
209 |
210 | bs, err := json.Marshal(finfos)
211 | if err != nil {
212 | return nil, err
213 | }
214 |
215 | return &MemoryFile{
216 | Name: "/",
217 | isDir: true,
218 | Reader: bytes.NewReader(bs),
219 | }, nil
220 | }
221 |
222 | bs, ok := m[name]
223 | if !ok {
224 | return nil, os.ErrNotExist
225 | }
226 |
227 | return &MemoryFile{
228 | Name: name,
229 | isDir: bs[0] == '[',
230 | Reader: bytes.NewReader(bs),
231 | }, nil
232 | }
233 |
234 | func TestStatic6(t *testing.T) {
235 | buff := bytes.NewBufferString("")
236 | recorder := httptest.NewRecorder()
237 | recorder.Body = buff
238 |
239 | var myFileSystem = MemoryFileSystem{
240 | "a.html": []byte(""),
241 | }
242 | tg := New()
243 | tg.Use(Static(StaticOptions{
244 | Prefix: "/public",
245 | RootPath: "./public",
246 | ListDir: false,
247 | IndexFiles: []string{"a.html"},
248 | FileSystem: myFileSystem,
249 | }))
250 |
251 | req, err := http.NewRequest("GET", "http://localhost:8000/public/", nil)
252 | if err != nil {
253 | t.Error(err)
254 | }
255 |
256 | tg.ServeHTTP(recorder, req)
257 | expect(t, recorder.Code, http.StatusOK)
258 | }
259 |
260 | func TestStatic7(t *testing.T) {
261 | buff := bytes.NewBufferString("")
262 | recorder := httptest.NewRecorder()
263 | recorder.Body = buff
264 |
265 | tg := New()
266 | tg.Use(Static(StaticOptions{
267 | RootPath: "./public",
268 | }))
269 |
270 | req, err := http.NewRequest("GET", "http://localhost:8000/test.html", nil)
271 | if err != nil {
272 | t.Error(err)
273 | }
274 |
275 | tg.ServeHTTP(recorder, req)
276 | expect(t, recorder.Code, http.StatusOK)
277 | expect(t, buff.String(), "hello tango")
278 | }
279 |
280 | func TestStatic8(t *testing.T) {
281 | buff := bytes.NewBufferString("")
282 | recorder := httptest.NewRecorder()
283 | recorder.Body = buff
284 |
285 | tg := New()
286 | tg.Use(Return())
287 | tg.Use(Static(StaticOptions{
288 | RootPath: "./public",
289 | }))
290 | tg.Get("/b", func() string {
291 | return "hello"
292 | })
293 |
294 | req, err := http.NewRequest("GET", "http://localhost:8000/test.html", nil)
295 | if err != nil {
296 | t.Error(err)
297 | }
298 |
299 | tg.ServeHTTP(recorder, req)
300 | expect(t, recorder.Code, http.StatusOK)
301 | expect(t, buff.String(), "hello tango")
302 |
303 | buff.Reset()
304 |
305 | req, err = http.NewRequest("GET", "http://localhost:8000/b", nil)
306 | if err != nil {
307 | t.Error(err)
308 | }
309 |
310 | tg.ServeHTTP(recorder, req)
311 | expect(t, recorder.Code, http.StatusOK)
312 | expect(t, buff.String(), "hello")
313 | }
314 |
--------------------------------------------------------------------------------
/tan.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "net/http"
9 | "os"
10 | "strconv"
11 | "strings"
12 | "sync"
13 | )
14 |
15 | // Version returns tango's version
16 | func Version() string {
17 | return "0.5.5.0412"
18 | }
19 |
20 | // Tango describes tango object
21 | type Tango struct {
22 | http.Server
23 | Router
24 | handlers []Handler
25 | logger Logger
26 | ErrHandler Handler
27 | ctxPool sync.Pool
28 | respPool sync.Pool
29 | }
30 |
31 | var (
32 | // ClassicHandlers the default handlers
33 | ClassicHandlers = []Handler{
34 | Logging(),
35 | Recovery(false),
36 | Compresses([]string{}),
37 | Static(StaticOptions{Prefix: "public"}),
38 | Return(),
39 | Param(),
40 | Contexts(),
41 | }
42 | )
43 |
44 | // Logger returns logger interface
45 | func (t *Tango) Logger() Logger {
46 | return t.logger
47 | }
48 |
49 | // Get sets a route with GET method
50 | func (t *Tango) Get(url string, c interface{}, middlewares ...Handler) {
51 | t.Route([]string{"GET", "HEAD:Get"}, url, c, middlewares...)
52 | }
53 |
54 | // Post sets a route with POST method
55 | func (t *Tango) Post(url string, c interface{}, middlewares ...Handler) {
56 | t.Route([]string{"POST"}, url, c, middlewares...)
57 | }
58 |
59 | // Head sets a route with HEAD method
60 | func (t *Tango) Head(url string, c interface{}, middlewares ...Handler) {
61 | t.Route([]string{"HEAD"}, url, c, middlewares...)
62 | }
63 |
64 | // Options sets a route with OPTIONS method
65 | func (t *Tango) Options(url string, c interface{}, middlewares ...Handler) {
66 | t.Route([]string{"OPTIONS"}, url, c, middlewares...)
67 | }
68 |
69 | // Trace sets a route with TRACE method
70 | func (t *Tango) Trace(url string, c interface{}, middlewares ...Handler) {
71 | t.Route([]string{"TRACE"}, url, c, middlewares...)
72 | }
73 |
74 | // Patch sets a route with PATCH method
75 | func (t *Tango) Patch(url string, c interface{}, middlewares ...Handler) {
76 | t.Route([]string{"PATCH"}, url, c, middlewares...)
77 | }
78 |
79 | // Delete sets a route with DELETE method
80 | func (t *Tango) Delete(url string, c interface{}, middlewares ...Handler) {
81 | t.Route([]string{"DELETE"}, url, c, middlewares...)
82 | }
83 |
84 | // Put sets a route with PUT method
85 | func (t *Tango) Put(url string, c interface{}, middlewares ...Handler) {
86 | t.Route([]string{"PUT"}, url, c, middlewares...)
87 | }
88 |
89 | // Any sets a route every support method is OK.
90 | func (t *Tango) Any(url string, c interface{}, middlewares ...Handler) {
91 | t.Route(SupportMethods, url, c, middlewares...)
92 | t.Route([]string{"HEAD:Get"}, url, c, middlewares...)
93 | }
94 |
95 | // Use addes some global handlers
96 | func (t *Tango) Use(handlers ...Handler) {
97 | t.handlers = append(t.handlers, handlers...)
98 | }
99 |
100 | // GetAddress parses address
101 | func getAddress(args ...interface{}) string {
102 | var host string
103 | var port int
104 |
105 | if len(args) == 1 {
106 | switch arg := args[0].(type) {
107 | case string:
108 | addrs := strings.Split(args[0].(string), ":")
109 | if len(addrs) == 1 {
110 | host = addrs[0]
111 | } else if len(addrs) >= 2 {
112 | host = addrs[0]
113 | _port, _ := strconv.ParseInt(addrs[1], 10, 0)
114 | port = int(_port)
115 | }
116 | case int:
117 | port = arg
118 | }
119 | } else if len(args) >= 2 {
120 | if arg, ok := args[0].(string); ok {
121 | host = arg
122 | }
123 | if arg, ok := args[1].(int); ok {
124 | port = arg
125 | }
126 | }
127 |
128 | if envHost := os.Getenv("HOST"); len(envHost) != 0 {
129 | host = envHost
130 | } else if len(host) == 0 {
131 | host = "0.0.0.0"
132 | }
133 |
134 | if envPort, _ := strconv.ParseInt(os.Getenv("PORT"), 10, 32); envPort != 0 {
135 | port = int(envPort)
136 | } else if port == 0 {
137 | port = 8000
138 | }
139 |
140 | addr := host + ":" + strconv.FormatInt(int64(port), 10)
141 |
142 | return addr
143 | }
144 |
145 | // Run the http server. Listening on os.GetEnv("PORT") or 8000 by default.
146 | func (t *Tango) Run(args ...interface{}) {
147 | addr := getAddress(args...)
148 | t.logger.Info("Listening on http://" + addr)
149 |
150 | t.Server.Addr = addr
151 | t.Server.Handler = t
152 |
153 | err := t.ListenAndServe()
154 | if err != nil {
155 | t.logger.Error(err)
156 | }
157 | }
158 |
159 | // RunTLS runs the https server with special cert and key files
160 | func (t *Tango) RunTLS(certFile, keyFile string, args ...interface{}) {
161 | addr := getAddress(args...)
162 |
163 | t.logger.Info("Listening on https://" + addr)
164 |
165 | t.Server.Addr = addr
166 | t.Server.Handler = t
167 |
168 | err := t.ListenAndServeTLS(certFile, keyFile)
169 | if err != nil {
170 | t.logger.Error(err)
171 | }
172 | }
173 |
174 | // HandlerFunc describes the handle function
175 | type HandlerFunc func(ctx *Context)
176 |
177 | // Handle executes the handler
178 | func (h HandlerFunc) Handle(ctx *Context) {
179 | h(ctx)
180 | }
181 |
182 | // WrapBefore wraps a http standard handler to tango's before action executes
183 | func WrapBefore(handler http.Handler) HandlerFunc {
184 | return func(ctx *Context) {
185 | handler.ServeHTTP(ctx.ResponseWriter, ctx.Req())
186 |
187 | ctx.Next()
188 | }
189 | }
190 |
191 | // WrapAfter wraps a http standard handler to tango's after action executes
192 | func WrapAfter(handler http.Handler) HandlerFunc {
193 | return func(ctx *Context) {
194 | ctx.Next()
195 |
196 | handler.ServeHTTP(ctx.ResponseWriter, ctx.Req())
197 | }
198 | }
199 |
200 | // UseHandler adds a standard http handler to tango's
201 | func (t *Tango) UseHandler(handler http.Handler) {
202 | t.Use(WrapBefore(handler))
203 | }
204 |
205 | // ServeHTTP implementes net/http interface so that it could run with net/http
206 | func (t *Tango) ServeHTTP(w http.ResponseWriter, req *http.Request) {
207 | resp := t.respPool.Get().(*responseWriter)
208 | resp.reset(w)
209 |
210 | ctx := t.ctxPool.Get().(*Context)
211 | ctx.tan = t
212 | ctx.reset(req, resp)
213 |
214 | ctx.invoke()
215 |
216 | // if there is no logging or error handle, so the last written check.
217 | if !ctx.Written() {
218 | p := req.URL.Path
219 | if len(req.URL.RawQuery) > 0 {
220 | p = p + "?" + req.URL.RawQuery
221 | }
222 |
223 | if ctx.Route() != nil {
224 | if ctx.Result == nil {
225 | ctx.WriteString("")
226 | t.logger.Info(req.Method, ctx.Status(), p)
227 | t.ctxPool.Put(ctx)
228 | t.respPool.Put(resp)
229 | return
230 | }
231 | panic("result should be handler before")
232 | }
233 |
234 | if ctx.Result == nil {
235 | ctx.Result = NotFound()
236 | }
237 |
238 | ctx.HandleError()
239 |
240 | t.logger.Error(req.Method, ctx.Status(), p)
241 | }
242 |
243 | t.ctxPool.Put(ctx)
244 | t.respPool.Put(resp)
245 | }
246 |
247 | // NewWithLog creates tango with the special logger and handlers
248 | func NewWithLog(logger Logger, handlers ...Handler) *Tango {
249 | tan := &Tango{
250 | Router: newRouter(),
251 | logger: logger,
252 | handlers: make([]Handler, 0),
253 | ErrHandler: Errors(),
254 | }
255 |
256 | tan.ctxPool.New = func() interface{} {
257 | return &Context{
258 | tan: tan,
259 | Logger: tan.logger,
260 | }
261 | }
262 |
263 | tan.respPool.New = func() interface{} {
264 | return &responseWriter{}
265 | }
266 |
267 | tan.Use(handlers...)
268 |
269 | return tan
270 | }
271 |
272 | // New creates tango with the default logger and handlers
273 | func New(handlers ...Handler) *Tango {
274 | return NewWithLog(NewLogger(os.Stdout), handlers...)
275 | }
276 |
277 | // Classic returns the tango with default handlers and logger
278 | func Classic(l ...Logger) *Tango {
279 | var logger Logger
280 | if len(l) == 0 {
281 | logger = NewLogger(os.Stdout)
282 | } else {
283 | logger = l[0]
284 | }
285 |
286 | return NewWithLog(
287 | logger,
288 | ClassicHandlers...,
289 | )
290 | }
291 |
--------------------------------------------------------------------------------
/form.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "errors"
9 | "html/template"
10 | "net/http"
11 | "net/url"
12 | "strconv"
13 | )
14 |
15 | // Forms a new enhancement of http.Request
16 | type Forms http.Request
17 |
18 | var _ Set = &Forms{}
19 |
20 | // Values returns http.Request values
21 | func (f *Forms) Values() url.Values {
22 | return (*http.Request)(f).Form
23 | }
24 |
25 | // String returns request form as string
26 | func (f *Forms) String(key string) (string, error) {
27 | return (*http.Request)(f).FormValue(key), nil
28 | }
29 |
30 | // Strings returns request form as strings
31 | func (f *Forms) Strings(key string) ([]string, error) {
32 | (*http.Request)(f).ParseMultipartForm(32 << 20)
33 | if v, ok := (*http.Request)(f).Form[key]; ok {
34 | return v, nil
35 | }
36 | return nil, errors.New("not exist")
37 | }
38 |
39 | // Escape returns request form as escaped string
40 | func (f *Forms) Escape(key string) (string, error) {
41 | return template.HTMLEscapeString((*http.Request)(f).FormValue(key)), nil
42 | }
43 |
44 | // Int returns request form as int
45 | func (f *Forms) Int(key string) (int, error) {
46 | return strconv.Atoi((*http.Request)(f).FormValue(key))
47 | }
48 |
49 | // Int32 returns request form as int32
50 | func (f *Forms) Int32(key string) (int32, error) {
51 | v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 32)
52 | return int32(v), err
53 | }
54 |
55 | // Int64 returns request form as int64
56 | func (f *Forms) Int64(key string) (int64, error) {
57 | return strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 64)
58 | }
59 |
60 | // Uint returns request form as uint
61 | func (f *Forms) Uint(key string) (uint, error) {
62 | v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
63 | return uint(v), err
64 | }
65 |
66 | // Uint32 returns request form as uint32
67 | func (f *Forms) Uint32(key string) (uint32, error) {
68 | v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 32)
69 | return uint32(v), err
70 | }
71 |
72 | // Uint64 returns request form as uint64
73 | func (f *Forms) Uint64(key string) (uint64, error) {
74 | return strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
75 | }
76 |
77 | // Bool returns request form as bool
78 | func (f *Forms) Bool(key string) (bool, error) {
79 | return strconv.ParseBool((*http.Request)(f).FormValue(key))
80 | }
81 |
82 | // Float32 returns request form as float32
83 | func (f *Forms) Float32(key string) (float32, error) {
84 | v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
85 | return float32(v), err
86 | }
87 |
88 | // Float64 returns request form as float64
89 | func (f *Forms) Float64(key string) (float64, error) {
90 | return strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
91 | }
92 |
93 | // MustString returns request form as string with default
94 | func (f *Forms) MustString(key string, defaults ...string) string {
95 | if v := (*http.Request)(f).FormValue(key); len(v) > 0 {
96 | return v
97 | }
98 | if len(defaults) > 0 {
99 | return defaults[0]
100 | }
101 | return ""
102 | }
103 |
104 | // MustStrings returns request form as strings with default
105 | func (f *Forms) MustStrings(key string, defaults ...[]string) []string {
106 | (*http.Request)(f).ParseMultipartForm(32 << 20)
107 | if v, ok := (*http.Request)(f).Form[key]; ok {
108 | return v
109 | }
110 | if len(defaults) > 0 {
111 | return defaults[0]
112 | }
113 | return []string{}
114 | }
115 |
116 | // MustEscape returns request form as escaped string with default
117 | func (f *Forms) MustEscape(key string, defaults ...string) string {
118 | if v := (*http.Request)(f).FormValue(key); len(v) > 0 {
119 | return template.HTMLEscapeString(v)
120 | }
121 | if len(defaults) > 0 {
122 | return defaults[0]
123 | }
124 | return ""
125 | }
126 |
127 | // MustInt returns request form as int with default
128 | func (f *Forms) MustInt(key string, defaults ...int) int {
129 | v, err := strconv.Atoi((*http.Request)(f).FormValue(key))
130 | if len(defaults) > 0 && err != nil {
131 | return defaults[0]
132 | }
133 | return v
134 | }
135 |
136 | // MustInt32 returns request form as int32 with default
137 | func (f *Forms) MustInt32(key string, defaults ...int32) int32 {
138 | v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 32)
139 | if len(defaults) > 0 && err != nil {
140 | return defaults[0]
141 | }
142 | return int32(v)
143 | }
144 |
145 | // MustInt64 returns request form as int64 with default
146 | func (f *Forms) MustInt64(key string, defaults ...int64) int64 {
147 | v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 64)
148 | if len(defaults) > 0 && err != nil {
149 | return defaults[0]
150 | }
151 | return v
152 | }
153 |
154 | // MustUint returns request form as uint with default
155 | func (f *Forms) MustUint(key string, defaults ...uint) uint {
156 | v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
157 | if len(defaults) > 0 && err != nil {
158 | return defaults[0]
159 | }
160 | return uint(v)
161 | }
162 |
163 | // MustUint32 returns request form as uint32 with default
164 | func (f *Forms) MustUint32(key string, defaults ...uint32) uint32 {
165 | v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 32)
166 | if len(defaults) > 0 && err != nil {
167 | return defaults[0]
168 | }
169 | return uint32(v)
170 | }
171 |
172 | // MustUint64 returns request form as uint64 with default
173 | func (f *Forms) MustUint64(key string, defaults ...uint64) uint64 {
174 | v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
175 | if len(defaults) > 0 && err != nil {
176 | return defaults[0]
177 | }
178 | return v
179 | }
180 |
181 | // MustFloat32 returns request form as float32 with default
182 | func (f *Forms) MustFloat32(key string, defaults ...float32) float32 {
183 | v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 32)
184 | if len(defaults) > 0 && err != nil {
185 | return defaults[0]
186 | }
187 | return float32(v)
188 | }
189 |
190 | // MustFloat64 returns request form as float64 with default
191 | func (f *Forms) MustFloat64(key string, defaults ...float64) float64 {
192 | v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
193 | if len(defaults) > 0 && err != nil {
194 | return defaults[0]
195 | }
196 | return v
197 | }
198 |
199 | // MustBool returns request form as bool with default
200 | func (f *Forms) MustBool(key string, defaults ...bool) bool {
201 | v, err := strconv.ParseBool((*http.Request)(f).FormValue(key))
202 | if len(defaults) > 0 && err != nil {
203 | return defaults[0]
204 | }
205 | return v
206 | }
207 |
208 | // Form returns request form as string with default
209 | func (ctx *Context) Form(key string, defaults ...string) string {
210 | return (*Forms)(ctx.req).MustString(key, defaults...)
211 | }
212 |
213 | // FormStrings returns request form as strings with default
214 | func (ctx *Context) FormStrings(key string, defaults ...[]string) []string {
215 | return (*Forms)(ctx.req).MustStrings(key, defaults...)
216 | }
217 |
218 | // FormEscape returns request form as escaped string with default
219 | func (ctx *Context) FormEscape(key string, defaults ...string) string {
220 | return (*Forms)(ctx.req).MustEscape(key, defaults...)
221 | }
222 |
223 | // FormInt returns request form as int with default
224 | func (ctx *Context) FormInt(key string, defaults ...int) int {
225 | return (*Forms)(ctx.req).MustInt(key, defaults...)
226 | }
227 |
228 | // FormInt32 returns request form as int32 with default
229 | func (ctx *Context) FormInt32(key string, defaults ...int32) int32 {
230 | return (*Forms)(ctx.req).MustInt32(key, defaults...)
231 | }
232 |
233 | // FormInt64 returns request form as int64 with default
234 | func (ctx *Context) FormInt64(key string, defaults ...int64) int64 {
235 | return (*Forms)(ctx.req).MustInt64(key, defaults...)
236 | }
237 |
238 | // FormUint returns request form as uint with default
239 | func (ctx *Context) FormUint(key string, defaults ...uint) uint {
240 | return (*Forms)(ctx.req).MustUint(key, defaults...)
241 | }
242 |
243 | // FormUint32 returns request form as uint32 with default
244 | func (ctx *Context) FormUint32(key string, defaults ...uint32) uint32 {
245 | return (*Forms)(ctx.req).MustUint32(key, defaults...)
246 | }
247 |
248 | // FormUint64 returns request form as uint64 with default
249 | func (ctx *Context) FormUint64(key string, defaults ...uint64) uint64 {
250 | return (*Forms)(ctx.req).MustUint64(key, defaults...)
251 | }
252 |
253 | // FormFloat32 returns request form as float32 with default
254 | func (ctx *Context) FormFloat32(key string, defaults ...float32) float32 {
255 | return (*Forms)(ctx.req).MustFloat32(key, defaults...)
256 | }
257 |
258 | // FormFloat64 returns request form as float64 with default
259 | func (ctx *Context) FormFloat64(key string, defaults ...float64) float64 {
260 | return (*Forms)(ctx.req).MustFloat64(key, defaults...)
261 | }
262 |
263 | // FormBool returns request form as bool with default
264 | func (ctx *Context) FormBool(key string, defaults ...bool) bool {
265 | return (*Forms)(ctx.req).MustBool(key, defaults...)
266 | }
267 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | Tango [](https://circleci.com/gh/lunny/tango) [](https://codecov.io/gh/lunny/tango)
2 | [](https://goreportcard.com/report/github.com/lunny/tango)
3 | [](https://discord.gg/7Ckxjwu)
4 | [English](README.md)
5 | =======================
6 |
7 | 
8 |
9 | Tango 是一个微内核的Go语言Web框架,采用模块化和注入式的设计理念。开发者可根据自身业务逻辑来选择性的装卸框架的功能,甚至利用丰富的中间件来搭建一个全栈式Web开发框架。
10 |
11 | ## 最近更新
12 | - [2016-5-12] 开放Route级别中间件支持
13 | - [2016-3-16] Group完善中间件支持,Route支持中间件
14 | - [2016-2-1] 新增 session-ssdb,支持将ssdb作为session的后端存储
15 | - [2015-10-23] 更新[renders](https://github.com/tango-contrib/renders)插件,解决模板修改后需要刷新两次才能生效的问题
16 |
17 | ## 特性
18 | - 强大而灵活的路由设计
19 | - 兼容已有的 `http.Handler`
20 | - 基于中间件的模块化设计,灵活定制框架功能
21 | - 高性能的依赖注入方式
22 |
23 | ## 安装Tango:
24 |
25 | go get github.com/lunny/tango
26 |
27 | ## 快速入门
28 |
29 | 一个经典的Tango例子如下:
30 |
31 | ```go
32 | package main
33 |
34 | import (
35 | "errors"
36 | "github.com/lunny/tango"
37 | )
38 |
39 | type Action struct {
40 | tango.JSON
41 | }
42 |
43 | func (Action) Get() interface{} {
44 | if true {
45 | return map[string]string{
46 | "say": "Hello tango!",
47 | }
48 | }
49 | return errors.New("something error")
50 | }
51 |
52 | func main() {
53 | t := tango.Classic()
54 | t.Get("/", new(Action))
55 | t.Run()
56 | }
57 | ```
58 |
59 | 然后在浏览器访问`http://localhost:8000`, 将会得到一个json返回
60 |
61 | ```
62 | {"say":"Hello tango!"}
63 | ```
64 |
65 | 如果将上述例子中的 `true` 改为 `false`, 将会得到一个json返回
66 |
67 | ```
68 | {"err":"something error"}
69 | ```
70 |
71 | 这段代码因为拥有一个内嵌的`tango.JSON`,所以返回值会被自动的转成Json
72 |
73 | ## 文档
74 |
75 | - [Manual](http://gobook.io/read/github.com/go-tango/manual-en-US/), And you are welcome to contribue for the book by git PR to [github.com/go-tango/manual-en-US](https://github.com/go-tango/manual-en-US)
76 | - [操作手册](http://gobook.io/read/github.com/go-tango/manual-zh-CN/),您也可以访问 [github.com/go-tango/manual-zh-CN](https://github.com/go-tango/manual-zh-CN)为本手册进行贡献
77 | - [API Reference](https://gowalker.org/github.com/lunny/tango)
78 |
79 | ## 交流讨论
80 |
81 | - QQ群:369240307
82 | - [论坛](https://groups.google.com/forum/#!forum/go-tango)
83 |
84 | ## 使用案例
85 |
86 | - [会计人论坛](https://www.kuaijiren.com) - 会计人论坛
87 | - [GopherTC](https://github.com/jimmykuu/gopher/tree/2.0) - Golang China
88 | - [Wego](https://github.com/go-tango/wego) tango结合[xorm](http://www.xorm.io/)开发的论坛
89 | - [Pugo](https://github.com/go-xiaohei/pugo) 博客
90 | - [DBWeb](https://github.com/go-xorm/dbweb) 基于Web的数据库管理工具
91 | - [Godaily](http://godaily.org) - [github](https://github.com/godaily/news) RSS聚合工具
92 | - [Gos](https://github.com/go-tango/gos) 简易的Web静态文件服务端
93 | - [GoFtpd](https://github.com/goftp/ftpd) - 纯Go的跨平台FTP服务器
94 |
95 | ## 中间件列表
96 |
97 | [中间件](https://github.com/tango-contrib)可以重用代码并且简化工作:
98 |
99 | - [recovery](https://github.com/lunny/tango/wiki/Recovery) - recover after panic
100 | - [compress](https://github.com/lunny/tango/wiki/Compress) - Gzip & Deflate compression
101 | - [static](https://github.com/lunny/tango/wiki/Static) - Serves static files
102 | - [logger](https://github.com/lunny/tango/wiki/Logger) - Log the request & inject Logger to action struct
103 | - [param](https://github.com/lunny/tango/wiki/Params) - get the router parameters
104 | - [return](https://github.com/lunny/tango/wiki/Return) - Handle the returned value smartlly
105 | - [context](https://github.com/lunny/tango/wiki/Context) - Inject context to action struct
106 | - [session](https://github.com/tango-contrib/session) - [](https://circleci.com/gh/tango-contrib/session/tree/master) [](https://codecov.io/gh/tango-contrib/session) Session manager, [session-redis](http://github.com/tango-contrib/session-redis), [session-nodb](http://github.com/tango-contrib/session-nodb), [session-ledis](http://github.com/tango-contrib/session-ledis), [session-ssdb](http://github.com/tango-contrib/session-ssdb)
107 | - [xsrf](https://github.com/tango-contrib/xsrf) - [](https://circleci.com/gh/tango-contrib/xsrf/tree/master) [](https://codecov.io/gh/tango-contrib/xsrf) Generates and validates csrf tokens
108 | - [binding](https://github.com/tango-contrib/binding) - [](https://circleci.com/gh/tango-contrib/binding/tree/master) [](https://codecov.io/gh/tango-contrib/binding) Bind and validates forms
109 | - [renders](https://github.com/tango-contrib/renders) - [](https://circleci.com/gh/tango-contrib/renders/tree/master) [](https://codecov.io/gh/tango-contrib/renders) Go template engine
110 | - [dispatch](https://github.com/tango-contrib/dispatch) - [](https://circleci.com/gh/tango-contrib/dispatch/tree/master) [](https://codecov.io/gh/tango-contrib/dispatch) Multiple Application support on one server
111 | - [tpongo2](https://github.com/tango-contrib/tpongo2) - [](https://circleci.com/gh/tango-contrib/tpongo2/tree/master) [](https://codecov.io/gh/tango-contrib/tpongo2) [Pongo2](https://github.com/flosch/pongo2) teamplte engine support
112 | - [captcha](https://github.com/tango-contrib/captcha) - [](https://circleci.com/gh/tango-contrib/captcha/tree/master) [](https://codecov.io/gh/tango-contrib/captcha) Captcha
113 | - [events](https://github.com/tango-contrib/events) - [](https://circleci.com/gh/tango-contrib/events/tree/master) [](https://codecov.io/gh/tango-contrib/events) Before and After
114 | - [flash](https://github.com/tango-contrib/flash) - [](https://circleci.com/gh/tango-contrib/flash/tree/master) [](https://codecov.io/gh/tango-contrib/flash) Share data between requests
115 | - [debug](https://github.com/tango-contrib/debug) - [](https://circleci.com/gh/tango-contrib/debug/tree/master) [](https://codecov.io/gh/tango-contrib/debug) show detail debug infomaton on log
116 | - [basicauth](https://github.com/tango-contrib/basicauth) - [](https://circleci.com/gh/tango-contrib/basicauth/tree/master) [](https://codecov.io/gh/tango-contrib/basicauth) basicauth middleware
117 | - [authz](https://github.com/tango-contrib/authz) - [](https://travis-ci.org/tango-contrib/authz) [](https://coveralls.io/github/tango-contrib/authz?branch=master) manage permissions via ACL, RBAC, ABAC
118 | - [cache](https://github.com/tango-contrib/cache) - [](https://circleci.com/gh/tango-contrib/cache/tree/master) [](https://codecov.io/gh/tango-contrib/cache) cache middleware - cache-memory, cache-file, [cache-ledis](https://github.com/tango-contrib/cache-ledis), [cache-nodb](https://github.com/tango-contrib/cache-nodb), [cache-mysql](https://github.com/tango-contrib/cache-mysql), [cache-postgres](https://github.com/tango-contrib/cache-postgres), [cache-memcache](https://github.com/tango-contrib/cache-memcache), [cache-redis](https://github.com/tango-contrib/cache-redis)
119 | - [rbac](https://github.com/tango-contrib/rbac) - [](https://circleci.com/gh/tango-contrib/rbac/tree/master) [](https://codecov.io/gh/tango-contrib/rbac) rbac control
120 |
121 | ## License
122 | This project is under BSD License. See the [LICENSE](LICENSE) file for the full license text.
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Tango [简体中文](README_CN.md)
2 | =======================
3 |
4 | [](https://circleci.com/gh/lunny/tango) [](https://codecov.io/gh/lunny/tango)
5 | [](https://goreportcard.com/report/github.com/lunny/tango)
6 | [](https://discord.gg/7Ckxjwu)
7 |
8 | 
9 |
10 | Package tango is a micro & pluggable web framework for Go.
11 |
12 | ##### Current version: v0.5.0 [Version History](https://github.com/lunny/tango/releases)
13 |
14 | ## Getting Started
15 |
16 | To install Tango:
17 |
18 | go get github.com/lunny/tango
19 |
20 | A classic usage of Tango below:
21 |
22 | ```go
23 | package main
24 |
25 | import (
26 | "errors"
27 | "github.com/lunny/tango"
28 | )
29 |
30 | type Action struct {
31 | tango.JSON
32 | }
33 |
34 | func (Action) Get() interface{} {
35 | if true {
36 | return map[string]string{
37 | "say": "Hello tango!",
38 | }
39 | }
40 | return errors.New("something error")
41 | }
42 |
43 | func main() {
44 | t := tango.Classic()
45 | t.Get("/", new(Action))
46 | t.Run()
47 | }
48 | ```
49 |
50 | Then visit `http://localhost:8000` on your browser. You will get
51 | ```
52 | {"say":"Hello tango!"}
53 | ```
54 |
55 | If you change `true` after `if` to `false`, then you will get
56 | ```
57 | {"err":"something error"}
58 | ```
59 |
60 | This code will automatically convert returned map or error to a json because we has an embedded struct `tango.JSON`.
61 |
62 | ## Features
63 |
64 | - Powerful routing & Flexible routes combinations.
65 | - Directly integrate with existing services.
66 | - Easy to plugin features with modular design.
67 | - High performance dependency injection embedded.
68 |
69 | ## Middlewares
70 |
71 | Middlewares allow you easily plugin features for your Tango applications.
72 |
73 | There are already many [middlewares](https://github.com/tango-contrib) to simplify your work:
74 |
75 | - [recovery](https://github.com/lunny/tango/wiki/Recovery) - recover after panic
76 | - [compress](https://github.com/lunny/tango/wiki/Compress) - Gzip & Deflate compression
77 | - [static](https://github.com/lunny/tango/wiki/Static) - Serves static files
78 | - [logger](https://github.com/lunny/tango/wiki/Logger) - Log the request & inject Logger to action struct
79 | - [param](https://github.com/lunny/tango/wiki/Params) - get the router parameters
80 | - [return](https://github.com/lunny/tango/wiki/Return) - Handle the returned value smartlly
81 | - [context](https://github.com/lunny/tango/wiki/Context) - Inject context to action struct
82 | - [session](https://github.com/tango-contrib/session) - [](https://circleci.com/gh/tango-contrib/session/tree/master) [](https://codecov.io/gh/tango-contrib/session) Session manager, [session-redis](http://github.com/tango-contrib/session-redis), [session-nodb](http://github.com/tango-contrib/session-nodb), [session-ledis](http://github.com/tango-contrib/session-ledis), [session-ssdb](http://github.com/tango-contrib/session-ssdb)
83 | - [xsrf](https://github.com/tango-contrib/xsrf) - [](https://circleci.com/gh/tango-contrib/xsrf/tree/master) [](https://codecov.io/gh/tango-contrib/xsrf) Generates and validates csrf tokens
84 | - [binding](https://github.com/tango-contrib/binding) - [](https://circleci.com/gh/tango-contrib/binding/tree/master) [](https://codecov.io/gh/tango-contrib/binding) Bind and validates forms
85 | - [renders](https://github.com/tango-contrib/renders) - [](https://circleci.com/gh/tango-contrib/renders/tree/master) [](https://codecov.io/gh/tango-contrib/renders) Go template engine
86 | - [dispatch](https://github.com/tango-contrib/dispatch) - [](https://circleci.com/gh/tango-contrib/dispatch/tree/master) [](https://codecov.io/gh/tango-contrib/dispatch) Multiple Application support on one server
87 | - [tpongo2](https://github.com/tango-contrib/tpongo2) - [](https://circleci.com/gh/tango-contrib/tpongo2/tree/master) [](https://codecov.io/gh/tango-contrib/tpongo2) [Pongo2](https://github.com/flosch/pongo2) teamplte engine support
88 | - [captcha](https://github.com/tango-contrib/captcha) - [](https://circleci.com/gh/tango-contrib/captcha/tree/master) [](https://codecov.io/gh/tango-contrib/captcha) Captcha
89 | - [events](https://github.com/tango-contrib/events) - [](https://circleci.com/gh/tango-contrib/events/tree/master) [](https://codecov.io/gh/tango-contrib/events) Before and After
90 | - [flash](https://github.com/tango-contrib/flash) - [](https://circleci.com/gh/tango-contrib/flash/tree/master) [](https://codecov.io/gh/tango-contrib/flash) Share data between requests
91 | - [debug](https://github.com/tango-contrib/debug) - [](https://circleci.com/gh/tango-contrib/debug/tree/master) [](https://codecov.io/gh/tango-contrib/debug) show detail debug infomaton on log
92 | - [basicauth](https://github.com/tango-contrib/basicauth) - [](https://circleci.com/gh/tango-contrib/basicauth/tree/master) [](https://codecov.io/gh/tango-contrib/basicauth) basicauth middleware
93 | - [authz](https://github.com/tango-contrib/authz) - [](https://travis-ci.org/tango-contrib/authz) [](https://coveralls.io/github/tango-contrib/authz?branch=master) manage permissions via ACL, RBAC, ABAC
94 | - [cache](https://github.com/tango-contrib/cache) - [](https://circleci.com/gh/tango-contrib/cache/tree/master) [](https://codecov.io/gh/tango-contrib/cache) cache middleware - cache-memory, cache-file, [cache-ledis](https://github.com/tango-contrib/cache-ledis), [cache-nodb](https://github.com/tango-contrib/cache-nodb), [cache-mysql](https://github.com/tango-contrib/cache-mysql), [cache-postgres](https://github.com/tango-contrib/cache-postgres), [cache-memcache](https://github.com/tango-contrib/cache-memcache), [cache-redis](https://github.com/tango-contrib/cache-redis)
95 | - [rbac](https://github.com/tango-contrib/rbac) - [](https://circleci.com/gh/tango-contrib/rbac/tree/master) [](https://codecov.io/gh/tango-contrib/rbac) rbac control
96 |
97 | ## Documentation
98 |
99 | - [Manual](http://gobook.io/read/github.com/go-tango/manual-en-US/), And you are welcome to contribue for the book by git PR to [github.com/go-tango/manual-en-US](https://github.com/go-tango/manual-en-US)
100 | - [操作手册](http://gobook.io/read/github.com/go-tango/manual-zh-CN/),您也可以访问 [github.com/go-tango/manual-zh-CN](https://github.com/go-tango/manual-zh-CN)为本手册进行贡献
101 | - [API Reference](https://gowalker.org/github.com/lunny/tango)
102 |
103 | ## Discuss
104 |
105 | - [Google Group - English](https://groups.google.com/forum/#!forum/go-tango)
106 | - QQ Group - 简体中文 #369240307
107 |
108 | ## Cases
109 |
110 | - [GopherTC](https://github.com/jimmykuu/gopher/tree/2.0) - China Discuss Forum
111 | - [Wego](https://github.com/go-tango/wego) - Discuss Forum
112 | - [dbweb](https://github.com/go-xorm/dbweb) - DB management web UI
113 | - [Godaily](http://godaily.org) - [github](https://github.com/godaily/news)
114 | - [Pugo](https://github.com/go-xiaohei/pugo) - A pugo blog
115 | - [Gos](https://github.com/go-tango/gos) - Static web server
116 | - [GoFtpd](https://github.com/goftp/ftpd) - Pure Go cross-platform ftp server
117 |
118 | ## License
119 |
120 | This project is under BSD License. See the [LICENSE](LICENSE) file for the full license text.
121 |
--------------------------------------------------------------------------------
/query.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "html/template"
9 | "net/http"
10 | "net/url"
11 | "strconv"
12 | )
13 |
14 | // Queries a new enhancement of http.Request
15 | type Queries http.Request
16 |
17 | var _ Set = &Queries{}
18 |
19 | // Values returns http.Request values
20 | func (f *Queries) Values() url.Values {
21 | return (*http.Request)(f).URL.Query()
22 | }
23 |
24 | // String returns request form as string
25 | func (f *Queries) String(key string) (string, error) {
26 | if v, ok := f.Values()[key]; ok {
27 | return v[0], nil
28 | }
29 | return "", ErrorKeyIsNotExist{key}
30 | }
31 |
32 | // Strings returns request form as strings
33 | func (f *Queries) Strings(key string) ([]string, error) {
34 | if v, ok := f.Values()[key]; ok {
35 | return v, nil
36 | }
37 | return nil, ErrorKeyIsNotExist{key}
38 | }
39 |
40 | // Escape returns request form as escaped string
41 | func (f *Queries) Escape(key string) (string, error) {
42 | s, err := f.String(key)
43 | if err != nil {
44 | return "", err
45 | }
46 | return template.HTMLEscapeString(s), nil
47 | }
48 |
49 | // Int returns request form as int
50 | func (f *Queries) Int(key string) (int, error) {
51 | s, err := f.String(key)
52 | if err != nil {
53 | return 0, err
54 | }
55 | return strconv.Atoi(s)
56 | }
57 |
58 | // Int32 returns request form as int32
59 | func (f *Queries) Int32(key string) (int32, error) {
60 | s, err := f.String(key)
61 | if err != nil {
62 | return 0, err
63 | }
64 | v, err := strconv.ParseInt(s, 10, 32)
65 | return int32(v), err
66 | }
67 |
68 | // Int64 returns request form as int64
69 | func (f *Queries) Int64(key string) (int64, error) {
70 | s, err := f.String(key)
71 | if err != nil {
72 | return 0, err
73 | }
74 | v, err := strconv.ParseInt(s, 10, 64)
75 | return v, err
76 | }
77 |
78 | // Uint returns request form as uint
79 | func (f *Queries) Uint(key string) (uint, error) {
80 | s, err := f.String(key)
81 | if err != nil {
82 | return 0, err
83 | }
84 | v, err := strconv.ParseUint(s, 10, 64)
85 | return uint(v), err
86 | }
87 |
88 | // Uint32 returns request form as uint32
89 | func (f *Queries) Uint32(key string) (uint32, error) {
90 | s, err := f.String(key)
91 | if err != nil {
92 | return 0, err
93 | }
94 | v, err := strconv.ParseUint(s, 10, 32)
95 | return uint32(v), err
96 | }
97 |
98 | // Uint64 returns request form as uint64
99 | func (f *Queries) Uint64(key string) (uint64, error) {
100 | s, err := f.String(key)
101 | if err != nil {
102 | return 0, err
103 | }
104 | return strconv.ParseUint(s, 10, 64)
105 | }
106 |
107 | // Bool returns request form as bool
108 | func (f *Queries) Bool(key string) (bool, error) {
109 | s, err := f.String(key)
110 | if err != nil {
111 | return false, err
112 | }
113 | return strconv.ParseBool(s)
114 | }
115 |
116 | // Float32 returns request form as float32
117 | func (f *Queries) Float32(key string) (float32, error) {
118 | s, err := f.String(key)
119 | if err != nil {
120 | return 0, err
121 | }
122 | v, err := strconv.ParseFloat(s, 64)
123 | return float32(v), err
124 | }
125 |
126 | // Float64 returns request form as float64
127 | func (f *Queries) Float64(key string) (float64, error) {
128 | s, err := f.String(key)
129 | if err != nil {
130 | return 0, err
131 | }
132 | return strconv.ParseFloat(s, 64)
133 | }
134 |
135 | // MustString returns request form as string with default
136 | func (f *Queries) MustString(key string, defaults ...string) string {
137 | if v, ok := f.Values()[key]; ok {
138 | return v[0]
139 | }
140 | if len(defaults) > 0 {
141 | return defaults[0]
142 | }
143 | return ""
144 | }
145 |
146 | // MustStrings returns request form as strings with default
147 | func (f *Queries) MustStrings(key string, defaults ...[]string) []string {
148 | (*http.Request)(f).ParseMultipartForm(32 << 20)
149 | if v, ok := f.Values()[key]; ok {
150 | return v
151 | }
152 | if len(defaults) > 0 {
153 | return defaults[0]
154 | }
155 | return []string{}
156 | }
157 |
158 | // MustEscape returns request form as escaped string with default
159 | func (f *Queries) MustEscape(key string, defaults ...string) string {
160 | if v, ok := f.Values()[key]; ok {
161 | return template.HTMLEscapeString(v[0])
162 | }
163 | if len(defaults) > 0 {
164 | return defaults[0]
165 | }
166 | return ""
167 | }
168 |
169 | // MustInt returns request form as int with default
170 | func (f *Queries) MustInt(key string, defaults ...int) int {
171 | if v, ok := f.Values()[key]; ok {
172 | i, err := strconv.Atoi(v[0])
173 | if err == nil {
174 | return i
175 | }
176 | }
177 | if len(defaults) > 0 {
178 | return defaults[0]
179 | }
180 | return 0
181 | }
182 |
183 | // MustInt32 returns request form as int32 with default
184 | func (f *Queries) MustInt32(key string, defaults ...int32) int32 {
185 | if v, ok := f.Values()[key]; ok {
186 | i, err := strconv.ParseInt(v[0], 10, 32)
187 | if err == nil {
188 | return int32(i)
189 | }
190 | }
191 | if len(defaults) > 0 {
192 | return defaults[0]
193 | }
194 | return 0
195 | }
196 |
197 | // MustInt64 returns request form as int64 with default
198 | func (f *Queries) MustInt64(key string, defaults ...int64) int64 {
199 | if v, ok := f.Values()[key]; ok {
200 | i, err := strconv.ParseInt(v[0], 10, 64)
201 | if err == nil {
202 | return i
203 | }
204 | }
205 | if len(defaults) > 0 {
206 | return defaults[0]
207 | }
208 | return 0
209 | }
210 |
211 | // MustUint returns request form as uint with default
212 | func (f *Queries) MustUint(key string, defaults ...uint) uint {
213 | if v, ok := f.Values()[key]; ok {
214 | i, err := strconv.ParseUint(v[0], 10, 64)
215 | if err == nil {
216 | return uint(i)
217 | }
218 | }
219 | if len(defaults) > 0 {
220 | return defaults[0]
221 | }
222 | return 0
223 | }
224 |
225 | // MustUint32 returns request form as uint32 with default
226 | func (f *Queries) MustUint32(key string, defaults ...uint32) uint32 {
227 | if v, ok := f.Values()[key]; ok {
228 | i, err := strconv.ParseUint(v[0], 10, 32)
229 | if err == nil {
230 | return uint32(i)
231 | }
232 | }
233 | if len(defaults) > 0 {
234 | return defaults[0]
235 | }
236 | return 0
237 | }
238 |
239 | // MustUint64 returns request form as uint64 with default
240 | func (f *Queries) MustUint64(key string, defaults ...uint64) uint64 {
241 | if v, ok := f.Values()[key]; ok {
242 | i, err := strconv.ParseUint(v[0], 10, 64)
243 | if err == nil {
244 | return i
245 | }
246 | }
247 | if len(defaults) > 0 {
248 | return defaults[0]
249 | }
250 | return 0
251 | }
252 |
253 | // MustFloat32 returns request form as float32 with default
254 | func (f *Queries) MustFloat32(key string, defaults ...float32) float32 {
255 | if v, ok := f.Values()[key]; ok {
256 | i, err := strconv.ParseFloat(v[0], 32)
257 | if err == nil {
258 | return float32(i)
259 | }
260 | }
261 | if len(defaults) > 0 {
262 | return defaults[0]
263 | }
264 | return 0
265 | }
266 |
267 | // MustFloat64 returns request form as float64 with default
268 | func (f *Queries) MustFloat64(key string, defaults ...float64) float64 {
269 | if v, ok := f.Values()[key]; ok {
270 | i, err := strconv.ParseFloat(v[0], 64)
271 | if err == nil {
272 | return i
273 | }
274 | }
275 | if len(defaults) > 0 {
276 | return defaults[0]
277 | }
278 | return 0
279 | }
280 |
281 | // MustBool returns request form as bool with default
282 | func (f *Queries) MustBool(key string, defaults ...bool) bool {
283 | if v, ok := f.Values()[key]; ok {
284 | i, err := strconv.ParseBool(v[0])
285 | if err == nil {
286 | return i
287 | }
288 | }
289 | if len(defaults) > 0 {
290 | return defaults[0]
291 | }
292 | return false
293 | }
294 |
295 | // Query returns request form as string with default
296 | func (ctx *Context) Query(key string, defaults ...string) string {
297 | return (*Queries)(ctx.req).MustString(key, defaults...)
298 | }
299 |
300 | // QueryStrings returns request form as strings with default
301 | func (ctx *Context) QueryStrings(key string, defaults ...[]string) []string {
302 | return (*Queries)(ctx.req).MustStrings(key, defaults...)
303 | }
304 |
305 | // QueryEscape returns request form as escaped string with default
306 | func (ctx *Context) QueryEscape(key string, defaults ...string) string {
307 | return (*Queries)(ctx.req).MustEscape(key, defaults...)
308 | }
309 |
310 | // QueryInt returns request form as int with default
311 | func (ctx *Context) QueryInt(key string, defaults ...int) int {
312 | return (*Queries)(ctx.req).MustInt(key, defaults...)
313 | }
314 |
315 | // QueryInt32 returns request form as int32 with default
316 | func (ctx *Context) QueryInt32(key string, defaults ...int32) int32 {
317 | return (*Queries)(ctx.req).MustInt32(key, defaults...)
318 | }
319 |
320 | // QueryInt64 returns request form as int64 with default
321 | func (ctx *Context) QueryInt64(key string, defaults ...int64) int64 {
322 | return (*Queries)(ctx.req).MustInt64(key, defaults...)
323 | }
324 |
325 | // QueryUint returns request form as uint with default
326 | func (ctx *Context) QueryUint(key string, defaults ...uint) uint {
327 | return (*Queries)(ctx.req).MustUint(key, defaults...)
328 | }
329 |
330 | // QueryUint32 returns request form as uint32 with default
331 | func (ctx *Context) QueryUint32(key string, defaults ...uint32) uint32 {
332 | return (*Queries)(ctx.req).MustUint32(key, defaults...)
333 | }
334 |
335 | // QueryUint64 returns request form as uint64 with default
336 | func (ctx *Context) QueryUint64(key string, defaults ...uint64) uint64 {
337 | return (*Queries)(ctx.req).MustUint64(key, defaults...)
338 | }
339 |
340 | // QueryFloat32 returns request form as float32 with default
341 | func (ctx *Context) QueryFloat32(key string, defaults ...float32) float32 {
342 | return (*Queries)(ctx.req).MustFloat32(key, defaults...)
343 | }
344 |
345 | // FormFloat64 returns request form as float64 with default
346 | func (ctx *Context) QueryFloat64(key string, defaults ...float64) float64 {
347 | return (*Queries)(ctx.req).MustFloat64(key, defaults...)
348 | }
349 |
350 | // FormBool returns request form as bool with default
351 | func (ctx *Context) QueryBool(key string, defaults ...bool) bool {
352 | return (*Queries)(ctx.req).MustBool(key, defaults...)
353 | }
354 |
--------------------------------------------------------------------------------
/return_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "encoding/xml"
10 | "errors"
11 | "net/http"
12 | "net/http/httptest"
13 | "strings"
14 | "testing"
15 | )
16 |
17 | type MyReturn struct {
18 | }
19 |
20 | func (m MyReturn) Get() string {
21 | return "string return"
22 | }
23 |
24 | func (m MyReturn) Post() []byte {
25 | return []byte("bytes return")
26 | }
27 |
28 | func (m MyReturn) Put() error {
29 | return errors.New("error return")
30 | }
31 |
32 | func TestReturn(t *testing.T) {
33 | buff := bytes.NewBufferString("")
34 | recorder := httptest.NewRecorder()
35 | recorder.Body = buff
36 |
37 | o := Classic()
38 | o.Any("/", new(MyReturn))
39 |
40 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
41 | if err != nil {
42 | t.Error(err)
43 | }
44 |
45 | o.ServeHTTP(recorder, req)
46 | expect(t, recorder.Code, http.StatusOK)
47 | refute(t, len(buff.String()), 0)
48 | expect(t, buff.String(), "string return")
49 |
50 | buff.Reset()
51 | req, err = http.NewRequest("POST", "http://localhost:8000/", nil)
52 | if err != nil {
53 | t.Error(err)
54 | }
55 |
56 | o.ServeHTTP(recorder, req)
57 | expect(t, recorder.Code, http.StatusOK)
58 | refute(t, len(buff.String()), 0)
59 | expect(t, buff.String(), "bytes return")
60 | }
61 |
62 | func TestReturnPut(t *testing.T) {
63 | buff := bytes.NewBufferString("")
64 | recorder := httptest.NewRecorder()
65 | recorder.Body = buff
66 |
67 | o := Classic()
68 | o.Any("/", new(MyReturn))
69 |
70 | req, err := http.NewRequest("PUT", "http://localhost:8000/", nil)
71 | if err != nil {
72 | t.Error(err)
73 | }
74 |
75 | o.ServeHTTP(recorder, req)
76 | expect(t, recorder.Code, http.StatusInternalServerError)
77 | refute(t, len(buff.String()), 0)
78 | expect(t, buff.String(), "error return")
79 | }
80 |
81 | type JSONReturn struct {
82 | JSON
83 | }
84 |
85 | func (JSONReturn) Get() interface{} {
86 | return map[string]interface{}{
87 | "test1": 1,
88 | "test2": "2",
89 | "test3": true,
90 | }
91 | }
92 |
93 | func TestReturnJson1(t *testing.T) {
94 | buff := bytes.NewBufferString("")
95 | recorder := httptest.NewRecorder()
96 | recorder.Body = buff
97 |
98 | o := Classic()
99 | o.Get("/", new(JSONReturn))
100 |
101 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
102 | if err != nil {
103 | t.Error(err)
104 | }
105 |
106 | o.ServeHTTP(recorder, req)
107 | expect(t, recorder.Code, http.StatusOK)
108 | refute(t, len(buff.String()), 0)
109 | expect(t, strings.TrimSpace(buff.String()), `{"test1":1,"test2":"2","test3":true}`)
110 | }
111 |
112 | type JSONErrReturn struct {
113 | JSON
114 | }
115 |
116 | func (JSONErrReturn) Get() error {
117 | return errors.New("error")
118 | }
119 |
120 | func TestReturnJsonError(t *testing.T) {
121 | buff := bytes.NewBufferString("")
122 | recorder := httptest.NewRecorder()
123 | recorder.Body = buff
124 |
125 | o := Classic()
126 | o.Get("/", new(JSONErrReturn))
127 |
128 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
129 | if err != nil {
130 | t.Error(err)
131 | }
132 |
133 | o.ServeHTTP(recorder, req)
134 | expect(t, recorder.Code, http.StatusOK)
135 | refute(t, len(buff.String()), 0)
136 | expect(t, strings.TrimSpace(buff.String()), `{"err":"error"}`)
137 | }
138 |
139 | type JSONErrReturn2 struct {
140 | JSON
141 | }
142 |
143 | func (JSONErrReturn2) Get() error {
144 | return Abort(http.StatusInternalServerError, "error")
145 | }
146 |
147 | func TestReturnJsonError2(t *testing.T) {
148 | buff := bytes.NewBufferString("")
149 | recorder := httptest.NewRecorder()
150 | recorder.Body = buff
151 |
152 | o := Classic()
153 | o.Get("/", new(JSONErrReturn2))
154 |
155 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
156 | if err != nil {
157 | t.Error(err)
158 | }
159 |
160 | o.ServeHTTP(recorder, req)
161 | expect(t, recorder.Code, http.StatusInternalServerError)
162 | refute(t, len(buff.String()), 0)
163 | expect(t, strings.TrimSpace(buff.String()), `{"err":"error"}`)
164 | }
165 |
166 | type JSONErrReturn3 struct {
167 | JSON
168 | }
169 |
170 | type MyError struct {
171 | }
172 |
173 | func (m *MyError) Error() string {
174 | return "error"
175 | }
176 |
177 | func (m *MyError) ErrorCode() int {
178 | return 1
179 | }
180 |
181 | func (JSONErrReturn3) Get() error {
182 | return &MyError{}
183 | }
184 |
185 | func TestReturnJsonError3(t *testing.T) {
186 | buff := bytes.NewBufferString("")
187 | recorder := httptest.NewRecorder()
188 | recorder.Body = buff
189 |
190 | o := Classic()
191 | o.Get("/", new(JSONErrReturn3))
192 |
193 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
194 | if err != nil {
195 | t.Error(err)
196 | }
197 |
198 | o.ServeHTTP(recorder, req)
199 | expect(t, recorder.Code, http.StatusOK)
200 | refute(t, len(buff.String()), 0)
201 | expect(t, strings.TrimSpace(buff.String()), `{"err":"error","err_code":1}`)
202 | }
203 |
204 | type JSONReturn1 struct {
205 | JSON
206 | }
207 |
208 | func (JSONReturn1) Get() string {
209 | return "return"
210 | }
211 |
212 | func TestReturnJson2(t *testing.T) {
213 | buff := bytes.NewBufferString("")
214 | recorder := httptest.NewRecorder()
215 | recorder.Body = buff
216 |
217 | o := Classic()
218 | o.Get("/", new(JSONReturn1))
219 |
220 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
221 | if err != nil {
222 | t.Error(err)
223 | }
224 |
225 | o.ServeHTTP(recorder, req)
226 | expect(t, recorder.Code, http.StatusOK)
227 | refute(t, len(buff.String()), 0)
228 | expect(t, strings.TrimSpace(buff.String()), `{"content":"return"}`)
229 | }
230 |
231 | type JSONReturn2 struct {
232 | JSON
233 | }
234 |
235 | func (JSONReturn2) Get() []byte {
236 | return []byte("return")
237 | }
238 |
239 | func TestReturnJson3(t *testing.T) {
240 | buff := bytes.NewBufferString("")
241 | recorder := httptest.NewRecorder()
242 | recorder.Body = buff
243 |
244 | o := Classic()
245 | o.Get("/", new(JSONReturn2))
246 |
247 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
248 | if err != nil {
249 | t.Error(err)
250 | }
251 |
252 | o.ServeHTTP(recorder, req)
253 | expect(t, recorder.Code, http.StatusOK)
254 | refute(t, len(buff.String()), 0)
255 | expect(t, strings.TrimSpace(buff.String()), `{"content":"return"}`)
256 | }
257 |
258 | type JSONReturn3 struct {
259 | JSON
260 | }
261 |
262 | func (JSONReturn3) Get() (int, interface{}) {
263 | if true {
264 | return 201, map[string]string{
265 | "say": "Hello tango!",
266 | }
267 | }
268 | return 500, errors.New("something error")
269 | }
270 |
271 | func TestReturnJson4(t *testing.T) {
272 | buff := bytes.NewBufferString("")
273 | recorder := httptest.NewRecorder()
274 | recorder.Body = buff
275 |
276 | o := Classic()
277 | o.Get("/", new(JSONReturn3))
278 |
279 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
280 | if err != nil {
281 | t.Error(err)
282 | }
283 |
284 | o.ServeHTTP(recorder, req)
285 | expect(t, recorder.Code, 201)
286 | refute(t, len(buff.String()), 0)
287 | expect(t, strings.TrimSpace(buff.String()), `{"say":"Hello tango!"}`)
288 | }
289 |
290 | type XMLReturn struct {
291 | XML
292 | }
293 |
294 | type Address struct {
295 | City, State string
296 | }
297 | type Person struct {
298 | XMLName xml.Name `xml:"person"`
299 | ID int `xml:"id,attr"`
300 | FirstName string `xml:"name>first"`
301 | LastName string `xml:"name>last"`
302 | Age int `xml:"age"`
303 | Height float32 `xml:"height,omitempty"`
304 | Married bool
305 | Address
306 | Comment string `xml:",comment"`
307 | }
308 |
309 | func (XMLReturn) Get() interface{} {
310 | v := &Person{ID: 13, FirstName: "John", LastName: "Doe", Age: 42}
311 | v.Comment = " Need more details. "
312 | v.Address = Address{"Hanga Roa", "Easter Island"}
313 | return v
314 | }
315 |
316 | func TestReturnXML(t *testing.T) {
317 | buff := bytes.NewBufferString("")
318 | recorder := httptest.NewRecorder()
319 | recorder.Body = buff
320 |
321 | o := Classic()
322 | o.Get("/", new(XMLReturn))
323 |
324 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
325 | if err != nil {
326 | t.Error(err)
327 | }
328 |
329 | o.ServeHTTP(recorder, req)
330 | expect(t, recorder.Code, http.StatusOK)
331 | refute(t, len(buff.String()), 0)
332 | expect(t, buff.String(), `JohnDoe42falseHanga RoaEaster Island`)
333 | }
334 |
335 | type XMLErrReturn struct {
336 | XML
337 | }
338 |
339 | func (XMLErrReturn) Get() error {
340 | return errors.New("error")
341 | }
342 |
343 | func TestReturnXmlError(t *testing.T) {
344 | buff := bytes.NewBufferString("")
345 | recorder := httptest.NewRecorder()
346 | recorder.Body = buff
347 |
348 | o := Classic()
349 | o.Get("/", new(XMLErrReturn))
350 |
351 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
352 | if err != nil {
353 | t.Error(err)
354 | }
355 |
356 | o.ServeHTTP(recorder, req)
357 | expect(t, recorder.Code, http.StatusOK)
358 | refute(t, len(buff.String()), 0)
359 | expect(t, strings.TrimSpace(buff.String()), `error`)
360 | }
361 |
362 | type JSONReturn7 struct {
363 | JSON
364 | }
365 |
366 | func (JSONReturn7) Get() (int, interface{}) {
367 | return 201, "sss"
368 | }
369 |
370 | func TestReturn7(t *testing.T) {
371 | buff := bytes.NewBufferString("")
372 | recorder := httptest.NewRecorder()
373 | recorder.Body = buff
374 |
375 | o := Classic()
376 | o.Get("/", new(JSONReturn7))
377 |
378 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
379 | if err != nil {
380 | t.Error(err)
381 | }
382 |
383 | o.ServeHTTP(recorder, req)
384 | expect(t, recorder.Code, 201)
385 | refute(t, len(buff.String()), 0)
386 | expect(t, strings.TrimSpace(buff.String()), `{"content":"sss"}`)
387 | }
388 |
389 | type Return8 struct {
390 | }
391 |
392 | func (Return8) Get() (int, interface{}) {
393 | return 403, "xxx"
394 | }
395 |
396 | func TestReturn8(t *testing.T) {
397 | buff := bytes.NewBufferString("")
398 | recorder := httptest.NewRecorder()
399 | recorder.Body = buff
400 |
401 | o := Classic()
402 | o.Get("/", new(Return8))
403 |
404 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
405 | if err != nil {
406 | t.Error(err)
407 | }
408 |
409 | o.ServeHTTP(recorder, req)
410 | expect(t, recorder.Code, 403)
411 | refute(t, len(buff.String()), 0)
412 | expect(t, strings.TrimSpace(buff.String()), `xxx`)
413 | }
414 |
--------------------------------------------------------------------------------
/param.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "errors"
9 | "html/template"
10 | "strconv"
11 | )
12 |
13 | type (
14 | param struct {
15 | Name string
16 | Value string
17 | }
18 | // Params defines params of http request
19 | Params []param
20 | )
21 |
22 | var _ Set = &Params{}
23 |
24 | // Get returns request form as string
25 | func (p *Params) Get(key string) string {
26 | if len(key) == 0 {
27 | return ""
28 | }
29 | if key[0] != ':' && key[0] != '*' {
30 | key = ":" + key
31 | }
32 |
33 | for _, v := range *p {
34 | if v.Name == key {
35 | return v.Value
36 | }
37 | }
38 | return ""
39 | }
40 |
41 | // String returns request form as string
42 | func (p *Params) String(key string) (string, error) {
43 | if len(key) == 0 {
44 | return "", errors.New("not exist")
45 | }
46 | if key[0] != ':' && key[0] != '*' {
47 | key = ":" + key
48 | }
49 |
50 | for _, v := range *p {
51 | if v.Name == key {
52 | return v.Value, nil
53 | }
54 | }
55 | return "", errors.New("not exist")
56 | }
57 |
58 | // Strings returns request form as slice of string
59 | func (p *Params) Strings(key string) ([]string, error) {
60 | if len(key) == 0 {
61 | return nil, errors.New("not exist")
62 | }
63 | if key[0] != ':' && key[0] != '*' {
64 | key = ":" + key
65 | }
66 |
67 | var s = make([]string, 0)
68 | for _, v := range *p {
69 | if v.Name == key {
70 | s = append(s, v.Value)
71 | }
72 | }
73 | if len(s) > 0 {
74 | return s, nil
75 | }
76 | return nil, errors.New("not exist")
77 | }
78 |
79 | // Escape returns request form as escaped string
80 | func (p *Params) Escape(key string) (string, error) {
81 | if len(key) == 0 {
82 | return "", errors.New("not exist")
83 | }
84 | if key[0] != ':' && key[0] != '*' {
85 | key = ":" + key
86 | }
87 |
88 | for _, v := range *p {
89 | if v.Name == key {
90 | return template.HTMLEscapeString(v.Value), nil
91 | }
92 | }
93 | return "", errors.New("not exist")
94 | }
95 |
96 | // Int returns request form as int
97 | func (p *Params) Int(key string) (int, error) {
98 | return strconv.Atoi(p.Get(key))
99 | }
100 |
101 | // Int32 returns request form as int32
102 | func (p *Params) Int32(key string) (int32, error) {
103 | v, err := strconv.ParseInt(p.Get(key), 10, 32)
104 | return int32(v), err
105 | }
106 |
107 | // Int64 returns request form as int64
108 | func (p *Params) Int64(key string) (int64, error) {
109 | return strconv.ParseInt(p.Get(key), 10, 64)
110 | }
111 |
112 | // Uint returns request form as uint
113 | func (p *Params) Uint(key string) (uint, error) {
114 | v, err := strconv.ParseUint(p.Get(key), 10, 64)
115 | return uint(v), err
116 | }
117 |
118 | // Uint32 returns request form as uint32
119 | func (p *Params) Uint32(key string) (uint32, error) {
120 | v, err := strconv.ParseUint(p.Get(key), 10, 32)
121 | return uint32(v), err
122 | }
123 |
124 | // Uint64 returns request form as uint64
125 | func (p *Params) Uint64(key string) (uint64, error) {
126 | return strconv.ParseUint(p.Get(key), 10, 64)
127 | }
128 |
129 | // Bool returns request form as bool
130 | func (p *Params) Bool(key string) (bool, error) {
131 | return strconv.ParseBool(p.Get(key))
132 | }
133 |
134 | // Float32 returns request form as float32
135 | func (p *Params) Float32(key string) (float32, error) {
136 | v, err := strconv.ParseFloat(p.Get(key), 32)
137 | return float32(v), err
138 | }
139 |
140 | // Float64 returns request form as float64
141 | func (p *Params) Float64(key string) (float64, error) {
142 | return strconv.ParseFloat(p.Get(key), 64)
143 | }
144 |
145 | // MustString returns request form as slice of string with default
146 | func (p *Params) MustString(key string, defaults ...string) string {
147 | if len(key) == 0 {
148 | return ""
149 | }
150 | if key[0] != ':' && key[0] != '*' {
151 | key = ":" + key
152 | }
153 |
154 | for _, v := range *p {
155 | if v.Name == key {
156 | return v.Value
157 | }
158 | }
159 | if len(defaults) > 0 {
160 | return defaults[0]
161 | }
162 | return ""
163 | }
164 |
165 | // MustStrings returns request form as slice of string with default
166 | func (p *Params) MustStrings(key string, defaults ...[]string) []string {
167 | if len(key) == 0 {
168 | return []string{}
169 | }
170 | if key[0] != ':' && key[0] != '*' {
171 | key = ":" + key
172 | }
173 |
174 | var s = make([]string, 0)
175 | for _, v := range *p {
176 | if v.Name == key {
177 | s = append(s, v.Value)
178 | }
179 | }
180 | if len(s) > 0 {
181 | return s
182 | }
183 | if len(defaults) > 0 {
184 | return defaults[0]
185 | }
186 | return []string{}
187 | }
188 |
189 | // MustEscape returns request form as escaped string with default
190 | func (p *Params) MustEscape(key string, defaults ...string) string {
191 | if len(key) == 0 {
192 | return ""
193 | }
194 | if key[0] != ':' && key[0] != '*' {
195 | key = ":" + key
196 | }
197 |
198 | for _, v := range *p {
199 | if v.Name == key {
200 | return template.HTMLEscapeString(v.Value)
201 | }
202 | }
203 | if len(defaults) > 0 {
204 | return defaults[0]
205 | }
206 | return ""
207 | }
208 |
209 | // MustInt returns request form as int with default
210 | func (p *Params) MustInt(key string, defaults ...int) int {
211 | v, err := strconv.Atoi(p.Get(key))
212 | if len(defaults) > 0 && err != nil {
213 | return defaults[0]
214 | }
215 | return v
216 | }
217 |
218 | // MustInt32 returns request form as int32 with default
219 | func (p *Params) MustInt32(key string, defaults ...int32) int32 {
220 | r, err := strconv.ParseInt(p.Get(key), 10, 32)
221 | if len(defaults) > 0 && err != nil {
222 | return defaults[0]
223 | }
224 |
225 | return int32(r)
226 | }
227 |
228 | // MustInt64 returns request form as int64 with default
229 | func (p *Params) MustInt64(key string, defaults ...int64) int64 {
230 | r, err := strconv.ParseInt(p.Get(key), 10, 64)
231 | if len(defaults) > 0 && err != nil {
232 | return defaults[0]
233 | }
234 | return r
235 | }
236 |
237 | // MustUint returns request form as uint with default
238 | func (p *Params) MustUint(key string, defaults ...uint) uint {
239 | v, err := strconv.ParseUint(p.Get(key), 10, 64)
240 | if len(defaults) > 0 && err != nil {
241 | return defaults[0]
242 | }
243 | return uint(v)
244 | }
245 |
246 | // MustUint32 returns request form as uint32 with default
247 | func (p *Params) MustUint32(key string, defaults ...uint32) uint32 {
248 | r, err := strconv.ParseUint(p.Get(key), 10, 32)
249 | if len(defaults) > 0 && err != nil {
250 | return defaults[0]
251 | }
252 |
253 | return uint32(r)
254 | }
255 |
256 | // MustUint64 returns request form as uint64 with default
257 | func (p *Params) MustUint64(key string, defaults ...uint64) uint64 {
258 | r, err := strconv.ParseUint(p.Get(key), 10, 64)
259 | if len(defaults) > 0 && err != nil {
260 | return defaults[0]
261 | }
262 | return r
263 | }
264 |
265 | // MustFloat32 returns request form as float32 with default
266 | func (p *Params) MustFloat32(key string, defaults ...float32) float32 {
267 | r, err := strconv.ParseFloat(p.Get(key), 32)
268 | if len(defaults) > 0 && err != nil {
269 | return defaults[0]
270 | }
271 | return float32(r)
272 | }
273 |
274 | // MustFloat64 returns request form as float64 with default
275 | func (p *Params) MustFloat64(key string, defaults ...float64) float64 {
276 | r, err := strconv.ParseFloat(p.Get(key), 64)
277 | if len(defaults) > 0 && err != nil {
278 | return defaults[0]
279 | }
280 | return r
281 | }
282 |
283 | // MustBool returns request form as bool with default
284 | func (p *Params) MustBool(key string, defaults ...bool) bool {
285 | r, err := strconv.ParseBool(p.Get(key))
286 | if len(defaults) > 0 && err != nil {
287 | return defaults[0]
288 | }
289 | return r
290 | }
291 |
292 | // Param returns request form as string with default
293 | func (ctx *Context) Param(key string, defaults ...string) string {
294 | return ctx.Params().MustString(key, defaults...)
295 | }
296 |
297 | // ParamStrings returns request form as slice of string with default
298 | func (ctx *Context) ParamStrings(key string, defaults ...[]string) []string {
299 | return ctx.Params().MustStrings(key, defaults...)
300 | }
301 |
302 | // ParamEscape returns request form as escaped string with default
303 | func (ctx *Context) ParamEscape(key string, defaults ...string) string {
304 | return ctx.Params().MustEscape(key, defaults...)
305 | }
306 |
307 | // ParamInt returns request form as int with default
308 | func (ctx *Context) ParamInt(key string, defaults ...int) int {
309 | return ctx.Params().MustInt(key, defaults...)
310 | }
311 |
312 | // ParamInt32 returns request form as int32 with default
313 | func (ctx *Context) ParamInt32(key string, defaults ...int32) int32 {
314 | return ctx.Params().MustInt32(key, defaults...)
315 | }
316 |
317 | // ParamInt64 returns request form as int64 with default
318 | func (ctx *Context) ParamInt64(key string, defaults ...int64) int64 {
319 | return ctx.Params().MustInt64(key, defaults...)
320 | }
321 |
322 | // ParamUint returns request form as uint with default
323 | func (ctx *Context) ParamUint(key string, defaults ...uint) uint {
324 | return ctx.Params().MustUint(key, defaults...)
325 | }
326 |
327 | // ParamUint32 returns request form as uint32 with default
328 | func (ctx *Context) ParamUint32(key string, defaults ...uint32) uint32 {
329 | return ctx.Params().MustUint32(key, defaults...)
330 | }
331 |
332 | // ParamUint64 returns request form as uint64 with default
333 | func (ctx *Context) ParamUint64(key string, defaults ...uint64) uint64 {
334 | return ctx.Params().MustUint64(key, defaults...)
335 | }
336 |
337 | // ParamFloat32 returns request form as float32 with default
338 | func (ctx *Context) ParamFloat32(key string, defaults ...float32) float32 {
339 | return ctx.Params().MustFloat32(key, defaults...)
340 | }
341 |
342 | // ParamFloat64 returns request form as float64 with default
343 | func (ctx *Context) ParamFloat64(key string, defaults ...float64) float64 {
344 | return ctx.Params().MustFloat64(key, defaults...)
345 | }
346 |
347 | // ParamBool returns request form as bool with default
348 | func (ctx *Context) ParamBool(key string, defaults ...bool) bool {
349 | return ctx.Params().MustBool(key, defaults...)
350 | }
351 |
352 | // Set sets key/value to params
353 | func (p *Params) Set(key, value string) {
354 | if len(key) == 0 {
355 | return
356 | }
357 | if key[0] != ':' && key[0] != '*' {
358 | key = ":" + key
359 | }
360 |
361 | for i, v := range *p {
362 | if v.Name == key {
363 | (*p)[i].Value = value
364 | return
365 | }
366 | }
367 |
368 | *p = append(*p, param{key, value})
369 | }
370 |
371 | // Paramer defines an interface to get params
372 | type Paramer interface {
373 | SetParams([]param)
374 | }
375 |
376 | // SetParams implemented Paramer
377 | func (p *Params) SetParams(params []param) {
378 | *p = params
379 | }
380 |
381 | // Param returns params handle to operate param
382 | func Param() HandlerFunc {
383 | return func(ctx *Context) {
384 | if action := ctx.Action(); action != nil {
385 | if p, ok := action.(Paramer); ok {
386 | p.SetParams(*ctx.Params())
387 | }
388 | }
389 | ctx.Next()
390 | }
391 | }
392 |
--------------------------------------------------------------------------------
/context_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "net/http"
11 | "net/http/httptest"
12 | "strings"
13 | "testing"
14 | )
15 |
16 | type CtxAction struct {
17 | Ctx
18 | }
19 |
20 | func (p *CtxAction) Get() {
21 | p.Ctx.Write([]byte("context"))
22 | }
23 |
24 | func TestContext1(t *testing.T) {
25 | buff := bytes.NewBufferString("")
26 | recorder := httptest.NewRecorder()
27 | recorder.Body = buff
28 |
29 | o := Classic()
30 | o.Get("/", new(CtxAction))
31 |
32 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
33 | if err != nil {
34 | t.Error(err)
35 | }
36 |
37 | o.ServeHTTP(recorder, req)
38 | expect(t, recorder.Code, http.StatusOK)
39 | refute(t, len(buff.String()), 0)
40 | expect(t, buff.String(), "context")
41 | }
42 |
43 | type CtxJSONAction struct {
44 | Ctx
45 | }
46 |
47 | func (p *CtxJSONAction) Get() error {
48 | return p.Ctx.ServeJson(map[string]string{
49 | "get": "ctx",
50 | })
51 | }
52 |
53 | func TestContext2(t *testing.T) {
54 | buff := bytes.NewBufferString("")
55 | recorder := httptest.NewRecorder()
56 | recorder.Body = buff
57 |
58 | o := Classic()
59 | o.Get("/", new(CtxJSONAction))
60 |
61 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
62 | if err != nil {
63 | t.Error(err)
64 | }
65 |
66 | o.ServeHTTP(recorder, req)
67 | expect(t, recorder.Code, http.StatusOK)
68 | refute(t, len(buff.String()), 0)
69 | expect(t, recorder.Header().Get("Content-Type"), "application/json; charset=UTF-8")
70 | expect(t, strings.TrimSpace(buff.String()), `{"get":"ctx"}`)
71 | }
72 |
73 | type CtxXMLAction struct {
74 | Ctx
75 | }
76 |
77 | type XMLStruct struct {
78 | Content string
79 | }
80 |
81 | func (p *CtxXMLAction) Get() error {
82 | return p.Ctx.ServeXml(XMLStruct{"content"})
83 | }
84 |
85 | func TestContext3(t *testing.T) {
86 | buff := bytes.NewBufferString("")
87 | recorder := httptest.NewRecorder()
88 | recorder.Body = buff
89 |
90 | o := Classic()
91 | o.Get("/", new(CtxXMLAction))
92 |
93 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
94 | if err != nil {
95 | t.Error(err)
96 | }
97 |
98 | o.ServeHTTP(recorder, req)
99 | expect(t, recorder.Code, http.StatusOK)
100 | expect(t, recorder.Header().Get("Content-Type"), "application/xml; charset=UTF-8")
101 | refute(t, len(buff.String()), 0)
102 | expect(t, strings.TrimSpace(buff.String()), `content`)
103 | }
104 |
105 | type CtxFileAction struct {
106 | Ctx
107 | }
108 |
109 | func (p *CtxFileAction) Get() error {
110 | return p.Ctx.ServeFile("./public/index.html")
111 | }
112 |
113 | func TestContext4(t *testing.T) {
114 | buff := bytes.NewBufferString("")
115 | recorder := httptest.NewRecorder()
116 | recorder.Body = buff
117 |
118 | o := Classic()
119 | o.Any("/", new(CtxFileAction))
120 |
121 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
122 | if err != nil {
123 | t.Error(err)
124 | }
125 |
126 | o.ServeHTTP(recorder, req)
127 | expect(t, recorder.Code, http.StatusOK)
128 | refute(t, len(buff.String()), 0)
129 | expect(t, strings.TrimSpace(buff.String()), `this is index.html`)
130 | }
131 |
132 | func TestContext5(t *testing.T) {
133 | buff := bytes.NewBufferString("")
134 | recorder := httptest.NewRecorder()
135 | recorder.Body = buff
136 |
137 | o := Classic()
138 | o.Any("/2", func() string {
139 | return "2"
140 | })
141 | o.Any("/", func(ctx *Context) {
142 | ctx.Redirect("/2")
143 | })
144 |
145 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
146 | if err != nil {
147 | t.Error(err)
148 | }
149 |
150 | o.ServeHTTP(recorder, req)
151 | expect(t, recorder.Code, http.StatusFound)
152 | refute(t, len(buff.String()), 0)
153 | expect(t, strings.TrimSpace(buff.String()), `Found.`)
154 | }
155 |
156 | type NotFoundAction struct {
157 | Ctx
158 | }
159 |
160 | func (n *NotFoundAction) Get() {
161 | n.NotFound("not found")
162 | }
163 |
164 | func TestContext6(t *testing.T) {
165 | buff := bytes.NewBufferString("")
166 | recorder := httptest.NewRecorder()
167 | recorder.Body = buff
168 |
169 | o := Classic()
170 | o.Any("/", new(NotFoundAction))
171 |
172 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
173 | if err != nil {
174 | t.Error(err)
175 | }
176 |
177 | o.ServeHTTP(recorder, req)
178 | expect(t, recorder.Code, http.StatusNotFound)
179 | refute(t, len(buff.String()), 0)
180 | expect(t, strings.TrimSpace(buff.String()), "not found")
181 | }
182 |
183 | type NotModifidAction struct {
184 | Ctx
185 | }
186 |
187 | func (n *NotModifidAction) Get() {
188 | n.NotModified()
189 | }
190 |
191 | func TestContext7(t *testing.T) {
192 | buff := bytes.NewBufferString("")
193 | recorder := httptest.NewRecorder()
194 | recorder.Body = buff
195 |
196 | o := Classic()
197 | o.Any("/", new(NotModifidAction))
198 |
199 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
200 | if err != nil {
201 | t.Error(err)
202 | }
203 |
204 | o.ServeHTTP(recorder, req)
205 | expect(t, recorder.Code, http.StatusNotModified)
206 | expect(t, len(buff.String()), 0)
207 | }
208 |
209 | type UnauthorizedAction struct {
210 | Ctx
211 | }
212 |
213 | func (n *UnauthorizedAction) Get() {
214 | n.Unauthorized()
215 | }
216 |
217 | func TestContext8(t *testing.T) {
218 | buff := bytes.NewBufferString("")
219 | recorder := httptest.NewRecorder()
220 | recorder.Body = buff
221 |
222 | o := Classic()
223 | o.Any("/", new(UnauthorizedAction))
224 |
225 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
226 | if err != nil {
227 | t.Error(err)
228 | }
229 |
230 | o.ServeHTTP(recorder, req)
231 | expect(t, recorder.Code, http.StatusUnauthorized)
232 | expect(t, buff.String(), http.StatusText(http.StatusUnauthorized))
233 | }
234 |
235 | type DownloadAction struct {
236 | Ctx
237 | }
238 |
239 | func (n *DownloadAction) Get() {
240 | n.Download("./public/index.html")
241 | }
242 |
243 | func TestContext9(t *testing.T) {
244 | buff := bytes.NewBufferString("")
245 | recorder := httptest.NewRecorder()
246 | recorder.Body = buff
247 |
248 | o := Classic()
249 | o.Any("/", new(DownloadAction))
250 |
251 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
252 | if err != nil {
253 | t.Error(err)
254 | }
255 |
256 | o.ServeHTTP(recorder, req)
257 |
258 | expect(t, recorder.Header().Get("Content-Disposition"), `attachment; filename="index.html"`)
259 | expect(t, recorder.Code, http.StatusOK)
260 | expect(t, buff.String(), "this is index.html")
261 | }
262 |
263 | // check unsupported function will panic
264 | func TestContext10(t *testing.T) {
265 | buff := bytes.NewBufferString("")
266 | recorder := httptest.NewRecorder()
267 | recorder.Body = buff
268 |
269 | o := Classic()
270 | var ifPanic bool
271 | defer func() {
272 | if err := recover(); err != nil {
273 | ifPanic = true
274 | }
275 |
276 | expect(t, ifPanic, true)
277 | }()
278 |
279 | o.Any("/", func(i int) {
280 | fmt.Println(i)
281 | })
282 | }
283 |
284 | type DownloadAction2 struct {
285 | Ctx
286 | }
287 |
288 | func (n *DownloadAction2) Get() {
289 | n.ServeFile("./public/a.html")
290 | }
291 |
292 | func TestContext11(t *testing.T) {
293 | buff := bytes.NewBufferString("")
294 | recorder := httptest.NewRecorder()
295 | recorder.Body = buff
296 |
297 | o := Classic()
298 | o.Any("/", new(DownloadAction2))
299 |
300 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
301 | if err != nil {
302 | t.Error(err)
303 | }
304 |
305 | o.ServeHTTP(recorder, req)
306 |
307 | expect(t, recorder.Code, http.StatusNotFound)
308 | }
309 |
310 | func TestContext12(t *testing.T) {
311 | buff := bytes.NewBufferString("")
312 | recorder := httptest.NewRecorder()
313 | recorder.Body = buff
314 |
315 | o := Classic()
316 | o.Any("/", func() string {
317 | return "text"
318 | })
319 |
320 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
321 | if err != nil {
322 | t.Error(err)
323 | }
324 |
325 | o.ServeHTTP(recorder, req)
326 |
327 | expect(t, recorder.Code, http.StatusOK)
328 | expect(t, buff.String(), "text")
329 | }
330 |
331 | func TestContext13(t *testing.T) {
332 | buff := bytes.NewBufferString("")
333 | recorder := httptest.NewRecorder()
334 | recorder.Body = buff
335 |
336 | o := Classic()
337 | o.Get("/", func(responseWriter http.ResponseWriter, req *http.Request) {
338 | responseWriter.Write([]byte("text"))
339 | })
340 |
341 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
342 | if err != nil {
343 | t.Error(err)
344 | }
345 |
346 | o.ServeHTTP(recorder, req)
347 |
348 | expect(t, recorder.Code, http.StatusOK)
349 | expect(t, buff.String(), "text")
350 | }
351 |
352 | func TestContext14(t *testing.T) {
353 | buff := bytes.NewBufferString("")
354 | recorder := httptest.NewRecorder()
355 | recorder.Body = buff
356 |
357 | o := Classic()
358 | o.Get("/", func(req *http.Request) string {
359 | return "text"
360 | })
361 |
362 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
363 | if err != nil {
364 | t.Error(err)
365 | }
366 |
367 | o.ServeHTTP(recorder, req)
368 |
369 | expect(t, recorder.Code, http.StatusOK)
370 | expect(t, buff.String(), "text")
371 | }
372 |
373 | func TestContextDecodeJSON(t *testing.T) {
374 | buff := bytes.NewBufferString("")
375 | recorder := httptest.NewRecorder()
376 | recorder.Body = buff
377 |
378 | type Data struct {
379 | Name string
380 | }
381 |
382 | o := Classic()
383 | o.Post("/", func(ctx *Context) string {
384 | var data Data
385 | err := ctx.DecodeJSON(&data)
386 | if err != nil {
387 | return err.Error()
388 | }
389 | return data.Name
390 | })
391 |
392 | req, err := http.NewRequest("POST", "http://localhost:8000/", bytes.NewBufferString(`
393 | {
394 | "Name": "lunny"
395 | }
396 | `))
397 | if err != nil {
398 | t.Error(err)
399 | }
400 |
401 | o.ServeHTTP(recorder, req)
402 |
403 | expect(t, recorder.Code, http.StatusOK)
404 | expect(t, buff.String(), "lunny")
405 | }
406 |
407 | func TestContextDecodeXML(t *testing.T) {
408 | buff := bytes.NewBufferString("")
409 | recorder := httptest.NewRecorder()
410 | recorder.Body = buff
411 |
412 | type Data struct {
413 | Name string `xml:"name"`
414 | }
415 |
416 | o := Classic()
417 | o.Post("/", func(ctx *Context) string {
418 | var data Data
419 | err := ctx.DecodeXML(&data)
420 | if err != nil {
421 | return err.Error()
422 | }
423 | return data.Name
424 | })
425 |
426 | req, err := http.NewRequest("POST", "http://localhost:8000/", bytes.NewBufferString(`
427 | lunny
428 | `))
429 | if err != nil {
430 | t.Error(err)
431 | }
432 |
433 | o.ServeHTTP(recorder, req)
434 |
435 | expect(t, recorder.Code, http.StatusOK)
436 | expect(t, buff.String(), "lunny")
437 | }
438 |
439 | type ActionTag struct {
440 | Name string `name`
441 | }
442 |
443 | func (a *ActionTag) Get() interface{} {
444 | return "lunny"
445 | }
446 |
447 | func TestContextActionTag(t *testing.T) {
448 | buff := bytes.NewBufferString("")
449 | recorder := httptest.NewRecorder()
450 | recorder.Body = buff
451 |
452 | o := Classic()
453 | o.Use(HandlerFunc(func(ctx *Context) {
454 | ctx.Next()
455 |
456 | v := ctx.ActionValue()
457 | if a, ok := v.Interface().(*ActionTag); ok {
458 | fmt.Println(a.Get())
459 | }
460 |
461 | tagName := ctx.ActionTag("Name")
462 | if tagName == "name" {
463 | fmt.Println("find the action")
464 | }
465 | }))
466 | o.Get("/", new(ActionTag))
467 |
468 | req, err := http.NewRequest("GET", "http://localhost:8000/", nil)
469 | if err != nil {
470 | t.Error(err)
471 | }
472 |
473 | o.ServeHTTP(recorder, req)
474 |
475 | expect(t, recorder.Code, http.StatusOK)
476 | expect(t, buff.String(), "lunny")
477 | }
478 |
--------------------------------------------------------------------------------
/group_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "net/http"
10 | "net/http/httptest"
11 | "testing"
12 | )
13 |
14 | func TestGroup1(t *testing.T) {
15 | buff := bytes.NewBufferString("")
16 | recorder := httptest.NewRecorder()
17 | recorder.Body = buff
18 |
19 | o := Classic()
20 | o.Group("/api", func(g *Group) {
21 | g.Get("/1", func() string {
22 | return "/1"
23 | })
24 | g.Post("/2", func() string {
25 | return "/2"
26 | })
27 | })
28 |
29 | req, err := http.NewRequest("GET", "http://localhost:8000/api/1", nil)
30 | if err != nil {
31 | t.Error(err)
32 | }
33 |
34 | o.ServeHTTP(recorder, req)
35 | expect(t, recorder.Code, http.StatusOK)
36 | refute(t, len(buff.String()), 0)
37 | expect(t, buff.String(), "/1")
38 |
39 | buff.Reset()
40 | req, err = http.NewRequest("POST", "http://localhost:8000/api/2", nil)
41 | if err != nil {
42 | t.Error(err)
43 | }
44 |
45 | o.ServeHTTP(recorder, req)
46 | expect(t, recorder.Code, http.StatusOK)
47 | refute(t, len(buff.String()), 0)
48 | expect(t, buff.String(), "/2")
49 | }
50 |
51 | func TestGroup2(t *testing.T) {
52 | buff := bytes.NewBufferString("")
53 | recorder := httptest.NewRecorder()
54 | recorder.Body = buff
55 |
56 | g := NewGroup()
57 | g.Any("/1", func() string {
58 | return "/1"
59 | })
60 | g.Options("/2", func() string {
61 | return "/2"
62 | })
63 |
64 | o := Classic()
65 | o.Group("/api", g)
66 |
67 | req, err := http.NewRequest("GET", "http://localhost:8000/api/1", nil)
68 | if err != nil {
69 | t.Error(err)
70 | }
71 |
72 | o.ServeHTTP(recorder, req)
73 | expect(t, recorder.Code, http.StatusOK)
74 | refute(t, len(buff.String()), 0)
75 | expect(t, buff.String(), "/1")
76 |
77 | buff.Reset()
78 | req, err = http.NewRequest("OPTIONS", "http://localhost:8000/api/2", nil)
79 | if err != nil {
80 | t.Error(err)
81 | }
82 |
83 | o.ServeHTTP(recorder, req)
84 | expect(t, recorder.Code, http.StatusOK)
85 | refute(t, len(buff.String()), 0)
86 | expect(t, buff.String(), "/2")
87 | }
88 |
89 | func TestGroup3(t *testing.T) {
90 | buff := bytes.NewBufferString("")
91 | recorder := httptest.NewRecorder()
92 | recorder.Body = buff
93 |
94 | o := Classic()
95 | o.Group("/api", func(g *Group) {
96 | g.Group("/v1", func(cg *Group) {
97 | cg.Trace("/1", func() string {
98 | return "/1"
99 | })
100 | cg.Patch("/2", func() string {
101 | return "/2"
102 | })
103 | })
104 | })
105 |
106 | req, err := http.NewRequest("TRACE", "http://localhost:8000/api/v1/1", nil)
107 | if err != nil {
108 | t.Error(err)
109 | }
110 |
111 | o.ServeHTTP(recorder, req)
112 | expect(t, recorder.Code, http.StatusOK)
113 | refute(t, len(buff.String()), 0)
114 | expect(t, buff.String(), "/1")
115 |
116 | buff.Reset()
117 | req, err = http.NewRequest("PATCH", "http://localhost:8000/api/v1/2", nil)
118 | if err != nil {
119 | t.Error(err)
120 | }
121 |
122 | o.ServeHTTP(recorder, req)
123 | expect(t, recorder.Code, http.StatusOK)
124 | refute(t, len(buff.String()), 0)
125 | expect(t, buff.String(), "/2")
126 | }
127 |
128 | func TestGroup4(t *testing.T) {
129 | buff := bytes.NewBufferString("")
130 | recorder := httptest.NewRecorder()
131 | recorder.Body = buff
132 |
133 | o := Classic()
134 | o.Group("", func(g *Group) {
135 | g.Delete("/api/1", func() string {
136 | return "/1"
137 | })
138 | g.Head("/api/2", func() string {
139 | return "/2"
140 | })
141 | })
142 |
143 | req, err := http.NewRequest("DELETE", "http://localhost:8000/api/1", nil)
144 | if err != nil {
145 | t.Error(err)
146 | }
147 |
148 | o.ServeHTTP(recorder, req)
149 | expect(t, recorder.Code, http.StatusOK)
150 | refute(t, len(buff.String()), 0)
151 | expect(t, buff.String(), "/1")
152 |
153 | buff.Reset()
154 | req, err = http.NewRequest("HEAD", "http://localhost:8000/api/2", nil)
155 | if err != nil {
156 | t.Error(err)
157 | }
158 |
159 | o.ServeHTTP(recorder, req)
160 | expect(t, recorder.Code, http.StatusOK)
161 | refute(t, len(buff.String()), 0)
162 | expect(t, buff.String(), "/2")
163 | }
164 |
165 | func TestGroup5(t *testing.T) {
166 | buff := bytes.NewBufferString("")
167 | recorder := httptest.NewRecorder()
168 | recorder.Body = buff
169 |
170 | o := Classic()
171 | var handlerGroup bool
172 | o.Group("/api", func(g *Group) {
173 | g.Use(HandlerFunc(func(ctx *Context) {
174 | handlerGroup = true
175 | ctx.Next()
176 | }))
177 | g.Put("/1", func() string {
178 | return "/1"
179 | })
180 | })
181 | o.Post("/2", func() string {
182 | return "/2"
183 | })
184 |
185 | req, err := http.NewRequest("PUT", "http://localhost:8000/api/1", nil)
186 | if err != nil {
187 | t.Error(err)
188 | }
189 |
190 | o.ServeHTTP(recorder, req)
191 | expect(t, recorder.Code, http.StatusOK)
192 | refute(t, len(buff.String()), 0)
193 | expect(t, buff.String(), "/1")
194 | expect(t, handlerGroup, true)
195 |
196 | handlerGroup = false
197 | buff.Reset()
198 | req, err = http.NewRequest("POST", "http://localhost:8000/2", nil)
199 | if err != nil {
200 | t.Error(err)
201 | }
202 |
203 | o.ServeHTTP(recorder, req)
204 | expect(t, recorder.Code, http.StatusOK)
205 | refute(t, len(buff.String()), 0)
206 | expect(t, buff.String(), "/2")
207 | expect(t, handlerGroup, false)
208 | }
209 |
210 | func TestGroup6(t *testing.T) {
211 | buff := bytes.NewBufferString("")
212 | recorder := httptest.NewRecorder()
213 | recorder.Body = buff
214 |
215 | var handlerGroup bool
216 | g := NewGroup()
217 | g.Use(HandlerFunc(func(ctx *Context) {
218 | handlerGroup = true
219 | ctx.Next()
220 | }))
221 | g.Get("/1", func() string {
222 | return "/1"
223 | })
224 |
225 | o := Classic()
226 | o.Group("/api", g)
227 | o.Post("/2", func() string {
228 | return "/2"
229 | })
230 |
231 | req, err := http.NewRequest("GET", "http://localhost:8000/api/1", nil)
232 | if err != nil {
233 | t.Error(err)
234 | }
235 |
236 | o.ServeHTTP(recorder, req)
237 | expect(t, recorder.Code, http.StatusOK)
238 | refute(t, len(buff.String()), 0)
239 | expect(t, buff.String(), "/1")
240 | expect(t, handlerGroup, true)
241 |
242 | handlerGroup = false
243 | buff.Reset()
244 | req, err = http.NewRequest("POST", "http://localhost:8000/2", nil)
245 | if err != nil {
246 | t.Error(err)
247 | }
248 |
249 | o.ServeHTTP(recorder, req)
250 | expect(t, recorder.Code, http.StatusOK)
251 | refute(t, len(buff.String()), 0)
252 | expect(t, buff.String(), "/2")
253 | expect(t, handlerGroup, false)
254 | }
255 |
256 | func TestGroup7(t *testing.T) {
257 | buff := bytes.NewBufferString("")
258 | recorder := httptest.NewRecorder()
259 | recorder.Body = buff
260 |
261 | var isPanic bool
262 | defer func() {
263 | if err := recover(); err != nil {
264 | isPanic = true
265 | }
266 | expect(t, isPanic, true)
267 | }()
268 |
269 | o := Classic()
270 | o.Group("/api", func() {
271 | })
272 |
273 | req, err := http.NewRequest("GET", "http://localhost:8000/api/1", nil)
274 | if err != nil {
275 | t.Error(err)
276 | }
277 |
278 | o.ServeHTTP(recorder, req)
279 | }
280 |
281 | func TestGroup8(t *testing.T) {
282 | buff := bytes.NewBufferString("")
283 | recorder := httptest.NewRecorder()
284 | recorder.Body = buff
285 |
286 | var handlerGroup bool
287 | g := NewGroup()
288 | g.Use(HandlerFunc(func(ctx *Context) {
289 | handlerGroup = true
290 | ctx.Next()
291 | }))
292 | g.Get("/1", func(ctx *Context) string {
293 | p := ctx.Params()
294 | return (*p)[0].Value
295 | })
296 |
297 | o := Classic()
298 | o.Group("/:user", g)
299 | o.Post("/2", func() string {
300 | return "/2"
301 | })
302 |
303 | req, err := http.NewRequest("GET", "http://localhost:8000/myname/1", nil)
304 | if err != nil {
305 | t.Error(err)
306 | }
307 |
308 | o.ServeHTTP(recorder, req)
309 | expect(t, recorder.Code, http.StatusOK)
310 | refute(t, len(buff.String()), 0)
311 | expect(t, buff.String(), "myname")
312 | expect(t, handlerGroup, true)
313 |
314 | handlerGroup = false
315 | buff.Reset()
316 | req, err = http.NewRequest("POST", "http://localhost:8000/2", nil)
317 | if err != nil {
318 | t.Error(err)
319 | }
320 |
321 | o.ServeHTTP(recorder, req)
322 | expect(t, recorder.Code, http.StatusOK)
323 | refute(t, len(buff.String()), 0)
324 | expect(t, buff.String(), "/2")
325 | expect(t, handlerGroup, false)
326 | }
327 |
328 | func TestGroup9(t *testing.T) {
329 | buff := bytes.NewBufferString("")
330 | recorder := httptest.NewRecorder()
331 | recorder.Body = buff
332 |
333 | o := Classic()
334 | var handlerGroup string
335 | o.Group("/api/v1", func(g *Group) {
336 | g.Group("/case/:case_id", func(tg *Group) {
337 | tg.Use(HandlerFunc(func(ctx *Context) {
338 | handlerGroup = ctx.Param("case_id")
339 | ctx.Next()
340 | }))
341 | tg.Put("/1", func() string {
342 | return "/1"
343 | })
344 | })
345 | })
346 | o.Post("/api/v1/2", func() string {
347 | return "/2"
348 | })
349 |
350 | req, err := http.NewRequest("PUT", "http://localhost:8000/api/v1/case/1/1", nil)
351 | if err != nil {
352 | t.Error(err)
353 | }
354 |
355 | o.ServeHTTP(recorder, req)
356 | expect(t, recorder.Code, http.StatusOK)
357 | refute(t, len(buff.String()), 0)
358 | expect(t, buff.String(), "/1")
359 | expect(t, handlerGroup, "1")
360 |
361 | handlerGroup = ""
362 | buff.Reset()
363 | req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/2", nil)
364 | if err != nil {
365 | t.Error(err)
366 | }
367 |
368 | o.ServeHTTP(recorder, req)
369 | expect(t, recorder.Code, http.StatusOK)
370 | refute(t, len(buff.String()), 0)
371 | expect(t, buff.String(), "/2")
372 | expect(t, handlerGroup, "")
373 | }
374 |
375 | func TestGroup10(t *testing.T) {
376 | buff := bytes.NewBufferString("")
377 | recorder := httptest.NewRecorder()
378 | recorder.Body = buff
379 |
380 | o := Classic()
381 | var handlerGroup string
382 | o.Group("/api/v1", func(g *Group) {
383 | g.Group("/case/:case_id", func(tg *Group) {
384 | tg.Put("/1", func() string {
385 | return "/1"
386 | })
387 | })
388 | }, HandlerFunc(func(ctx *Context) {
389 | handlerGroup = ctx.Param("case_id")
390 | ctx.Next()
391 | }))
392 | o.Post("/api/v1/2", func() string {
393 | return "/2"
394 | })
395 |
396 | req, err := http.NewRequest("PUT", "http://localhost:8000/api/v1/case/1/1", nil)
397 | if err != nil {
398 | t.Error(err)
399 | }
400 |
401 | o.ServeHTTP(recorder, req)
402 | expect(t, recorder.Code, http.StatusOK)
403 | refute(t, len(buff.String()), 0)
404 | expect(t, buff.String(), "/1")
405 | expect(t, handlerGroup, "1")
406 |
407 | handlerGroup = ""
408 | buff.Reset()
409 | req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/2", nil)
410 | if err != nil {
411 | t.Error(err)
412 | }
413 |
414 | o.ServeHTTP(recorder, req)
415 | expect(t, recorder.Code, http.StatusOK)
416 | refute(t, len(buff.String()), 0)
417 | expect(t, buff.String(), "/2")
418 | expect(t, handlerGroup, "")
419 | }
420 |
421 | func TestGroup11(t *testing.T) {
422 | buff := bytes.NewBufferString("")
423 | recorder := httptest.NewRecorder()
424 | recorder.Body = buff
425 |
426 | o := Classic()
427 | var handlerGroup string
428 | o.Group("/api/v1", func(g *Group) {
429 | g.Group("/case/:case_id", func(tg *Group) {
430 | tg.Put("/1", func() string {
431 | return "/1"
432 | })
433 | }, HandlerFunc(func(ctx *Context) {
434 | handlerGroup = ctx.Param("case_id")
435 | ctx.Next()
436 | }))
437 | })
438 | o.Post("/api/v1/2", func() string {
439 | return "/2"
440 | })
441 |
442 | req, err := http.NewRequest("PUT", "http://localhost:8000/api/v1/case/1/1", nil)
443 | if err != nil {
444 | t.Error(err)
445 | }
446 |
447 | o.ServeHTTP(recorder, req)
448 | expect(t, recorder.Code, http.StatusOK)
449 | refute(t, len(buff.String()), 0)
450 | expect(t, buff.String(), "/1")
451 | expect(t, handlerGroup, "1")
452 |
453 | handlerGroup = ""
454 | buff.Reset()
455 | req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/2", nil)
456 | if err != nil {
457 | t.Error(err)
458 | }
459 |
460 | o.ServeHTTP(recorder, req)
461 | expect(t, recorder.Code, http.StatusOK)
462 | refute(t, len(buff.String()), 0)
463 | expect(t, buff.String(), "/2")
464 | expect(t, handlerGroup, "")
465 | }
466 |
--------------------------------------------------------------------------------
/context.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "encoding/json"
10 | "encoding/xml"
11 | "fmt"
12 | "io"
13 | "io/ioutil"
14 | "net/http"
15 | "os"
16 | "path/filepath"
17 | "reflect"
18 | "strings"
19 | )
20 |
21 | // Handler defines middleware interface
22 | type Handler interface {
23 | Handle(*Context)
24 | }
25 |
26 | // Context defines request and response context
27 | type Context struct {
28 | tan *Tango
29 | Logger
30 |
31 | idx int
32 | req *http.Request
33 | ResponseWriter
34 | route *Route
35 | params Params
36 | callArgs []reflect.Value
37 | matched bool
38 | stage byte
39 |
40 | action interface{}
41 | Result interface{}
42 | }
43 |
44 | func (ctx *Context) reset(req *http.Request, resp ResponseWriter) {
45 | ctx.req = req
46 | ctx.ResponseWriter = resp
47 | ctx.idx = 0
48 | ctx.stage = 0
49 | ctx.route = nil
50 | ctx.params = nil
51 | ctx.callArgs = nil
52 | ctx.matched = false
53 | ctx.action = nil
54 | ctx.Result = nil
55 | }
56 |
57 | // HandleError handles errors
58 | func (ctx *Context) HandleError() {
59 | ctx.tan.ErrHandler.Handle(ctx)
60 | }
61 |
62 | // Req returns current HTTP Request information
63 | func (ctx *Context) Req() *http.Request {
64 | return ctx.req
65 | }
66 |
67 | // SetRequest sets the request to context
68 | func (ctx *Context) SetRequest(req *http.Request) {
69 | ctx.req = req
70 | }
71 |
72 | // IsAjax returns if the request is an ajax request
73 | func (ctx *Context) IsAjax() bool {
74 | return ctx.Req().Header.Get("X-Requested-With") == "XMLHttpRequest"
75 | }
76 |
77 | // SecureCookies generates a secret cookie
78 | func (ctx *Context) SecureCookies(secret string) Cookies {
79 | return &secureCookies{
80 | (*cookies)(ctx),
81 | secret,
82 | }
83 | }
84 |
85 | // Cookies returns the cookies
86 | func (ctx *Context) Cookies() Cookies {
87 | return (*cookies)(ctx)
88 | }
89 |
90 | // Forms returns the query/body names and values
91 | func (ctx *Context) Forms() *Forms {
92 | ctx.req.ParseForm()
93 | return (*Forms)(ctx.req)
94 | }
95 |
96 | // Queries returns the query names and values
97 | func (ctx *Context) Queries() *Queries {
98 | return (*Queries)(ctx.req)
99 | }
100 |
101 | // Route returns route
102 | func (ctx *Context) Route() *Route {
103 | ctx.newAction()
104 | return ctx.route
105 | }
106 |
107 | // Params returns the URL params
108 | func (ctx *Context) Params() *Params {
109 | ctx.newAction()
110 | return &ctx.params
111 | }
112 |
113 | // IP returns remote IP
114 | func (ctx *Context) IP() string {
115 | proxy := []string{}
116 | if ips := ctx.Req().Header.Get("X-Forwarded-For"); ips != "" {
117 | proxy = strings.Split(ips, ",")
118 | }
119 | if len(proxy) > 0 && proxy[0] != "" {
120 | return proxy[0]
121 | }
122 | ip := strings.Split(ctx.Req().RemoteAddr, ":")
123 | if len(ip) > 0 {
124 | if ip[0] != "[" {
125 | return ip[0]
126 | }
127 | }
128 | return "127.0.0.1"
129 | }
130 |
131 | // Action returns action
132 | func (ctx *Context) Action() interface{} {
133 | ctx.newAction()
134 | return ctx.action
135 | }
136 |
137 | // ActionValue returns action value
138 | func (ctx *Context) ActionValue() reflect.Value {
139 | ctx.newAction()
140 | return ctx.callArgs[0]
141 | }
142 |
143 | // ActionTag returns field tag on action struct
144 | // TODO: cache the name
145 | func (ctx *Context) ActionTag(fieldName string) string {
146 | ctx.newAction()
147 | if ctx.route.routeType == StructPtrRoute || ctx.route.routeType == StructRoute {
148 | tp := ctx.callArgs[0].Type()
149 | if tp.Kind() == reflect.Ptr {
150 | tp = tp.Elem()
151 | }
152 | field, ok := tp.FieldByName(fieldName)
153 | if !ok {
154 | return ""
155 | }
156 | return string(field.Tag)
157 | }
158 | return ""
159 | }
160 |
161 | // WriteString writes a string to response write
162 | func (ctx *Context) WriteString(content string) (int, error) {
163 | return io.WriteString(ctx.ResponseWriter, content)
164 | }
165 |
166 | func (ctx *Context) newAction() {
167 | if !ctx.matched {
168 | reqPath := removeStick(ctx.Req().URL.Path)
169 | ctx.route, ctx.params = ctx.tan.Match(reqPath, ctx.Req().Method)
170 | if ctx.route != nil {
171 | vc := ctx.route.newAction()
172 | ctx.action = vc.Interface()
173 | switch ctx.route.routeType {
174 | case StructPtrRoute:
175 | ctx.callArgs = []reflect.Value{vc.Elem()}
176 | case StructRoute:
177 | ctx.callArgs = []reflect.Value{vc}
178 | case FuncRoute:
179 | ctx.callArgs = []reflect.Value{}
180 | case FuncHTTPRoute:
181 | ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.ResponseWriter),
182 | reflect.ValueOf(ctx.Req())}
183 | case FuncReqRoute:
184 | ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.Req())}
185 | case FuncResponseRoute:
186 | ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx.ResponseWriter)}
187 | case FuncCtxRoute:
188 | ctx.callArgs = []reflect.Value{reflect.ValueOf(ctx)}
189 | default:
190 | panic("routeType error")
191 | }
192 | }
193 | ctx.matched = true
194 | }
195 | }
196 |
197 | // Next call next middleware or action
198 | // WARNING: don't invoke this method on action
199 | func (ctx *Context) Next() {
200 | ctx.idx++
201 | ctx.invoke()
202 | }
203 |
204 | func (ctx *Context) execute() {
205 | ctx.newAction()
206 | // route is matched
207 | if ctx.action != nil {
208 | if len(ctx.route.handlers) > 0 && ctx.stage == 0 {
209 | ctx.idx = 0
210 | ctx.stage = 1
211 | ctx.invoke()
212 | return
213 | }
214 |
215 | var ret []reflect.Value
216 | switch fn := ctx.route.raw.(type) {
217 | case func(*Context):
218 | fn(ctx)
219 | case func(http.ResponseWriter, *http.Request):
220 | fn(ctx.ResponseWriter, ctx.req)
221 | case func():
222 | fn()
223 | case func(*http.Request):
224 | fn(ctx.req)
225 | case func(http.ResponseWriter):
226 | fn(ctx.ResponseWriter)
227 | default:
228 | ret = ctx.route.method.Call(ctx.callArgs)
229 | }
230 |
231 | if len(ret) == 1 {
232 | ctx.Result = ret[0].Interface()
233 | } else if len(ret) == 2 {
234 | if code, ok := ret[0].Interface().(int); ok {
235 | ctx.Result = &StatusResult{code, ret[1].Interface()}
236 | }
237 | }
238 | // not route matched
239 | } else {
240 | if !ctx.Written() {
241 | ctx.NotFound()
242 | }
243 | }
244 | }
245 |
246 | func (ctx *Context) invoke() {
247 | if ctx.stage == 0 {
248 | if ctx.idx < len(ctx.tan.handlers) {
249 | ctx.tan.handlers[ctx.idx].Handle(ctx)
250 | } else {
251 | ctx.execute()
252 | }
253 | } else if ctx.stage == 1 {
254 | if ctx.idx < len(ctx.route.handlers) {
255 | ctx.route.handlers[ctx.idx].Handle(ctx)
256 | } else {
257 | ctx.execute()
258 | }
259 | }
260 | }
261 |
262 | func toHTTPError(err error) (msg string, httpStatus int) {
263 | if os.IsNotExist(err) {
264 | return "404 page not found", http.StatusNotFound
265 | }
266 | if os.IsPermission(err) {
267 | return "403 Forbidden", http.StatusForbidden
268 | }
269 | // Default:
270 | return "500 Internal Server Error", http.StatusInternalServerError
271 | }
272 |
273 | // ServeFile serves a file
274 | func (ctx *Context) ServeFile(path string) error {
275 | dir, file := filepath.Split(path)
276 | return ctx.ServeContent(file, http.Dir(dir))
277 | }
278 |
279 | // ServeContent serve content
280 | func (ctx *Context) ServeContent(path string, fileSystem http.FileSystem) error {
281 | f, err := fileSystem.Open(path)
282 | if err != nil {
283 | msg, code := toHTTPError(err)
284 | http.Error(ctx, msg, code)
285 | return nil
286 | }
287 | defer f.Close()
288 |
289 | d, err := f.Stat()
290 | if err != nil {
291 | msg, code := toHTTPError(err)
292 | http.Error(ctx, msg, code)
293 | return nil
294 | }
295 |
296 | if d.IsDir() {
297 | http.Error(ctx, http.StatusText(http.StatusForbidden), http.StatusForbidden)
298 | return nil
299 | }
300 |
301 | http.ServeContent(ctx, ctx.Req(), d.Name(), d.ModTime(), f)
302 | return nil
303 | }
304 |
305 | // ServeXml serves marshaled XML content from obj
306 | // Deprecated: use ServeXML instead
307 | func (ctx *Context) ServeXml(obj interface{}) error {
308 | return ctx.ServeXML(obj)
309 | }
310 |
311 | // ServeXML serves marshaled XML content from obj
312 | func (ctx *Context) ServeXML(obj interface{}) error {
313 | encoder := xml.NewEncoder(ctx)
314 | ctx.Header().Set("Content-Type", "application/xml; charset=UTF-8")
315 | err := encoder.Encode(obj)
316 | if err != nil {
317 | ctx.Header().Del("Content-Type")
318 | }
319 | return err
320 | }
321 |
322 | // ServeJson serves marshaled JSON content from obj
323 | // Deprecated: use ServeJSON instead
324 | func (ctx *Context) ServeJson(obj interface{}) error {
325 | return ctx.ServeJSON(obj)
326 | }
327 |
328 | // ServeJSON serves marshaled JSON content from obj
329 | func (ctx *Context) ServeJSON(obj interface{}) error {
330 | encoder := json.NewEncoder(ctx)
331 | ctx.Header().Set("Content-Type", "application/json; charset=UTF-8")
332 | err := encoder.Encode(obj)
333 | if err != nil {
334 | ctx.Header().Del("Content-Type")
335 | }
336 | return err
337 | }
338 |
339 | // Body returns body's content
340 | func (ctx *Context) Body() ([]byte, error) {
341 | if ctx.req.Body == nil {
342 | return []byte{}, nil
343 | }
344 |
345 | body, err := ioutil.ReadAll(ctx.req.Body)
346 | if err != nil {
347 | return nil, err
348 | }
349 |
350 | ctx.req.Body.Close()
351 | ctx.req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
352 |
353 | return body, nil
354 | }
355 |
356 | // DecodeJson decodes body as JSON format to obj
357 | // Deprecated: use DecodeJSON instead
358 | func (ctx *Context) DecodeJson(obj interface{}) error {
359 | return ctx.DecodeJSON(obj)
360 | }
361 |
362 | // DecodeJSON decodes body as JSON format to obj
363 | func (ctx *Context) DecodeJSON(obj interface{}) error {
364 | body, err := ctx.Body()
365 | if err != nil {
366 | return err
367 | }
368 |
369 | return json.Unmarshal(body, obj)
370 | }
371 |
372 | // DecodeXml decodes body as XML format to obj
373 | // Deprecated: use DecodeXML instead
374 | func (ctx *Context) DecodeXml(obj interface{}) error {
375 | return ctx.DecodeXML(obj)
376 | }
377 |
378 | // DecodeXML decodes body as XML format to obj
379 | func (ctx *Context) DecodeXML(obj interface{}) error {
380 | body, err := ctx.Body()
381 | if err != nil {
382 | return err
383 | }
384 |
385 | return xml.Unmarshal(body, obj)
386 | }
387 |
388 | // Download provides a locale file to http client
389 | func (ctx *Context) Download(fpath string) error {
390 | f, err := os.Open(fpath)
391 | if err != nil {
392 | return err
393 | }
394 | defer f.Close()
395 |
396 | fName := filepath.Base(fpath)
397 | ctx.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%v\"", fName))
398 | _, err = io.Copy(ctx, f)
399 | return err
400 | }
401 |
402 | // SaveToFile saves the HTTP post file form to local file path
403 | func (ctx *Context) SaveToFile(formName, savePath string) error {
404 | file, _, err := ctx.Req().FormFile(formName)
405 | if err != nil {
406 | return err
407 | }
408 | defer file.Close()
409 |
410 | f, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
411 | if err != nil {
412 | return err
413 | }
414 | defer f.Close()
415 | _, err = io.Copy(f, file)
416 | return err
417 | }
418 |
419 | // Redirect redirects the request to another URL
420 | func (ctx *Context) Redirect(url string, status ...int) {
421 | s := http.StatusFound
422 | if len(status) > 0 {
423 | s = status[0]
424 | }
425 | http.Redirect(ctx.ResponseWriter, ctx.Req(), url, s)
426 | }
427 |
428 | // NotModified writes a 304 HTTP response
429 | func (ctx *Context) NotModified() {
430 | ctx.WriteHeader(http.StatusNotModified)
431 | }
432 |
433 | // Unauthorized writes a 401 HTTP response
434 | func (ctx *Context) Unauthorized() {
435 | ctx.Abort(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
436 | }
437 |
438 | // NotFound writes a 404 HTTP response
439 | func (ctx *Context) NotFound(message ...string) {
440 | if len(message) == 0 {
441 | ctx.Abort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
442 | return
443 | }
444 | ctx.Abort(http.StatusNotFound, message[0])
445 | }
446 |
447 | // Abort is a helper method that sends an HTTP header and an optional
448 | // body. It is useful for returning 4xx or 5xx errors.
449 | // Once it has been called, any return value from the handler will
450 | // not be written to the response.
451 | func (ctx *Context) Abort(status int, body ...string) {
452 | ctx.Result = Abort(status, body...)
453 | ctx.HandleError()
454 | }
455 |
456 | // Contexter describes an interface to set *Context
457 | type Contexter interface {
458 | SetContext(*Context)
459 | }
460 |
461 | // Ctx implements Contexter
462 | type Ctx struct {
463 | *Context
464 | }
465 |
466 | // SetContext set *Context to action struct
467 | func (c *Ctx) SetContext(ctx *Context) {
468 | c.Context = ctx
469 | }
470 |
471 | // Contexts returns a middleware to inject Context to action struct
472 | func Contexts() HandlerFunc {
473 | return func(ctx *Context) {
474 | if action := ctx.Action(); action != nil {
475 | if a, ok := action.(Contexter); ok {
476 | a.SetContext(ctx)
477 | }
478 | }
479 | ctx.Next()
480 | }
481 | }
482 |
--------------------------------------------------------------------------------
/router.go:
--------------------------------------------------------------------------------
1 | package tango
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "net/http"
7 | "reflect"
8 | "regexp"
9 | "sort"
10 | "strings"
11 | )
12 |
13 | // RouteType defines route types
14 | type RouteType byte
15 |
16 | // enumerates route types
17 | const (
18 | FuncRoute RouteType = iota + 1 // 1 func ()
19 | FuncHTTPRoute // 2 func (http.ResponseWriter, *http.Request)
20 | FuncReqRoute // 3 func (*http.Request)
21 | FuncResponseRoute // 4 func (http.ResponseWriter)
22 | FuncCtxRoute // 5 func (*tango.Context)
23 | StructRoute // 6 func (st) ()
24 | StructPtrRoute // 7 func (*struct) ()
25 | )
26 |
27 | // enumerates all supported HTTP methods
28 | var (
29 | SupportMethods = []string{
30 | "GET",
31 | "POST",
32 | "HEAD",
33 | "DELETE",
34 | "PUT",
35 | "OPTIONS",
36 | "TRACE",
37 | "PATCH",
38 | }
39 |
40 | PoolSize = 10
41 | )
42 |
43 | // Route defines HTTP route
44 | type Route struct {
45 | raw interface{}
46 | method reflect.Value
47 | handlers []Handler
48 | routeType RouteType
49 | pool *pool
50 | }
51 |
52 | // NewRoute returns a route
53 | func NewRoute(v interface{}, t reflect.Type,
54 | method reflect.Value, tp RouteType, handlers []Handler) *Route {
55 | var pool *pool
56 | if tp == StructRoute || tp == StructPtrRoute {
57 | pool = newPool(PoolSize, t)
58 | }
59 | return &Route{
60 | raw: v,
61 | routeType: tp,
62 | method: method,
63 | pool: pool,
64 | handlers: handlers,
65 | }
66 | }
67 |
68 | // Raw returns raw data to define route.
69 | func (r *Route) Raw() interface{} {
70 | return r.raw
71 | }
72 |
73 | // Method returns finalize execute method.
74 | func (r *Route) Method() reflect.Value {
75 | return r.method
76 | }
77 |
78 | // RouteType returns route type.
79 | func (r *Route) RouteType() RouteType {
80 | return r.routeType
81 | }
82 |
83 | // IsStruct returns if the execute is a struct
84 | func (r *Route) IsStruct() bool {
85 | return r.routeType == StructRoute || r.routeType == StructPtrRoute
86 | }
87 |
88 | func (r *Route) newAction() reflect.Value {
89 | if !r.IsStruct() {
90 | return r.method
91 | }
92 |
93 | return r.pool.New()
94 | }
95 |
96 | // Router describes the interface of route
97 | type Router interface {
98 | Route(methods interface{}, path string, handler interface{}, middlewares ...Handler)
99 | Match(requestPath, method string) (*Route, Params)
100 | }
101 |
102 | var specialBytes = []byte(`.\+*?|[]{}^$`)
103 |
104 | func isSpecial(ch byte) bool {
105 | return bytes.IndexByte(specialBytes, ch) > -1
106 | }
107 |
108 | func isAlpha(ch byte) bool {
109 | return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
110 | }
111 |
112 | func isDigit(ch byte) bool {
113 | return '0' <= ch && ch <= '9'
114 | }
115 |
116 | func isAlnum(ch byte) bool {
117 | return isAlpha(ch) || isDigit(ch)
118 | }
119 |
120 | type (
121 | router struct {
122 | trees map[string]*node
123 | }
124 | ntype byte
125 | node struct {
126 | tp ntype // Type of node it contains
127 | handle *Route // executor
128 | regexp *regexp.Regexp // regexp if tp is rnode
129 | content string // static content or named
130 | edges edges // children
131 | path string // executor path
132 | }
133 | edges []*node
134 | )
135 |
136 | func (e edges) Len() int { return len(e) }
137 |
138 | func (e edges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
139 |
140 | // static route will be put the first, so it will be match first.
141 | // two static route, content longer is first.
142 | func (e edges) Less(i, j int) bool {
143 | if e[i].tp == snode {
144 | if e[j].tp == snode {
145 | return len(e[i].content) > len(e[j].content)
146 | }
147 | return true
148 | }
149 | if e[j].tp == snode {
150 | return false
151 | }
152 | return i < j
153 | }
154 |
155 | const (
156 | snode ntype = iota // static, should equal
157 | nnode // named node, match a non-/ is ok
158 | anode // catch-all node, match any
159 | rnode // regex node, should match
160 | )
161 |
162 | func (n *node) equal(o *node) bool {
163 | if n.tp != o.tp || n.content != o.content {
164 | return false
165 | }
166 | return true
167 | }
168 |
169 | // newRouter return a new router
170 | func newRouter() (r *router) {
171 | r = &router{
172 | trees: make(map[string]*node),
173 | }
174 | for _, m := range SupportMethods {
175 | r.trees[m] = &node{
176 | edges: edges{},
177 | }
178 | }
179 | return
180 | }
181 |
182 | // /:name1/:name2 /:name1-:name2 /(:name1)sss(:name2)
183 | // /(*name) /(:name[0-9]+) /(:name[a-z]+)
184 | func parseNodes(path string) []*node {
185 | var i, j int
186 | l := len(path)
187 | var nodes = make([]*node, 0)
188 | var bracket int
189 | for ; i < l; i++ {
190 | if path[i] == ':' {
191 | nodes = append(nodes, &node{tp: snode, content: path[j : i-bracket]})
192 | j = i
193 | var regex string
194 | if bracket == 1 {
195 | var start = -1
196 | for ; i < l && ')' != path[i]; i++ {
197 | if start == -1 && isSpecial(path[i]) {
198 | start = i
199 | }
200 | }
201 | if path[i] != ')' {
202 | panic("lack of )")
203 | }
204 | if start > -1 {
205 | regex = path[start:i]
206 | }
207 | } else {
208 | i = i + 1
209 | for ; i < l && isAlnum(path[i]); i++ {
210 | }
211 | }
212 |
213 | if len(regex) > 0 {
214 | nodes = append(nodes, &node{tp: rnode,
215 | regexp: regexp.MustCompile("(" + regex + ")"),
216 | content: path[j : i-len(regex)]})
217 | } else {
218 | nodes = append(nodes, &node{tp: nnode, content: path[j:i]})
219 | }
220 | i = i + bracket
221 | j = i
222 | bracket = 0
223 | if i == l {
224 | return nodes
225 | }
226 | } else if path[i] == '*' {
227 | nodes = append(nodes, &node{tp: snode, content: path[j : i-bracket]})
228 | j = i
229 | if bracket == 1 {
230 | for ; i < l && ')' != path[i]; i++ {
231 | }
232 | } else {
233 | i = i + 1
234 | for ; i < l && isAlnum(path[i]); i++ {
235 | }
236 | }
237 | nodes = append(nodes, &node{tp: anode, content: path[j:i]})
238 | i = i + bracket
239 | bracket = 0
240 | j = i
241 | if i == l {
242 | return nodes
243 | }
244 | } else if path[i] == '(' {
245 | bracket = 1
246 | } else if path[i] == '/' {
247 | if bracket == 0 && i > j {
248 | nodes = append(nodes, &node{tp: snode, content: path[j:i]})
249 | j = i
250 | }
251 | } else {
252 | bracket = 0
253 | }
254 | }
255 |
256 | nodes = append(nodes, &node{
257 | tp: snode,
258 | content: path[j:i],
259 | })
260 |
261 | return nodes
262 | }
263 |
264 | func printNode(i int, node *node) {
265 | for _, c := range node.edges {
266 | for j := 0; j < i; j++ {
267 | fmt.Print(" ")
268 | }
269 | if i > 1 {
270 | fmt.Print("┗", " ")
271 | }
272 |
273 | fmt.Print(c.content)
274 | if c.handle != nil {
275 | fmt.Print(" ", c.handle.method.Type())
276 | fmt.Printf(" %p", c.handle.method.Interface())
277 | }
278 | fmt.Println()
279 | printNode(i+1, c)
280 | }
281 | }
282 |
283 | func (r *router) printTrees() {
284 | for _, method := range SupportMethods {
285 | if len(r.trees[method].edges) > 0 {
286 | fmt.Println(method)
287 | printNode(1, r.trees[method])
288 | fmt.Println()
289 | }
290 | }
291 | }
292 |
293 | func (r *router) addRoute(method, path string, h *Route) {
294 | nodes := parseNodes(path)
295 | nodes[len(nodes)-1].handle = h
296 | nodes[len(nodes)-1].path = path
297 | if !validNodes(nodes) {
298 | panic(fmt.Sprintln("express", path, "is not supported"))
299 | }
300 | r.addnodes(method, nodes)
301 | //r.printTrees()
302 | }
303 |
304 | func (r *router) matchNode(n *node, url string, params Params) (*node, Params) {
305 | if n.tp == snode {
306 | if strings.HasPrefix(url, n.content) {
307 | if len(url) == len(n.content) {
308 | return n, params
309 | }
310 | for _, c := range n.edges {
311 | e, newParams := r.matchNode(c, url[len(n.content):], params)
312 | if e != nil {
313 | return e, newParams
314 | }
315 | }
316 | }
317 | } else if n.tp == anode {
318 | for _, c := range n.edges {
319 | idx := strings.LastIndex(url, c.content)
320 | if idx > -1 {
321 | params = append(params, param{n.content, url[:idx]})
322 | return r.matchNode(c, url[idx:], params)
323 | }
324 | }
325 | return n, append(params, param{n.content, url})
326 | } else if n.tp == nnode {
327 | for _, c := range n.edges {
328 | idx := strings.Index(url, c.content)
329 | if idx > -1 {
330 | params = append(params, param{n.content, url[:idx]})
331 | return r.matchNode(c, url[idx:], params)
332 | }
333 | }
334 | idx := strings.IndexByte(url, '/')
335 | if idx < 0 {
336 | params = append(params, param{n.content, url})
337 | return n, params
338 | }
339 | } else if n.tp == rnode {
340 | idx := strings.IndexByte(url, '/')
341 | if idx > -1 {
342 | if n.regexp.MatchString(url[:idx]) {
343 | for _, c := range n.edges {
344 | h, newParams := r.matchNode(c, url[idx:], params)
345 | if h != nil {
346 | return h, append([]param{param{n.content, url[:idx]}}, newParams...)
347 | }
348 | }
349 | }
350 | return nil, params
351 | }
352 |
353 | for _, c := range n.edges {
354 | idx := strings.Index(url, c.content)
355 | if idx > -1 && n.regexp.MatchString(url[:idx]) {
356 | params = append(params, param{n.content, url[:idx]})
357 | return r.matchNode(c, url[idx:], params)
358 | }
359 | }
360 |
361 | if n.regexp.MatchString(url) {
362 | params = append(params, param{n.content, url})
363 | return n, params
364 | }
365 | }
366 | return nil, params
367 | }
368 |
369 | // Match for request url, match router
370 | func (r *router) Match(url, method string) (*Route, Params) {
371 | cn, ok := r.trees[method]
372 | if !ok {
373 | return nil, nil
374 | }
375 | var params = make(Params, 0, strings.Count(url, "/"))
376 | for _, n := range cn.edges {
377 | e, newParams := r.matchNode(n, url, params)
378 | if e != nil {
379 | return e.handle, newParams
380 | }
381 | }
382 | return nil, nil
383 | }
384 |
385 | // addnode adds node nodes[i] to parent node p
386 | func (r *router) addnode(p *node, nodes []*node, i int) *node {
387 | if len(p.edges) == 0 {
388 | p.edges = make([]*node, 0)
389 | }
390 |
391 | for _, pc := range p.edges {
392 | if pc.equal(nodes[i]) {
393 | if i == len(nodes)-1 {
394 | pc.handle = nodes[i].handle
395 | }
396 | return pc
397 | }
398 | }
399 |
400 | p.edges = append(p.edges, nodes[i])
401 | sort.Sort(p.edges)
402 | return nodes[i]
403 | }
404 |
405 | // validNodes validates parsed nodes, all non-static route should have static route children.
406 | func validNodes(nodes []*node) bool {
407 | if len(nodes) == 0 {
408 | return false
409 | }
410 | var lastTp = nodes[0]
411 | for _, node := range nodes[1:] {
412 | if lastTp.tp != snode && node.tp != snode {
413 | return false
414 | }
415 | lastTp = node
416 | }
417 | return true
418 | }
419 |
420 | // addnodes adds nodes to trees
421 | func (r *router) addnodes(method string, nodes []*node) {
422 | cn := r.trees[method]
423 | var p = cn
424 | for i := 0; i < len(nodes); i++ {
425 | p = r.addnode(p, nodes, i)
426 | }
427 | }
428 |
429 | func removeStick(uri string) string {
430 | uri = strings.TrimRight(uri, "/")
431 | if uri == "" {
432 | uri = "/"
433 | }
434 | return uri
435 | }
436 |
437 | // Route adds route
438 | func (r *router) Route(ms interface{}, url string, c interface{}, handlers ...Handler) {
439 | vc := reflect.ValueOf(c)
440 | if vc.Kind() == reflect.Func {
441 | switch ms.(type) {
442 | case string:
443 | s := strings.Split(ms.(string), ":")
444 | r.addFunc([]string{s[0]}, url, c, handlers)
445 | case []string:
446 | var newSlice []string
447 | for _, m := range ms.([]string) {
448 | s := strings.Split(m, ":")
449 | newSlice = append(newSlice, s[0])
450 | }
451 | r.addFunc(newSlice, url, c, handlers)
452 | default:
453 | panic("unknow methods format")
454 | }
455 | } else if vc.Kind() == reflect.Ptr && vc.Elem().Kind() == reflect.Struct {
456 | if handler, ok := vc.Interface().(http.Handler); ok {
457 | r.Route(ms, url, handler.ServeHTTP, handlers...)
458 | return
459 | }
460 | var methods = make(map[string]string)
461 | switch ms.(type) {
462 | case string:
463 | s := strings.Split(ms.(string), ":")
464 | if len(s) == 1 {
465 | methods[s[0]] = strings.Title(strings.ToLower(s[0]))
466 | } else if len(s) == 2 {
467 | methods[s[0]] = strings.TrimSpace(s[1])
468 | } else {
469 | panic("unknow methods format")
470 | }
471 | case []string:
472 | for _, m := range ms.([]string) {
473 | s := strings.Split(m, ":")
474 | if len(s) == 1 {
475 | methods[s[0]] = strings.Title(strings.ToLower(s[0]))
476 | } else if len(s) == 2 {
477 | methods[s[0]] = strings.TrimSpace(s[1])
478 | } else {
479 | panic("unknow format")
480 | }
481 | }
482 | case map[string]string:
483 | methods = ms.(map[string]string)
484 | default:
485 | panic("unsupported methods")
486 | }
487 |
488 | r.addStruct(methods, url, c, handlers)
489 | } else {
490 | panic("not support route type")
491 | }
492 | }
493 |
494 | /*
495 | Tango supports 5 form funcs
496 |
497 | func()
498 | func(*Context)
499 | func(http.ResponseWriter, *http.Request)
500 | func(http.ResponseWriter)
501 | func(*http.Request)
502 |
503 | it can has or has not return value
504 | */
505 | func (r *router) addFunc(methods []string, url string, c interface{}, handlers []Handler) {
506 | vc := reflect.ValueOf(c)
507 | t := vc.Type()
508 | var rt RouteType
509 |
510 | if t.NumIn() == 0 {
511 | rt = FuncRoute
512 | } else if t.NumIn() == 1 {
513 | if t.In(0) == reflect.TypeOf(new(Context)) {
514 | rt = FuncCtxRoute
515 | } else if t.In(0) == reflect.TypeOf(new(http.Request)) {
516 | rt = FuncReqRoute
517 | } else if t.In(0).Kind() == reflect.Interface && t.In(0).Name() == "ResponseWriter" &&
518 | t.In(0).PkgPath() == "net/http" {
519 | rt = FuncResponseRoute
520 | } else {
521 | panic(fmt.Sprintln("no support function type", methods, url, c))
522 | }
523 | } else if t.NumIn() == 2 &&
524 | (t.In(0).Kind() == reflect.Interface && t.In(0).Name() == "ResponseWriter" &&
525 | t.In(0).PkgPath() == "net/http") &&
526 | t.In(1) == reflect.TypeOf(new(http.Request)) {
527 | rt = FuncHTTPRoute
528 | } else {
529 | panic(fmt.Sprintln("no support function type", methods, url, c))
530 | }
531 |
532 | url = removeStick(url)
533 | for _, m := range methods {
534 | r.addRoute(m, url, NewRoute(c, t, vc, rt, handlers))
535 | }
536 | }
537 |
538 | func (r *router) addStruct(methods map[string]string, url string, c interface{}, handlers []Handler) {
539 | vc := reflect.ValueOf(c)
540 | t := vc.Type().Elem()
541 |
542 | // added a default method Get, Post
543 | for name, method := range methods {
544 | if m, ok := t.MethodByName(method); ok {
545 | r.addRoute(name, removeStick(url), NewRoute(c, t, m.Func, StructPtrRoute, handlers))
546 | } else if m, ok := vc.Type().MethodByName(method); ok {
547 | r.addRoute(name, removeStick(url), NewRoute(c, t, m.Func, StructRoute, handlers))
548 | } else if m, ok := t.MethodByName("Any"); ok {
549 | r.addRoute(name, removeStick(url), NewRoute(c, t, m.Func, StructPtrRoute, handlers))
550 | } else if m, ok := vc.Type().MethodByName("Any"); ok {
551 | r.addRoute(name, removeStick(url), NewRoute(c, t, m.Func, StructRoute, handlers))
552 | }
553 | }
554 | }
555 |
--------------------------------------------------------------------------------
/form_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Tango Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tango
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "net/http"
11 | "net/http/httptest"
12 | "strings"
13 | "testing"
14 | )
15 |
16 | type Form1Action struct {
17 | Ctx
18 | }
19 |
20 | func (a *Form1Action) Get() string {
21 | v, _ := a.Forms().Int("test")
22 | return fmt.Sprintf("%d", v)
23 | }
24 |
25 | func TestForm1(t *testing.T) {
26 | buff := bytes.NewBufferString("")
27 | recorder := httptest.NewRecorder()
28 | recorder.Body = buff
29 |
30 | o := Classic()
31 | o.Get("/", new(Form1Action))
32 |
33 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
34 | if err != nil {
35 | t.Error(err)
36 | }
37 |
38 | o.ServeHTTP(recorder, req)
39 | expect(t, recorder.Code, http.StatusOK)
40 | refute(t, len(buff.String()), 0)
41 | expect(t, buff.String(), "1")
42 | }
43 |
44 | type Form2Action struct {
45 | Ctx
46 | }
47 |
48 | func (a *Form2Action) Post() string {
49 | v, _ := a.Forms().Int32("test")
50 | return fmt.Sprintf("%d", v)
51 | }
52 |
53 | func TestForm2(t *testing.T) {
54 | buff := bytes.NewBufferString("")
55 | recorder := httptest.NewRecorder()
56 | recorder.Body = buff
57 |
58 | o := Classic()
59 | o.Post("/", new(Form2Action))
60 |
61 | req, err := http.NewRequest("POST", "http://localhost:8000", strings.NewReader("test=1"))
62 | if err != nil {
63 | t.Error(err)
64 | }
65 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
66 |
67 | o.ServeHTTP(recorder, req)
68 | expect(t, recorder.Code, http.StatusOK)
69 | refute(t, len(buff.String()), 0)
70 | expect(t, buff.String(), "1")
71 | }
72 |
73 | type Form3Action struct {
74 | Ctx
75 | }
76 |
77 | func (a *Form3Action) Get() string {
78 | v, _ := a.Forms().Int64("test")
79 | return fmt.Sprintf("%d", v)
80 | }
81 |
82 | func TestForm3(t *testing.T) {
83 | buff := bytes.NewBufferString("")
84 | recorder := httptest.NewRecorder()
85 | recorder.Body = buff
86 |
87 | o := Classic()
88 | o.Get("/", new(Form3Action))
89 |
90 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
91 | if err != nil {
92 | t.Error(err)
93 | }
94 |
95 | o.ServeHTTP(recorder, req)
96 | expect(t, recorder.Code, http.StatusOK)
97 | refute(t, len(buff.String()), 0)
98 | expect(t, buff.String(), "1")
99 | }
100 |
101 | type Form4Action struct {
102 | Ctx
103 | }
104 |
105 | func (a *Form4Action) Get() string {
106 | v, _ := a.Forms().Uint("test")
107 | return fmt.Sprintf("%d", v)
108 | }
109 |
110 | func TestForm4(t *testing.T) {
111 | buff := bytes.NewBufferString("")
112 | recorder := httptest.NewRecorder()
113 | recorder.Body = buff
114 |
115 | o := Classic()
116 | o.Get("/", new(Form4Action))
117 |
118 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
119 | if err != nil {
120 | t.Error(err)
121 | }
122 |
123 | o.ServeHTTP(recorder, req)
124 | expect(t, recorder.Code, http.StatusOK)
125 | refute(t, len(buff.String()), 0)
126 | expect(t, buff.String(), "1")
127 | }
128 |
129 | type Form5Action struct {
130 | Ctx
131 | }
132 |
133 | func (a *Form5Action) Get() string {
134 | v, _ := a.Forms().Uint32("test")
135 | return fmt.Sprintf("%d", v)
136 | }
137 |
138 | func TestForm5(t *testing.T) {
139 | buff := bytes.NewBufferString("")
140 | recorder := httptest.NewRecorder()
141 | recorder.Body = buff
142 |
143 | o := Classic()
144 | o.Get("/", new(Form5Action))
145 |
146 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
147 | if err != nil {
148 | t.Error(err)
149 | }
150 |
151 | o.ServeHTTP(recorder, req)
152 | expect(t, recorder.Code, http.StatusOK)
153 | refute(t, len(buff.String()), 0)
154 | expect(t, buff.String(), "1")
155 | }
156 |
157 | type Form6Action struct {
158 | Ctx
159 | }
160 |
161 | func (a *Form6Action) Get() string {
162 | v, _ := a.Forms().Uint64("test")
163 | return fmt.Sprintf("%d", v)
164 | }
165 |
166 | func TestForm6(t *testing.T) {
167 | buff := bytes.NewBufferString("")
168 | recorder := httptest.NewRecorder()
169 | recorder.Body = buff
170 |
171 | o := Classic()
172 | o.Get("/", new(Form6Action))
173 |
174 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
175 | if err != nil {
176 | t.Error(err)
177 | }
178 |
179 | o.ServeHTTP(recorder, req)
180 | expect(t, recorder.Code, http.StatusOK)
181 | refute(t, len(buff.String()), 0)
182 | expect(t, buff.String(), "1")
183 | }
184 |
185 | type Form7Action struct {
186 | Ctx
187 | }
188 |
189 | func (a *Form7Action) Get() string {
190 | v, _ := a.Forms().Float32("test")
191 | return fmt.Sprintf("%.2f", v)
192 | }
193 |
194 | func TestForm7(t *testing.T) {
195 | buff := bytes.NewBufferString("")
196 | recorder := httptest.NewRecorder()
197 | recorder.Body = buff
198 |
199 | o := Classic()
200 | o.Get("/", new(Form7Action))
201 |
202 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
203 | if err != nil {
204 | t.Error(err)
205 | }
206 |
207 | o.ServeHTTP(recorder, req)
208 | expect(t, recorder.Code, http.StatusOK)
209 | refute(t, len(buff.String()), 0)
210 | expect(t, buff.String(), "1.00")
211 | }
212 |
213 | type Form8Action struct {
214 | Ctx
215 | }
216 |
217 | func (a *Form8Action) Get() string {
218 | v, _ := a.Forms().Float64("test")
219 | return fmt.Sprintf("%.2f", v)
220 | }
221 |
222 | func TestForm8(t *testing.T) {
223 | buff := bytes.NewBufferString("")
224 | recorder := httptest.NewRecorder()
225 | recorder.Body = buff
226 |
227 | o := Classic()
228 | o.Get("/", new(Form8Action))
229 |
230 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
231 | if err != nil {
232 | t.Error(err)
233 | }
234 |
235 | o.ServeHTTP(recorder, req)
236 | expect(t, recorder.Code, http.StatusOK)
237 | refute(t, len(buff.String()), 0)
238 | expect(t, buff.String(), "1.00")
239 | }
240 |
241 | type Form9Action struct {
242 | Ctx
243 | }
244 |
245 | func (a *Form9Action) Get() string {
246 | v, _ := a.Forms().Bool("test")
247 | return fmt.Sprintf("%v", v)
248 | }
249 |
250 | func TestForm9(t *testing.T) {
251 | buff := bytes.NewBufferString("")
252 | recorder := httptest.NewRecorder()
253 | recorder.Body = buff
254 |
255 | o := Classic()
256 | o.Get("/", new(Form9Action))
257 |
258 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
259 | if err != nil {
260 | t.Error(err)
261 | }
262 |
263 | o.ServeHTTP(recorder, req)
264 | expect(t, recorder.Code, http.StatusOK)
265 | refute(t, len(buff.String()), 0)
266 | expect(t, buff.String(), "true")
267 | }
268 |
269 | type Form10Action struct {
270 | Ctx
271 | }
272 |
273 | func (a *Form10Action) Get() string {
274 | v, _ := a.Forms().String("test")
275 | return fmt.Sprintf("%v", v)
276 | }
277 |
278 | func TestForm10(t *testing.T) {
279 | buff := bytes.NewBufferString("")
280 | recorder := httptest.NewRecorder()
281 | recorder.Body = buff
282 |
283 | o := Classic()
284 | o.Get("/", new(Form10Action))
285 |
286 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
287 | if err != nil {
288 | t.Error(err)
289 | }
290 |
291 | o.ServeHTTP(recorder, req)
292 | expect(t, recorder.Code, http.StatusOK)
293 | refute(t, len(buff.String()), 0)
294 | expect(t, buff.String(), "1")
295 | }
296 |
297 | type Form11Action struct {
298 | Ctx
299 | }
300 |
301 | func (a *Form11Action) Get() string {
302 | v := a.Forms().MustInt("test")
303 | return fmt.Sprintf("%d", v)
304 | }
305 |
306 | func TestForm11(t *testing.T) {
307 | buff := bytes.NewBufferString("")
308 | recorder := httptest.NewRecorder()
309 | recorder.Body = buff
310 |
311 | o := Classic()
312 | o.Get("/", new(Form11Action))
313 |
314 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
315 | if err != nil {
316 | t.Error(err)
317 | }
318 |
319 | o.ServeHTTP(recorder, req)
320 | expect(t, recorder.Code, http.StatusOK)
321 | refute(t, len(buff.String()), 0)
322 | expect(t, buff.String(), "1")
323 | }
324 |
325 | type Form12Action struct {
326 | Ctx
327 | }
328 |
329 | func (a *Form12Action) Get() string {
330 | v := a.Forms().MustInt32("test")
331 | return fmt.Sprintf("%d", v)
332 | }
333 |
334 | func TestForm12(t *testing.T) {
335 | buff := bytes.NewBufferString("")
336 | recorder := httptest.NewRecorder()
337 | recorder.Body = buff
338 |
339 | o := Classic()
340 | o.Get("/", new(Form12Action))
341 |
342 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
343 | if err != nil {
344 | t.Error(err)
345 | }
346 |
347 | o.ServeHTTP(recorder, req)
348 | expect(t, recorder.Code, http.StatusOK)
349 | refute(t, len(buff.String()), 0)
350 | expect(t, buff.String(), "1")
351 | }
352 |
353 | type Form13Action struct {
354 | Ctx
355 | }
356 |
357 | func (a *Form13Action) Get() string {
358 | v := a.Forms().MustInt64("test")
359 | return fmt.Sprintf("%d", v)
360 | }
361 |
362 | func TestForm13(t *testing.T) {
363 | buff := bytes.NewBufferString("")
364 | recorder := httptest.NewRecorder()
365 | recorder.Body = buff
366 |
367 | o := Classic()
368 | o.Get("/", new(Form13Action))
369 |
370 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
371 | if err != nil {
372 | t.Error(err)
373 | }
374 |
375 | o.ServeHTTP(recorder, req)
376 | expect(t, recorder.Code, http.StatusOK)
377 | refute(t, len(buff.String()), 0)
378 | expect(t, buff.String(), "1")
379 | }
380 |
381 | type Form14Action struct {
382 | Ctx
383 | }
384 |
385 | func (a *Form14Action) Get() string {
386 | v := a.Forms().MustUint("test")
387 | return fmt.Sprintf("%d", v)
388 | }
389 |
390 | func TestForm14(t *testing.T) {
391 | buff := bytes.NewBufferString("")
392 | recorder := httptest.NewRecorder()
393 | recorder.Body = buff
394 |
395 | o := Classic()
396 | o.Get("/", new(Form14Action))
397 |
398 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
399 | if err != nil {
400 | t.Error(err)
401 | }
402 |
403 | o.ServeHTTP(recorder, req)
404 | expect(t, recorder.Code, http.StatusOK)
405 | refute(t, len(buff.String()), 0)
406 | expect(t, buff.String(), "1")
407 | }
408 |
409 | type Form15Action struct {
410 | Ctx
411 | }
412 |
413 | func (a *Form15Action) Get() string {
414 | v := a.Forms().MustUint32("test")
415 | return fmt.Sprintf("%d", v)
416 | }
417 |
418 | func TestForm15(t *testing.T) {
419 | buff := bytes.NewBufferString("")
420 | recorder := httptest.NewRecorder()
421 | recorder.Body = buff
422 |
423 | o := Classic()
424 | o.Get("/", new(Form15Action))
425 |
426 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
427 | if err != nil {
428 | t.Error(err)
429 | }
430 |
431 | o.ServeHTTP(recorder, req)
432 | expect(t, recorder.Code, http.StatusOK)
433 | refute(t, len(buff.String()), 0)
434 | expect(t, buff.String(), "1")
435 | }
436 |
437 | type Form16Action struct {
438 | Ctx
439 | }
440 |
441 | func (a *Form16Action) Get() string {
442 | v := a.Forms().MustUint64("test")
443 | return fmt.Sprintf("%d", v)
444 | }
445 |
446 | func TestForm16(t *testing.T) {
447 | buff := bytes.NewBufferString("")
448 | recorder := httptest.NewRecorder()
449 | recorder.Body = buff
450 |
451 | o := Classic()
452 | o.Get("/", new(Form16Action))
453 |
454 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
455 | if err != nil {
456 | t.Error(err)
457 | }
458 |
459 | o.ServeHTTP(recorder, req)
460 | expect(t, recorder.Code, http.StatusOK)
461 | refute(t, len(buff.String()), 0)
462 | expect(t, buff.String(), "1")
463 | }
464 |
465 | type Form17Action struct {
466 | Ctx
467 | }
468 |
469 | func (a *Form17Action) Get() string {
470 | v := a.Forms().MustFloat32("test")
471 | return fmt.Sprintf("%.2f", v)
472 | }
473 |
474 | func TestForm17(t *testing.T) {
475 | buff := bytes.NewBufferString("")
476 | recorder := httptest.NewRecorder()
477 | recorder.Body = buff
478 |
479 | o := Classic()
480 | o.Get("/", new(Form17Action))
481 |
482 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
483 | if err != nil {
484 | t.Error(err)
485 | }
486 |
487 | o.ServeHTTP(recorder, req)
488 | expect(t, recorder.Code, http.StatusOK)
489 | refute(t, len(buff.String()), 0)
490 | expect(t, buff.String(), "1.00")
491 | }
492 |
493 | type Form18Action struct {
494 | Ctx
495 | }
496 |
497 | func (a *Form18Action) Get() string {
498 | v := a.Forms().MustFloat64("test")
499 | return fmt.Sprintf("%.2f", v)
500 | }
501 |
502 | func TestForm18(t *testing.T) {
503 | buff := bytes.NewBufferString("")
504 | recorder := httptest.NewRecorder()
505 | recorder.Body = buff
506 |
507 | o := Classic()
508 | o.Get("/", new(Form18Action))
509 |
510 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
511 | if err != nil {
512 | t.Error(err)
513 | }
514 |
515 | o.ServeHTTP(recorder, req)
516 | expect(t, recorder.Code, http.StatusOK)
517 | refute(t, len(buff.String()), 0)
518 | expect(t, buff.String(), "1.00")
519 | }
520 |
521 | type Form19Action struct {
522 | Ctx
523 | }
524 |
525 | func (a *Form19Action) Get() string {
526 | v := a.Forms().MustBool("test")
527 | return fmt.Sprintf("%v", v)
528 | }
529 |
530 | func TestForm19(t *testing.T) {
531 | buff := bytes.NewBufferString("")
532 | recorder := httptest.NewRecorder()
533 | recorder.Body = buff
534 |
535 | o := Classic()
536 | o.Get("/", new(Form19Action))
537 |
538 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
539 | if err != nil {
540 | t.Error(err)
541 | }
542 |
543 | o.ServeHTTP(recorder, req)
544 | expect(t, recorder.Code, http.StatusOK)
545 | refute(t, len(buff.String()), 0)
546 | expect(t, buff.String(), "true")
547 | }
548 |
549 | type Form20Action struct {
550 | Ctx
551 | }
552 |
553 | func (a *Form20Action) Get() string {
554 | v := a.Forms().MustString("test")
555 | return fmt.Sprintf("%s", v)
556 | }
557 |
558 | func TestForm20(t *testing.T) {
559 | buff := bytes.NewBufferString("")
560 | recorder := httptest.NewRecorder()
561 | recorder.Body = buff
562 |
563 | o := Classic()
564 | o.Get("/", new(Form20Action))
565 |
566 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
567 | if err != nil {
568 | t.Error(err)
569 | }
570 |
571 | o.ServeHTTP(recorder, req)
572 | expect(t, recorder.Code, http.StatusOK)
573 | refute(t, len(buff.String()), 0)
574 | expect(t, buff.String(), "1")
575 | }
576 |
577 | type Form21Action struct {
578 | Ctx
579 | }
580 |
581 | func (a *Form21Action) Get() string {
582 | v := a.FormInt("test")
583 | return fmt.Sprintf("%d", v)
584 | }
585 |
586 | func TestForm21(t *testing.T) {
587 | buff := bytes.NewBufferString("")
588 | recorder := httptest.NewRecorder()
589 | recorder.Body = buff
590 |
591 | o := Classic()
592 | o.Get("/", new(Form21Action))
593 |
594 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
595 | if err != nil {
596 | t.Error(err)
597 | }
598 |
599 | o.ServeHTTP(recorder, req)
600 | expect(t, recorder.Code, http.StatusOK)
601 | refute(t, len(buff.String()), 0)
602 | expect(t, buff.String(), "1")
603 | }
604 |
605 | type Form22Action struct {
606 | Ctx
607 | }
608 |
609 | func (a *Form22Action) Get() string {
610 | v := a.FormInt32("test")
611 | return fmt.Sprintf("%d", v)
612 | }
613 |
614 | func TestForm22(t *testing.T) {
615 | buff := bytes.NewBufferString("")
616 | recorder := httptest.NewRecorder()
617 | recorder.Body = buff
618 |
619 | o := Classic()
620 | o.Get("/", new(Form22Action))
621 |
622 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
623 | if err != nil {
624 | t.Error(err)
625 | }
626 |
627 | o.ServeHTTP(recorder, req)
628 | expect(t, recorder.Code, http.StatusOK)
629 | refute(t, len(buff.String()), 0)
630 | expect(t, buff.String(), "1")
631 | }
632 |
633 | type Form23Action struct {
634 | Ctx
635 | }
636 |
637 | func (a *Form23Action) Get() string {
638 | v := a.FormInt64("test")
639 | return fmt.Sprintf("%d", v)
640 | }
641 |
642 | func TestForm23(t *testing.T) {
643 | buff := bytes.NewBufferString("")
644 | recorder := httptest.NewRecorder()
645 | recorder.Body = buff
646 |
647 | o := Classic()
648 | o.Get("/", new(Form23Action))
649 |
650 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
651 | if err != nil {
652 | t.Error(err)
653 | }
654 |
655 | o.ServeHTTP(recorder, req)
656 | expect(t, recorder.Code, http.StatusOK)
657 | refute(t, len(buff.String()), 0)
658 | expect(t, buff.String(), "1")
659 | }
660 |
661 | type Form24Action struct {
662 | Ctx
663 | }
664 |
665 | func (a *Form24Action) Get() string {
666 | v := a.FormUint("test")
667 | return fmt.Sprintf("%d", v)
668 | }
669 |
670 | func TestForm24(t *testing.T) {
671 | buff := bytes.NewBufferString("")
672 | recorder := httptest.NewRecorder()
673 | recorder.Body = buff
674 |
675 | o := Classic()
676 | o.Get("/", new(Form24Action))
677 |
678 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
679 | if err != nil {
680 | t.Error(err)
681 | }
682 |
683 | o.ServeHTTP(recorder, req)
684 | expect(t, recorder.Code, http.StatusOK)
685 | refute(t, len(buff.String()), 0)
686 | expect(t, buff.String(), "1")
687 | }
688 |
689 | type Form25Action struct {
690 | Ctx
691 | }
692 |
693 | func (a *Form25Action) Get() string {
694 | v := a.FormUint32("test")
695 | return fmt.Sprintf("%d", v)
696 | }
697 |
698 | func TestForm25(t *testing.T) {
699 | buff := bytes.NewBufferString("")
700 | recorder := httptest.NewRecorder()
701 | recorder.Body = buff
702 |
703 | o := Classic()
704 | o.Get("/", new(Form25Action))
705 |
706 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
707 | if err != nil {
708 | t.Error(err)
709 | }
710 |
711 | o.ServeHTTP(recorder, req)
712 | expect(t, recorder.Code, http.StatusOK)
713 | refute(t, len(buff.String()), 0)
714 | expect(t, buff.String(), "1")
715 | }
716 |
717 | type Form26Action struct {
718 | Ctx
719 | }
720 |
721 | func (a *Form26Action) Get() string {
722 | v := a.FormUint64("test")
723 | return fmt.Sprintf("%d", v)
724 | }
725 |
726 | func TestForm26(t *testing.T) {
727 | buff := bytes.NewBufferString("")
728 | recorder := httptest.NewRecorder()
729 | recorder.Body = buff
730 |
731 | o := Classic()
732 | o.Get("/", new(Form26Action))
733 |
734 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
735 | if err != nil {
736 | t.Error(err)
737 | }
738 |
739 | o.ServeHTTP(recorder, req)
740 | expect(t, recorder.Code, http.StatusOK)
741 | refute(t, len(buff.String()), 0)
742 | expect(t, buff.String(), "1")
743 | }
744 |
745 | type Form27Action struct {
746 | Ctx
747 | }
748 |
749 | func (a *Form27Action) Get() string {
750 | v := a.FormFloat32("test")
751 | return fmt.Sprintf("%.2f", v)
752 | }
753 |
754 | func TestForm27(t *testing.T) {
755 | buff := bytes.NewBufferString("")
756 | recorder := httptest.NewRecorder()
757 | recorder.Body = buff
758 |
759 | o := Classic()
760 | o.Get("/", new(Form27Action))
761 |
762 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
763 | if err != nil {
764 | t.Error(err)
765 | }
766 |
767 | o.ServeHTTP(recorder, req)
768 | expect(t, recorder.Code, http.StatusOK)
769 | refute(t, len(buff.String()), 0)
770 | expect(t, buff.String(), "1.00")
771 | }
772 |
773 | type Form28Action struct {
774 | Ctx
775 | }
776 |
777 | func (a *Form28Action) Get() string {
778 | v := a.FormFloat64("test")
779 | return fmt.Sprintf("%.2f", v)
780 | }
781 |
782 | func TestForm28(t *testing.T) {
783 | buff := bytes.NewBufferString("")
784 | recorder := httptest.NewRecorder()
785 | recorder.Body = buff
786 |
787 | o := Classic()
788 | o.Get("/", new(Form28Action))
789 |
790 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
791 | if err != nil {
792 | t.Error(err)
793 | }
794 |
795 | o.ServeHTTP(recorder, req)
796 | expect(t, recorder.Code, http.StatusOK)
797 | refute(t, len(buff.String()), 0)
798 | expect(t, buff.String(), "1.00")
799 | }
800 |
801 | type Form29Action struct {
802 | Ctx
803 | }
804 |
805 | func (a *Form29Action) Get() string {
806 | v := a.FormBool("test")
807 | return fmt.Sprintf("%v", v)
808 | }
809 |
810 | func TestForm29(t *testing.T) {
811 | buff := bytes.NewBufferString("")
812 | recorder := httptest.NewRecorder()
813 | recorder.Body = buff
814 |
815 | o := Classic()
816 | o.Get("/", new(Form29Action))
817 |
818 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
819 | if err != nil {
820 | t.Error(err)
821 | }
822 |
823 | o.ServeHTTP(recorder, req)
824 | expect(t, recorder.Code, http.StatusOK)
825 | refute(t, len(buff.String()), 0)
826 | expect(t, buff.String(), "true")
827 | }
828 |
829 | type Form30Action struct {
830 | Ctx
831 | }
832 |
833 | func (a *Form30Action) Get() string {
834 | v := a.Form("test")
835 | return fmt.Sprintf("%s", v)
836 | }
837 |
838 | func TestForm30(t *testing.T) {
839 | buff := bytes.NewBufferString("")
840 | recorder := httptest.NewRecorder()
841 | recorder.Body = buff
842 |
843 | o := Classic()
844 | o.Get("/", new(Form30Action))
845 |
846 | req, err := http.NewRequest("GET", "http://localhost:8000/?test=1", nil)
847 | if err != nil {
848 | t.Error(err)
849 | }
850 |
851 | o.ServeHTTP(recorder, req)
852 | expect(t, recorder.Code, http.StatusOK)
853 | refute(t, len(buff.String()), 0)
854 | expect(t, buff.String(), "1")
855 | }
856 |
--------------------------------------------------------------------------------