Merge pull request #66 from chihaya/kill-godeps
Remove godep sources from the repository
This commit is contained in:
commit
61f483c2f3
56 changed files with 2 additions and 6831 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/config.json
|
/config.json
|
||||||
/chihaya
|
/chihaya
|
||||||
|
/Godeps/_workspace
|
||||||
|
|
|
@ -6,6 +6,7 @@ sudo: false
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- go get github.com/tools/godep
|
- go get github.com/tools/godep
|
||||||
|
- godep restore
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- godep go test ./...
|
- godep go test ./...
|
||||||
|
|
2
Godeps/_workspace/.gitignore
generated
vendored
2
Godeps/_workspace/.gitignore
generated
vendored
|
@ -1,2 +0,0 @@
|
||||||
/pkg
|
|
||||||
/bin
|
|
23
Godeps/_workspace/src/github.com/chihaya/bencode/.gitignore
generated
vendored
23
Godeps/_workspace/src/github.com/chihaya/bencode/.gitignore
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
11
Godeps/_workspace/src/github.com/chihaya/bencode/.travis.yml
generated
vendored
11
Godeps/_workspace/src/github.com/chihaya/bencode/.travis.yml
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go: 1.3
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
irc:
|
|
||||||
channels:
|
|
||||||
- "irc.freenode.net#chihaya"
|
|
||||||
on_success: always
|
|
||||||
on_failure: always
|
|
||||||
email: false
|
|
5
Godeps/_workspace/src/github.com/chihaya/bencode/AUTHORS
generated
vendored
5
Godeps/_workspace/src/github.com/chihaya/bencode/AUTHORS
generated
vendored
|
@ -1,5 +0,0 @@
|
||||||
# This is the official list of Chihaya authors for copyright purposes, in alphabetical order.
|
|
||||||
|
|
||||||
Jimmy Zelinskie <jimmyzelinskie@gmail.com>
|
|
||||||
Justin Li <jli@j-li.net>
|
|
||||||
|
|
25
Godeps/_workspace/src/github.com/chihaya/bencode/LICENSE
generated
vendored
25
Godeps/_workspace/src/github.com/chihaya/bencode/LICENSE
generated
vendored
|
@ -1,25 +0,0 @@
|
||||||
bencode is released under a BSD 2-Clause license, reproduced below.
|
|
||||||
|
|
||||||
Copyright (c) 2015, The Chihaya Authors
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
10
Godeps/_workspace/src/github.com/chihaya/bencode/README.md
generated
vendored
10
Godeps/_workspace/src/github.com/chihaya/bencode/README.md
generated
vendored
|
@ -1,10 +0,0 @@
|
||||||
# bencode [![Build Status](https://api.travis-ci.org/chihaya/bencode.svg?branch=master)](https://travis-ci.org/chihaya/bencode)
|
|
||||||
|
|
||||||
Package bencode implements bencoding of data as defined in [BEP 3][].
|
|
||||||
It uses type assertion over reflection for performance.
|
|
||||||
|
|
||||||
[BEP 3]: http://www.bittorrent.org/beps/bep_0003.html
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Refer to the [GoDoc](http://godoc.org/github.com/chihaya/bencode).
|
|
23
Godeps/_workspace/src/github.com/chihaya/bencode/bencode.go
generated
vendored
23
Godeps/_workspace/src/github.com/chihaya/bencode/bencode.go
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
// Copyright 2015 The Chihaya Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package bencode implements bencoding of data as defined in BEP 3 using
|
|
||||||
// type assertion over reflection for performance.
|
|
||||||
package bencode
|
|
||||||
|
|
||||||
// Dict represents a bencode dictionary.
|
|
||||||
type Dict map[string]interface{}
|
|
||||||
|
|
||||||
// NewDict allocates the memory for a Dict.
|
|
||||||
func NewDict() Dict {
|
|
||||||
return make(Dict)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List represents a bencode list.
|
|
||||||
type List []interface{}
|
|
||||||
|
|
||||||
// NewList allocates the memory for a List.
|
|
||||||
func NewList() List {
|
|
||||||
return make(List, 0)
|
|
||||||
}
|
|
135
Godeps/_workspace/src/github.com/chihaya/bencode/decoder.go
generated
vendored
135
Godeps/_workspace/src/github.com/chihaya/bencode/decoder.go
generated
vendored
|
@ -1,135 +0,0 @@
|
||||||
// Copyright 2015 The Chihaya Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bencode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Decoder reads bencoded objects from an input stream.
|
|
||||||
type Decoder struct {
|
|
||||||
r *bufio.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecoder returns a new decoder that reads from r.
|
|
||||||
func NewDecoder(r io.Reader) *Decoder {
|
|
||||||
return &Decoder{r: bufio.NewReader(r)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode unmarshals the next bencoded value in the stream.
|
|
||||||
func (dec *Decoder) Decode() (interface{}, error) {
|
|
||||||
return unmarshal(dec.r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal deserializes and returns the bencoded value in buf.
|
|
||||||
func Unmarshal(buf []byte) (interface{}, error) {
|
|
||||||
r := bufio.NewReader(bytes.NewBuffer(buf))
|
|
||||||
return unmarshal(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshal reads bencoded values from a bufio.Reader
|
|
||||||
func unmarshal(r *bufio.Reader) (interface{}, error) {
|
|
||||||
tok, err := r.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tok {
|
|
||||||
case 'i':
|
|
||||||
return readTerminatedInt(r, 'e')
|
|
||||||
|
|
||||||
case 'l':
|
|
||||||
list := NewList()
|
|
||||||
for {
|
|
||||||
ok, err := readTerminator(r, 'e')
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := unmarshal(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
list = append(list, v)
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
|
|
||||||
case 'd':
|
|
||||||
dict := NewDict()
|
|
||||||
for {
|
|
||||||
ok, err := readTerminator(r, 'e')
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := unmarshal(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, ok := v.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("bencode: non-string map key")
|
|
||||||
}
|
|
||||||
|
|
||||||
dict[key], err = unmarshal(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dict, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
err = r.UnreadByte()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
length, err := readTerminatedInt(r, ':')
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("bencode: unknown input sequence")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, length)
|
|
||||||
n, err := r.Read(buf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if int64(n) != length {
|
|
||||||
return nil, errors.New("bencode: short read")
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(buf), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTerminator(r *bufio.Reader, term byte) (bool, error) {
|
|
||||||
tok, err := r.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
} else if tok == term {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, r.UnreadByte()
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTerminatedInt(r *bufio.Reader, term byte) (int64, error) {
|
|
||||||
buf, err := r.ReadSlice(term)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
} else if len(buf) <= 1 {
|
|
||||||
return 0, errors.New("bencode: empty integer field")
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.ParseInt(string(buf[:len(buf)-1]), 10, 64)
|
|
||||||
}
|
|
89
Godeps/_workspace/src/github.com/chihaya/bencode/decoder_test.go
generated
vendored
89
Godeps/_workspace/src/github.com/chihaya/bencode/decoder_test.go
generated
vendored
|
@ -1,89 +0,0 @@
|
||||||
// Copyright 2015 The Chihaya Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bencode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var unmarshalTests = []struct {
|
|
||||||
input string
|
|
||||||
expected interface{}
|
|
||||||
}{
|
|
||||||
{"i42e", int64(42)},
|
|
||||||
{"i-42e", int64(-42)},
|
|
||||||
|
|
||||||
{"7:example", "example"},
|
|
||||||
|
|
||||||
{"l3:one3:twoe", List{"one", "two"}},
|
|
||||||
{"le", List{}},
|
|
||||||
|
|
||||||
{"d3:one2:aa3:two2:bbe", Dict{"one": "aa", "two": "bb"}},
|
|
||||||
{"de", Dict{}},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshal(t *testing.T) {
|
|
||||||
for _, test := range unmarshalTests {
|
|
||||||
got, err := Unmarshal([]byte(test.input))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else if !reflect.DeepEqual(got, test.expected) {
|
|
||||||
t.Errorf("\ngot: %#v\nexpected: %#v", got, test.expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type bufferLoop struct {
|
|
||||||
val string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *bufferLoop) Read(b []byte) (int, error) {
|
|
||||||
n := copy(b, r.val)
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkUnmarshalScalar(b *testing.B) {
|
|
||||||
d1 := NewDecoder(&bufferLoop{"7:example"})
|
|
||||||
d2 := NewDecoder(&bufferLoop{"i42e"})
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
d1.Decode()
|
|
||||||
d2.Decode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshalLarge(t *testing.T) {
|
|
||||||
data := Dict{
|
|
||||||
"k1": List{"a", "b", "c"},
|
|
||||||
"k2": int64(42),
|
|
||||||
"k3": "val",
|
|
||||||
"k4": int64(-42),
|
|
||||||
}
|
|
||||||
buf, _ := Marshal(data)
|
|
||||||
dec := NewDecoder(&bufferLoop{string(buf)})
|
|
||||||
|
|
||||||
got, err := dec.Decode()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else if !reflect.DeepEqual(got, data) {
|
|
||||||
t.Errorf("\ngot: %#v\nexpected: %#v", got, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkUnmarshalLarge(b *testing.B) {
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"k1": []string{"a", "b", "c"},
|
|
||||||
"k2": 42,
|
|
||||||
"k3": "val",
|
|
||||||
"k4": uint(42),
|
|
||||||
}
|
|
||||||
buf, _ := Marshal(data)
|
|
||||||
dec := NewDecoder(&bufferLoop{string(buf)})
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
dec.Decode()
|
|
||||||
}
|
|
||||||
}
|
|
157
Godeps/_workspace/src/github.com/chihaya/bencode/encoder.go
generated
vendored
157
Godeps/_workspace/src/github.com/chihaya/bencode/encoder.go
generated
vendored
|
@ -1,157 +0,0 @@
|
||||||
// Copyright 2015 The Chihaya Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bencode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An Encoder writes bencoded objects to an output stream.
|
|
||||||
type Encoder struct {
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncoder returns a new encoder that writes to w.
|
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
|
||||||
return &Encoder{w: w}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode writes the bencoding of v to the stream.
|
|
||||||
func (enc *Encoder) Encode(v interface{}) error {
|
|
||||||
return marshal(enc.w, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal returns the bencoding of v.
|
|
||||||
func Marshal(v interface{}) ([]byte, error) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
err := marshal(buf, v)
|
|
||||||
return buf.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshaler is the interface implemented by objects that can marshal
|
|
||||||
// themselves.
|
|
||||||
type Marshaler interface {
|
|
||||||
MarshalBencode() ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal writes types bencoded to an io.Writer
|
|
||||||
func marshal(w io.Writer, data interface{}) error {
|
|
||||||
switch v := data.(type) {
|
|
||||||
case Marshaler:
|
|
||||||
bencoded, err := v.MarshalBencode()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(bencoded)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case string:
|
|
||||||
marshalString(w, v)
|
|
||||||
|
|
||||||
case int:
|
|
||||||
marshalInt(w, int64(v))
|
|
||||||
|
|
||||||
case uint:
|
|
||||||
marshalUint(w, uint64(v))
|
|
||||||
|
|
||||||
case int16:
|
|
||||||
marshalInt(w, int64(v))
|
|
||||||
|
|
||||||
case uint16:
|
|
||||||
marshalUint(w, uint64(v))
|
|
||||||
|
|
||||||
case int64:
|
|
||||||
marshalInt(w, v)
|
|
||||||
|
|
||||||
case uint64:
|
|
||||||
marshalUint(w, v)
|
|
||||||
|
|
||||||
case []byte:
|
|
||||||
marshalBytes(w, v)
|
|
||||||
|
|
||||||
case time.Duration: // Assume seconds
|
|
||||||
marshalInt(w, int64(v/time.Second))
|
|
||||||
|
|
||||||
case Dict:
|
|
||||||
marshal(w, map[string]interface{}(v))
|
|
||||||
|
|
||||||
case []Dict:
|
|
||||||
w.Write([]byte{'l'})
|
|
||||||
for _, val := range v {
|
|
||||||
err := marshal(w, val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write([]byte{'e'})
|
|
||||||
|
|
||||||
case map[string]interface{}:
|
|
||||||
w.Write([]byte{'d'})
|
|
||||||
for key, val := range v {
|
|
||||||
marshalString(w, key)
|
|
||||||
err := marshal(w, val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write([]byte{'e'})
|
|
||||||
|
|
||||||
case []string:
|
|
||||||
w.Write([]byte{'l'})
|
|
||||||
for _, val := range v {
|
|
||||||
err := marshal(w, val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write([]byte{'e'})
|
|
||||||
|
|
||||||
case List:
|
|
||||||
marshal(w, []interface{}(v))
|
|
||||||
|
|
||||||
case []interface{}:
|
|
||||||
w.Write([]byte{'l'})
|
|
||||||
for _, val := range v {
|
|
||||||
err := marshal(w, val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write([]byte{'e'})
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("attempted to marshal unsupported type:\n%t", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalInt(w io.Writer, v int64) {
|
|
||||||
w.Write([]byte{'i'})
|
|
||||||
w.Write([]byte(strconv.FormatInt(v, 10)))
|
|
||||||
w.Write([]byte{'e'})
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalUint(w io.Writer, v uint64) {
|
|
||||||
w.Write([]byte{'i'})
|
|
||||||
w.Write([]byte(strconv.FormatUint(v, 10)))
|
|
||||||
w.Write([]byte{'e'})
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalBytes(w io.Writer, v []byte) {
|
|
||||||
w.Write([]byte(strconv.Itoa(len(v))))
|
|
||||||
w.Write([]byte{':'})
|
|
||||||
w.Write(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalString(w io.Writer, v string) {
|
|
||||||
marshalBytes(w, []byte(v))
|
|
||||||
}
|
|
72
Godeps/_workspace/src/github.com/chihaya/bencode/encoder_test.go
generated
vendored
72
Godeps/_workspace/src/github.com/chihaya/bencode/encoder_test.go
generated
vendored
|
@ -1,72 +0,0 @@
|
||||||
// Copyright 2015 The Chihaya Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bencode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var marshalTests = []struct {
|
|
||||||
input interface{}
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{int(42), "i42e"},
|
|
||||||
{int(-42), "i-42e"},
|
|
||||||
{uint(43), "i43e"},
|
|
||||||
{int64(44), "i44e"},
|
|
||||||
{uint64(45), "i45e"},
|
|
||||||
{int16(44), "i44e"},
|
|
||||||
{uint16(45), "i45e"},
|
|
||||||
|
|
||||||
{"example", "7:example"},
|
|
||||||
{[]byte("example"), "7:example"},
|
|
||||||
{30 * time.Minute, "i1800e"},
|
|
||||||
|
|
||||||
{[]string{"one", "two"}, "l3:one3:twoe"},
|
|
||||||
{[]interface{}{"one", "two"}, "l3:one3:twoe"},
|
|
||||||
{[]string{}, "le"},
|
|
||||||
|
|
||||||
{map[string]interface{}{"one": "aa", "two": "bb"}, "d3:one2:aa3:two2:bbe"},
|
|
||||||
{map[string]interface{}{}, "de"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarshal(t *testing.T) {
|
|
||||||
for _, test := range marshalTests {
|
|
||||||
got, err := Marshal(test.input)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else if string(got) != test.expected {
|
|
||||||
t.Errorf("\ngot: %s\nexpected: %s", got, test.expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMarshalScalar(b *testing.B) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
encoder := NewEncoder(buf)
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
encoder.Encode("test")
|
|
||||||
encoder.Encode(123)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMarshalLarge(b *testing.B) {
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"k1": []string{"a", "b", "c"},
|
|
||||||
"k2": 42,
|
|
||||||
"k3": "val",
|
|
||||||
"k4": uint(42),
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
encoder := NewEncoder(buf)
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
encoder.Encode(data)
|
|
||||||
}
|
|
||||||
}
|
|
191
Godeps/_workspace/src/github.com/golang/glog/LICENSE
generated
vendored
191
Godeps/_workspace/src/github.com/golang/glog/LICENSE
generated
vendored
|
@ -1,191 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction, and
|
|
||||||
distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
|
||||||
owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
|
||||||
that control, are controlled by, or are under common control with that entity.
|
|
||||||
For the purposes of this definition, "control" means (i) the power, direct or
|
|
||||||
indirect, to cause the direction or management of such entity, whether by
|
|
||||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
||||||
permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications, including
|
|
||||||
but not limited to software source code, documentation source, and configuration
|
|
||||||
files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical transformation or
|
|
||||||
translation of a Source form, including but not limited to compiled object code,
|
|
||||||
generated documentation, and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
|
||||||
available under the License, as indicated by a copyright notice that is included
|
|
||||||
in or attached to the work (an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
|
||||||
is based on (or derived from) the Work and for which the editorial revisions,
|
|
||||||
annotations, elaborations, or other modifications represent, as a whole, an
|
|
||||||
original work of authorship. For the purposes of this License, Derivative Works
|
|
||||||
shall not include works that remain separable from, or merely link (or bind by
|
|
||||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including the original version
|
|
||||||
of the Work and any modifications or additions to that Work or Derivative Works
|
|
||||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
|
||||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
|
||||||
on behalf of the copyright owner. For the purposes of this definition,
|
|
||||||
"submitted" means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems, and
|
|
||||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
|
||||||
the purpose of discussing and improving the Work, but excluding communication
|
|
||||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
|
||||||
owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
|
||||||
of whom a Contribution has been received by Licensor and subsequently
|
|
||||||
incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this License, each Contributor hereby
|
|
||||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
|
||||||
Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License.
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this License, each Contributor hereby
|
|
||||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
|
||||||
irrevocable (except as stated in this section) patent license to make, have
|
|
||||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
|
||||||
such license applies only to those patent claims licensable by such Contributor
|
|
||||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
|
||||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
|
||||||
submitted. If You institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
|
||||||
Contribution incorporated within the Work constitutes direct or contributory
|
|
||||||
patent infringement, then any patent licenses granted to You under this License
|
|
||||||
for that Work shall terminate as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution.
|
|
||||||
|
|
||||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
|
||||||
in any medium, with or without modifications, and in Source or Object form,
|
|
||||||
provided that You meet the following conditions:
|
|
||||||
|
|
||||||
You must give any other recipients of the Work or Derivative Works a copy of
|
|
||||||
this License; and
|
|
||||||
You must cause any modified files to carry prominent notices stating that You
|
|
||||||
changed the files; and
|
|
||||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
|
||||||
all copyright, patent, trademark, and attribution notices from the Source form
|
|
||||||
of the Work, excluding those notices that do not pertain to any part of the
|
|
||||||
Derivative Works; and
|
|
||||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
|
||||||
Derivative Works that You distribute must include a readable copy of the
|
|
||||||
attribution notices contained within such NOTICE file, excluding those notices
|
|
||||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
|
||||||
following places: within a NOTICE text file distributed as part of the
|
|
||||||
Derivative Works; within the Source form or documentation, if provided along
|
|
||||||
with the Derivative Works; or, within a display generated by the Derivative
|
|
||||||
Works, if and wherever such third-party notices normally appear. The contents of
|
|
||||||
the NOTICE file are for informational purposes only and do not modify the
|
|
||||||
License. You may add Your own attribution notices within Derivative Works that
|
|
||||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
|
||||||
provided that such additional attribution notices cannot be construed as
|
|
||||||
modifying the License.
|
|
||||||
You may add Your own copyright statement to Your modifications and may provide
|
|
||||||
additional or different license terms and conditions for use, reproduction, or
|
|
||||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
|
||||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
|
||||||
with the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions.
|
|
||||||
|
|
||||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
|
||||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
|
||||||
conditions of this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
|
||||||
any separate license agreement you may have executed with Licensor regarding
|
|
||||||
such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks.
|
|
||||||
|
|
||||||
This License does not grant permission to use the trade names, trademarks,
|
|
||||||
service marks, or product names of the Licensor, except as required for
|
|
||||||
reasonable and customary use in describing the origin of the Work and
|
|
||||||
reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
|
||||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
|
||||||
including, without limitation, any warranties or conditions of TITLE,
|
|
||||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
|
||||||
solely responsible for determining the appropriateness of using or
|
|
||||||
redistributing the Work and assume any risks associated with Your exercise of
|
|
||||||
permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability.
|
|
||||||
|
|
||||||
In no event and under no legal theory, whether in tort (including negligence),
|
|
||||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
|
||||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special, incidental,
|
|
||||||
or consequential damages of any character arising as a result of this License or
|
|
||||||
out of the use or inability to use the Work (including but not limited to
|
|
||||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
|
||||||
any and all other commercial damages or losses), even if such Contributor has
|
|
||||||
been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability.
|
|
||||||
|
|
||||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
|
||||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
|
||||||
other liability obligations and/or rights consistent with this License. However,
|
|
||||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
|
||||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
|
||||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason of your
|
|
||||||
accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following boilerplate
|
|
||||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
|
||||||
identifying information. (Don't include the brackets!) The text should be
|
|
||||||
enclosed in the appropriate comment syntax for the file format. We also
|
|
||||||
recommend that a file or class name and description of purpose be included on
|
|
||||||
the same "printed page" as the copyright notice for easier identification within
|
|
||||||
third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
44
Godeps/_workspace/src/github.com/golang/glog/README
generated
vendored
44
Godeps/_workspace/src/github.com/golang/glog/README
generated
vendored
|
@ -1,44 +0,0 @@
|
||||||
glog
|
|
||||||
====
|
|
||||||
|
|
||||||
Leveled execution logs for Go.
|
|
||||||
|
|
||||||
This is an efficient pure Go implementation of leveled logs in the
|
|
||||||
manner of the open source C++ package
|
|
||||||
http://code.google.com/p/google-glog
|
|
||||||
|
|
||||||
By binding methods to booleans it is possible to use the log package
|
|
||||||
without paying the expense of evaluating the arguments to the log.
|
|
||||||
Through the -vmodule flag, the package also provides fine-grained
|
|
||||||
control over logging at the file level.
|
|
||||||
|
|
||||||
The comment from glog.go introduces the ideas:
|
|
||||||
|
|
||||||
Package glog implements logging analogous to the Google-internal
|
|
||||||
C++ INFO/ERROR/V setup. It provides functions Info, Warning,
|
|
||||||
Error, Fatal, plus formatting variants such as Infof. It
|
|
||||||
also provides V-style logging controlled by the -v and
|
|
||||||
-vmodule=file=2 flags.
|
|
||||||
|
|
||||||
Basic examples:
|
|
||||||
|
|
||||||
glog.Info("Prepare to repel boarders")
|
|
||||||
|
|
||||||
glog.Fatalf("Initialization failed: %s", err)
|
|
||||||
|
|
||||||
See the documentation for the V function for an explanation
|
|
||||||
of these examples:
|
|
||||||
|
|
||||||
if glog.V(2) {
|
|
||||||
glog.Info("Starting transaction...")
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(2).Infoln("Processed", nItems, "elements")
|
|
||||||
|
|
||||||
|
|
||||||
The repository contains an open source version of the log package
|
|
||||||
used inside Google. The master copy of the source lives inside
|
|
||||||
Google, not here. The code in this repo is for export only and is not itself
|
|
||||||
under development. Feature requests will be ignored.
|
|
||||||
|
|
||||||
Send bug reports to golang-nuts@googlegroups.com.
|
|
1177
Godeps/_workspace/src/github.com/golang/glog/glog.go
generated
vendored
1177
Godeps/_workspace/src/github.com/golang/glog/glog.go
generated
vendored
File diff suppressed because it is too large
Load diff
124
Godeps/_workspace/src/github.com/golang/glog/glog_file.go
generated
vendored
124
Godeps/_workspace/src/github.com/golang/glog/glog_file.go
generated
vendored
|
@ -1,124 +0,0 @@
|
||||||
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
|
|
||||||
//
|
|
||||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// File I/O for logs.
|
|
||||||
|
|
||||||
package glog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MaxSize is the maximum size of a log file in bytes.
|
|
||||||
var MaxSize uint64 = 1024 * 1024 * 1800
|
|
||||||
|
|
||||||
// logDirs lists the candidate directories for new log files.
|
|
||||||
var logDirs []string
|
|
||||||
|
|
||||||
// If non-empty, overrides the choice of directory in which to write logs.
|
|
||||||
// See createLogDirs for the full list of possible destinations.
|
|
||||||
var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory")
|
|
||||||
|
|
||||||
func createLogDirs() {
|
|
||||||
if *logDir != "" {
|
|
||||||
logDirs = append(logDirs, *logDir)
|
|
||||||
}
|
|
||||||
logDirs = append(logDirs, os.TempDir())
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
pid = os.Getpid()
|
|
||||||
program = filepath.Base(os.Args[0])
|
|
||||||
host = "unknownhost"
|
|
||||||
userName = "unknownuser"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
h, err := os.Hostname()
|
|
||||||
if err == nil {
|
|
||||||
host = shortHostname(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
current, err := user.Current()
|
|
||||||
if err == nil {
|
|
||||||
userName = current.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize userName since it may contain filepath separators on Windows.
|
|
||||||
userName = strings.Replace(userName, `\`, "_", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// shortHostname returns its argument, truncating at the first period.
|
|
||||||
// For instance, given "www.google.com" it returns "www".
|
|
||||||
func shortHostname(hostname string) string {
|
|
||||||
if i := strings.Index(hostname, "."); i >= 0 {
|
|
||||||
return hostname[:i]
|
|
||||||
}
|
|
||||||
return hostname
|
|
||||||
}
|
|
||||||
|
|
||||||
// logName returns a new log file name containing tag, with start time t, and
|
|
||||||
// the name for the symlink for tag.
|
|
||||||
func logName(tag string, t time.Time) (name, link string) {
|
|
||||||
name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d",
|
|
||||||
program,
|
|
||||||
host,
|
|
||||||
userName,
|
|
||||||
tag,
|
|
||||||
t.Year(),
|
|
||||||
t.Month(),
|
|
||||||
t.Day(),
|
|
||||||
t.Hour(),
|
|
||||||
t.Minute(),
|
|
||||||
t.Second(),
|
|
||||||
pid)
|
|
||||||
return name, program + "." + tag
|
|
||||||
}
|
|
||||||
|
|
||||||
var onceLogDirs sync.Once
|
|
||||||
|
|
||||||
// create creates a new log file and returns the file and its filename, which
|
|
||||||
// contains tag ("INFO", "FATAL", etc.) and t. If the file is created
|
|
||||||
// successfully, create also attempts to update the symlink for that tag, ignoring
|
|
||||||
// errors.
|
|
||||||
func create(tag string, t time.Time) (f *os.File, filename string, err error) {
|
|
||||||
onceLogDirs.Do(createLogDirs)
|
|
||||||
if len(logDirs) == 0 {
|
|
||||||
return nil, "", errors.New("log: no log dirs")
|
|
||||||
}
|
|
||||||
name, link := logName(tag, t)
|
|
||||||
var lastErr error
|
|
||||||
for _, dir := range logDirs {
|
|
||||||
fname := filepath.Join(dir, name)
|
|
||||||
f, err := os.Create(fname)
|
|
||||||
if err == nil {
|
|
||||||
symlink := filepath.Join(dir, link)
|
|
||||||
os.Remove(symlink) // ignore err
|
|
||||||
os.Symlink(name, symlink) // ignore err
|
|
||||||
return f, fname, nil
|
|
||||||
}
|
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
|
|
||||||
}
|
|
415
Godeps/_workspace/src/github.com/golang/glog/glog_test.go
generated
vendored
415
Godeps/_workspace/src/github.com/golang/glog/glog_test.go
generated
vendored
|
@ -1,415 +0,0 @@
|
||||||
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
|
|
||||||
//
|
|
||||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package glog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
stdLog "log"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test that shortHostname works as advertised.
|
|
||||||
func TestShortHostname(t *testing.T) {
|
|
||||||
for hostname, expect := range map[string]string{
|
|
||||||
"": "",
|
|
||||||
"host": "host",
|
|
||||||
"host.google.com": "host",
|
|
||||||
} {
|
|
||||||
if got := shortHostname(hostname); expect != got {
|
|
||||||
t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter.
|
|
||||||
type flushBuffer struct {
|
|
||||||
bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *flushBuffer) Flush() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *flushBuffer) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap sets the log writers and returns the old array.
|
|
||||||
func (l *loggingT) swap(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) {
|
|
||||||
l.mu.Lock()
|
|
||||||
defer l.mu.Unlock()
|
|
||||||
old = l.file
|
|
||||||
for i, w := range writers {
|
|
||||||
logging.file[i] = w
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBuffers sets the log writers to all new byte buffers and returns the old array.
|
|
||||||
func (l *loggingT) newBuffers() [numSeverity]flushSyncWriter {
|
|
||||||
return l.swap([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})
|
|
||||||
}
|
|
||||||
|
|
||||||
// contents returns the specified log value as a string.
|
|
||||||
func contents(s severity) string {
|
|
||||||
return logging.file[s].(*flushBuffer).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// contains reports whether the string is contained in the log.
|
|
||||||
func contains(s severity, str string, t *testing.T) bool {
|
|
||||||
return strings.Contains(contents(s), str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setFlags configures the logging flags how the test expects them.
|
|
||||||
func setFlags() {
|
|
||||||
logging.toStderr = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that Info works as advertised.
|
|
||||||
func TestInfo(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
Info("test")
|
|
||||||
if !contains(infoLog, "I", t) {
|
|
||||||
t.Errorf("Info has wrong character: %q", contents(infoLog))
|
|
||||||
}
|
|
||||||
if !contains(infoLog, "test", t) {
|
|
||||||
t.Error("Info failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfoDepth(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
|
|
||||||
f := func() { InfoDepth(1, "depth-test1") }
|
|
||||||
|
|
||||||
// The next three lines must stay together
|
|
||||||
_, _, wantLine, _ := runtime.Caller(0)
|
|
||||||
InfoDepth(0, "depth-test0")
|
|
||||||
f()
|
|
||||||
|
|
||||||
msgs := strings.Split(strings.TrimSuffix(contents(infoLog), "\n"), "\n")
|
|
||||||
if len(msgs) != 2 {
|
|
||||||
t.Fatalf("Got %d lines, expected 2", len(msgs))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, m := range msgs {
|
|
||||||
if !strings.HasPrefix(m, "I") {
|
|
||||||
t.Errorf("InfoDepth[%d] has wrong character: %q", i, m)
|
|
||||||
}
|
|
||||||
w := fmt.Sprintf("depth-test%d", i)
|
|
||||||
if !strings.Contains(m, w) {
|
|
||||||
t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pull out the line number (between : and ])
|
|
||||||
msg := m[strings.LastIndex(m, ":")+1:]
|
|
||||||
x := strings.Index(msg, "]")
|
|
||||||
if x < 0 {
|
|
||||||
t.Errorf("InfoDepth[%d]: missing ']': %q", i, m)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
line, err := strconv.Atoi(msg[:x])
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("InfoDepth[%d]: bad line number: %q", i, m)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
wantLine++
|
|
||||||
if wantLine != line {
|
|
||||||
t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
CopyStandardLogTo("INFO")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that CopyStandardLogTo panics on bad input.
|
|
||||||
func TestCopyStandardLogToPanic(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") {
|
|
||||||
t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
CopyStandardLogTo("LOG")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that using the standard log package logs to INFO.
|
|
||||||
func TestStandardLog(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
stdLog.Print("test")
|
|
||||||
if !contains(infoLog, "I", t) {
|
|
||||||
t.Errorf("Info has wrong character: %q", contents(infoLog))
|
|
||||||
}
|
|
||||||
if !contains(infoLog, "test", t) {
|
|
||||||
t.Error("Info failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the header has the correct format.
|
|
||||||
func TestHeader(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
defer func(previous func() time.Time) { timeNow = previous }(timeNow)
|
|
||||||
timeNow = func() time.Time {
|
|
||||||
return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)
|
|
||||||
}
|
|
||||||
pid = 1234
|
|
||||||
Info("test")
|
|
||||||
var line int
|
|
||||||
format := "I0102 15:04:05.067890 1234 glog_test.go:%d] test\n"
|
|
||||||
n, err := fmt.Sscanf(contents(infoLog), format, &line)
|
|
||||||
if n != 1 || err != nil {
|
|
||||||
t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog))
|
|
||||||
}
|
|
||||||
// Scanf treats multiple spaces as equivalent to a single space,
|
|
||||||
// so check for correct space-padding also.
|
|
||||||
want := fmt.Sprintf(format, line)
|
|
||||||
if contents(infoLog) != want {
|
|
||||||
t.Errorf("log format error: got:\n\t%q\nwant:\t%q", contents(infoLog), want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that an Error log goes to Warning and Info.
|
|
||||||
// Even in the Info log, the source character will be E, so the data should
|
|
||||||
// all be identical.
|
|
||||||
func TestError(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
Error("test")
|
|
||||||
if !contains(errorLog, "E", t) {
|
|
||||||
t.Errorf("Error has wrong character: %q", contents(errorLog))
|
|
||||||
}
|
|
||||||
if !contains(errorLog, "test", t) {
|
|
||||||
t.Error("Error failed")
|
|
||||||
}
|
|
||||||
str := contents(errorLog)
|
|
||||||
if !contains(warningLog, str, t) {
|
|
||||||
t.Error("Warning failed")
|
|
||||||
}
|
|
||||||
if !contains(infoLog, str, t) {
|
|
||||||
t.Error("Info failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that a Warning log goes to Info.
|
|
||||||
// Even in the Info log, the source character will be W, so the data should
|
|
||||||
// all be identical.
|
|
||||||
func TestWarning(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
Warning("test")
|
|
||||||
if !contains(warningLog, "W", t) {
|
|
||||||
t.Errorf("Warning has wrong character: %q", contents(warningLog))
|
|
||||||
}
|
|
||||||
if !contains(warningLog, "test", t) {
|
|
||||||
t.Error("Warning failed")
|
|
||||||
}
|
|
||||||
str := contents(warningLog)
|
|
||||||
if !contains(infoLog, str, t) {
|
|
||||||
t.Error("Info failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that a V log goes to Info.
|
|
||||||
func TestV(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
logging.verbosity.Set("2")
|
|
||||||
defer logging.verbosity.Set("0")
|
|
||||||
V(2).Info("test")
|
|
||||||
if !contains(infoLog, "I", t) {
|
|
||||||
t.Errorf("Info has wrong character: %q", contents(infoLog))
|
|
||||||
}
|
|
||||||
if !contains(infoLog, "test", t) {
|
|
||||||
t.Error("Info failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that a vmodule enables a log in this file.
|
|
||||||
func TestVmoduleOn(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
logging.vmodule.Set("glog_test=2")
|
|
||||||
defer logging.vmodule.Set("")
|
|
||||||
if !V(1) {
|
|
||||||
t.Error("V not enabled for 1")
|
|
||||||
}
|
|
||||||
if !V(2) {
|
|
||||||
t.Error("V not enabled for 2")
|
|
||||||
}
|
|
||||||
if V(3) {
|
|
||||||
t.Error("V enabled for 3")
|
|
||||||
}
|
|
||||||
V(2).Info("test")
|
|
||||||
if !contains(infoLog, "I", t) {
|
|
||||||
t.Errorf("Info has wrong character: %q", contents(infoLog))
|
|
||||||
}
|
|
||||||
if !contains(infoLog, "test", t) {
|
|
||||||
t.Error("Info failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that a vmodule of another file does not enable a log in this file.
|
|
||||||
func TestVmoduleOff(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
logging.vmodule.Set("notthisfile=2")
|
|
||||||
defer logging.vmodule.Set("")
|
|
||||||
for i := 1; i <= 3; i++ {
|
|
||||||
if V(Level(i)) {
|
|
||||||
t.Errorf("V enabled for %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
V(2).Info("test")
|
|
||||||
if contents(infoLog) != "" {
|
|
||||||
t.Error("V logged incorrectly")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// vGlobs are patterns that match/don't match this file at V=2.
|
|
||||||
var vGlobs = map[string]bool{
|
|
||||||
// Easy to test the numeric match here.
|
|
||||||
"glog_test=1": false, // If -vmodule sets V to 1, V(2) will fail.
|
|
||||||
"glog_test=2": true,
|
|
||||||
"glog_test=3": true, // If -vmodule sets V to 1, V(3) will succeed.
|
|
||||||
// These all use 2 and check the patterns. All are true.
|
|
||||||
"*=2": true,
|
|
||||||
"?l*=2": true,
|
|
||||||
"????_*=2": true,
|
|
||||||
"??[mno]?_*t=2": true,
|
|
||||||
// These all use 2 and check the patterns. All are false.
|
|
||||||
"*x=2": false,
|
|
||||||
"m*=2": false,
|
|
||||||
"??_*=2": false,
|
|
||||||
"?[abc]?_*t=2": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that vmodule globbing works as advertised.
|
|
||||||
func testVmoduleGlob(pat string, match bool, t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
defer logging.vmodule.Set("")
|
|
||||||
logging.vmodule.Set(pat)
|
|
||||||
if V(2) != Verbose(match) {
|
|
||||||
t.Errorf("incorrect match for %q: got %t expected %t", pat, V(2), match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that a vmodule globbing works as advertised.
|
|
||||||
func TestVmoduleGlob(t *testing.T) {
|
|
||||||
for glob, match := range vGlobs {
|
|
||||||
testVmoduleGlob(glob, match, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRollover(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
var err error
|
|
||||||
defer func(previous func(error)) { logExitFunc = previous }(logExitFunc)
|
|
||||||
logExitFunc = func(e error) {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
defer func(previous uint64) { MaxSize = previous }(MaxSize)
|
|
||||||
MaxSize = 512
|
|
||||||
|
|
||||||
Info("x") // Be sure we have a file.
|
|
||||||
info, ok := logging.file[infoLog].(*syncBuffer)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("info wasn't created")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("info has initial error: %v", err)
|
|
||||||
}
|
|
||||||
fname0 := info.file.Name()
|
|
||||||
Info(strings.Repeat("x", int(MaxSize))) // force a rollover
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("info has error after big write: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the next log file gets a file name with a different
|
|
||||||
// time stamp.
|
|
||||||
//
|
|
||||||
// TODO: determine whether we need to support subsecond log
|
|
||||||
// rotation. C++ does not appear to handle this case (nor does it
|
|
||||||
// handle Daylight Savings Time properly).
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
|
|
||||||
Info("x") // create a new file
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error after rotation: %v", err)
|
|
||||||
}
|
|
||||||
fname1 := info.file.Name()
|
|
||||||
if fname0 == fname1 {
|
|
||||||
t.Errorf("info.f.Name did not change: %v", fname0)
|
|
||||||
}
|
|
||||||
if info.nbytes >= MaxSize {
|
|
||||||
t.Errorf("file size was not reset: %d", info.nbytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogBacktraceAt(t *testing.T) {
|
|
||||||
setFlags()
|
|
||||||
defer logging.swap(logging.newBuffers())
|
|
||||||
// The peculiar style of this code simplifies line counting and maintenance of the
|
|
||||||
// tracing block below.
|
|
||||||
var infoLine string
|
|
||||||
setTraceLocation := func(file string, line int, ok bool, delta int) {
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("could not get file:line")
|
|
||||||
}
|
|
||||||
_, file = filepath.Split(file)
|
|
||||||
infoLine = fmt.Sprintf("%s:%d", file, line+delta)
|
|
||||||
err := logging.traceLocation.Set(infoLine)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("error setting log_backtrace_at: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// Start of tracing block. These lines know about each other's relative position.
|
|
||||||
_, file, line, ok := runtime.Caller(0)
|
|
||||||
setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls.
|
|
||||||
Info("we want a stack trace here")
|
|
||||||
}
|
|
||||||
numAppearances := strings.Count(contents(infoLog), infoLine)
|
|
||||||
if numAppearances < 2 {
|
|
||||||
// Need 2 appearances, one in the log header and one in the trace:
|
|
||||||
// log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here
|
|
||||||
// ...
|
|
||||||
// github.com/glog/glog_test.go:280 (0x41ba91)
|
|
||||||
// ...
|
|
||||||
// We could be more precise but that would require knowing the details
|
|
||||||
// of the traceback format, which may not be dependable.
|
|
||||||
t.Fatal("got no trace back; log is ", contents(infoLog))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkHeader(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
buf, _, _ := logging.header(infoLog, 0)
|
|
||||||
logging.putBuffer(buf)
|
|
||||||
}
|
|
||||||
}
|
|
8
Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml
generated
vendored
8
Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml
generated
vendored
|
@ -1,8 +0,0 @@
|
||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.1
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
- 1.4
|
|
||||||
- tip
|
|
24
Godeps/_workspace/src/github.com/julienschmidt/httprouter/LICENSE
generated
vendored
24
Godeps/_workspace/src/github.com/julienschmidt/httprouter/LICENSE
generated
vendored
|
@ -1,24 +0,0 @@
|
||||||
Copyright (c) 2013 Julien Schmidt. All rights reserved.
|
|
||||||
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* The names of the contributors may not be used to endorse or promote
|
|
||||||
products derived from this software without specific prior written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY
|
|
||||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
323
Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md
generated
vendored
323
Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md
generated
vendored
|
@ -1,323 +0,0 @@
|
||||||
# HttpRouter [![Build Status](https://travis-ci.org/julienschmidt/httprouter.png?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![Coverage](http://gocover.io/_badge/github.com/julienschmidt/httprouter?0)](http://gocover.io/github.com/julienschmidt/httprouter) [![GoDoc](http://godoc.org/github.com/julienschmidt/httprouter?status.png)](http://godoc.org/github.com/julienschmidt/httprouter)
|
|
||||||
|
|
||||||
HttpRouter is a lightweight high performance HTTP request router
|
|
||||||
(also called *multiplexer* or just *mux* for short) for [Go](http://golang.org/).
|
|
||||||
|
|
||||||
In contrast to the [default mux](http://golang.org/pkg/net/http/#ServeMux) of Go's net/http package, this router supports
|
|
||||||
variables in the routing pattern and matches against the request method.
|
|
||||||
It also scales better.
|
|
||||||
|
|
||||||
The router is optimized for high performance and a small memory footprint.
|
|
||||||
It scales well even with very long paths and a large number of routes.
|
|
||||||
A compressing dynamic trie (radix tree) structure is used for efficient matching.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
**Only explicit matches:** With other routers, like [http.ServeMux](http://golang.org/pkg/net/http/#ServeMux),
|
|
||||||
a requested URL path could match multiple patterns. Therefore they have some
|
|
||||||
awkward pattern priority rules, like *longest match* or *first registered,
|
|
||||||
first matched*. By design of this router, a request can only match exactly one
|
|
||||||
or no route. As a result, there are also no unintended matches, which makes it
|
|
||||||
great for SEO and improves the user experience.
|
|
||||||
|
|
||||||
**Stop caring about trailing slashes:** Choose the URL style you like, the
|
|
||||||
router automatically redirects the client if a trailing slash is missing or if
|
|
||||||
there is one extra. Of course it only does so, if the new path has a handler.
|
|
||||||
If you don't like it, you can [turn off this behavior](http://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash).
|
|
||||||
|
|
||||||
**Path auto-correction:** Besides detecting the missing or additional trailing
|
|
||||||
slash at no extra cost, the router can also fix wrong cases and remove
|
|
||||||
superfluous path elements (like `../` or `//`).
|
|
||||||
Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users?
|
|
||||||
HttpRouter can help him by making a case-insensitive look-up and redirecting him
|
|
||||||
to the correct URL.
|
|
||||||
|
|
||||||
**Parameters in your routing pattern:** Stop parsing the requested URL path,
|
|
||||||
just give the path segment a name and the router delivers the dynamic value to
|
|
||||||
you. Because of the design of the router, path parameters are very cheap.
|
|
||||||
|
|
||||||
**Zero Garbage:** The matching and dispatching process generates zero bytes of
|
|
||||||
garbage. In fact, the only heap allocations that are made, is by building the
|
|
||||||
slice of the key-value pairs for path parameters. If the request path contains
|
|
||||||
no parameters, not a single heap allocation is necessary.
|
|
||||||
|
|
||||||
**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark).
|
|
||||||
See below for technical details of the implementation.
|
|
||||||
|
|
||||||
**No more server crashes:** You can set a [Panic handler](http://godoc.org/github.com/julienschmidt/httprouter#Router.PanicHandler) to deal with panics
|
|
||||||
occurring during handling a HTTP request. The router then recovers and lets the
|
|
||||||
PanicHandler log what happened and deliver a nice error page.
|
|
||||||
|
|
||||||
Of course you can also set **custom [NotFound](http://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) and [MethodNotAllowed](http://godoc.org/github.com/julienschmidt/httprouter#Router.MethodNotAllowed) handlers** and [**serve static files**](http://godoc.org/github.com/julienschmidt/httprouter#Router.ServeFiles).
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details.
|
|
||||||
|
|
||||||
Let's start with a trivial example:
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
"net/http"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
||||||
fmt.Fprint(w, "Welcome!\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
router := httprouter.New()
|
|
||||||
router.GET("/", Index)
|
|
||||||
router.GET("/hello/:name", Hello)
|
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", router))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Named parameters
|
|
||||||
As you can see, `:name` is a *named parameter*.
|
|
||||||
The values are accessible via `httprouter.Params`, which is just a slice of `httprouter.Param`s.
|
|
||||||
You can get the value of a parameter either by its index in the slice, or by using the `ByName(name)` method:
|
|
||||||
`:name` can be retrived by `ByName("name")`.
|
|
||||||
|
|
||||||
Named parameters only match a single path segment:
|
|
||||||
```
|
|
||||||
Pattern: /user/:user
|
|
||||||
|
|
||||||
/user/gordon match
|
|
||||||
/user/you match
|
|
||||||
/user/gordon/profile no match
|
|
||||||
/user/ no match
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other.
|
|
||||||
|
|
||||||
### Catch-All parameters
|
|
||||||
The second type are *catch-all* parameters and have the form `*name`.
|
|
||||||
Like the name suggests, they match everything.
|
|
||||||
Therefore they must always be at the **end** of the pattern:
|
|
||||||
```
|
|
||||||
Pattern: /src/*filepath
|
|
||||||
|
|
||||||
/src/ match
|
|
||||||
/src/somefile.go match
|
|
||||||
/src/subdir/somefile.go match
|
|
||||||
```
|
|
||||||
|
|
||||||
## How does it work?
|
|
||||||
The router relies on a tree structure which makes heavy use of *common prefixes*,
|
|
||||||
it is basically a *compact* [*prefix tree*](http://en.wikipedia.org/wiki/Trie)
|
|
||||||
(or just [*Radix tree*](http://en.wikipedia.org/wiki/Radix_tree)).
|
|
||||||
Nodes with a common prefix also share a common parent. Here is a short example
|
|
||||||
what the routing tree for the `GET` request method could look like:
|
|
||||||
|
|
||||||
```
|
|
||||||
Priority Path Handle
|
|
||||||
9 \ *<1>
|
|
||||||
3 ├s nil
|
|
||||||
2 |├earch\ *<2>
|
|
||||||
1 |└upport\ *<3>
|
|
||||||
2 ├blog\ *<4>
|
|
||||||
1 | └:post nil
|
|
||||||
1 | └\ *<5>
|
|
||||||
2 ├about-us\ *<6>
|
|
||||||
1 | └team\ *<7>
|
|
||||||
1 └contact\ *<8>
|
|
||||||
```
|
|
||||||
Every `*<num>` represents the memory address of a handler function (a pointer).
|
|
||||||
If you follow a path trough the tree from the root to the leaf, you get the
|
|
||||||
complete route path, e.g `\blog\:post\`, where `:post` is just a placeholder
|
|
||||||
([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a
|
|
||||||
tree structure also allows us to use dynamic parts like the `:post` parameter,
|
|
||||||
since we actually match against the routing patterns instead of just comparing
|
|
||||||
hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark),
|
|
||||||
this works very well and efficient.
|
|
||||||
|
|
||||||
Since URL paths have a hierarchical structure and make use only of a limited set
|
|
||||||
of characters (byte values), it is very likely that there are a lot of common
|
|
||||||
prefixes. This allows us to easily reduce the routing into ever smaller problems.
|
|
||||||
Moreover the router manages a separate tree for every request method.
|
|
||||||
For one thing it is more space efficient than holding a method->handle map in
|
|
||||||
every single node, for another thing is also allows us to greatly reduce the
|
|
||||||
routing problem before even starting the look-up in the prefix-tree.
|
|
||||||
|
|
||||||
For even better scalability, the child nodes on each tree level are ordered by
|
|
||||||
priority, where the priority is just the number of handles registered in sub
|
|
||||||
nodes (children, grandchildren, and so on..).
|
|
||||||
This helps in two ways:
|
|
||||||
|
|
||||||
1. Nodes which are part of the most routing paths are evaluated first. This
|
|
||||||
helps to make as much routes as possible to be reachable as fast as possible.
|
|
||||||
2. It is some sort of cost compensation. The longest reachable path (highest
|
|
||||||
cost) can always be evaluated first. The following scheme visualizes the tree
|
|
||||||
structure. Nodes are evaluated from top to bottom and from left to right.
|
|
||||||
|
|
||||||
```
|
|
||||||
├------------
|
|
||||||
├---------
|
|
||||||
├-----
|
|
||||||
├----
|
|
||||||
├--
|
|
||||||
├--
|
|
||||||
└-
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Why doesn't this work with http.Handler?
|
|
||||||
**It does!** The router itself implements the http.Handler interface.
|
|
||||||
Moreover the router provides convenient [adapters for http.Handler](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handler)s and [http.HandlerFunc](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandlerFunc)s
|
|
||||||
which allows them to be used as a [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) when registering a route.
|
|
||||||
The only disadvantage is, that no parameter values can be retrieved when a
|
|
||||||
http.Handler or http.HandlerFunc is used, since there is no efficient way to
|
|
||||||
pass the values with the existing function parameters.
|
|
||||||
Therefore [httprouter.Handle](http://godoc.org/github.com/julienschmidt/httprouter#Router.Handle) has a third function parameter.
|
|
||||||
|
|
||||||
Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.
|
|
||||||
|
|
||||||
|
|
||||||
## Where can I find Middleware *X*?
|
|
||||||
This package just provides a very efficient request router with a few extra
|
|
||||||
features. The router is just a [http.Handler](http://golang.org/pkg/net/http/#Handler),
|
|
||||||
you can chain any http.Handler compatible middleware before the router,
|
|
||||||
for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers).
|
|
||||||
Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/),
|
|
||||||
it's very easy!
|
|
||||||
|
|
||||||
Alternatively, you could try [a web framework based on HttpRouter](#web-frameworks-based-on-httprouter).
|
|
||||||
|
|
||||||
### Multi-domain / Sub-domains
|
|
||||||
Here is a quick example: Does your server serve multiple domains / hosts?
|
|
||||||
You want to use sub-domains?
|
|
||||||
Define a router per host!
|
|
||||||
```go
|
|
||||||
// We need an object that implements the http.Handler interface.
|
|
||||||
// Therefore we need a type for which we implement the ServeHTTP method.
|
|
||||||
// We just use a map here, in which we map host names (with port) to http.Handlers
|
|
||||||
type HostSwitch map[string]http.Handler
|
|
||||||
|
|
||||||
// Implement the ServerHTTP method on our new type
|
|
||||||
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Check if a http.Handler is registered for the given host.
|
|
||||||
// If yes, use it to handle the request.
|
|
||||||
if handler := hs[r.Host]; handler != nil {
|
|
||||||
handler.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
// Handle host names for wich no handler is registered
|
|
||||||
http.Error(w, "Forbidden", 403) // Or Redirect?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Initialize a router as usual
|
|
||||||
router := httprouter.New()
|
|
||||||
router.GET("/", Index)
|
|
||||||
router.GET("/hello/:name", Hello)
|
|
||||||
|
|
||||||
// Make a new HostSwitch and insert the router (our http handler)
|
|
||||||
// for example.com and port 12345
|
|
||||||
hs := make(HostSwitch)
|
|
||||||
hs["example.com:12345"] = router
|
|
||||||
|
|
||||||
// Use the HostSwitch to listen and serve on port 12345
|
|
||||||
log.Fatal(http.ListenAndServe(":12345", hs))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Basic Authentication
|
|
||||||
Another quick example: Basic Authentification (RFC 2617) for handles:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
"net/http"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BasicAuth(h httprouter.Handle, user, pass []byte) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
const basicAuthPrefix string = "Basic "
|
|
||||||
|
|
||||||
// Get the Basic Authentication credentials
|
|
||||||
auth := r.Header.Get("Authorization")
|
|
||||||
if strings.HasPrefix(auth, basicAuthPrefix) {
|
|
||||||
// Check credentials
|
|
||||||
payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):])
|
|
||||||
if err == nil {
|
|
||||||
pair := bytes.SplitN(payload, []byte(":"), 2)
|
|
||||||
if len(pair) == 2 &&
|
|
||||||
bytes.Equal(pair[0], user) &&
|
|
||||||
bytes.Equal(pair[1], pass) {
|
|
||||||
|
|
||||||
// Delegate request to the given handle
|
|
||||||
h(w, r, ps)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request Basic Authentication otherwise
|
|
||||||
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
||||||
fmt.Fprint(w, "Not protected!\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
||||||
fmt.Fprint(w, "Protected!\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
user := []byte("gordon")
|
|
||||||
pass := []byte("secret!")
|
|
||||||
|
|
||||||
router := httprouter.New()
|
|
||||||
router.GET("/", Index)
|
|
||||||
router.GET("/protected/", BasicAuth(Protected, user, pass))
|
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", router))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chaining with the NotFound handler
|
|
||||||
|
|
||||||
**NOTE: It might be required to set [Router.HandleMethodNotAllowed](http://godoc.org/github.com/julienschmidt/httprouter#Router.HandleMethodNotAllowed) to `false` to avoid problems.**
|
|
||||||
|
|
||||||
You can use another [http.HandlerFunc](http://golang.org/pkg/net/http/#HandlerFunc), for example another router, to handle requests which could not be matched by this router by using the [Router.NotFound](http://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) handler. This allows chaining.
|
|
||||||
|
|
||||||
### Static files
|
|
||||||
The `NotFound` handler can for example be used to serve static files from the root path `/` (like an index.html file along with other assets):
|
|
||||||
```go
|
|
||||||
// Serve static files from the ./public directory
|
|
||||||
router.NotFound = http.FileServer(http.Dir("public")).ServeHTTP
|
|
||||||
```
|
|
||||||
|
|
||||||
But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`.
|
|
||||||
|
|
||||||
## Web Frameworks based on HttpRouter
|
|
||||||
If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:
|
|
||||||
* [Ace](https://github.com/plimble/ace): Blazing fast Go Web Framework
|
|
||||||
* [api2go](https://github.com/univedo/api2go): A JSON API Implementation for Go
|
|
||||||
* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance
|
|
||||||
* [Goat](https://github.com/bahlo/goat): A minimalistic REST API server in Go
|
|
||||||
* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine
|
|
||||||
* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow
|
|
||||||
* [kami](https://github.com/guregu/kami): A tiny web framework using x/net/context
|
|
||||||
* [Medeina](https://github.com/imdario/medeina): Inspired by Ruby's Roda and Cuba
|
|
||||||
* [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang
|
|
||||||
* [Roxanna](https://github.com/iamthemuffinman/Roxanna): An amalgamation of httprouter, better logging, and hot reload
|
|
||||||
* [siesta](https://github.com/VividCortex/siesta): Composable HTTP handlers with contexts
|
|
123
Godeps/_workspace/src/github.com/julienschmidt/httprouter/path.go
generated
vendored
123
Godeps/_workspace/src/github.com/julienschmidt/httprouter/path.go
generated
vendored
|
@ -1,123 +0,0 @@
|
||||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
|
||||||
// Based on the path package, Copyright 2009 The Go Authors.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be found
|
|
||||||
// in the LICENSE file.
|
|
||||||
|
|
||||||
package httprouter
|
|
||||||
|
|
||||||
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
|
|
||||||
// for p, eliminating . and .. elements.
|
|
||||||
//
|
|
||||||
// The following rules are applied iteratively until no further processing can
|
|
||||||
// be done:
|
|
||||||
// 1. Replace multiple slashes with a single slash.
|
|
||||||
// 2. Eliminate each . path name element (the current directory).
|
|
||||||
// 3. Eliminate each inner .. path name element (the parent directory)
|
|
||||||
// along with the non-.. element that precedes it.
|
|
||||||
// 4. Eliminate .. elements that begin a rooted path:
|
|
||||||
// that is, replace "/.." by "/" at the beginning of a path.
|
|
||||||
//
|
|
||||||
// If the result of this process is an empty string, "/" is returned
|
|
||||||
func CleanPath(p string) string {
|
|
||||||
// Turn empty string into "/"
|
|
||||||
if p == "" {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
n := len(p)
|
|
||||||
var buf []byte
|
|
||||||
|
|
||||||
// Invariants:
|
|
||||||
// reading from path; r is index of next byte to process.
|
|
||||||
// writing to buf; w is index of next byte to write.
|
|
||||||
|
|
||||||
// path must start with '/'
|
|
||||||
r := 1
|
|
||||||
w := 1
|
|
||||||
|
|
||||||
if p[0] != '/' {
|
|
||||||
r = 0
|
|
||||||
buf = make([]byte, n+1)
|
|
||||||
buf[0] = '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
trailing := n > 2 && p[n-1] == '/'
|
|
||||||
|
|
||||||
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
|
||||||
// gets completely inlined (bufApp). So in contrast to the path package this
|
|
||||||
// loop has no expensive function calls (except 1x make)
|
|
||||||
|
|
||||||
for r < n {
|
|
||||||
switch {
|
|
||||||
case p[r] == '/':
|
|
||||||
// empty path element, trailing slash is added after the end
|
|
||||||
r++
|
|
||||||
|
|
||||||
case p[r] == '.' && r+1 == n:
|
|
||||||
trailing = true
|
|
||||||
r++
|
|
||||||
|
|
||||||
case p[r] == '.' && p[r+1] == '/':
|
|
||||||
// . element
|
|
||||||
r++
|
|
||||||
|
|
||||||
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
|
||||||
// .. element: remove to last /
|
|
||||||
r += 2
|
|
||||||
|
|
||||||
if w > 1 {
|
|
||||||
// can backtrack
|
|
||||||
w--
|
|
||||||
|
|
||||||
if buf == nil {
|
|
||||||
for w > 1 && p[w] != '/' {
|
|
||||||
w--
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for w > 1 && buf[w] != '/' {
|
|
||||||
w--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// real path element.
|
|
||||||
// add slash if needed
|
|
||||||
if w > 1 {
|
|
||||||
bufApp(&buf, p, w, '/')
|
|
||||||
w++
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy element
|
|
||||||
for r < n && p[r] != '/' {
|
|
||||||
bufApp(&buf, p, w, p[r])
|
|
||||||
w++
|
|
||||||
r++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-append trailing slash
|
|
||||||
if trailing && w > 1 {
|
|
||||||
bufApp(&buf, p, w, '/')
|
|
||||||
w++
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf == nil {
|
|
||||||
return p[:w]
|
|
||||||
}
|
|
||||||
return string(buf[:w])
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal helper to lazily create a buffer if necessary
|
|
||||||
func bufApp(buf *[]byte, s string, w int, c byte) {
|
|
||||||
if *buf == nil {
|
|
||||||
if s[w] == c {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
*buf = make([]byte, len(s))
|
|
||||||
copy(*buf, s[:w])
|
|
||||||
}
|
|
||||||
(*buf)[w] = c
|
|
||||||
}
|
|
92
Godeps/_workspace/src/github.com/julienschmidt/httprouter/path_test.go
generated
vendored
92
Godeps/_workspace/src/github.com/julienschmidt/httprouter/path_test.go
generated
vendored
|
@ -1,92 +0,0 @@
|
||||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
|
||||||
// Based on the path package, Copyright 2009 The Go Authors.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be found
|
|
||||||
// in the LICENSE file.
|
|
||||||
|
|
||||||
package httprouter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cleanTests = []struct {
|
|
||||||
path, result string
|
|
||||||
}{
|
|
||||||
// Already clean
|
|
||||||
{"/", "/"},
|
|
||||||
{"/abc", "/abc"},
|
|
||||||
{"/a/b/c", "/a/b/c"},
|
|
||||||
{"/abc/", "/abc/"},
|
|
||||||
{"/a/b/c/", "/a/b/c/"},
|
|
||||||
|
|
||||||
// missing root
|
|
||||||
{"", "/"},
|
|
||||||
{"abc", "/abc"},
|
|
||||||
{"abc/def", "/abc/def"},
|
|
||||||
{"a/b/c", "/a/b/c"},
|
|
||||||
|
|
||||||
// Remove doubled slash
|
|
||||||
{"//", "/"},
|
|
||||||
{"/abc//", "/abc/"},
|
|
||||||
{"/abc/def//", "/abc/def/"},
|
|
||||||
{"/a/b/c//", "/a/b/c/"},
|
|
||||||
{"/abc//def//ghi", "/abc/def/ghi"},
|
|
||||||
{"//abc", "/abc"},
|
|
||||||
{"///abc", "/abc"},
|
|
||||||
{"//abc//", "/abc/"},
|
|
||||||
|
|
||||||
// Remove . elements
|
|
||||||
{".", "/"},
|
|
||||||
{"./", "/"},
|
|
||||||
{"/abc/./def", "/abc/def"},
|
|
||||||
{"/./abc/def", "/abc/def"},
|
|
||||||
{"/abc/.", "/abc/"},
|
|
||||||
|
|
||||||
// Remove .. elements
|
|
||||||
{"..", "/"},
|
|
||||||
{"../", "/"},
|
|
||||||
{"../../", "/"},
|
|
||||||
{"../..", "/"},
|
|
||||||
{"../../abc", "/abc"},
|
|
||||||
{"/abc/def/ghi/../jkl", "/abc/def/jkl"},
|
|
||||||
{"/abc/def/../ghi/../jkl", "/abc/jkl"},
|
|
||||||
{"/abc/def/..", "/abc"},
|
|
||||||
{"/abc/def/../..", "/"},
|
|
||||||
{"/abc/def/../../..", "/"},
|
|
||||||
{"/abc/def/../../..", "/"},
|
|
||||||
{"/abc/def/../../../ghi/jkl/../../../mno", "/mno"},
|
|
||||||
|
|
||||||
// Combinations
|
|
||||||
{"abc/./../def", "/def"},
|
|
||||||
{"abc//./../def", "/def"},
|
|
||||||
{"abc/../../././../def", "/def"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathClean(t *testing.T) {
|
|
||||||
for _, test := range cleanTests {
|
|
||||||
if s := CleanPath(test.path); s != test.result {
|
|
||||||
t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result)
|
|
||||||
}
|
|
||||||
if s := CleanPath(test.result); s != test.result {
|
|
||||||
t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathCleanMallocs(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping malloc count in short mode")
|
|
||||||
}
|
|
||||||
if runtime.GOMAXPROCS(0) > 1 {
|
|
||||||
t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range cleanTests {
|
|
||||||
allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) })
|
|
||||||
if allocs > 0 {
|
|
||||||
t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
363
Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go
generated
vendored
363
Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go
generated
vendored
|
@ -1,363 +0,0 @@
|
||||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be found
|
|
||||||
// in the LICENSE file.
|
|
||||||
|
|
||||||
// Package httprouter is a trie based high performance HTTP request router.
|
|
||||||
//
|
|
||||||
// A trivial example is:
|
|
||||||
//
|
|
||||||
// package main
|
|
||||||
//
|
|
||||||
// import (
|
|
||||||
// "fmt"
|
|
||||||
// "github.com/julienschmidt/httprouter"
|
|
||||||
// "net/http"
|
|
||||||
// "log"
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
||||||
// fmt.Fprint(w, "Welcome!\n")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
// fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func main() {
|
|
||||||
// router := httprouter.New()
|
|
||||||
// router.GET("/", Index)
|
|
||||||
// router.GET("/hello/:name", Hello)
|
|
||||||
//
|
|
||||||
// log.Fatal(http.ListenAndServe(":8080", router))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The router matches incoming requests by the request method and the path.
|
|
||||||
// If a handle is registered for this path and method, the router delegates the
|
|
||||||
// request to that function.
|
|
||||||
// For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to
|
|
||||||
// register handles, for all other methods router.Handle can be used.
|
|
||||||
//
|
|
||||||
// The registered path, against which the router matches incoming requests, can
|
|
||||||
// contain two types of parameters:
|
|
||||||
// Syntax Type
|
|
||||||
// :name named parameter
|
|
||||||
// *name catch-all parameter
|
|
||||||
//
|
|
||||||
// Named parameters are dynamic path segments. They match anything until the
|
|
||||||
// next '/' or the path end:
|
|
||||||
// Path: /blog/:category/:post
|
|
||||||
//
|
|
||||||
// Requests:
|
|
||||||
// /blog/go/request-routers match: category="go", post="request-routers"
|
|
||||||
// /blog/go/request-routers/ no match, but the router would redirect
|
|
||||||
// /blog/go/ no match
|
|
||||||
// /blog/go/request-routers/comments no match
|
|
||||||
//
|
|
||||||
// Catch-all parameters match anything until the path end, including the
|
|
||||||
// directory index (the '/' before the catch-all). Since they match anything
|
|
||||||
// until the end, catch-all paramerters must always be the final path element.
|
|
||||||
// Path: /files/*filepath
|
|
||||||
//
|
|
||||||
// Requests:
|
|
||||||
// /files/ match: filepath="/"
|
|
||||||
// /files/LICENSE match: filepath="/LICENSE"
|
|
||||||
// /files/templates/article.html match: filepath="/templates/article.html"
|
|
||||||
// /files no match, but the router would redirect
|
|
||||||
//
|
|
||||||
// The value of parameters is saved as a slice of the Param struct, consisting
|
|
||||||
// each of a key and a value. The slice is passed to the Handle func as a third
|
|
||||||
// parameter.
|
|
||||||
// There are two ways to retrieve the value of a parameter:
|
|
||||||
// // by the name of the parameter
|
|
||||||
// user := ps.ByName("user") // defined by :user or *user
|
|
||||||
//
|
|
||||||
// // by the index of the parameter. This way you can also get the name (key)
|
|
||||||
// thirdKey := ps[2].Key // the name of the 3rd parameter
|
|
||||||
// thirdValue := ps[2].Value // the value of the 3rd parameter
|
|
||||||
package httprouter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handle is a function that can be registered to a route to handle HTTP
|
|
||||||
// requests. Like http.HandlerFunc, but has a third parameter for the values of
|
|
||||||
// wildcards (variables).
|
|
||||||
type Handle func(http.ResponseWriter, *http.Request, Params)
|
|
||||||
|
|
||||||
// Param is a single URL parameter, consisting of a key and a value.
|
|
||||||
type Param struct {
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Params is a Param-slice, as returned by the router.
|
|
||||||
// The slice is ordered, the first URL parameter is also the first slice value.
|
|
||||||
// It is therefore safe to read values by the index.
|
|
||||||
type Params []Param
|
|
||||||
|
|
||||||
// ByName returns the value of the first Param which key matches the given name.
|
|
||||||
// If no matching Param is found, an empty string is returned.
|
|
||||||
func (ps Params) ByName(name string) string {
|
|
||||||
for i := range ps {
|
|
||||||
if ps[i].Key == name {
|
|
||||||
return ps[i].Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Router is a http.Handler which can be used to dispatch requests to different
|
|
||||||
// handler functions via configurable routes
|
|
||||||
type Router struct {
|
|
||||||
trees map[string]*node
|
|
||||||
|
|
||||||
// Enables automatic redirection if the current route can't be matched but a
|
|
||||||
// handler for the path with (without) the trailing slash exists.
|
|
||||||
// For example if /foo/ is requested but a route only exists for /foo, the
|
|
||||||
// client is redirected to /foo with http status code 301 for GET requests
|
|
||||||
// and 307 for all other request methods.
|
|
||||||
RedirectTrailingSlash bool
|
|
||||||
|
|
||||||
// If enabled, the router tries to fix the current request path, if no
|
|
||||||
// handle is registered for it.
|
|
||||||
// First superfluous path elements like ../ or // are removed.
|
|
||||||
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
|
||||||
// If a handle can be found for this route, the router makes a redirection
|
|
||||||
// to the corrected path with status code 301 for GET requests and 307 for
|
|
||||||
// all other request methods.
|
|
||||||
// For example /FOO and /..//Foo could be redirected to /foo.
|
|
||||||
// RedirectTrailingSlash is independent of this option.
|
|
||||||
RedirectFixedPath bool
|
|
||||||
|
|
||||||
// If enabled, the router checks if another method is allowed for the
|
|
||||||
// current route, if the current request can not be routed.
|
|
||||||
// If this is the case, the request is answered with 'Method Not Allowed'
|
|
||||||
// and HTTP status code 405.
|
|
||||||
// If no other Method is allowed, the request is delegated to the NotFound
|
|
||||||
// handler.
|
|
||||||
HandleMethodNotAllowed bool
|
|
||||||
|
|
||||||
// Configurable http.HandlerFunc which is called when no matching route is
|
|
||||||
// found. If it is not set, http.NotFound is used.
|
|
||||||
NotFound http.HandlerFunc
|
|
||||||
|
|
||||||
// Configurable http.HandlerFunc which is called when a request
|
|
||||||
// cannot be routed and HandleMethodNotAllowed is true.
|
|
||||||
// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
|
|
||||||
MethodNotAllowed http.HandlerFunc
|
|
||||||
|
|
||||||
// Function to handle panics recovered from http handlers.
|
|
||||||
// It should be used to generate a error page and return the http error code
|
|
||||||
// 500 (Internal Server Error).
|
|
||||||
// The handler can be used to keep your server from crashing because of
|
|
||||||
// unrecovered panics.
|
|
||||||
PanicHandler func(http.ResponseWriter, *http.Request, interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the Router conforms with the http.Handler interface
|
|
||||||
var _ http.Handler = New()
|
|
||||||
|
|
||||||
// New returns a new initialized Router.
|
|
||||||
// Path auto-correction, including trailing slashes, is enabled by default.
|
|
||||||
func New() *Router {
|
|
||||||
return &Router{
|
|
||||||
RedirectTrailingSlash: true,
|
|
||||||
RedirectFixedPath: true,
|
|
||||||
HandleMethodNotAllowed: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET is a shortcut for router.Handle("GET", path, handle)
|
|
||||||
func (r *Router) GET(path string, handle Handle) {
|
|
||||||
r.Handle("GET", path, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
|
|
||||||
func (r *Router) HEAD(path string, handle Handle) {
|
|
||||||
r.Handle("HEAD", path, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
|
|
||||||
func (r *Router) OPTIONS(path string, handle Handle) {
|
|
||||||
r.Handle("OPTIONS", path, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST is a shortcut for router.Handle("POST", path, handle)
|
|
||||||
func (r *Router) POST(path string, handle Handle) {
|
|
||||||
r.Handle("POST", path, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT is a shortcut for router.Handle("PUT", path, handle)
|
|
||||||
func (r *Router) PUT(path string, handle Handle) {
|
|
||||||
r.Handle("PUT", path, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
|
|
||||||
func (r *Router) PATCH(path string, handle Handle) {
|
|
||||||
r.Handle("PATCH", path, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
|
|
||||||
func (r *Router) DELETE(path string, handle Handle) {
|
|
||||||
r.Handle("DELETE", path, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle registers a new request handle with the given path and method.
|
|
||||||
//
|
|
||||||
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
|
||||||
// functions can be used.
|
|
||||||
//
|
|
||||||
// This function is intended for bulk loading and to allow the usage of less
|
|
||||||
// frequently used, non-standardized or custom methods (e.g. for internal
|
|
||||||
// communication with a proxy).
|
|
||||||
func (r *Router) Handle(method, path string, handle Handle) {
|
|
||||||
if path[0] != '/' {
|
|
||||||
panic("path must begin with '/' in path '" + path + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.trees == nil {
|
|
||||||
r.trees = make(map[string]*node)
|
|
||||||
}
|
|
||||||
|
|
||||||
root := r.trees[method]
|
|
||||||
if root == nil {
|
|
||||||
root = new(node)
|
|
||||||
r.trees[method] = root
|
|
||||||
}
|
|
||||||
|
|
||||||
root.addRoute(path, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler is an adapter which allows the usage of an http.Handler as a
|
|
||||||
// request handle.
|
|
||||||
func (r *Router) Handler(method, path string, handler http.Handler) {
|
|
||||||
r.Handle(method, path,
|
|
||||||
func(w http.ResponseWriter, req *http.Request, _ Params) {
|
|
||||||
handler.ServeHTTP(w, req)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
|
|
||||||
// request handle.
|
|
||||||
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
|
|
||||||
r.Handler(method, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeFiles serves files from the given file system root.
|
|
||||||
// The path must end with "/*filepath", files are then served from the local
|
|
||||||
// path /defined/root/dir/*filepath.
|
|
||||||
// For example if root is "/etc" and *filepath is "passwd", the local file
|
|
||||||
// "/etc/passwd" would be served.
|
|
||||||
// Internally a http.FileServer is used, therefore http.NotFound is used instead
|
|
||||||
// of the Router's NotFound handler.
|
|
||||||
// To use the operating system's file system implementation,
|
|
||||||
// use http.Dir:
|
|
||||||
// router.ServeFiles("/src/*filepath", http.Dir("/var/www"))
|
|
||||||
func (r *Router) ServeFiles(path string, root http.FileSystem) {
|
|
||||||
if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
|
|
||||||
panic("path must end with /*filepath in path '" + path + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
fileServer := http.FileServer(root)
|
|
||||||
|
|
||||||
r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) {
|
|
||||||
req.URL.Path = ps.ByName("filepath")
|
|
||||||
fileServer.ServeHTTP(w, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
|
|
||||||
if rcv := recover(); rcv != nil {
|
|
||||||
r.PanicHandler(w, req, rcv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup allows the manual lookup of a method + path combo.
|
|
||||||
// This is e.g. useful to build a framework around this router.
|
|
||||||
// If the path was found, it returns the handle function and the path parameter
|
|
||||||
// values. Otherwise the third return value indicates whether a redirection to
|
|
||||||
// the same path with an extra / without the trailing slash should be performed.
|
|
||||||
func (r *Router) Lookup(method, path string) (Handle, Params, bool) {
|
|
||||||
if root := r.trees[method]; root != nil {
|
|
||||||
return root.getValue(path)
|
|
||||||
}
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP makes the router implement the http.Handler interface.
|
|
||||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
if r.PanicHandler != nil {
|
|
||||||
defer r.recv(w, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
if root := r.trees[req.Method]; root != nil {
|
|
||||||
path := req.URL.Path
|
|
||||||
|
|
||||||
if handle, ps, tsr := root.getValue(path); handle != nil {
|
|
||||||
handle(w, req, ps)
|
|
||||||
return
|
|
||||||
} else if req.Method != "CONNECT" && path != "/" {
|
|
||||||
code := 301 // Permanent redirect, request with GET method
|
|
||||||
if req.Method != "GET" {
|
|
||||||
// Temporary redirect, request with same method
|
|
||||||
// As of Go 1.3, Go does not support status code 308.
|
|
||||||
code = 307
|
|
||||||
}
|
|
||||||
|
|
||||||
if tsr && r.RedirectTrailingSlash {
|
|
||||||
if len(path) > 1 && path[len(path)-1] == '/' {
|
|
||||||
req.URL.Path = path[:len(path)-1]
|
|
||||||
} else {
|
|
||||||
req.URL.Path = path + "/"
|
|
||||||
}
|
|
||||||
http.Redirect(w, req, req.URL.String(), code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to fix the request path
|
|
||||||
if r.RedirectFixedPath {
|
|
||||||
fixedPath, found := root.findCaseInsensitivePath(
|
|
||||||
CleanPath(path),
|
|
||||||
r.RedirectTrailingSlash,
|
|
||||||
)
|
|
||||||
if found {
|
|
||||||
req.URL.Path = string(fixedPath)
|
|
||||||
http.Redirect(w, req, req.URL.String(), code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle 405
|
|
||||||
if r.HandleMethodNotAllowed {
|
|
||||||
for method := range r.trees {
|
|
||||||
// Skip the requested method - we already tried this one
|
|
||||||
if method == req.Method {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
handle, _, _ := r.trees[method].getValue(req.URL.Path)
|
|
||||||
if handle != nil {
|
|
||||||
if r.MethodNotAllowed != nil {
|
|
||||||
r.MethodNotAllowed(w, req)
|
|
||||||
} else {
|
|
||||||
http.Error(w,
|
|
||||||
http.StatusText(http.StatusMethodNotAllowed),
|
|
||||||
http.StatusMethodNotAllowed,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle 404
|
|
||||||
if r.NotFound != nil {
|
|
||||||
r.NotFound(w, req)
|
|
||||||
} else {
|
|
||||||
http.NotFound(w, req)
|
|
||||||
}
|
|
||||||
}
|
|
378
Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go
generated
vendored
378
Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go
generated
vendored
|
@ -1,378 +0,0 @@
|
||||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be found
|
|
||||||
// in the LICENSE file.
|
|
||||||
|
|
||||||
package httprouter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockResponseWriter struct{}
|
|
||||||
|
|
||||||
func (m *mockResponseWriter) Header() (h http.Header) {
|
|
||||||
return http.Header{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockResponseWriter) Write(p []byte) (n int, err error) {
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockResponseWriter) WriteString(s string) (n int, err error) {
|
|
||||||
return len(s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockResponseWriter) WriteHeader(int) {}
|
|
||||||
|
|
||||||
func TestParams(t *testing.T) {
|
|
||||||
ps := Params{
|
|
||||||
Param{"param1", "value1"},
|
|
||||||
Param{"param2", "value2"},
|
|
||||||
Param{"param3", "value3"},
|
|
||||||
}
|
|
||||||
for i := range ps {
|
|
||||||
if val := ps.ByName(ps[i].Key); val != ps[i].Value {
|
|
||||||
t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if val := ps.ByName("noKey"); val != "" {
|
|
||||||
t.Errorf("Expected empty string for not found key; got: %s", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouter(t *testing.T) {
|
|
||||||
router := New()
|
|
||||||
|
|
||||||
routed := false
|
|
||||||
router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) {
|
|
||||||
routed = true
|
|
||||||
want := Params{Param{"name", "gopher"}}
|
|
||||||
if !reflect.DeepEqual(ps, want) {
|
|
||||||
t.Fatalf("wrong wildcard values: want %v, got %v", want, ps)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
w := new(mockResponseWriter)
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/user/gopher", nil)
|
|
||||||
router.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if !routed {
|
|
||||||
t.Fatal("routing failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type handlerStruct struct {
|
|
||||||
handeled *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
*h.handeled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterAPI(t *testing.T) {
|
|
||||||
var get, head, options, post, put, patch, delete, handler, handlerFunc bool
|
|
||||||
|
|
||||||
httpHandler := handlerStruct{&handler}
|
|
||||||
|
|
||||||
router := New()
|
|
||||||
router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
|
||||||
get = true
|
|
||||||
})
|
|
||||||
router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
|
||||||
head = true
|
|
||||||
})
|
|
||||||
router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
|
||||||
options = true
|
|
||||||
})
|
|
||||||
router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
|
||||||
post = true
|
|
||||||
})
|
|
||||||
router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
|
||||||
put = true
|
|
||||||
})
|
|
||||||
router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
|
||||||
patch = true
|
|
||||||
})
|
|
||||||
router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
|
||||||
delete = true
|
|
||||||
})
|
|
||||||
router.Handler("GET", "/Handler", httpHandler)
|
|
||||||
router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
handlerFunc = true
|
|
||||||
})
|
|
||||||
|
|
||||||
w := new(mockResponseWriter)
|
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", "/GET", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !get {
|
|
||||||
t.Error("routing GET failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ = http.NewRequest("HEAD", "/GET", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !head {
|
|
||||||
t.Error("routing HEAD failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ = http.NewRequest("OPTIONS", "/GET", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !options {
|
|
||||||
t.Error("routing OPTIONS failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ = http.NewRequest("POST", "/POST", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !post {
|
|
||||||
t.Error("routing POST failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ = http.NewRequest("PUT", "/PUT", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !put {
|
|
||||||
t.Error("routing PUT failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ = http.NewRequest("PATCH", "/PATCH", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !patch {
|
|
||||||
t.Error("routing PATCH failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ = http.NewRequest("DELETE", "/DELETE", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !delete {
|
|
||||||
t.Error("routing DELETE failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ = http.NewRequest("GET", "/Handler", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !handler {
|
|
||||||
t.Error("routing Handler failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ = http.NewRequest("GET", "/HandlerFunc", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !handlerFunc {
|
|
||||||
t.Error("routing HandlerFunc failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterRoot(t *testing.T) {
|
|
||||||
router := New()
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
router.GET("noSlashRoot", nil)
|
|
||||||
})
|
|
||||||
if recv == nil {
|
|
||||||
t.Fatal("registering path not beginning with '/' did not panic")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterNotAllowed(t *testing.T) {
|
|
||||||
handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
|
|
||||||
|
|
||||||
router := New()
|
|
||||||
router.POST("/path", handlerFunc)
|
|
||||||
|
|
||||||
// Test not allowed
|
|
||||||
r, _ := http.NewRequest("GET", "/path", nil)
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !(w.Code == http.StatusMethodNotAllowed) {
|
|
||||||
t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
|
|
||||||
}
|
|
||||||
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
responseText := "custom method"
|
|
||||||
router.MethodNotAllowed = func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusTeapot)
|
|
||||||
w.Write([]byte(responseText))
|
|
||||||
}
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if got := w.Body.String(); !(got == responseText) {
|
|
||||||
t.Errorf("unexpected response got %q want %q", got, responseText)
|
|
||||||
}
|
|
||||||
if w.Code != http.StatusTeapot {
|
|
||||||
t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterNotFound(t *testing.T) {
|
|
||||||
handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
|
|
||||||
|
|
||||||
router := New()
|
|
||||||
router.GET("/path", handlerFunc)
|
|
||||||
router.GET("/dir/", handlerFunc)
|
|
||||||
router.GET("/", handlerFunc)
|
|
||||||
|
|
||||||
testRoutes := []struct {
|
|
||||||
route string
|
|
||||||
code int
|
|
||||||
header string
|
|
||||||
}{
|
|
||||||
{"/path/", 301, "map[Location:[/path]]"}, // TSR -/
|
|
||||||
{"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/
|
|
||||||
{"", 301, "map[Location:[/]]"}, // TSR +/
|
|
||||||
{"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case
|
|
||||||
{"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case
|
|
||||||
{"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/
|
|
||||||
{"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/
|
|
||||||
{"/../path", 301, "map[Location:[/path]]"}, // CleanPath
|
|
||||||
{"/nope", 404, ""}, // NotFound
|
|
||||||
}
|
|
||||||
for _, tr := range testRoutes {
|
|
||||||
r, _ := http.NewRequest("GET", tr.route, nil)
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header()) == tr.header)) {
|
|
||||||
t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test custom not found handler
|
|
||||||
var notFound bool
|
|
||||||
router.NotFound = func(rw http.ResponseWriter, r *http.Request) {
|
|
||||||
rw.WriteHeader(404)
|
|
||||||
notFound = true
|
|
||||||
}
|
|
||||||
r, _ := http.NewRequest("GET", "/nope", nil)
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !(w.Code == 404 && notFound == true) {
|
|
||||||
t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test other method than GET (want 307 instead of 301)
|
|
||||||
router.PATCH("/path", handlerFunc)
|
|
||||||
r, _ = http.NewRequest("PATCH", "/path/", nil)
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
|
|
||||||
t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test special case where no node for the prefix "/" exists
|
|
||||||
router = New()
|
|
||||||
router.GET("/a", handlerFunc)
|
|
||||||
r, _ = http.NewRequest("GET", "/", nil)
|
|
||||||
w = httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !(w.Code == 404) {
|
|
||||||
t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterPanicHandler(t *testing.T) {
|
|
||||||
router := New()
|
|
||||||
panicHandled := false
|
|
||||||
|
|
||||||
router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) {
|
|
||||||
panicHandled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) {
|
|
||||||
panic("oops!")
|
|
||||||
})
|
|
||||||
|
|
||||||
w := new(mockResponseWriter)
|
|
||||||
req, _ := http.NewRequest("PUT", "/user/gopher", nil)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if rcv := recover(); rcv != nil {
|
|
||||||
t.Fatal("handling panic failed")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
router.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if !panicHandled {
|
|
||||||
t.Fatal("simulating failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterLookup(t *testing.T) {
|
|
||||||
routed := false
|
|
||||||
wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {
|
|
||||||
routed = true
|
|
||||||
}
|
|
||||||
wantParams := Params{Param{"name", "gopher"}}
|
|
||||||
|
|
||||||
router := New()
|
|
||||||
|
|
||||||
// try empty router first
|
|
||||||
handle, _, tsr := router.Lookup("GET", "/nope")
|
|
||||||
if handle != nil {
|
|
||||||
t.Fatalf("Got handle for unregistered pattern: %v", handle)
|
|
||||||
}
|
|
||||||
if tsr {
|
|
||||||
t.Error("Got wrong TSR recommendation!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert route and try again
|
|
||||||
router.GET("/user/:name", wantHandle)
|
|
||||||
|
|
||||||
handle, params, tsr := router.Lookup("GET", "/user/gopher")
|
|
||||||
if handle == nil {
|
|
||||||
t.Fatal("Got no handle!")
|
|
||||||
} else {
|
|
||||||
handle(nil, nil, nil)
|
|
||||||
if !routed {
|
|
||||||
t.Fatal("Routing failed!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(params, wantParams) {
|
|
||||||
t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
handle, _, tsr = router.Lookup("GET", "/user/gopher/")
|
|
||||||
if handle != nil {
|
|
||||||
t.Fatalf("Got handle for unregistered pattern: %v", handle)
|
|
||||||
}
|
|
||||||
if !tsr {
|
|
||||||
t.Error("Got no TSR recommendation!")
|
|
||||||
}
|
|
||||||
|
|
||||||
handle, _, tsr = router.Lookup("GET", "/nope")
|
|
||||||
if handle != nil {
|
|
||||||
t.Fatalf("Got handle for unregistered pattern: %v", handle)
|
|
||||||
}
|
|
||||||
if tsr {
|
|
||||||
t.Error("Got wrong TSR recommendation!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockFileSystem struct {
|
|
||||||
opened bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mfs *mockFileSystem) Open(name string) (http.File, error) {
|
|
||||||
mfs.opened = true
|
|
||||||
return nil, errors.New("this is just a mock")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouterServeFiles(t *testing.T) {
|
|
||||||
router := New()
|
|
||||||
mfs := &mockFileSystem{}
|
|
||||||
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
router.ServeFiles("/noFilepath", mfs)
|
|
||||||
})
|
|
||||||
if recv == nil {
|
|
||||||
t.Fatal("registering path not ending with '*filepath' did not panic")
|
|
||||||
}
|
|
||||||
|
|
||||||
router.ServeFiles("/*filepath", mfs)
|
|
||||||
w := new(mockResponseWriter)
|
|
||||||
r, _ := http.NewRequest("GET", "/favicon.ico", nil)
|
|
||||||
router.ServeHTTP(w, r)
|
|
||||||
if !mfs.opened {
|
|
||||||
t.Error("serving file failed")
|
|
||||||
}
|
|
||||||
}
|
|
555
Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go
generated
vendored
555
Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go
generated
vendored
|
@ -1,555 +0,0 @@
|
||||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be found
|
|
||||||
// in the LICENSE file.
|
|
||||||
|
|
||||||
package httprouter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a <= b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func countParams(path string) uint8 {
|
|
||||||
var n uint
|
|
||||||
for i := 0; i < len(path); i++ {
|
|
||||||
if path[i] != ':' && path[i] != '*' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
if n >= 255 {
|
|
||||||
return 255
|
|
||||||
}
|
|
||||||
return uint8(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
type nodeType uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
static nodeType = 0
|
|
||||||
param nodeType = 1
|
|
||||||
catchAll nodeType = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type node struct {
|
|
||||||
path string
|
|
||||||
wildChild bool
|
|
||||||
nType nodeType
|
|
||||||
maxParams uint8
|
|
||||||
indices string
|
|
||||||
children []*node
|
|
||||||
handle Handle
|
|
||||||
priority uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// increments priority of the given child and reorders if necessary
|
|
||||||
func (n *node) incrementChildPrio(pos int) int {
|
|
||||||
n.children[pos].priority++
|
|
||||||
prio := n.children[pos].priority
|
|
||||||
|
|
||||||
// adjust position (move to front)
|
|
||||||
newPos := pos
|
|
||||||
for newPos > 0 && n.children[newPos-1].priority < prio {
|
|
||||||
// swap node positions
|
|
||||||
tmpN := n.children[newPos-1]
|
|
||||||
n.children[newPos-1] = n.children[newPos]
|
|
||||||
n.children[newPos] = tmpN
|
|
||||||
|
|
||||||
newPos--
|
|
||||||
}
|
|
||||||
|
|
||||||
// build new index char string
|
|
||||||
if newPos != pos {
|
|
||||||
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
|
|
||||||
n.indices[pos:pos+1] + // the index char we move
|
|
||||||
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
|
|
||||||
}
|
|
||||||
|
|
||||||
return newPos
|
|
||||||
}
|
|
||||||
|
|
||||||
// addRoute adds a node with the given handle to the path.
|
|
||||||
// Not concurrency-safe!
|
|
||||||
func (n *node) addRoute(path string, handle Handle) {
|
|
||||||
fullPath := path
|
|
||||||
n.priority++
|
|
||||||
numParams := countParams(path)
|
|
||||||
|
|
||||||
// non-empty tree
|
|
||||||
if len(n.path) > 0 || len(n.children) > 0 {
|
|
||||||
walk:
|
|
||||||
for {
|
|
||||||
// Update maxParams of the current node
|
|
||||||
if numParams > n.maxParams {
|
|
||||||
n.maxParams = numParams
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the longest common prefix.
|
|
||||||
// This also implies that the common prefix contains no ':' or '*'
|
|
||||||
// since the existing key can't contain those chars.
|
|
||||||
i := 0
|
|
||||||
max := min(len(path), len(n.path))
|
|
||||||
for i < max && path[i] == n.path[i] {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split edge
|
|
||||||
if i < len(n.path) {
|
|
||||||
child := node{
|
|
||||||
path: n.path[i:],
|
|
||||||
wildChild: n.wildChild,
|
|
||||||
indices: n.indices,
|
|
||||||
children: n.children,
|
|
||||||
handle: n.handle,
|
|
||||||
priority: n.priority - 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update maxParams (max of all children)
|
|
||||||
for i := range child.children {
|
|
||||||
if child.children[i].maxParams > child.maxParams {
|
|
||||||
child.maxParams = child.children[i].maxParams
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n.children = []*node{&child}
|
|
||||||
// []byte for proper unicode char conversion, see #65
|
|
||||||
n.indices = string([]byte{n.path[i]})
|
|
||||||
n.path = path[:i]
|
|
||||||
n.handle = nil
|
|
||||||
n.wildChild = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make new node a child of this node
|
|
||||||
if i < len(path) {
|
|
||||||
path = path[i:]
|
|
||||||
|
|
||||||
if n.wildChild {
|
|
||||||
n = n.children[0]
|
|
||||||
n.priority++
|
|
||||||
|
|
||||||
// Update maxParams of the child node
|
|
||||||
if numParams > n.maxParams {
|
|
||||||
n.maxParams = numParams
|
|
||||||
}
|
|
||||||
numParams--
|
|
||||||
|
|
||||||
// Check if the wildcard matches
|
|
||||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
|
|
||||||
// check for longer wildcard, e.g. :name and :names
|
|
||||||
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("path segment '" + path +
|
|
||||||
"' conflicts with existing wildcard '" + n.path +
|
|
||||||
"' in path '" + fullPath + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
c := path[0]
|
|
||||||
|
|
||||||
// slash after param
|
|
||||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
|
||||||
n = n.children[0]
|
|
||||||
n.priority++
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a child with the next path byte exists
|
|
||||||
for i := 0; i < len(n.indices); i++ {
|
|
||||||
if c == n.indices[i] {
|
|
||||||
i = n.incrementChildPrio(i)
|
|
||||||
n = n.children[i]
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise insert it
|
|
||||||
if c != ':' && c != '*' {
|
|
||||||
// []byte for proper unicode char conversion, see #65
|
|
||||||
n.indices += string([]byte{c})
|
|
||||||
child := &node{
|
|
||||||
maxParams: numParams,
|
|
||||||
}
|
|
||||||
n.children = append(n.children, child)
|
|
||||||
n.incrementChildPrio(len(n.indices) - 1)
|
|
||||||
n = child
|
|
||||||
}
|
|
||||||
n.insertChild(numParams, path, fullPath, handle)
|
|
||||||
return
|
|
||||||
|
|
||||||
} else if i == len(path) { // Make node a (in-path) leaf
|
|
||||||
if n.handle != nil {
|
|
||||||
panic("a handle is already registered for path ''" + fullPath + "'")
|
|
||||||
}
|
|
||||||
n.handle = handle
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else { // Empty tree
|
|
||||||
n.insertChild(numParams, path, fullPath, handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) {
|
|
||||||
var offset int // already handled bytes of the path
|
|
||||||
|
|
||||||
// find prefix until first wildcard (beginning with ':'' or '*'')
|
|
||||||
for i, max := 0, len(path); numParams > 0; i++ {
|
|
||||||
c := path[i]
|
|
||||||
if c != ':' && c != '*' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// find wildcard end (either '/' or path end)
|
|
||||||
end := i + 1
|
|
||||||
for end < max && path[end] != '/' {
|
|
||||||
switch path[end] {
|
|
||||||
// the wildcard name must not contain ':' and '*'
|
|
||||||
case ':', '*':
|
|
||||||
panic("only one wildcard per path segment is allowed, has: '" +
|
|
||||||
path[i:] + "' in path '" + fullPath + "'")
|
|
||||||
default:
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if this Node existing children which would be
|
|
||||||
// unreachable if we insert the wildcard here
|
|
||||||
if len(n.children) > 0 {
|
|
||||||
panic("wildcard route '" + path[i:end] +
|
|
||||||
"' conflicts with existing children in path '" + fullPath + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the wildcard has a name
|
|
||||||
if end-i < 2 {
|
|
||||||
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c == ':' { // param
|
|
||||||
// split path at the beginning of the wildcard
|
|
||||||
if i > 0 {
|
|
||||||
n.path = path[offset:i]
|
|
||||||
offset = i
|
|
||||||
}
|
|
||||||
|
|
||||||
child := &node{
|
|
||||||
nType: param,
|
|
||||||
maxParams: numParams,
|
|
||||||
}
|
|
||||||
n.children = []*node{child}
|
|
||||||
n.wildChild = true
|
|
||||||
n = child
|
|
||||||
n.priority++
|
|
||||||
numParams--
|
|
||||||
|
|
||||||
// if the path doesn't end with the wildcard, then there
|
|
||||||
// will be another non-wildcard subpath starting with '/'
|
|
||||||
if end < max {
|
|
||||||
n.path = path[offset:end]
|
|
||||||
offset = end
|
|
||||||
|
|
||||||
child := &node{
|
|
||||||
maxParams: numParams,
|
|
||||||
priority: 1,
|
|
||||||
}
|
|
||||||
n.children = []*node{child}
|
|
||||||
n = child
|
|
||||||
}
|
|
||||||
|
|
||||||
} else { // catchAll
|
|
||||||
if end != max || numParams > 1 {
|
|
||||||
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
|
||||||
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// currently fixed width 1 for '/'
|
|
||||||
i--
|
|
||||||
if path[i] != '/' {
|
|
||||||
panic("no / before catch-all in path '" + fullPath + "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
n.path = path[offset:i]
|
|
||||||
|
|
||||||
// first node: catchAll node with empty path
|
|
||||||
child := &node{
|
|
||||||
wildChild: true,
|
|
||||||
nType: catchAll,
|
|
||||||
maxParams: 1,
|
|
||||||
}
|
|
||||||
n.children = []*node{child}
|
|
||||||
n.indices = string(path[i])
|
|
||||||
n = child
|
|
||||||
n.priority++
|
|
||||||
|
|
||||||
// second node: node holding the variable
|
|
||||||
child = &node{
|
|
||||||
path: path[i:],
|
|
||||||
nType: catchAll,
|
|
||||||
maxParams: 1,
|
|
||||||
handle: handle,
|
|
||||||
priority: 1,
|
|
||||||
}
|
|
||||||
n.children = []*node{child}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert remaining path part and handle to the leaf
|
|
||||||
n.path = path[offset:]
|
|
||||||
n.handle = handle
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the handle registered with the given path (key). The values of
|
|
||||||
// wildcards are saved to a map.
|
|
||||||
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
|
||||||
// made if a handle exists with an extra (without the) trailing slash for the
|
|
||||||
// given path.
|
|
||||||
func (n *node) getValue(path string) (handle Handle, p Params, tsr bool) {
|
|
||||||
walk: // Outer loop for walking the tree
|
|
||||||
for {
|
|
||||||
if len(path) > len(n.path) {
|
|
||||||
if path[:len(n.path)] == n.path {
|
|
||||||
path = path[len(n.path):]
|
|
||||||
// If this node does not have a wildcard (param or catchAll)
|
|
||||||
// child, we can just look up the next child node and continue
|
|
||||||
// to walk down the tree
|
|
||||||
if !n.wildChild {
|
|
||||||
c := path[0]
|
|
||||||
for i := 0; i < len(n.indices); i++ {
|
|
||||||
if c == n.indices[i] {
|
|
||||||
n = n.children[i]
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing found.
|
|
||||||
// We can recommend to redirect to the same URL without a
|
|
||||||
// trailing slash if a leaf exists for that path.
|
|
||||||
tsr = (path == "/" && n.handle != nil)
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle wildcard child
|
|
||||||
n = n.children[0]
|
|
||||||
switch n.nType {
|
|
||||||
case param:
|
|
||||||
// find param end (either '/' or path end)
|
|
||||||
end := 0
|
|
||||||
for end < len(path) && path[end] != '/' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
|
|
||||||
// save param value
|
|
||||||
if p == nil {
|
|
||||||
// lazy allocation
|
|
||||||
p = make(Params, 0, n.maxParams)
|
|
||||||
}
|
|
||||||
i := len(p)
|
|
||||||
p = p[:i+1] // expand slice within preallocated capacity
|
|
||||||
p[i].Key = n.path[1:]
|
|
||||||
p[i].Value = path[:end]
|
|
||||||
|
|
||||||
// we need to go deeper!
|
|
||||||
if end < len(path) {
|
|
||||||
if len(n.children) > 0 {
|
|
||||||
path = path[end:]
|
|
||||||
n = n.children[0]
|
|
||||||
continue walk
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... but we can't
|
|
||||||
tsr = (len(path) == end+1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if handle = n.handle; handle != nil {
|
|
||||||
return
|
|
||||||
} else if len(n.children) == 1 {
|
|
||||||
// No handle found. Check if a handle for this path + a
|
|
||||||
// trailing slash exists for TSR recommendation
|
|
||||||
n = n.children[0]
|
|
||||||
tsr = (n.path == "/" && n.handle != nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
case catchAll:
|
|
||||||
// save param value
|
|
||||||
if p == nil {
|
|
||||||
// lazy allocation
|
|
||||||
p = make(Params, 0, n.maxParams)
|
|
||||||
}
|
|
||||||
i := len(p)
|
|
||||||
p = p[:i+1] // expand slice within preallocated capacity
|
|
||||||
p[i].Key = n.path[2:]
|
|
||||||
p[i].Value = path
|
|
||||||
|
|
||||||
handle = n.handle
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("invalid node type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if path == n.path {
|
|
||||||
// We should have reached the node containing the handle.
|
|
||||||
// Check if this node has a handle registered.
|
|
||||||
if handle = n.handle; handle != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// No handle found. Check if a handle for this path + a
|
|
||||||
// trailing slash exists for trailing slash recommendation
|
|
||||||
for i := 0; i < len(n.indices); i++ {
|
|
||||||
if n.indices[i] == '/' {
|
|
||||||
n = n.children[i]
|
|
||||||
tsr = (len(n.path) == 1 && n.handle != nil) ||
|
|
||||||
(n.nType == catchAll && n.children[0].handle != nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL with an
|
|
||||||
// extra trailing slash if a leaf exists for that path
|
|
||||||
tsr = (path == "/") ||
|
|
||||||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
|
||||||
path == n.path[:len(n.path)-1] && n.handle != nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Makes a case-insensitive lookup of the given path and tries to find a handler.
|
|
||||||
// It can optionally also fix trailing slashes.
|
|
||||||
// It returns the case-corrected path and a bool indicating whether the lookup
|
|
||||||
// was successful.
|
|
||||||
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
|
|
||||||
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
|
|
||||||
|
|
||||||
// Outer loop for walking the tree
|
|
||||||
for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) {
|
|
||||||
path = path[len(n.path):]
|
|
||||||
ciPath = append(ciPath, n.path...)
|
|
||||||
|
|
||||||
if len(path) > 0 {
|
|
||||||
// If this node does not have a wildcard (param or catchAll) child,
|
|
||||||
// we can just look up the next child node and continue to walk down
|
|
||||||
// the tree
|
|
||||||
if !n.wildChild {
|
|
||||||
r := unicode.ToLower(rune(path[0]))
|
|
||||||
for i, index := range n.indices {
|
|
||||||
// must use recursive approach since both index and
|
|
||||||
// ToLower(index) could exist. We must check both.
|
|
||||||
if r == unicode.ToLower(index) {
|
|
||||||
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
|
|
||||||
if found {
|
|
||||||
return append(ciPath, out...), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL
|
|
||||||
// without a trailing slash if a leaf exists for that path
|
|
||||||
found = (fixTrailingSlash && path == "/" && n.handle != nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n = n.children[0]
|
|
||||||
switch n.nType {
|
|
||||||
case param:
|
|
||||||
// find param end (either '/' or path end)
|
|
||||||
k := 0
|
|
||||||
for k < len(path) && path[k] != '/' {
|
|
||||||
k++
|
|
||||||
}
|
|
||||||
|
|
||||||
// add param value to case insensitive path
|
|
||||||
ciPath = append(ciPath, path[:k]...)
|
|
||||||
|
|
||||||
// we need to go deeper!
|
|
||||||
if k < len(path) {
|
|
||||||
if len(n.children) > 0 {
|
|
||||||
path = path[k:]
|
|
||||||
n = n.children[0]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... but we can't
|
|
||||||
if fixTrailingSlash && len(path) == k+1 {
|
|
||||||
return ciPath, true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.handle != nil {
|
|
||||||
return ciPath, true
|
|
||||||
} else if fixTrailingSlash && len(n.children) == 1 {
|
|
||||||
// No handle found. Check if a handle for this path + a
|
|
||||||
// trailing slash exists
|
|
||||||
n = n.children[0]
|
|
||||||
if n.path == "/" && n.handle != nil {
|
|
||||||
return append(ciPath, '/'), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
case catchAll:
|
|
||||||
return append(ciPath, path...), true
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("invalid node type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We should have reached the node containing the handle.
|
|
||||||
// Check if this node has a handle registered.
|
|
||||||
if n.handle != nil {
|
|
||||||
return ciPath, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// No handle found.
|
|
||||||
// Try to fix the path by adding a trailing slash
|
|
||||||
if fixTrailingSlash {
|
|
||||||
for i := 0; i < len(n.indices); i++ {
|
|
||||||
if n.indices[i] == '/' {
|
|
||||||
n = n.children[i]
|
|
||||||
if (len(n.path) == 1 && n.handle != nil) ||
|
|
||||||
(n.nType == catchAll && n.children[0].handle != nil) {
|
|
||||||
return append(ciPath, '/'), true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing found.
|
|
||||||
// Try to fix the path by adding / removing a trailing slash
|
|
||||||
if fixTrailingSlash {
|
|
||||||
if path == "/" {
|
|
||||||
return ciPath, true
|
|
||||||
}
|
|
||||||
if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
|
|
||||||
strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) &&
|
|
||||||
n.handle != nil {
|
|
||||||
return append(ciPath, n.path...), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
611
Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go
generated
vendored
611
Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go
generated
vendored
|
@ -1,611 +0,0 @@
|
||||||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be found
|
|
||||||
// in the LICENSE file.
|
|
||||||
|
|
||||||
package httprouter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func printChildren(n *node, prefix string) {
|
|
||||||
fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)
|
|
||||||
for l := len(n.path); l > 0; l-- {
|
|
||||||
prefix += " "
|
|
||||||
}
|
|
||||||
for _, child := range n.children {
|
|
||||||
printChildren(child, prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used as a workaround since we can't compare functions or their adresses
|
|
||||||
var fakeHandlerValue string
|
|
||||||
|
|
||||||
func fakeHandler(val string) Handle {
|
|
||||||
return func(http.ResponseWriter, *http.Request, Params) {
|
|
||||||
fakeHandlerValue = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testRequests []struct {
|
|
||||||
path string
|
|
||||||
nilHandler bool
|
|
||||||
route string
|
|
||||||
ps Params
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkRequests(t *testing.T, tree *node, requests testRequests) {
|
|
||||||
for _, request := range requests {
|
|
||||||
handler, ps, _ := tree.getValue(request.path)
|
|
||||||
|
|
||||||
if handler == nil {
|
|
||||||
if !request.nilHandler {
|
|
||||||
t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
|
|
||||||
}
|
|
||||||
} else if request.nilHandler {
|
|
||||||
t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
|
|
||||||
} else {
|
|
||||||
handler(nil, nil, nil)
|
|
||||||
if fakeHandlerValue != request.route {
|
|
||||||
t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(ps, request.ps) {
|
|
||||||
t.Errorf("Params mismatch for route '%s'", request.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPriorities(t *testing.T, n *node) uint32 {
|
|
||||||
var prio uint32
|
|
||||||
for i := range n.children {
|
|
||||||
prio += checkPriorities(t, n.children[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.handle != nil {
|
|
||||||
prio++
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.priority != prio {
|
|
||||||
t.Errorf(
|
|
||||||
"priority mismatch for node '%s': is %d, should be %d",
|
|
||||||
n.path, n.priority, prio,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return prio
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkMaxParams(t *testing.T, n *node) uint8 {
|
|
||||||
var maxParams uint8
|
|
||||||
for i := range n.children {
|
|
||||||
params := checkMaxParams(t, n.children[i])
|
|
||||||
if params > maxParams {
|
|
||||||
maxParams = params
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if n.nType != static && !n.wildChild {
|
|
||||||
maxParams++
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.maxParams != maxParams {
|
|
||||||
t.Errorf(
|
|
||||||
"maxParams mismatch for node '%s': is %d, should be %d",
|
|
||||||
n.path, n.maxParams, maxParams,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxParams
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCountParams(t *testing.T) {
|
|
||||||
if countParams("/path/:param1/static/*catch-all") != 2 {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
if countParams(strings.Repeat("/:param", 256)) != 255 {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeAddAndGet(t *testing.T) {
|
|
||||||
tree := &node{}
|
|
||||||
|
|
||||||
routes := [...]string{
|
|
||||||
"/hi",
|
|
||||||
"/contact",
|
|
||||||
"/co",
|
|
||||||
"/c",
|
|
||||||
"/a",
|
|
||||||
"/ab",
|
|
||||||
"/doc/",
|
|
||||||
"/doc/go_faq.html",
|
|
||||||
"/doc/go1.html",
|
|
||||||
"/α",
|
|
||||||
"/β",
|
|
||||||
}
|
|
||||||
for _, route := range routes {
|
|
||||||
tree.addRoute(route, fakeHandler(route))
|
|
||||||
}
|
|
||||||
|
|
||||||
//printChildren(tree, "")
|
|
||||||
|
|
||||||
checkRequests(t, tree, testRequests{
|
|
||||||
{"/a", false, "/a", nil},
|
|
||||||
{"/", true, "", nil},
|
|
||||||
{"/hi", false, "/hi", nil},
|
|
||||||
{"/contact", false, "/contact", nil},
|
|
||||||
{"/co", false, "/co", nil},
|
|
||||||
{"/con", true, "", nil}, // key mismatch
|
|
||||||
{"/cona", true, "", nil}, // key mismatch
|
|
||||||
{"/no", true, "", nil}, // no matching child
|
|
||||||
{"/ab", false, "/ab", nil},
|
|
||||||
{"/α", false, "/α", nil},
|
|
||||||
{"/β", false, "/β", nil},
|
|
||||||
})
|
|
||||||
|
|
||||||
checkPriorities(t, tree)
|
|
||||||
checkMaxParams(t, tree)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeWildcard(t *testing.T) {
|
|
||||||
tree := &node{}
|
|
||||||
|
|
||||||
routes := [...]string{
|
|
||||||
"/",
|
|
||||||
"/cmd/:tool/:sub",
|
|
||||||
"/cmd/:tool/",
|
|
||||||
"/src/*filepath",
|
|
||||||
"/search/",
|
|
||||||
"/search/:query",
|
|
||||||
"/user_:name",
|
|
||||||
"/user_:name/about",
|
|
||||||
"/files/:dir/*filepath",
|
|
||||||
"/doc/",
|
|
||||||
"/doc/go_faq.html",
|
|
||||||
"/doc/go1.html",
|
|
||||||
"/info/:user/public",
|
|
||||||
"/info/:user/project/:project",
|
|
||||||
}
|
|
||||||
for _, route := range routes {
|
|
||||||
tree.addRoute(route, fakeHandler(route))
|
|
||||||
}
|
|
||||||
|
|
||||||
//printChildren(tree, "")
|
|
||||||
|
|
||||||
checkRequests(t, tree, testRequests{
|
|
||||||
{"/", false, "/", nil},
|
|
||||||
{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
|
|
||||||
{"/cmd/test", true, "", Params{Param{"tool", "test"}}},
|
|
||||||
{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}},
|
|
||||||
{"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}},
|
|
||||||
{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
|
|
||||||
{"/search/", false, "/search/", nil},
|
|
||||||
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
|
|
||||||
{"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
|
|
||||||
{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
|
|
||||||
{"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}},
|
|
||||||
{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}},
|
|
||||||
{"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}},
|
|
||||||
{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}},
|
|
||||||
})
|
|
||||||
|
|
||||||
checkPriorities(t, tree)
|
|
||||||
checkMaxParams(t, tree)
|
|
||||||
}
|
|
||||||
|
|
||||||
func catchPanic(testFunc func()) (recv interface{}) {
|
|
||||||
defer func() {
|
|
||||||
recv = recover()
|
|
||||||
}()
|
|
||||||
|
|
||||||
testFunc()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type testRoute struct {
|
|
||||||
path string
|
|
||||||
conflict bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func testRoutes(t *testing.T, routes []testRoute) {
|
|
||||||
tree := &node{}
|
|
||||||
|
|
||||||
for _, route := range routes {
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
tree.addRoute(route.path, nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
if route.conflict {
|
|
||||||
if recv == nil {
|
|
||||||
t.Errorf("no panic for conflicting route '%s'", route.path)
|
|
||||||
}
|
|
||||||
} else if recv != nil {
|
|
||||||
t.Errorf("unexpected panic for route '%s': %v", route.path, recv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//printChildren(tree, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeWildcardConflict(t *testing.T) {
|
|
||||||
routes := []testRoute{
|
|
||||||
{"/cmd/:tool/:sub", false},
|
|
||||||
{"/cmd/vet", true},
|
|
||||||
{"/src/*filepath", false},
|
|
||||||
{"/src/*filepathx", true},
|
|
||||||
{"/src/", true},
|
|
||||||
{"/src1/", false},
|
|
||||||
{"/src1/*filepath", true},
|
|
||||||
{"/src2*filepath", true},
|
|
||||||
{"/search/:query", false},
|
|
||||||
{"/search/invalid", true},
|
|
||||||
{"/user_:name", false},
|
|
||||||
{"/user_x", true},
|
|
||||||
{"/user_:name", false},
|
|
||||||
{"/id:id", false},
|
|
||||||
{"/id/:id", true},
|
|
||||||
}
|
|
||||||
testRoutes(t, routes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeChildConflict(t *testing.T) {
|
|
||||||
routes := []testRoute{
|
|
||||||
{"/cmd/vet", false},
|
|
||||||
{"/cmd/:tool/:sub", true},
|
|
||||||
{"/src/AUTHORS", false},
|
|
||||||
{"/src/*filepath", true},
|
|
||||||
{"/user_x", false},
|
|
||||||
{"/user_:name", true},
|
|
||||||
{"/id/:id", false},
|
|
||||||
{"/id:id", true},
|
|
||||||
{"/:id", true},
|
|
||||||
{"/*filepath", true},
|
|
||||||
}
|
|
||||||
testRoutes(t, routes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeDupliatePath(t *testing.T) {
|
|
||||||
tree := &node{}
|
|
||||||
|
|
||||||
routes := [...]string{
|
|
||||||
"/",
|
|
||||||
"/doc/",
|
|
||||||
"/src/*filepath",
|
|
||||||
"/search/:query",
|
|
||||||
"/user_:name",
|
|
||||||
}
|
|
||||||
for _, route := range routes {
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
tree.addRoute(route, fakeHandler(route))
|
|
||||||
})
|
|
||||||
if recv != nil {
|
|
||||||
t.Fatalf("panic inserting route '%s': %v", route, recv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add again
|
|
||||||
recv = catchPanic(func() {
|
|
||||||
tree.addRoute(route, nil)
|
|
||||||
})
|
|
||||||
if recv == nil {
|
|
||||||
t.Fatalf("no panic while inserting duplicate route '%s", route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//printChildren(tree, "")
|
|
||||||
|
|
||||||
checkRequests(t, tree, testRequests{
|
|
||||||
{"/", false, "/", nil},
|
|
||||||
{"/doc/", false, "/doc/", nil},
|
|
||||||
{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
|
|
||||||
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
|
|
||||||
{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyWildcardName(t *testing.T) {
|
|
||||||
tree := &node{}
|
|
||||||
|
|
||||||
routes := [...]string{
|
|
||||||
"/user:",
|
|
||||||
"/user:/",
|
|
||||||
"/cmd/:/",
|
|
||||||
"/src/*",
|
|
||||||
}
|
|
||||||
for _, route := range routes {
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
tree.addRoute(route, nil)
|
|
||||||
})
|
|
||||||
if recv == nil {
|
|
||||||
t.Fatalf("no panic while inserting route with empty wildcard name '%s", route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeCatchAllConflict(t *testing.T) {
|
|
||||||
routes := []testRoute{
|
|
||||||
{"/src/*filepath/x", true},
|
|
||||||
{"/src2/", false},
|
|
||||||
{"/src2/*filepath/x", true},
|
|
||||||
}
|
|
||||||
testRoutes(t, routes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeCatchAllConflictRoot(t *testing.T) {
|
|
||||||
routes := []testRoute{
|
|
||||||
{"/", false},
|
|
||||||
{"/*filepath", true},
|
|
||||||
}
|
|
||||||
testRoutes(t, routes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeDoubleWildcard(t *testing.T) {
|
|
||||||
const panicMsg = "only one wildcard per path segment is allowed"
|
|
||||||
|
|
||||||
routes := [...]string{
|
|
||||||
"/:foo:bar",
|
|
||||||
"/:foo:bar/",
|
|
||||||
"/:foo*bar",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range routes {
|
|
||||||
tree := &node{}
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
tree.addRoute(route, nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {
|
|
||||||
t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*func TestTreeDuplicateWildcard(t *testing.T) {
|
|
||||||
tree := &node{}
|
|
||||||
|
|
||||||
routes := [...]string{
|
|
||||||
"/:id/:name/:id",
|
|
||||||
}
|
|
||||||
for _, route := range routes {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func TestTreeTrailingSlashRedirect(t *testing.T) {
|
|
||||||
tree := &node{}
|
|
||||||
|
|
||||||
routes := [...]string{
|
|
||||||
"/hi",
|
|
||||||
"/b/",
|
|
||||||
"/search/:query",
|
|
||||||
"/cmd/:tool/",
|
|
||||||
"/src/*filepath",
|
|
||||||
"/x",
|
|
||||||
"/x/y",
|
|
||||||
"/y/",
|
|
||||||
"/y/z",
|
|
||||||
"/0/:id",
|
|
||||||
"/0/:id/1",
|
|
||||||
"/1/:id/",
|
|
||||||
"/1/:id/2",
|
|
||||||
"/aa",
|
|
||||||
"/a/",
|
|
||||||
"/doc",
|
|
||||||
"/doc/go_faq.html",
|
|
||||||
"/doc/go1.html",
|
|
||||||
"/no/a",
|
|
||||||
"/no/b",
|
|
||||||
"/api/hello/:name",
|
|
||||||
}
|
|
||||||
for _, route := range routes {
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
tree.addRoute(route, fakeHandler(route))
|
|
||||||
})
|
|
||||||
if recv != nil {
|
|
||||||
t.Fatalf("panic inserting route '%s': %v", route, recv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//printChildren(tree, "")
|
|
||||||
|
|
||||||
tsrRoutes := [...]string{
|
|
||||||
"/hi/",
|
|
||||||
"/b",
|
|
||||||
"/search/gopher/",
|
|
||||||
"/cmd/vet",
|
|
||||||
"/src",
|
|
||||||
"/x/",
|
|
||||||
"/y",
|
|
||||||
"/0/go/",
|
|
||||||
"/1/go",
|
|
||||||
"/a",
|
|
||||||
"/doc/",
|
|
||||||
}
|
|
||||||
for _, route := range tsrRoutes {
|
|
||||||
handler, _, tsr := tree.getValue(route)
|
|
||||||
if handler != nil {
|
|
||||||
t.Fatalf("non-nil handler for TSR route '%s", route)
|
|
||||||
} else if !tsr {
|
|
||||||
t.Errorf("expected TSR recommendation for route '%s'", route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
noTsrRoutes := [...]string{
|
|
||||||
"/",
|
|
||||||
"/no",
|
|
||||||
"/no/",
|
|
||||||
"/_",
|
|
||||||
"/_/",
|
|
||||||
"/api/world/abc",
|
|
||||||
}
|
|
||||||
for _, route := range noTsrRoutes {
|
|
||||||
handler, _, tsr := tree.getValue(route)
|
|
||||||
if handler != nil {
|
|
||||||
t.Fatalf("non-nil handler for No-TSR route '%s", route)
|
|
||||||
} else if tsr {
|
|
||||||
t.Errorf("expected no TSR recommendation for route '%s'", route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeFindCaseInsensitivePath(t *testing.T) {
|
|
||||||
tree := &node{}
|
|
||||||
|
|
||||||
routes := [...]string{
|
|
||||||
"/hi",
|
|
||||||
"/b/",
|
|
||||||
"/ABC/",
|
|
||||||
"/search/:query",
|
|
||||||
"/cmd/:tool/",
|
|
||||||
"/src/*filepath",
|
|
||||||
"/x",
|
|
||||||
"/x/y",
|
|
||||||
"/y/",
|
|
||||||
"/y/z",
|
|
||||||
"/0/:id",
|
|
||||||
"/0/:id/1",
|
|
||||||
"/1/:id/",
|
|
||||||
"/1/:id/2",
|
|
||||||
"/aa",
|
|
||||||
"/a/",
|
|
||||||
"/doc",
|
|
||||||
"/doc/go_faq.html",
|
|
||||||
"/doc/go1.html",
|
|
||||||
"/doc/go/away",
|
|
||||||
"/no/a",
|
|
||||||
"/no/b",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, route := range routes {
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
tree.addRoute(route, fakeHandler(route))
|
|
||||||
})
|
|
||||||
if recv != nil {
|
|
||||||
t.Fatalf("panic inserting route '%s': %v", route, recv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check out == in for all registered routes
|
|
||||||
// With fixTrailingSlash = true
|
|
||||||
for _, route := range routes {
|
|
||||||
out, found := tree.findCaseInsensitivePath(route, true)
|
|
||||||
if !found {
|
|
||||||
t.Errorf("Route '%s' not found!", route)
|
|
||||||
} else if string(out) != route {
|
|
||||||
t.Errorf("Wrong result for route '%s': %s", route, string(out))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// With fixTrailingSlash = false
|
|
||||||
for _, route := range routes {
|
|
||||||
out, found := tree.findCaseInsensitivePath(route, false)
|
|
||||||
if !found {
|
|
||||||
t.Errorf("Route '%s' not found!", route)
|
|
||||||
} else if string(out) != route {
|
|
||||||
t.Errorf("Wrong result for route '%s': %s", route, string(out))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
in string
|
|
||||||
out string
|
|
||||||
found bool
|
|
||||||
slash bool
|
|
||||||
}{
|
|
||||||
{"/HI", "/hi", true, false},
|
|
||||||
{"/HI/", "/hi", true, true},
|
|
||||||
{"/B", "/b/", true, true},
|
|
||||||
{"/B/", "/b/", true, false},
|
|
||||||
{"/abc", "/ABC/", true, true},
|
|
||||||
{"/abc/", "/ABC/", true, false},
|
|
||||||
{"/aBc", "/ABC/", true, true},
|
|
||||||
{"/aBc/", "/ABC/", true, false},
|
|
||||||
{"/abC", "/ABC/", true, true},
|
|
||||||
{"/abC/", "/ABC/", true, false},
|
|
||||||
{"/SEARCH/QUERY", "/search/QUERY", true, false},
|
|
||||||
{"/SEARCH/QUERY/", "/search/QUERY", true, true},
|
|
||||||
{"/CMD/TOOL/", "/cmd/TOOL/", true, false},
|
|
||||||
{"/CMD/TOOL", "/cmd/TOOL/", true, true},
|
|
||||||
{"/SRC/FILE/PATH", "/src/FILE/PATH", true, false},
|
|
||||||
{"/x/Y", "/x/y", true, false},
|
|
||||||
{"/x/Y/", "/x/y", true, true},
|
|
||||||
{"/X/y", "/x/y", true, false},
|
|
||||||
{"/X/y/", "/x/y", true, true},
|
|
||||||
{"/X/Y", "/x/y", true, false},
|
|
||||||
{"/X/Y/", "/x/y", true, true},
|
|
||||||
{"/Y/", "/y/", true, false},
|
|
||||||
{"/Y", "/y/", true, true},
|
|
||||||
{"/Y/z", "/y/z", true, false},
|
|
||||||
{"/Y/z/", "/y/z", true, true},
|
|
||||||
{"/Y/Z", "/y/z", true, false},
|
|
||||||
{"/Y/Z/", "/y/z", true, true},
|
|
||||||
{"/y/Z", "/y/z", true, false},
|
|
||||||
{"/y/Z/", "/y/z", true, true},
|
|
||||||
{"/Aa", "/aa", true, false},
|
|
||||||
{"/Aa/", "/aa", true, true},
|
|
||||||
{"/AA", "/aa", true, false},
|
|
||||||
{"/AA/", "/aa", true, true},
|
|
||||||
{"/aA", "/aa", true, false},
|
|
||||||
{"/aA/", "/aa", true, true},
|
|
||||||
{"/A/", "/a/", true, false},
|
|
||||||
{"/A", "/a/", true, true},
|
|
||||||
{"/DOC", "/doc", true, false},
|
|
||||||
{"/DOC/", "/doc", true, true},
|
|
||||||
{"/NO", "", false, true},
|
|
||||||
{"/DOC/GO", "", false, true},
|
|
||||||
}
|
|
||||||
// With fixTrailingSlash = true
|
|
||||||
for _, test := range tests {
|
|
||||||
out, found := tree.findCaseInsensitivePath(test.in, true)
|
|
||||||
if found != test.found || (found && (string(out) != test.out)) {
|
|
||||||
t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
|
|
||||||
test.in, string(out), found, test.out, test.found)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// With fixTrailingSlash = false
|
|
||||||
for _, test := range tests {
|
|
||||||
out, found := tree.findCaseInsensitivePath(test.in, false)
|
|
||||||
if test.slash {
|
|
||||||
if found { // test needs a trailingSlash fix. It must not be found!
|
|
||||||
t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if found != test.found || (found && (string(out) != test.out)) {
|
|
||||||
t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
|
|
||||||
test.in, string(out), found, test.out, test.found)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTreeInvalidNodeType(t *testing.T) {
|
|
||||||
const panicMsg = "invalid node type"
|
|
||||||
|
|
||||||
tree := &node{}
|
|
||||||
tree.addRoute("/", fakeHandler("/"))
|
|
||||||
tree.addRoute("/:page", fakeHandler("/:page"))
|
|
||||||
|
|
||||||
// set invalid node type
|
|
||||||
tree.children[0].nType = 42
|
|
||||||
|
|
||||||
// normal lookup
|
|
||||||
recv := catchPanic(func() {
|
|
||||||
tree.getValue("/test")
|
|
||||||
})
|
|
||||||
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
|
||||||
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case-insensitive lookup
|
|
||||||
recv = catchPanic(func() {
|
|
||||||
tree.findCaseInsensitivePath("/test", true)
|
|
||||||
})
|
|
||||||
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
|
||||||
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
|
||||||
}
|
|
||||||
}
|
|
1
Godeps/_workspace/src/github.com/pushrax/bufferpool/.travis.yml
generated
vendored
1
Godeps/_workspace/src/github.com/pushrax/bufferpool/.travis.yml
generated
vendored
|
@ -1 +0,0 @@
|
||||||
language: go
|
|
4
Godeps/_workspace/src/github.com/pushrax/bufferpool/AUTHORS
generated
vendored
4
Godeps/_workspace/src/github.com/pushrax/bufferpool/AUTHORS
generated
vendored
|
@ -1,4 +0,0 @@
|
||||||
# This is the official list of Bufferpool authors for copyright purposes.
|
|
||||||
|
|
||||||
Jimmy Zelinskie <jimmyzelinskie@gmail.com>
|
|
||||||
Justin Li <jli@j-li.net>
|
|
24
Godeps/_workspace/src/github.com/pushrax/bufferpool/LICENSE
generated
vendored
24
Godeps/_workspace/src/github.com/pushrax/bufferpool/LICENSE
generated
vendored
|
@ -1,24 +0,0 @@
|
||||||
Bufferpool is released under a BSD 2-Clause license, reproduced below.
|
|
||||||
|
|
||||||
Copyright (c) 2013, The Bufferpool Authors
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
9
Godeps/_workspace/src/github.com/pushrax/bufferpool/README.md
generated
vendored
9
Godeps/_workspace/src/github.com/pushrax/bufferpool/README.md
generated
vendored
|
@ -1,9 +0,0 @@
|
||||||
# bufferpool [![Build Status](https://secure.travis-ci.org/pushrax/bufferpool.png)](http://travis-ci.org/pushrax/bufferpool)
|
|
||||||
|
|
||||||
The bufferpool package implements a thread-safe pool of reusable, equally sized `byte.Buffer`s.
|
|
||||||
If you're allocating `byte.Buffer`s very frequently, you can use this to speed up your
|
|
||||||
program and take strain off the garbage collector.
|
|
||||||
|
|
||||||
## docs
|
|
||||||
|
|
||||||
[GoDoc](http://godoc.org/github.com/pushrax/bufferpool)
|
|
69
Godeps/_workspace/src/github.com/pushrax/bufferpool/bufferpool.go
generated
vendored
69
Godeps/_workspace/src/github.com/pushrax/bufferpool/bufferpool.go
generated
vendored
|
@ -1,69 +0,0 @@
|
||||||
// Copyright 2013 The Bufferpool Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package bufferpool implements a capacity-limited pool of reusable,
|
|
||||||
// equally-sized buffers.
|
|
||||||
package bufferpool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A BufferPool is a capacity-limited pool of equally sized buffers.
|
|
||||||
type BufferPool struct {
|
|
||||||
bufferSize int
|
|
||||||
pool chan []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a newly allocated BufferPool with the given maximum pool size
|
|
||||||
// and buffer size.
|
|
||||||
func New(poolSize, bufferSize int) *BufferPool {
|
|
||||||
return &BufferPool{
|
|
||||||
bufferSize,
|
|
||||||
make(chan []byte, poolSize),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take is used to obtain a new zeroed buffer. This will allocate a new buffer
|
|
||||||
// if the pool was empty.
|
|
||||||
func (pool *BufferPool) Take() *bytes.Buffer {
|
|
||||||
return bytes.NewBuffer(pool.TakeSlice()[:0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// TakeSlice is used to obtain a new slice. This will allocate a new slice
|
|
||||||
// if the pool was empty.
|
|
||||||
func (pool *BufferPool) TakeSlice() (slice []byte) {
|
|
||||||
select {
|
|
||||||
case slice = <-pool.pool:
|
|
||||||
default:
|
|
||||||
slice = make([]byte, pool.bufferSize)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give is used to attempt to return a buffer to the pool. It may not
|
|
||||||
// be added to the pool if it was already full.
|
|
||||||
func (pool *BufferPool) Give(buf *bytes.Buffer) error {
|
|
||||||
buf.Reset()
|
|
||||||
slice := buf.Bytes()
|
|
||||||
|
|
||||||
if cap(slice) < pool.bufferSize {
|
|
||||||
return errors.New("Gave an incorrectly sized buffer to the pool.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return pool.GiveSlice(slice[:buf.Len()])
|
|
||||||
}
|
|
||||||
|
|
||||||
// GiveSlice is used to attempt to return a slice to the pool. It may not
|
|
||||||
// be added to the pool if it was already full.
|
|
||||||
func (pool *BufferPool) GiveSlice(slice []byte) error {
|
|
||||||
select {
|
|
||||||
case pool.pool <- slice:
|
|
||||||
// Everything went smoothly!
|
|
||||||
default:
|
|
||||||
return errors.New("Gave a buffer to a full pool.")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
67
Godeps/_workspace/src/github.com/pushrax/bufferpool/bufferpool_test.go
generated
vendored
67
Godeps/_workspace/src/github.com/pushrax/bufferpool/bufferpool_test.go
generated
vendored
|
@ -1,67 +0,0 @@
|
||||||
// Copyright 2013 The Bufferpool Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package bufferpool_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/pushrax/bufferpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleNew() {
|
|
||||||
bp := bufferpool.New(10, 255)
|
|
||||||
|
|
||||||
dogBuffer := bp.Take()
|
|
||||||
dogBuffer.WriteString("Dog!")
|
|
||||||
bp.Give(dogBuffer)
|
|
||||||
|
|
||||||
catBuffer := bp.Take() // dogBuffer is reused and reset.
|
|
||||||
catBuffer.WriteString("Cat!")
|
|
||||||
|
|
||||||
fmt.Println(catBuffer)
|
|
||||||
// Output:
|
|
||||||
// Cat!
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTakeFromEmpty(t *testing.T) {
|
|
||||||
bp := bufferpool.New(1, 1)
|
|
||||||
poolBuf := bp.Take()
|
|
||||||
if !bytes.Equal(poolBuf.Bytes(), []byte("")) {
|
|
||||||
t.Fatalf("Buffer from empty bufferpool was allocated incorrectly.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTakeFromFilled(t *testing.T) {
|
|
||||||
bp := bufferpool.New(1, 1)
|
|
||||||
|
|
||||||
origBuf := bytes.NewBuffer([]byte("X"))
|
|
||||||
bp.Give(origBuf)
|
|
||||||
|
|
||||||
reusedBuf := bp.Take()
|
|
||||||
if !bytes.Equal(reusedBuf.Bytes(), []byte("")) {
|
|
||||||
t.Fatalf("Buffer from filled bufferpool was recycled incorrectly.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare addresses of the first element in the underlying slice.
|
|
||||||
if &origBuf.Bytes()[:1][0] != &reusedBuf.Bytes()[:1][0] {
|
|
||||||
t.Fatalf("Recycled buffer points at different address.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSliceSemantics(t *testing.T) {
|
|
||||||
bp := bufferpool.New(1, 8)
|
|
||||||
|
|
||||||
buf := bp.Take()
|
|
||||||
buf.WriteString("abc")
|
|
||||||
bp.Give(buf)
|
|
||||||
|
|
||||||
buf2 := bp.TakeSlice()
|
|
||||||
|
|
||||||
if !bytes.Equal(buf2[:3], []byte("abc")) {
|
|
||||||
t.Fatalf("Buffer from filled bufferpool was recycled incorrectly.")
|
|
||||||
}
|
|
||||||
}
|
|
6
Godeps/_workspace/src/github.com/pushrax/faststats/.travis.yml
generated
vendored
6
Godeps/_workspace/src/github.com/pushrax/faststats/.travis.yml
generated
vendored
|
@ -1,6 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go: 1.3
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
4
Godeps/_workspace/src/github.com/pushrax/faststats/AUTHORS
generated
vendored
4
Godeps/_workspace/src/github.com/pushrax/faststats/AUTHORS
generated
vendored
|
@ -1,4 +0,0 @@
|
||||||
# This is the official list of faststats authors for copyright purposes.
|
|
||||||
|
|
||||||
Justin Li <jli@j-li.net>
|
|
||||||
|
|
24
Godeps/_workspace/src/github.com/pushrax/faststats/LICENSE
generated
vendored
24
Godeps/_workspace/src/github.com/pushrax/faststats/LICENSE
generated
vendored
|
@ -1,24 +0,0 @@
|
||||||
faststats is released under a BSD 2-Clause license, reproduced below.
|
|
||||||
|
|
||||||
Copyright (c) 2015, The faststats Authors
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
5
Godeps/_workspace/src/github.com/pushrax/faststats/README.md
generated
vendored
5
Godeps/_workspace/src/github.com/pushrax/faststats/README.md
generated
vendored
|
@ -1,5 +0,0 @@
|
||||||
# faststats [![Build Status](https://api.travis-ci.org/pushrax/faststats.svg?branch=master)](https://travis-ci.org/pushrax/faststats)
|
|
||||||
|
|
||||||
faststats is a Go package for calculating various statistical measures in real time.
|
|
||||||
It is intended to be used in online networking applications, where significant overhead just for stats collection is undesirable.
|
|
||||||
|
|
10
Godeps/_workspace/src/github.com/pushrax/faststats/faststats.go
generated
vendored
10
Godeps/_workspace/src/github.com/pushrax/faststats/faststats.go
generated
vendored
|
@ -1,10 +0,0 @@
|
||||||
// Copyright 2015 The faststats Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package faststats
|
|
||||||
|
|
||||||
type Measure interface {
|
|
||||||
AddSample(sample float64)
|
|
||||||
Value() float64
|
|
||||||
}
|
|
11
Godeps/_workspace/src/github.com/pushrax/faststats/json.go
generated
vendored
11
Godeps/_workspace/src/github.com/pushrax/faststats/json.go
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
// Copyright 2015 The faststats Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package faststats
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
func (p *Percentile) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(p.Value())
|
|
||||||
}
|
|
101
Godeps/_workspace/src/github.com/pushrax/faststats/percentile.go
generated
vendored
101
Godeps/_workspace/src/github.com/pushrax/faststats/percentile.go
generated
vendored
|
@ -1,101 +0,0 @@
|
||||||
// Copyright 2015 The faststats Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package faststats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Percentile implements an efficient percentile calculation of
|
|
||||||
// arbitrary float64 samples.
|
|
||||||
type Percentile struct {
|
|
||||||
percentile float64
|
|
||||||
|
|
||||||
samples int64
|
|
||||||
offset int64
|
|
||||||
|
|
||||||
values []float64
|
|
||||||
value uint64 // These bits are really a float64.
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPercentile returns a Percentile with a given threshold.
|
|
||||||
func NewPercentile(percentile float64) *Percentile {
|
|
||||||
return &Percentile{
|
|
||||||
percentile: percentile,
|
|
||||||
|
|
||||||
// 256 samples is fast, and accurate for most distributions.
|
|
||||||
values: make([]float64, 0, 256),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPercentileWithWindow returns a Percentile with a given threshold
|
|
||||||
// and window size (accuracy).
|
|
||||||
func NewPercentileWithWindow(percentile float64, sampleWindow int) *Percentile {
|
|
||||||
return &Percentile{
|
|
||||||
percentile: percentile,
|
|
||||||
values: make([]float64, 0, sampleWindow),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the current value at the stored percentile.
|
|
||||||
// It is thread-safe, and may be called concurrently with AddSample.
|
|
||||||
func (p *Percentile) Value() float64 {
|
|
||||||
bits := atomic.LoadUint64(&p.value)
|
|
||||||
return math.Float64frombits(bits)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSample adds a single float64 sample to the data set.
|
|
||||||
// It is not thread-safe, and must not be called in parallel.
|
|
||||||
func (p *Percentile) AddSample(sample float64) {
|
|
||||||
p.samples++
|
|
||||||
|
|
||||||
if len(p.values) == cap(p.values) {
|
|
||||||
target := float64(p.samples)*p.percentile - float64(cap(p.values))/2
|
|
||||||
offset := round(math.Max(target, 0))
|
|
||||||
|
|
||||||
if sample > p.values[0] {
|
|
||||||
if offset > p.offset {
|
|
||||||
idx := sort.SearchFloat64s(p.values[1:], sample)
|
|
||||||
copy(p.values, p.values[1:idx+1])
|
|
||||||
|
|
||||||
p.values[idx] = sample
|
|
||||||
p.offset++
|
|
||||||
} else if sample < p.values[len(p.values)-1] {
|
|
||||||
idx := sort.SearchFloat64s(p.values, sample)
|
|
||||||
copy(p.values[idx+1:], p.values[idx:])
|
|
||||||
|
|
||||||
p.values[idx] = sample
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if offset > p.offset {
|
|
||||||
p.offset++
|
|
||||||
} else {
|
|
||||||
copy(p.values[1:], p.values)
|
|
||||||
p.values[0] = sample
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
idx := sort.SearchFloat64s(p.values, sample)
|
|
||||||
p.values = p.values[:len(p.values)+1]
|
|
||||||
copy(p.values[idx+1:], p.values[idx:])
|
|
||||||
p.values[idx] = sample
|
|
||||||
}
|
|
||||||
|
|
||||||
bits := math.Float64bits(p.values[p.index()])
|
|
||||||
atomic.StoreUint64(&p.value, bits)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Percentile) index() int64 {
|
|
||||||
idx := round(float64(p.samples)*p.percentile - float64(p.offset))
|
|
||||||
last := int64(len(p.values)) - 1
|
|
||||||
|
|
||||||
if idx > last {
|
|
||||||
return last
|
|
||||||
}
|
|
||||||
|
|
||||||
return idx
|
|
||||||
}
|
|
77
Godeps/_workspace/src/github.com/pushrax/faststats/percentile_test.go
generated
vendored
77
Godeps/_workspace/src/github.com/pushrax/faststats/percentile_test.go
generated
vendored
|
@ -1,77 +0,0 @@
|
||||||
// Copyright 2015 The faststats Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package faststats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPercentiles(t *testing.T) {
|
|
||||||
rand.Seed(time.Now().Unix())
|
|
||||||
|
|
||||||
testPercentile(t, uniform(10000, 1), 0.5)
|
|
||||||
testPercentile(t, uniform(10000, 1), 0.9)
|
|
||||||
testPercentile(t, uniform(10000, 10000), 0.5)
|
|
||||||
testPercentile(t, uniform(10000, 10000), 0.9)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogNormPercentiles(t *testing.T) {
|
|
||||||
rand.Seed(time.Now().Unix())
|
|
||||||
|
|
||||||
testPercentile(t, logNorm(10000, 1), 0.5)
|
|
||||||
testPercentile(t, logNorm(10000, 1), 0.9)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPercentile(t *testing.T, numbers sort.Float64Slice, percentile float64) {
|
|
||||||
p := NewPercentile(percentile)
|
|
||||||
|
|
||||||
for i := 0; i < len(numbers); i++ {
|
|
||||||
p.AddSample(numbers[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(numbers)
|
|
||||||
got := p.Value()
|
|
||||||
index := round(float64(len(numbers)) * percentile)
|
|
||||||
|
|
||||||
if got != numbers[index] && got != numbers[index-1] && got != numbers[index+1] {
|
|
||||||
t.Errorf("Percentile incorrect\n actual: %f\nexpected: %f, %f, %f\n", got, numbers[index-1], numbers[index], numbers[index+1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkPercentiles64(b *testing.B) {
|
|
||||||
bencharkPercentile(b, uniform(b.N, 1), 64, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkPercentiles128(b *testing.B) {
|
|
||||||
bencharkPercentile(b, uniform(b.N, 1), 128, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkPercentiles256(b *testing.B) {
|
|
||||||
bencharkPercentile(b, uniform(b.N, 1), 256, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkPercentiles512(b *testing.B) {
|
|
||||||
bencharkPercentile(b, uniform(b.N, 1), 512, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLNPercentiles128(b *testing.B) {
|
|
||||||
bencharkPercentile(b, logNorm(b.N, 1), 128, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLNPercentiles256(b *testing.B) {
|
|
||||||
bencharkPercentile(b, logNorm(b.N, 1), 258, 0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bencharkPercentile(b *testing.B, numbers sort.Float64Slice, window int, percentile float64) {
|
|
||||||
p := NewPercentileWithWindow(percentile, window)
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
p.AddSample(numbers[i])
|
|
||||||
}
|
|
||||||
}
|
|
40
Godeps/_workspace/src/github.com/pushrax/faststats/util.go
generated
vendored
40
Godeps/_workspace/src/github.com/pushrax/faststats/util.go
generated
vendored
|
@ -1,40 +0,0 @@
|
||||||
// Copyright 2015 The faststats Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package faststats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
func round(value float64) int64 {
|
|
||||||
if value < 0.0 {
|
|
||||||
value -= 0.5
|
|
||||||
} else {
|
|
||||||
value += 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
return int64(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uniform(n int, scale float64) []float64 {
|
|
||||||
numbers := make([]float64, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
numbers[i] = rand.Float64() * scale
|
|
||||||
}
|
|
||||||
|
|
||||||
return numbers
|
|
||||||
}
|
|
||||||
|
|
||||||
func logNorm(n int, scale float64) []float64 {
|
|
||||||
numbers := make([]float64, n)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
numbers[i] = math.Exp(rand.NormFloat64()) * scale
|
|
||||||
}
|
|
||||||
|
|
||||||
return numbers
|
|
||||||
}
|
|
6
Godeps/_workspace/src/github.com/pushrax/flatjson/.travis.yml
generated
vendored
6
Godeps/_workspace/src/github.com/pushrax/flatjson/.travis.yml
generated
vendored
|
@ -1,6 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go: 1.3
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
4
Godeps/_workspace/src/github.com/pushrax/flatjson/AUTHORS
generated
vendored
4
Godeps/_workspace/src/github.com/pushrax/flatjson/AUTHORS
generated
vendored
|
@ -1,4 +0,0 @@
|
||||||
# This is the official list of flatjson authors for copyright purposes.
|
|
||||||
|
|
||||||
Justin Li <jli@j-li.net>
|
|
||||||
|
|
24
Godeps/_workspace/src/github.com/pushrax/flatjson/LICENSE
generated
vendored
24
Godeps/_workspace/src/github.com/pushrax/flatjson/LICENSE
generated
vendored
|
@ -1,24 +0,0 @@
|
||||||
flatjson is released under a BSD 2-Clause license, reproduced below.
|
|
||||||
|
|
||||||
Copyright (c) 2015, The flatjson Authors
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
68
Godeps/_workspace/src/github.com/pushrax/flatjson/README.md
generated
vendored
68
Godeps/_workspace/src/github.com/pushrax/flatjson/README.md
generated
vendored
|
@ -1,68 +0,0 @@
|
||||||
# flatjson [![Build Status](https://api.travis-ci.org/pushrax/flatjson.svg?branch=master)](https://travis-ci.org/pushrax/flatjson)
|
|
||||||
|
|
||||||
flatjson is a Go package for collapsing structs into a flat map, which can then be JSON encoded.
|
|
||||||
The map values are pointers to the original struct fields, so it does not need to be regenerated when the values are updated.
|
|
||||||
|
|
||||||
Example use case:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Connections": {
|
|
||||||
"Open": 2,
|
|
||||||
"Accepted": 4
|
|
||||||
},
|
|
||||||
"ResponseTime": {
|
|
||||||
"P50": 0.045775,
|
|
||||||
"P90": 0.074299,
|
|
||||||
"P95": 0.096207
|
|
||||||
},
|
|
||||||
"Peers.IPv6": {
|
|
||||||
"Current": 0,
|
|
||||||
"Joined": 0,
|
|
||||||
"Left": 0,
|
|
||||||
"Reaped": 0,
|
|
||||||
"Completed": 0,
|
|
||||||
"Seeds": {
|
|
||||||
"Current": 0,
|
|
||||||
"Joined": 0,
|
|
||||||
"Left": 0,
|
|
||||||
"Reaped": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Memory": {
|
|
||||||
"Alloc": 682208,
|
|
||||||
"TotalAlloc": 1032488,
|
|
||||||
"Sys": 5441784,
|
|
||||||
"Lookups": 28,
|
|
||||||
"Mallocs": 3326,
|
|
||||||
"Frees": 2567
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
is instead serialized as:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Connections.Accepted": 4,
|
|
||||||
"Connections.Open": 2,
|
|
||||||
"Memory.Alloc": 682208,
|
|
||||||
"Memory.Frees": 2567,
|
|
||||||
"Memory.Lookups": 281,
|
|
||||||
"Memory.Mallocs": 3326,
|
|
||||||
"Memory.Sys": 5441784,
|
|
||||||
"Memory.TotalAlloc": 1032488,
|
|
||||||
"Peers.IPv6.Completed": 0,
|
|
||||||
"Peers.IPv6.Current": 0,
|
|
||||||
"Peers.IPv6.Joined": 0,
|
|
||||||
"Peers.IPv6.Left": 0,
|
|
||||||
"Peers.IPv6.Reaped": 0,
|
|
||||||
"Peers.IPv6.Seeds.Current": 0,
|
|
||||||
"Peers.IPv6.Seeds.Joined": 0,
|
|
||||||
"Peers.IPv6.Seeds.Left": 0,
|
|
||||||
"Peers.IPv6.Seeds.Reaped": 0,
|
|
||||||
"ResponseTime.P50": 0.045775,
|
|
||||||
"ResponseTime.P90": 0.074299,
|
|
||||||
"ResponseTime.P95": 0.096207
|
|
||||||
}
|
|
||||||
```
|
|
104
Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson.go
generated
vendored
104
Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson.go
generated
vendored
|
@ -1,104 +0,0 @@
|
||||||
// Copyright 2015 The flatjson Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the BSD 2-Clause license,
|
|
||||||
// which can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package flatjson implements a means of converting a struct into a flattened
|
|
||||||
// map suitable for JSON encoding. The values in the map are pointers to the
|
|
||||||
// original struct fields, so the map can be generated once and encoded whenever
|
|
||||||
// the underlying values change.
|
|
||||||
package flatjson
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Map map[string]interface{}
|
|
||||||
|
|
||||||
// Flatten returns the Map representation of val.
|
|
||||||
func Flatten(val interface{}) Map {
|
|
||||||
rval := reflect.ValueOf(val)
|
|
||||||
rval = extractStruct(rval, rval)
|
|
||||||
|
|
||||||
if rval.Kind() != reflect.Struct {
|
|
||||||
panic("Flatten: must be called with a struct type")
|
|
||||||
}
|
|
||||||
|
|
||||||
m := Map{}
|
|
||||||
recursiveFlatten(rval, "", m)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyForField(field reflect.StructField, v reflect.Value) (string, bool) {
|
|
||||||
if tag := field.Tag.Get("json"); tag != "" {
|
|
||||||
tokens := strings.SplitN(tag, ",", 2)
|
|
||||||
name := tokens[0]
|
|
||||||
opts := ""
|
|
||||||
|
|
||||||
if len(tokens) > 1 {
|
|
||||||
opts = tokens[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == "-" || strings.Contains(opts, "omitempty") && isEmptyValue(v) {
|
|
||||||
return "", false
|
|
||||||
} else if name != "" {
|
|
||||||
return name, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if field.Anonymous {
|
|
||||||
return "", true
|
|
||||||
}
|
|
||||||
return field.Name, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractStruct(val, fallback reflect.Value) reflect.Value {
|
|
||||||
switch val.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
return val
|
|
||||||
case reflect.Ptr:
|
|
||||||
return extractStruct(val.Elem(), fallback)
|
|
||||||
case reflect.Interface:
|
|
||||||
return extractStruct(val.Elem(), fallback)
|
|
||||||
default:
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func recursiveFlatten(val reflect.Value, prefix string, output Map) int {
|
|
||||||
valType := val.Type()
|
|
||||||
added := 0
|
|
||||||
|
|
||||||
for i := 0; i < val.NumField(); i++ {
|
|
||||||
child := val.Field(i)
|
|
||||||
childType := valType.Field(i)
|
|
||||||
childPrefix := prefix
|
|
||||||
|
|
||||||
key, anonymous := keyForField(childType, child)
|
|
||||||
|
|
||||||
if childType.PkgPath != "" || (key == "" && !anonymous) {
|
|
||||||
continue
|
|
||||||
} else if !anonymous {
|
|
||||||
childPrefix = prefix + key + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
child = extractStruct(child, child)
|
|
||||||
|
|
||||||
if child.Kind() == reflect.Struct {
|
|
||||||
childAdded := recursiveFlatten(child, childPrefix, output)
|
|
||||||
if childAdded != 0 {
|
|
||||||
added += childAdded
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output[prefix+key] = child.Addr().Interface()
|
|
||||||
added++
|
|
||||||
}
|
|
||||||
|
|
||||||
return added
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEmptyValue(v reflect.Value) bool {
|
|
||||||
return v.Interface() == reflect.Zero(v.Type()).Interface()
|
|
||||||
}
|
|
140
Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson_test.go
generated
vendored
140
Godeps/_workspace/src/github.com/pushrax/flatjson/flatjson_test.go
generated
vendored
|
@ -1,140 +0,0 @@
|
||||||
package flatjson_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/pushrax/flatjson"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Child struct {
|
|
||||||
C int `json:"CC"`
|
|
||||||
D string `json:"CD"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicFlatten(t *testing.T) {
|
|
||||||
val := &Child{10, "str"}
|
|
||||||
|
|
||||||
expected := flatjson.Map{
|
|
||||||
"CC": 10.0, // JSON numbers are all float64.
|
|
||||||
"CD": "str",
|
|
||||||
}
|
|
||||||
|
|
||||||
testFlattening(t, val, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmbeddedFlatten(t *testing.T) {
|
|
||||||
val := &struct {
|
|
||||||
Child // Embedded.
|
|
||||||
Other Child // Regular child.
|
|
||||||
A int
|
|
||||||
}{}
|
|
||||||
|
|
||||||
expected := flatjson.Map{
|
|
||||||
"A": 0.0,
|
|
||||||
"CC": 0.0,
|
|
||||||
"CD": "",
|
|
||||||
"Other.CC": 0.0,
|
|
||||||
"Other.CD": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
testFlattening(t, val, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndirection(t *testing.T) {
|
|
||||||
o2 := &Child{5, "6"}
|
|
||||||
|
|
||||||
val := &struct {
|
|
||||||
*Child
|
|
||||||
Other1 interface{} `json:"O1"`
|
|
||||||
Other2 **Child `json:"O2"`
|
|
||||||
Other3 *Child `json:",omitempty"`
|
|
||||||
}{
|
|
||||||
Child: &Child{1, "2"},
|
|
||||||
Other1: &Child{3, "4"},
|
|
||||||
Other2: &o2,
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := flatjson.Map{
|
|
||||||
"CC": 1.0,
|
|
||||||
"CD": "2",
|
|
||||||
"O1.CC": 3.0,
|
|
||||||
"O1.CD": "4",
|
|
||||||
"O2.CC": 5.0,
|
|
||||||
"O2.CD": "6",
|
|
||||||
}
|
|
||||||
|
|
||||||
testFlattening(t, val, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
type L3 struct{ A string }
|
|
||||||
type L2 struct{ L3 }
|
|
||||||
type L1 struct{ L2 }
|
|
||||||
type L0 struct{ L1 }
|
|
||||||
|
|
||||||
func TestDeepNesting(t *testing.T) {
|
|
||||||
val := &L0{}
|
|
||||||
val.A = "abc"
|
|
||||||
|
|
||||||
expected := flatjson.Map{"A": "abc"}
|
|
||||||
testFlattening(t, val, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TL1 struct {
|
|
||||||
L2 `json:"L2"`
|
|
||||||
}
|
|
||||||
type TL0 struct {
|
|
||||||
TL1 `json:"L1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeepTagNesting(t *testing.T) {
|
|
||||||
val := &TL0{}
|
|
||||||
val.A = "abc"
|
|
||||||
|
|
||||||
expected := flatjson.Map{"L1.L2.A": "abc"}
|
|
||||||
testFlattening(t, val, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidInputs(t *testing.T) {
|
|
||||||
val := &struct{ A int }{10}
|
|
||||||
expected := flatjson.Map{"A": 10.0}
|
|
||||||
|
|
||||||
testFlattening(t, val, expected)
|
|
||||||
testFlattening(t, &val, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidInputs(t *testing.T) {
|
|
||||||
testPanic(t, struct{ A int }{})
|
|
||||||
testPanic(t, 123)
|
|
||||||
testPanic(t, "abc")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPanic(t *testing.T, val interface{}) {
|
|
||||||
defer func() {
|
|
||||||
if recover() == nil {
|
|
||||||
t.Errorf("Expected panic for input %#v\n", val)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
testFlattening(t, val, flatjson.Map{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFlattening(t *testing.T, val interface{}, expected flatjson.Map) {
|
|
||||||
flat := flatjson.Flatten(val)
|
|
||||||
|
|
||||||
enc, err := json.Marshal(flat)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := flatjson.Map{}
|
|
||||||
err = json.Unmarshal(enc, &got)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, expected) {
|
|
||||||
t.Errorf("Unmarshalled to unexpected value:\n got: %#v\nexpected: %#v\n", got, expected)
|
|
||||||
}
|
|
||||||
}
|
|
23
Godeps/_workspace/src/github.com/tylerb/graceful/.gitignore
generated
vendored
23
Godeps/_workspace/src/github.com/tylerb/graceful/.gitignore
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
21
Godeps/_workspace/src/github.com/tylerb/graceful/LICENSE
generated
vendored
21
Godeps/_workspace/src/github.com/tylerb/graceful/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Tyler Bunnell
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
137
Godeps/_workspace/src/github.com/tylerb/graceful/README.md
generated
vendored
137
Godeps/_workspace/src/github.com/tylerb/graceful/README.md
generated
vendored
|
@ -1,137 +0,0 @@
|
||||||
graceful [![GoDoc](https://godoc.org/github.com/tylerb/graceful?status.png)](http://godoc.org/github.com/tylerb/graceful) [![Build Status](https://drone.io/github.com/tylerb/graceful/status.png)](https://drone.io/github.com/tylerb/graceful/latest) [![Coverage Status](https://coveralls.io/repos/tylerb/graceful/badge.svg?branch=dronedebug)](https://coveralls.io/r/tylerb/graceful?branch=dronedebug) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tylerb/graceful?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
||||||
========
|
|
||||||
|
|
||||||
Graceful is a Go 1.3+ package enabling graceful shutdown of http.Handler servers.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
To install, simply execute:
|
|
||||||
|
|
||||||
```
|
|
||||||
go get gopkg.in/tylerb/graceful.v1
|
|
||||||
```
|
|
||||||
|
|
||||||
I am using [gopkg.in](http://http://labix.org/gopkg.in) to control releases.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Using Graceful is easy. Simply create your http.Handler and pass it to the `Run` function:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gopkg.in/tylerb/graceful.v1"
|
|
||||||
"net/http"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
fmt.Fprintf(w, "Welcome to the home page!")
|
|
||||||
})
|
|
||||||
|
|
||||||
graceful.Run(":3001",10*time.Second,mux)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Another example, using [Negroni](https://github.com/codegangsta/negroni), functions in much the same manner:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/codegangsta/negroni"
|
|
||||||
"gopkg.in/tylerb/graceful.v1"
|
|
||||||
"net/http"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
fmt.Fprintf(w, "Welcome to the home page!")
|
|
||||||
})
|
|
||||||
|
|
||||||
n := negroni.Classic()
|
|
||||||
n.UseHandler(mux)
|
|
||||||
//n.Run(":3000")
|
|
||||||
graceful.Run(":3001",10*time.Second,n)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In addition to Run there are the http.Server counterparts ListenAndServe, ListenAndServeTLS and Serve, which allow you to configure HTTPS, custom timeouts and error handling.
|
|
||||||
Graceful may also be used by instantiating its Server type directly, which embeds an http.Server:
|
|
||||||
|
|
||||||
```go
|
|
||||||
mux := // ...
|
|
||||||
|
|
||||||
srv := &graceful.Server{
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
|
|
||||||
Server: &http.Server{
|
|
||||||
Addr: ":1234",
|
|
||||||
Handler: mux,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
srv.ListenAndServe()
|
|
||||||
```
|
|
||||||
|
|
||||||
This form allows you to set the ConnState callback, which works in the same way as in http.Server:
|
|
||||||
|
|
||||||
```go
|
|
||||||
mux := // ...
|
|
||||||
|
|
||||||
srv := &graceful.Server{
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
|
|
||||||
ConnState: func(conn net.Conn, state http.ConnState) {
|
|
||||||
// conn has a new state
|
|
||||||
},
|
|
||||||
|
|
||||||
Server: &http.Server{
|
|
||||||
Addr: ":1234",
|
|
||||||
Handler: mux,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
srv.ListenAndServe()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Behaviour
|
|
||||||
|
|
||||||
When Graceful is sent a SIGINT or SIGTERM (possibly from ^C or a kill command), it:
|
|
||||||
|
|
||||||
1. Disables keepalive connections.
|
|
||||||
2. Closes the listening socket, allowing another process to listen on that port immediately.
|
|
||||||
3. Starts a timer of `timeout` duration to give active requests a chance to finish.
|
|
||||||
4. When timeout expires, closes all active connections.
|
|
||||||
5. Closes the `stopChan`, waking up any blocking goroutines.
|
|
||||||
6. Returns from the function, allowing the server to terminate.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
If the `timeout` argument to `Run` is 0, the server never times out, allowing all active requests to complete.
|
|
||||||
|
|
||||||
If you wish to stop the server in some way other than an OS signal, you may call the `Stop()` function.
|
|
||||||
This function stops the server, gracefully, using the new timeout value you provide. The `StopChan()` function
|
|
||||||
returns a channel on which you can block while waiting for the server to stop. This channel will be closed when
|
|
||||||
the server is stopped, allowing your execution to proceed. Multiple goroutines can block on this channel at the
|
|
||||||
same time and all will be signalled when stopping is complete.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
If you would like to contribute, please:
|
|
||||||
|
|
||||||
1. Create a GitHub issue regarding the contribution. Features and bugs should be discussed beforehand.
|
|
||||||
2. Fork the repository.
|
|
||||||
3. Create a pull request with your solution. This pull request should reference and close the issues (Fix #2).
|
|
||||||
|
|
||||||
All pull requests should:
|
|
||||||
|
|
||||||
1. Pass [gometalinter -t .](https://github.com/alecthomas/gometalinter) with no warnings.
|
|
||||||
2. Be `go fmt` formatted.
|
|
302
Godeps/_workspace/src/github.com/tylerb/graceful/graceful.go
generated
vendored
302
Godeps/_workspace/src/github.com/tylerb/graceful/graceful.go
generated
vendored
|
@ -1,302 +0,0 @@
|
||||||
package graceful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/netutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Server wraps an http.Server with graceful connection handling.
|
|
||||||
// It may be used directly in the same way as http.Server, or may
|
|
||||||
// be constructed with the global functions in this package.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
// srv := &graceful.Server{
|
|
||||||
// Timeout: 5 * time.Second,
|
|
||||||
// Server: &http.Server{Addr: ":1234", Handler: handler},
|
|
||||||
// }
|
|
||||||
// srv.ListenAndServe()
|
|
||||||
type Server struct {
|
|
||||||
*http.Server
|
|
||||||
|
|
||||||
// Timeout is the duration to allow outstanding requests to survive
|
|
||||||
// before forcefully terminating them.
|
|
||||||
Timeout time.Duration
|
|
||||||
|
|
||||||
// Limit the number of outstanding requests
|
|
||||||
ListenLimit int
|
|
||||||
|
|
||||||
// ConnState specifies an optional callback function that is
|
|
||||||
// called when a client connection changes state. This is a proxy
|
|
||||||
// to the underlying http.Server's ConnState, and the original
|
|
||||||
// must not be set directly.
|
|
||||||
ConnState func(net.Conn, http.ConnState)
|
|
||||||
|
|
||||||
// ShutdownInitiated is an optional callback function that is called
|
|
||||||
// when shutdown is initiated. It can be used to notify the client
|
|
||||||
// side of long lived connections (e.g. websockets) to reconnect.
|
|
||||||
ShutdownInitiated func()
|
|
||||||
|
|
||||||
// NoSignalHandling prevents graceful from automatically shutting down
|
|
||||||
// on SIGINT and SIGTERM. If set to true, you must shut down the server
|
|
||||||
// manually with Stop().
|
|
||||||
NoSignalHandling bool
|
|
||||||
|
|
||||||
// interrupt signals the listener to stop serving connections,
|
|
||||||
// and the server to shut down.
|
|
||||||
interrupt chan os.Signal
|
|
||||||
|
|
||||||
// stopChan is the channel on which callers may block while waiting for
|
|
||||||
// the server to stop.
|
|
||||||
stopChan chan struct{}
|
|
||||||
|
|
||||||
// stopLock is used to protect access to the stopChan.
|
|
||||||
stopLock sync.RWMutex
|
|
||||||
|
|
||||||
// connections holds all connections managed by graceful
|
|
||||||
connections map[net.Conn]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run serves the http.Handler with graceful shutdown enabled.
|
|
||||||
//
|
|
||||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
|
||||||
// If timeout is 0, the server never times out. It waits for all active requests to finish.
|
|
||||||
func Run(addr string, timeout time.Duration, n http.Handler) {
|
|
||||||
srv := &Server{
|
|
||||||
Timeout: timeout,
|
|
||||||
Server: &http.Server{Addr: addr, Handler: n},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
|
||||||
if opErr, ok := err.(*net.OpError); !ok || (ok && opErr.Op != "accept") {
|
|
||||||
logger := log.New(os.Stdout, "[graceful] ", 0)
|
|
||||||
logger.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe is equivalent to http.Server.ListenAndServe with graceful shutdown enabled.
|
|
||||||
//
|
|
||||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
|
||||||
// If timeout is 0, the server never times out. It waits for all active requests to finish.
|
|
||||||
func ListenAndServe(server *http.Server, timeout time.Duration) error {
|
|
||||||
srv := &Server{Timeout: timeout, Server: server}
|
|
||||||
return srv.ListenAndServe()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe is equivalent to http.Server.ListenAndServe with graceful shutdown enabled.
|
|
||||||
func (srv *Server) ListenAndServe() error {
|
|
||||||
// Create the listener so we can control their lifetime
|
|
||||||
addr := srv.Addr
|
|
||||||
if addr == "" {
|
|
||||||
addr = ":http"
|
|
||||||
}
|
|
||||||
l, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if srv.ListenLimit != 0 {
|
|
||||||
l = netutil.LimitListener(l, srv.ListenLimit)
|
|
||||||
}
|
|
||||||
return srv.Serve(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServeTLS is equivalent to http.Server.ListenAndServeTLS with graceful shutdown enabled.
|
|
||||||
//
|
|
||||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
|
||||||
// If timeout is 0, the server never times out. It waits for all active requests to finish.
|
|
||||||
func ListenAndServeTLS(server *http.Server, certFile, keyFile string, timeout time.Duration) error {
|
|
||||||
srv := &Server{Timeout: timeout, Server: server}
|
|
||||||
return srv.ListenAndServeTLS(certFile, keyFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServeTLS is equivalent to http.Server.ListenAndServeTLS with graceful shutdown enabled.
|
|
||||||
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error {
|
|
||||||
// Create the listener ourselves so we can control its lifetime
|
|
||||||
addr := srv.Addr
|
|
||||||
if addr == "" {
|
|
||||||
addr = ":https"
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &tls.Config{}
|
|
||||||
if srv.TLSConfig != nil {
|
|
||||||
*config = *srv.TLSConfig
|
|
||||||
}
|
|
||||||
if config.NextProtos == nil {
|
|
||||||
config.NextProtos = []string{"http/1.1"}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
config.Certificates = make([]tls.Certificate, 1)
|
|
||||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsListener := tls.NewListener(conn, config)
|
|
||||||
return srv.Serve(tlsListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve is equivalent to http.Server.Serve with graceful shutdown enabled.
|
|
||||||
//
|
|
||||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
|
||||||
// If timeout is 0, the server never times out. It waits for all active requests to finish.
|
|
||||||
func Serve(server *http.Server, l net.Listener, timeout time.Duration) error {
|
|
||||||
srv := &Server{Timeout: timeout, Server: server}
|
|
||||||
return srv.Serve(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve is equivalent to http.Server.Serve with graceful shutdown enabled.
|
|
||||||
func (srv *Server) Serve(listener net.Listener) error {
|
|
||||||
// Track connection state
|
|
||||||
add := make(chan net.Conn)
|
|
||||||
remove := make(chan net.Conn)
|
|
||||||
|
|
||||||
srv.Server.ConnState = func(conn net.Conn, state http.ConnState) {
|
|
||||||
switch state {
|
|
||||||
case http.StateNew:
|
|
||||||
add <- conn
|
|
||||||
case http.StateClosed, http.StateHijacked:
|
|
||||||
remove <- conn
|
|
||||||
}
|
|
||||||
if srv.ConnState != nil {
|
|
||||||
srv.ConnState(conn, state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manage open connections
|
|
||||||
shutdown := make(chan chan struct{})
|
|
||||||
kill := make(chan struct{})
|
|
||||||
go srv.manageConnections(add, remove, shutdown, kill)
|
|
||||||
|
|
||||||
interrupt := srv.interruptChan()
|
|
||||||
|
|
||||||
// Set up the interrupt handler
|
|
||||||
if !srv.NoSignalHandling {
|
|
||||||
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
}
|
|
||||||
|
|
||||||
go srv.handleInterrupt(interrupt, listener)
|
|
||||||
|
|
||||||
// Serve with graceful listener.
|
|
||||||
// Execution blocks here until listener.Close() is called, above.
|
|
||||||
err := srv.Server.Serve(listener)
|
|
||||||
|
|
||||||
srv.shutdown(shutdown, kill)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop instructs the type to halt operations and close
|
|
||||||
// the stop channel when it is finished.
|
|
||||||
//
|
|
||||||
// timeout is grace period for which to wait before shutting
|
|
||||||
// down the server. The timeout value passed here will override the
|
|
||||||
// timeout given when constructing the server, as this is an explicit
|
|
||||||
// command to stop the server.
|
|
||||||
func (srv *Server) Stop(timeout time.Duration) {
|
|
||||||
srv.Timeout = timeout
|
|
||||||
interrupt := srv.interruptChan()
|
|
||||||
interrupt <- syscall.SIGINT
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopChan gets the stop channel which will block until
|
|
||||||
// stopping has completed, at which point it is closed.
|
|
||||||
// Callers should never close the stop channel.
|
|
||||||
func (srv *Server) StopChan() <-chan struct{} {
|
|
||||||
srv.stopLock.Lock()
|
|
||||||
if srv.stopChan == nil {
|
|
||||||
srv.stopChan = make(chan struct{})
|
|
||||||
}
|
|
||||||
srv.stopLock.Unlock()
|
|
||||||
return srv.stopChan
|
|
||||||
}
|
|
||||||
|
|
||||||
func (srv *Server) manageConnections(add, remove chan net.Conn, shutdown chan chan struct{}, kill chan struct{}) {
|
|
||||||
{
|
|
||||||
var done chan struct{}
|
|
||||||
srv.connections = map[net.Conn]struct{}{}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case conn := <-add:
|
|
||||||
srv.connections[conn] = struct{}{}
|
|
||||||
case conn := <-remove:
|
|
||||||
delete(srv.connections, conn)
|
|
||||||
if done != nil && len(srv.connections) == 0 {
|
|
||||||
done <- struct{}{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case done = <-shutdown:
|
|
||||||
if len(srv.connections) == 0 {
|
|
||||||
done <- struct{}{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-kill:
|
|
||||||
for k := range srv.connections {
|
|
||||||
_ = k.Close() // nothing to do here if it errors
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (srv *Server) interruptChan() chan os.Signal {
|
|
||||||
srv.stopLock.Lock()
|
|
||||||
if srv.interrupt == nil {
|
|
||||||
srv.interrupt = make(chan os.Signal, 1)
|
|
||||||
}
|
|
||||||
srv.stopLock.Unlock()
|
|
||||||
|
|
||||||
return srv.interrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (srv *Server) handleInterrupt(interrupt chan os.Signal, listener net.Listener) {
|
|
||||||
<-interrupt
|
|
||||||
|
|
||||||
srv.SetKeepAlivesEnabled(false)
|
|
||||||
_ = listener.Close() // we are shutting down anyway. ignore error.
|
|
||||||
|
|
||||||
if srv.ShutdownInitiated != nil {
|
|
||||||
srv.ShutdownInitiated()
|
|
||||||
}
|
|
||||||
|
|
||||||
signal.Stop(interrupt)
|
|
||||||
close(interrupt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (srv *Server) shutdown(shutdown chan chan struct{}, kill chan struct{}) {
|
|
||||||
// Request done notification
|
|
||||||
done := make(chan struct{})
|
|
||||||
shutdown <- done
|
|
||||||
|
|
||||||
if srv.Timeout > 0 {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(srv.Timeout):
|
|
||||||
close(kill)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
<-done
|
|
||||||
}
|
|
||||||
// Close the stopChan to wake up any blocked goroutines.
|
|
||||||
srv.stopLock.Lock()
|
|
||||||
if srv.stopChan != nil {
|
|
||||||
close(srv.stopChan)
|
|
||||||
}
|
|
||||||
srv.stopLock.Unlock()
|
|
||||||
}
|
|
379
Godeps/_workspace/src/github.com/tylerb/graceful/graceful_test.go
generated
vendored
379
Godeps/_workspace/src/github.com/tylerb/graceful/graceful_test.go
generated
vendored
|
@ -1,379 +0,0 @@
|
||||||
package graceful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
killTime = 500 * time.Millisecond
|
|
||||||
timeoutTime = 1000 * time.Millisecond
|
|
||||||
waitTime = 100 * time.Millisecond
|
|
||||||
)
|
|
||||||
|
|
||||||
func runQuery(t *testing.T, expected int, shouldErr bool, wg *sync.WaitGroup, once *sync.Once) {
|
|
||||||
wg.Add(1)
|
|
||||||
defer wg.Done()
|
|
||||||
client := http.Client{}
|
|
||||||
r, err := client.Get("http://localhost:3000")
|
|
||||||
if shouldErr && err == nil {
|
|
||||||
once.Do(func() {
|
|
||||||
t.Fatal("Expected an error but none was encountered.")
|
|
||||||
})
|
|
||||||
} else if shouldErr && err != nil {
|
|
||||||
if checkErr(t, err, once) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r != nil && r.StatusCode != expected {
|
|
||||||
once.Do(func() {
|
|
||||||
t.Fatalf("Incorrect status code on response. Expected %d. Got %d", expected, r.StatusCode)
|
|
||||||
})
|
|
||||||
} else if r == nil {
|
|
||||||
once.Do(func() {
|
|
||||||
t.Fatal("No response when a response was expected.")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkErr(t *testing.T, err error, once *sync.Once) bool {
|
|
||||||
if err.(*url.Error).Err == io.EOF {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
errno := err.(*url.Error).Err.(*net.OpError).Err.(syscall.Errno)
|
|
||||||
if errno == syscall.ECONNREFUSED {
|
|
||||||
return true
|
|
||||||
} else if err != nil {
|
|
||||||
once.Do(func() {
|
|
||||||
t.Fatal("Error on Get:", err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func createListener(sleep time.Duration) (*http.Server, net.Listener, error) {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
|
||||||
time.Sleep(sleep)
|
|
||||||
rw.WriteHeader(http.StatusOK)
|
|
||||||
})
|
|
||||||
|
|
||||||
server := &http.Server{Addr: ":3000", Handler: mux}
|
|
||||||
l, err := net.Listen("tcp", ":3000")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
return server, l, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func runServer(timeout, sleep time.Duration, c chan os.Signal) error {
|
|
||||||
server, l, err := createListener(sleep)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &Server{Timeout: timeout, Server: server, interrupt: c}
|
|
||||||
return srv.Serve(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func launchTestQueries(t *testing.T, wg *sync.WaitGroup, c chan os.Signal) {
|
|
||||||
var once sync.Once
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
go runQuery(t, http.StatusOK, false, wg, &once)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
|
||||||
c <- os.Interrupt
|
|
||||||
time.Sleep(waitTime)
|
|
||||||
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
go runQuery(t, 0, true, wg, &once)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGracefulRun(t *testing.T) {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
runServer(killTime, killTime/2, c)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go launchTestQueries(t, &wg, c)
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGracefulRunTimesOut(t *testing.T) {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
runServer(killTime, killTime*10, c)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var once sync.Once
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
go runQuery(t, 0, true, &wg, &once)
|
|
||||||
}
|
|
||||||
time.Sleep(waitTime)
|
|
||||||
c <- os.Interrupt
|
|
||||||
time.Sleep(waitTime)
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
go runQuery(t, 0, true, &wg, &once)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGracefulRunDoesntTimeOut(t *testing.T) {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
runServer(0, killTime*2, c)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go launchTestQueries(t, &wg, c)
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGracefulRunNoRequests(t *testing.T) {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
runServer(0, killTime*2, c)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
c <- os.Interrupt
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGracefulForwardsConnState(t *testing.T) {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
states := make(map[http.ConnState]int)
|
|
||||||
var stateLock sync.Mutex
|
|
||||||
|
|
||||||
connState := func(conn net.Conn, state http.ConnState) {
|
|
||||||
stateLock.Lock()
|
|
||||||
states[state]++
|
|
||||||
stateLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
expected := map[http.ConnState]int{
|
|
||||||
http.StateNew: 8,
|
|
||||||
http.StateActive: 8,
|
|
||||||
http.StateClosed: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
server, l, _ := createListener(killTime / 2)
|
|
||||||
srv := &Server{
|
|
||||||
ConnState: connState,
|
|
||||||
Timeout: killTime,
|
|
||||||
Server: server,
|
|
||||||
interrupt: c,
|
|
||||||
}
|
|
||||||
srv.Serve(l)
|
|
||||||
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go launchTestQueries(t, &wg, c)
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
stateLock.Lock()
|
|
||||||
if !reflect.DeepEqual(states, expected) {
|
|
||||||
t.Errorf("Incorrect connection state tracking.\n actual: %v\nexpected: %v\n", states, expected)
|
|
||||||
}
|
|
||||||
stateLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGracefulExplicitStop(t *testing.T) {
|
|
||||||
server, l, err := createListener(1 * time.Millisecond)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &Server{Timeout: killTime, Server: server}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
go srv.Serve(l)
|
|
||||||
time.Sleep(waitTime)
|
|
||||||
srv.Stop(killTime)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// block on the stopChan until the server has shut down
|
|
||||||
select {
|
|
||||||
case <-srv.StopChan():
|
|
||||||
case <-time.After(timeoutTime):
|
|
||||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGracefulExplicitStopOverride(t *testing.T) {
|
|
||||||
server, l, err := createListener(1 * time.Millisecond)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &Server{Timeout: killTime, Server: server}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
go srv.Serve(l)
|
|
||||||
time.Sleep(waitTime)
|
|
||||||
srv.Stop(killTime / 2)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// block on the stopChan until the server has shut down
|
|
||||||
select {
|
|
||||||
case <-srv.StopChan():
|
|
||||||
case <-time.After(killTime):
|
|
||||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShutdownInitiatedCallback(t *testing.T) {
|
|
||||||
server, l, err := createListener(1 * time.Millisecond)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
called := make(chan struct{})
|
|
||||||
cb := func() { close(called) }
|
|
||||||
|
|
||||||
srv := &Server{Server: server, ShutdownInitiated: cb}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
go srv.Serve(l)
|
|
||||||
time.Sleep(waitTime)
|
|
||||||
srv.Stop(killTime)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-called:
|
|
||||||
case <-time.After(killTime):
|
|
||||||
t.Fatal("Timed out while waiting for ShutdownInitiated callback to be called")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func hijackingListener(srv *Server) (*http.Server, net.Listener, error) {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
|
||||||
conn, bufrw, err := rw.(http.Hijacker).Hijack()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(rw, "webserver doesn't support hijacking", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
bufrw.WriteString("HTTP/1.1 200 OK\r\n\r\n")
|
|
||||||
bufrw.Flush()
|
|
||||||
})
|
|
||||||
|
|
||||||
server := &http.Server{Addr: ":3000", Handler: mux}
|
|
||||||
l, err := net.Listen("tcp", ":3000")
|
|
||||||
return server, l, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotifyClosed(t *testing.T) {
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
srv := &Server{Timeout: killTime, interrupt: c}
|
|
||||||
server, l, err := hijackingListener(srv)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv.Server = server
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
srv.Serve(l)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var once sync.Once
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
runQuery(t, http.StatusOK, false, &wg, &once)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv.Stop(0)
|
|
||||||
|
|
||||||
// block on the stopChan until the server has shut down
|
|
||||||
select {
|
|
||||||
case <-srv.StopChan():
|
|
||||||
case <-time.After(timeoutTime):
|
|
||||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(srv.connections) > 0 {
|
|
||||||
t.Fatal("hijacked connections should not be managed")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStopDeadlock(t *testing.T) {
|
|
||||||
c := make(chan struct{})
|
|
||||||
|
|
||||||
server, l, err := createListener(1 * time.Millisecond)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &Server{Server: server, NoSignalHandling: true}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
time.Sleep(waitTime)
|
|
||||||
srv.Serve(l)
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
srv.Stop(0)
|
|
||||||
close(c)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-c:
|
|
||||||
case <-time.After(timeoutTime):
|
|
||||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
|
||||||
}
|
|
||||||
}
|
|
40
Godeps/_workspace/src/github.com/tylerb/graceful/tests/main.go
generated
vendored
40
Godeps/_workspace/src/github.com/tylerb/graceful/tests/main.go
generated
vendored
|
@ -1,40 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
|
||||||
"github.com/tylerb/graceful"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
wg.Add(3)
|
|
||||||
go func() {
|
|
||||||
n := negroni.New()
|
|
||||||
fmt.Println("Launching server on :3000")
|
|
||||||
graceful.Run(":3000", 0, n)
|
|
||||||
fmt.Println("Terminated server on :3000")
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
n := negroni.New()
|
|
||||||
fmt.Println("Launching server on :3001")
|
|
||||||
graceful.Run(":3001", 0, n)
|
|
||||||
fmt.Println("Terminated server on :3001")
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
n := negroni.New()
|
|
||||||
fmt.Println("Launching server on :3002")
|
|
||||||
graceful.Run(":3002", 0, n)
|
|
||||||
fmt.Println("Terminated server on :3002")
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
fmt.Println("Press ctrl+c. All servers should terminate.")
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
}
|
|
48
Godeps/_workspace/src/golang.org/x/net/netutil/listen.go
generated
vendored
48
Godeps/_workspace/src/golang.org/x/net/netutil/listen.go
generated
vendored
|
@ -1,48 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package netutil provides network utility functions, complementing the more
|
|
||||||
// common ones in the net package.
|
|
||||||
package netutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LimitListener returns a Listener that accepts at most n simultaneous
|
|
||||||
// connections from the provided Listener.
|
|
||||||
func LimitListener(l net.Listener, n int) net.Listener {
|
|
||||||
return &limitListener{l, make(chan struct{}, n)}
|
|
||||||
}
|
|
||||||
|
|
||||||
type limitListener struct {
|
|
||||||
net.Listener
|
|
||||||
sem chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *limitListener) acquire() { l.sem <- struct{}{} }
|
|
||||||
func (l *limitListener) release() { <-l.sem }
|
|
||||||
|
|
||||||
func (l *limitListener) Accept() (net.Conn, error) {
|
|
||||||
l.acquire()
|
|
||||||
c, err := l.Listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
l.release()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &limitListenerConn{Conn: c, release: l.release}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type limitListenerConn struct {
|
|
||||||
net.Conn
|
|
||||||
releaseOnce sync.Once
|
|
||||||
release func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *limitListenerConn) Close() error {
|
|
||||||
err := l.Conn.Close()
|
|
||||||
l.releaseOnce.Do(l.release)
|
|
||||||
return err
|
|
||||||
}
|
|
103
Godeps/_workspace/src/golang.org/x/net/netutil/listen_test.go
generated
vendored
103
Godeps/_workspace/src/golang.org/x/net/netutil/listen_test.go
generated
vendored
|
@ -1,103 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.3
|
|
||||||
|
|
||||||
// (We only run this test on Go 1.3 because the HTTP client timeout behavior
|
|
||||||
// was bad in previous releases, causing occasional deadlocks.)
|
|
||||||
|
|
||||||
package netutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLimitListener(t *testing.T) {
|
|
||||||
const (
|
|
||||||
max = 5
|
|
||||||
num = 200
|
|
||||||
)
|
|
||||||
|
|
||||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Listen: %v", err)
|
|
||||||
}
|
|
||||||
defer l.Close()
|
|
||||||
l = LimitListener(l, max)
|
|
||||||
|
|
||||||
var open int32
|
|
||||||
go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if n := atomic.AddInt32(&open, 1); n > max {
|
|
||||||
t.Errorf("%d open connections, want <= %d", n, max)
|
|
||||||
}
|
|
||||||
defer atomic.AddInt32(&open, -1)
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
fmt.Fprint(w, "some body")
|
|
||||||
}))
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var failed int32
|
|
||||||
for i := 0; i < num; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
c := http.Client{Timeout: 3 * time.Second}
|
|
||||||
r, err := c.Get("http://" + l.Addr().String())
|
|
||||||
if err != nil {
|
|
||||||
t.Logf("Get: %v", err)
|
|
||||||
atomic.AddInt32(&failed, 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
io.Copy(ioutil.Discard, r.Body)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
// We expect some Gets to fail as the kernel's accept queue is filled,
|
|
||||||
// but most should succeed.
|
|
||||||
if failed >= num/2 {
|
|
||||||
t.Errorf("too many Gets failed: %v", failed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorListener struct {
|
|
||||||
net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (errorListener) Accept() (net.Conn, error) {
|
|
||||||
return nil, errFake
|
|
||||||
}
|
|
||||||
|
|
||||||
var errFake = errors.New("fake error from errorListener")
|
|
||||||
|
|
||||||
// This used to hang.
|
|
||||||
func TestLimitListenerError(t *testing.T) {
|
|
||||||
donec := make(chan bool, 1)
|
|
||||||
go func() {
|
|
||||||
const n = 2
|
|
||||||
ll := LimitListener(errorListener{}, n)
|
|
||||||
for i := 0; i < n+1; i++ {
|
|
||||||
_, err := ll.Accept()
|
|
||||||
if err != errFake {
|
|
||||||
t.Fatalf("Accept error = %v; want errFake", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
donec <- true
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-donec:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("timeout. deadlock?")
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue