OSDN Git Service

feat(warder): add warder backbone (#181)
authorHAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
Tue, 25 Jun 2019 11:50:41 +0000 (19:50 +0800)
committerPaladz <yzhu101@uottawa.ca>
Tue, 25 Jun 2019 11:50:41 +0000 (19:50 +0800)
* init warder

* init

* add

* gogogo

* add

* dododo

* gogogo

* add validateTx

* add

* build tx

* update

* check

* dododod

* fk

aaaaaaaaaa

* update check

* add colect_minutes

* fix

* init collect

* what can i say

* find

* find

* update

* ???

* zhen xiang

* clean

* fix

* dodo

* clean

* update

* fix

* fix typo

* clean

* update

* add service.Warder

* hostPort

* path

* fix

* dododo

* add add add

* add

* add

* update

* add

* update

* update

* update

* fix https://github.com/Bytom/vapor/pull/181#discussion_r294070592

* fix https://github.com/Bytom/vapor/pull/181#discussion_r294070868

* remove blank line

* fix logic for https://github.com/Bytom/vapor/pull/181#discussion_r294070592

* xpr

* dododo

* decode fedprog

* add todos

* sort

* sep

* checked

* temp

* clean

* ???

* rename

* clean

* add time_range

* pause

* pause again

* can compile

* do

* use https://github.com/Bytom/vapor/pull/181#pullrequestreview-250219072 3

* add input

* parse

* add fedProg

* add

* fix

* di

* clean

* fix

* clean

* add todo

* what can i sat

* clean

* combine

* fix

* renew

* add type SigningInstruction

* comment out useless

* revert

* rename to assetStore

* update

* dododo

* fj

* add output

* type

* init

* update

* emm

* dododo

* update

* clean up

* addInputWitness

* init

* fix sidechainKeeper.getCrossChainReqs()

* opt

* opt

* rename

* clean

* clean

* fix

* add

* fix

* clean

* renew

* merge

* clean

* fix https://github.com/Bytom/vapor/pull/181#discussion_r295148126

* add todo

* rename

* rename GetByAssetID

* fmtKey

* fix cache query

* clean

* fix no valid transaction

* fix

* rename

* fix

* fix

* revert

* clean

* fix

* fix

* fix collect

* clean

* add tryProcess

* pause

* init

* draft

* fix

* draft

* more

* sign

* rearrange

* clean

* clean

* what can i say

* redesign

* fix getSignData

* fix getSigns

* init finalizeTx

* add labels

* add  getInputsCnt

* ???

* clean

* wtf

* good

* clean

* clean

* clean

* good

* fix

* clean

* clean

* miao

* update

* clean

* init api.NewServer

* init

* minor

* comment out synchron first

* rename

* init

* add venfor

* clean up

* add listener

* add middleware

* add

* init handlerMiddleware

* add

* fix typo

* update

* update

* clean

* update

* clean

* updare

* add

* add todos

* add

* do

* update

* add

* update

* update

* clean up

* clean

* clean

* temp clean

* clean up

* clean

* clean

* clean

* clean up

* clean

* clean

* fix

* clean

* combine

* clean

* add github.com/mattn/go-isatty

* add github.com/ugorji/go/codec

* add gopkg.in/go-playground/validator.v8

* clean

* temp

* clean

* init

* init

* add minor

* add json

* ???

* temp

* clean

* fix json

* fix json

* clean

* add

* add

* add

* add

* clean

* clean

* clean

* move

* fix https://github.com/Bytom/vapor/pull/181#discussion_r297058765

* fix

169 files changed:
cmd/fedd/main.go
docs/federation/README-en.md
docs/federation/federation.sql
federation/api/common.go [new file with mode: 0644]
federation/api/display.go [new file with mode: 0644]
federation/api/errors.go [new file with mode: 0644]
federation/api/handler.go [new file with mode: 0644]
federation/api/pagination.go [new file with mode: 0644]
federation/api/response.go [new file with mode: 0644]
federation/api/server.go [new file with mode: 0644]
federation/common/const.go
federation/config/config.go
federation/database/asset_store.go [new file with mode: 0644]
federation/database/cache.go [deleted file]
federation/database/orm/asset.go
federation/database/orm/cross_transaction.go
federation/database/orm/cross_transaction_req.go
federation/database/orm/cross_transaction_sign.go [deleted file]
federation/database/orm/warder.go [deleted file]
federation/service/node.go
federation/synchron/mainchain_keeper.go
federation/synchron/sidechain_keeper.go
federation/util/script.go [new file with mode: 0644]
protocol/validation/tx.go
vendor/github.com/gin-contrib/sse/LICENSE [new file with mode: 0644]
vendor/github.com/gin-contrib/sse/README.md [new file with mode: 0644]
vendor/github.com/gin-contrib/sse/sse-decoder.go [new file with mode: 0644]
vendor/github.com/gin-contrib/sse/sse-encoder.go [new file with mode: 0644]
vendor/github.com/gin-contrib/sse/writer.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/AUTHORS.md [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/BENCHMARKS.md [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/CHANGELOG.md [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/CODE_OF_CONDUCT.md [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/CONTRIBUTING.md [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/LICENSE [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/Makefile [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/README.md [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/auth.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/binding.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/default_validator.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/form.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/form_mapping.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/json.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/msgpack.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/protobuf.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/query.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/binding/xml.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/codecov.yml [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/context.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/context_appengine.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/coverage.sh [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/debug.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/deprecated.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/doc.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/errors.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/fs.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/gin.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/json/json.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/json/jsoniter.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/logger.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/mode.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/path.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/recovery.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/render/data.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/render/html.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/render/json.go [new file with mode: 0755]
vendor/github.com/gin-gonic/gin/render/msgpack.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/render/reader.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/render/redirect.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/render/render.go [new file with mode: 0755]
vendor/github.com/gin-gonic/gin/render/text.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/render/xml.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/render/yaml.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/response_writer.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/response_writer_1.7.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/response_writer_1.8.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/routergroup.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/test_helpers.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/tree.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/utils.go [new file with mode: 0644]
vendor/github.com/gin-gonic/gin/wercker.yml [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/.travis.yml [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/LICENSE [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/README.md [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/doc.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/example_test.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/isatty_bsd.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/isatty_linux.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/isatty_others.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/isatty_others_test.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/isatty_solaris.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/isatty_windows.go [new file with mode: 0644]
vendor/github.com/mattn/go-isatty/isatty_windows_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/0doc.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/README.md [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/binc.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/build.sh [new file with mode: 0755]
vendor/github.com/ugorji/go/codec/cbor.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/cbor_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/codec_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/codecgen.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/codecgen/README.md [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/codecgen/gen.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/codecgen/goversion_pkgpath_gte_go111.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/codecgen/goversion_pkgpath_lt_go111.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/codecgen/z.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/decode.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/encode.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/fast-path.generated.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/fast-path.go.tmpl [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/fast-path.not.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/gen-dec-array.go.tmpl [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/gen-dec-map.go.tmpl [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/gen-enc-chan.go.tmpl [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/gen-helper.generated.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/gen-helper.go.tmpl [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/gen.generated.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/gen.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/go.mod [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_arrayof_gte_go15.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_arrayof_lt_go15.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_makemap_gte_go19.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_makemap_lt_go19.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_gte_go110.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_unexportedembeddedptr_lt_go110.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_unsupported_lt_go14.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go15.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_vendor_eq_go16.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_vendor_gte_go17.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/goversion_vendor_lt_go15.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/helper.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/helper_internal.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/helper_not_unsafe.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/helper_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/helper_unsafe.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/json.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/mammoth-test.go.tmpl [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/mammoth2-test.go.tmpl [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/mammoth2_codecgen_generated_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/mammoth2_generated_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/mammoth_generated_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/msgpack.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/py_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/rpc.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/shared_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/simple.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/test-cbor-goldens.json [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/test.py [new file with mode: 0755]
vendor/github.com/ugorji/go/codec/values_flex_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/values_test.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/xml.go [new file with mode: 0644]
vendor/github.com/ugorji/go/codec/z_all_test.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/.gitignore [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/LICENSE [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/README.md [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/baked_in.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/benchmarks_test.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/cache.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/doc.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/examples/custom/custom.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/examples/simple/simple.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/examples/struct-level/struct_level.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/examples_test.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/logo.png [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/regexes.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/util.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/validator.go [new file with mode: 0644]
vendor/gopkg.in/go-playground/validator.v8/validator_test.go [new file with mode: 0644]

index 2ba8ec6..28d16cc 100644 (file)
@@ -5,6 +5,7 @@ import (
 
        log "github.com/sirupsen/logrus"
 
+       "github.com/vapor/federation/api"
        "github.com/vapor/federation/config"
        "github.com/vapor/federation/database"
        "github.com/vapor/federation/synchron"
@@ -17,8 +18,10 @@ func main() {
                log.WithField("err", err).Panic("initialize mysql db error")
        }
 
-       go synchron.NewMainchainKeeper(db, &cfg.Mainchain).Run()
-       go synchron.NewSidechainKeeper(db, &cfg.Sidechain).Run()
+       assetStore := database.NewAssetStore(db)
+       go synchron.NewMainchainKeeper(db, assetStore, cfg).Run()
+       go synchron.NewSidechainKeeper(db, assetStore, cfg).Run()
+       go api.NewServer(db, cfg).Run()
 
        // keep the main func running in case of terminating goroutines
        var wg sync.WaitGroup
index c6e05b9..cbec063 100644 (file)
@@ -11,7 +11,7 @@ A `fed_cfg.json` would look like this:
 
 ```json
 {
-    "gin-gonic" : {
+    "api" : {
         "listening_port" : 3000,
         "is_release_mode": false
     },
@@ -28,23 +28,10 @@ A `fed_cfg.json` would look like this:
     "warders" : [
         {
             "position" : 1,
-            "xpub" : "7f23aae65ee4307c38d342699e328f21834488e18191ebd66823d220b5a58303496c9d09731784372bade78d5e9a4a6249b2cfe2e3a85464e5a4017aa5611e47",
-            "host_port" : "192.168.0.2:3000",
-            "is_local" : false
-        },
-        {
-            "position" : 1,
-            "xpub" : "585e20143db413e45fbc82f03cb61f177e9916ef1df0012daa8cbf6dbb1025ce8f98e51ae319327b63505b64fdbbf6d36ef916d79e6dd67d51b0bfe76fe544c5",
-            "host_port" : "127.0.0.1:3000",
-            "is_local" : true
-        },
-        {
-            "position" : 1,
-            "xpub" : "b58170b51ca61604028ba1cb412377dfc2bc6567c0afc84c83aae1c0c297d0227ccf568561df70851f4144bbf069b525129f2434133c145e35949375b22a6c9d",
-            "host_port" : "192.168.0.3:3000",
-            "is_local" : false
+            "xpub" : "50ef22b3a3fca7bc08916187cc9ec2f4005c9c6b1353aa1decbd4be3f3bb0fbe1967589f0d9dec13a388c0412002d2c267bdf3b920864e1ddc50581be5604ce1"
         }
     ],
+    "quorum": 1,
     "mainchain" : {
         "name" : "bytom",
         "confirmations" : 10,
index b2613c5..fe5c668 100644 (file)
@@ -12,21 +12,6 @@ CREATE SCHEMA IF NOT EXISTS `federation`;
 
 USE `federation`;
 
-# Dump of table warders
-# ------------------------------------------------------------
-
-CREATE TABLE `warders` (
-  `id` tinyint(1) NOT NULL AUTO_INCREMENT,
-  `pubkey` varchar(64) NOT NULL,
-  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
-  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `pubkey` (`pubkey`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-LOCK TABLES `warders` WRITE;
-UNLOCK TABLES;
-
 
 # Dump of table chains
 # ------------------------------------------------------------
@@ -110,26 +95,6 @@ LOCK TABLES `cross_transaction_reqs` WRITE;
 UNLOCK TABLES;
 
 
-# Dump of table cross_transaction_signs
-# ------------------------------------------------------------
-CREATE TABLE `cross_transaction_signs` (
-  `id` int(11) NOT NULL AUTO_INCREMENT,
-  `cross_transaction_id` int(11) NOT NULL,
-  `warder_id` tinyint(1) NOT NULL,
-  `signatures` text NOT NULL,
-  `status` tinyint(1) NOT NULL,
-  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `sign_id` (`cross_transaction_id`,`warder_id`),
-  CONSTRAINT `cross_transaction_signs_ibfk_1` FOREIGN KEY (`warder_id`) REFERENCES `warders` (`id`),
-  CONSTRAINT `cross_transaction_signs_ibfk_2` FOREIGN KEY (`cross_transaction_id`) REFERENCES `cross_transactions` (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-LOCK TABLES `cross_transaction_signs` WRITE;
-UNLOCK TABLES;
-
-
 # Dump of table assets
 # ------------------------------------------------------------
 
diff --git a/federation/api/common.go b/federation/api/common.go
new file mode 100644 (file)
index 0000000..161667a
--- /dev/null
@@ -0,0 +1,18 @@
+package api
+
+import (
+       "reflect"
+
+       "github.com/gin-gonic/gin"
+)
+
+const (
+       serverLabel  = "server_label"
+       reqBodyLabel = "request_body_label"
+)
+
+var (
+       errorType           = reflect.TypeOf((*error)(nil)).Elem()
+       contextType         = reflect.TypeOf((*gin.Context)(nil))
+       paginationQueryType = reflect.TypeOf((*PaginationQuery)(nil))
+)
diff --git a/federation/api/display.go b/federation/api/display.go
new file mode 100644 (file)
index 0000000..fed1506
--- /dev/null
@@ -0,0 +1,58 @@
+package api
+
+import (
+       "github.com/vapor/errors"
+)
+
+var (
+       errMissingFilterKey  = errors.New("missing filter key")
+       errInvalidFilterType = errors.New("invalid filter type")
+)
+
+// Display defines how the data is displayed
+type Display struct {
+       Filter map[string]interface{} `json:"filter"`
+       Sorter Sorter                 `json:"sort"`
+}
+
+type Sorter struct {
+       By    string `json:"by"`
+       Order string `json:"order"`
+}
+
+// GetFilterString give the filter keyword return the string value
+func (d *Display) GetFilterString(filterKey string) (string, error) {
+       if _, ok := d.Filter[filterKey]; !ok {
+               return "", errMissingFilterKey
+       }
+       switch val := d.Filter[filterKey].(type) {
+       case string:
+               return val, nil
+       }
+       return "", errInvalidFilterType
+}
+
+// GetFilterNum give the filter keyword return the numeric value
+func (d *Display) GetFilterNum(filterKey string) (interface{}, error) {
+       if _, ok := d.Filter[filterKey]; !ok {
+               return 0, errMissingFilterKey
+       }
+       switch val := d.Filter[filterKey].(type) {
+       case int, int16, int32, int64, int8, uint, uint16, uint32, uint64, uint8, float32, float64:
+               return val, nil
+       }
+
+       return 0, errInvalidFilterType
+}
+
+// GetFilterBoolean give the filter keyword return the boolean value
+func (d *Display) GetFilterBoolean(filterKey string) (bool, error) {
+       if _, ok := d.Filter[filterKey]; !ok {
+               return false, errMissingFilterKey
+       }
+       switch val := d.Filter[filterKey].(type) {
+       case bool:
+               return val, nil
+       }
+       return false, errInvalidFilterType
+}
diff --git a/federation/api/errors.go b/federation/api/errors.go
new file mode 100644 (file)
index 0000000..f216325
--- /dev/null
@@ -0,0 +1,23 @@
+package api
+
+import (
+       "github.com/vapor/errors"
+)
+
+//FormatErrResp format error response
+func formatErrResp(err error) response {
+       // default error response
+       response := response{
+               Code: 300,
+               Msg:  "request error",
+       }
+
+       root := errors.Root(err)
+       if errCode, ok := respErrFormatter[root]; ok {
+               response.Code = errCode
+               response.Msg = root.Error()
+       }
+       return response
+}
+
+var respErrFormatter = map[error]int{}
diff --git a/federation/api/handler.go b/federation/api/handler.go
new file mode 100644 (file)
index 0000000..f4a2515
--- /dev/null
@@ -0,0 +1,45 @@
+package api
+
+import (
+       "database/sql"
+       "fmt"
+
+       "github.com/gin-gonic/gin"
+
+       "github.com/vapor/errors"
+       "github.com/vapor/federation/common"
+       "github.com/vapor/federation/database/orm"
+)
+
+type listCrosschainTxsReq struct{ Display }
+
+func (s *Server) ListCrosschainTxs(c *gin.Context, listTxsReq *listCrosschainTxsReq, query *PaginationQuery) ([]*orm.CrossTransaction, error) {
+       var ormTxs []*orm.CrossTransaction
+       txFilter := &orm.CrossTransaction{}
+       if listPending, err := listTxsReq.GetFilterBoolean("list_pending"); err == nil && listPending {
+               txFilter.Status = common.CrossTxPendingStatus
+       }
+       if listCompleted, err := listTxsReq.GetFilterBoolean("list_completed"); err == nil && listCompleted {
+               txFilter.Status = common.CrossTxCompletedStatus
+       }
+       if txHash, err := listTxsReq.GetFilterString("source_tx_hash"); err == nil && txHash != "" {
+               txFilter.SourceTxHash = txHash
+       }
+       if txHash, err := listTxsReq.GetFilterString("dest_tx_hash"); err == nil && txHash != "" {
+               txFilter.DestTxHash = sql.NullString{txHash, true}
+       }
+       txQuery := s.db.Preload("Chain").Preload("Reqs").Preload("Reqs.Asset").Where(txFilter)
+       if onlyFromMainchain, err := listTxsReq.GetFilterBoolean("only_from_mainchain"); err == nil && onlyFromMainchain {
+               txQuery = txQuery.Joins("join chains on chains.id = cross_transactions.chain_id").Where("chains.name = ?", common.MainchainName)
+       }
+       if onlyFromSidechain, err := listTxsReq.GetFilterBoolean("only_from_sidechain"); err == nil && onlyFromSidechain {
+               txQuery = txQuery.Joins("join chains on chains.id = cross_transactions.chain_id").Where("chains.name = ?", common.SidechainName)
+       }
+       txQuery = txQuery.Order(fmt.Sprintf("cross_transactions.source_block_height %s", listTxsReq.Sorter.Order))
+       txQuery = txQuery.Order(fmt.Sprintf("cross_transactions.source_tx_index %s", listTxsReq.Sorter.Order))
+       if err := txQuery.Offset(query.Start).Limit(query.Limit).Find(&ormTxs).Error; err != nil {
+               return nil, errors.Wrap(err, "query txs")
+       }
+
+       return ormTxs, nil
+}
diff --git a/federation/api/pagination.go b/federation/api/pagination.go
new file mode 100644 (file)
index 0000000..f93c1bd
--- /dev/null
@@ -0,0 +1,73 @@
+package api
+
+import (
+       "fmt"
+       "reflect"
+       "strconv"
+
+       "github.com/gin-gonic/gin"
+
+       "github.com/vapor/errors"
+)
+
+const (
+       defaultSatrtStr = "0"
+       defaultLimitStr = "10"
+       maxPageLimit    = 1000
+)
+
+var (
+       errParsePaginationStart = fmt.Errorf("parse pagination start")
+       errParsePaginationLimit = fmt.Errorf("parse pagination limit")
+)
+
+type PaginationQuery struct {
+       Start uint64 `json:"start"`
+       Limit uint64 `json:"limit"`
+}
+
+// parsePagination request meets the standard on https://developer.atlassian.com/server/confluence/pagination-in-the-rest-api/
+func parsePagination(c *gin.Context) (*PaginationQuery, error) {
+       startStr := c.DefaultQuery("start", defaultSatrtStr)
+       limitStr := c.DefaultQuery("limit", defaultLimitStr)
+
+       start, err := strconv.ParseUint(startStr, 10, 64)
+       if err != nil {
+               return nil, errors.Wrap(err, errParsePaginationStart)
+       }
+
+       limit, err := strconv.ParseUint(limitStr, 10, 64)
+       if err != nil {
+               return nil, errors.Wrap(err, errParsePaginationLimit)
+       }
+
+       if limit > maxPageLimit {
+               limit = maxPageLimit
+       }
+
+       return &PaginationQuery{
+               Start: start,
+               Limit: limit,
+       }, nil
+}
+
+type PaginationInfo struct {
+       Start   uint64
+       Limit   uint64
+       HasNext bool
+}
+
+func processPaginationIfPresent(fun handlerFun, args []interface{}, result []interface{}, context *gin.Context) bool {
+       ft := reflect.TypeOf(fun)
+       if ft.NumIn() != 3 {
+               return false
+       }
+
+       list := result[0]
+       size := reflect.ValueOf(list).Len()
+       query := args[2].(*PaginationQuery)
+
+       paginationInfo := &PaginationInfo{Start: query.Start, Limit: query.Limit, HasNext: size == int(query.Limit)}
+       respondSuccessPaginationResp(context, list, paginationInfo)
+       return true
+}
diff --git a/federation/api/response.go b/federation/api/response.go
new file mode 100644 (file)
index 0000000..ebb9874
--- /dev/null
@@ -0,0 +1,65 @@
+package api
+
+import (
+       "fmt"
+       "net/http"
+       "strings"
+
+       "github.com/gin-gonic/gin"
+       log "github.com/sirupsen/logrus"
+)
+
+// response describes the response standard. Code & Msg are always present.
+// Data is present for a success response only.
+type response struct {
+       Code   int                    `json:"code"`
+       Msg    string                 `json:"msg"`
+       Result map[string]interface{} `json:"result,omitempty"`
+}
+
+func respondErrorResp(c *gin.Context, err error) {
+       log.WithFields(log.Fields{
+               "url":     c.Request.URL,
+               "request": c.Value(reqBodyLabel),
+               "err":     err,
+       }).Error("request fail")
+       resp := formatErrResp(err)
+       c.AbortWithStatusJSON(http.StatusOK, resp)
+}
+
+func respondSuccessResp(c *gin.Context, data interface{}) {
+       result := make(map[string]interface{})
+       result["data"] = data
+       c.AbortWithStatusJSON(http.StatusOK, response{Code: 200, Result: result})
+}
+
+type links struct {
+       Next string `json:"next,omitempty"`
+}
+
+func respondSuccessPaginationResp(c *gin.Context, data interface{}, paginationInfo *PaginationInfo) {
+       url := fmt.Sprintf("%v", c.Request.URL)
+       base := strings.Split(url, "?")[0]
+       start := paginationInfo.Start
+       limit := paginationInfo.Limit
+
+       l := links{}
+       if paginationInfo.HasNext {
+               // To efficiently build a string using Write methods
+               // https://stackoverflow.com/questions/1760757/how-to-efficiently-concatenate-strings-in-go
+               // https://tip.golang.org/pkg/strings/#Builder
+               var b strings.Builder
+               fmt.Fprintf(&b, "%s?limit=%d&start=%d", base, limit, start+limit)
+               l.Next = b.String()
+       }
+       result := make(map[string]interface{})
+       result["data"] = data
+       result["start"] = start
+       result["limit"] = limit
+       result["_links"] = l
+
+       c.AbortWithStatusJSON(http.StatusOK, response{
+               Code:   http.StatusOK,
+               Result: result,
+       })
+}
diff --git a/federation/api/server.go b/federation/api/server.go
new file mode 100644 (file)
index 0000000..330b1de
--- /dev/null
@@ -0,0 +1,231 @@
+package api
+
+import (
+       "encoding/json"
+       "fmt"
+       "net/http"
+       "reflect"
+       "strings"
+
+       "github.com/gin-gonic/gin"
+       "github.com/jinzhu/gorm"
+
+       "github.com/vapor/errors"
+       "github.com/vapor/federation/config"
+)
+
+type Server struct {
+       cfg    *config.Config
+       db     *gorm.DB
+       engine *gin.Engine
+}
+
+func NewServer(db *gorm.DB, cfg *config.Config) *Server {
+       server := &Server{
+               cfg: cfg,
+               db:  db,
+       }
+       if cfg.API.IsReleaseMode {
+               gin.SetMode(gin.ReleaseMode)
+       }
+       server.setupRouter()
+       return server
+}
+
+func (server *Server) setupRouter() {
+       r := gin.Default()
+       r.Use(server.middleware())
+
+       v1 := r.Group("/api/v1")
+       v1.POST("/federation/list-crosschain-txs", handlerMiddleware(server.ListCrosschainTxs))
+
+       server.engine = r
+}
+
+func (s *Server) Run() {
+       s.engine.Run(fmt.Sprintf(":%d", s.cfg.API.ListeningPort))
+}
+
+func (s *Server) middleware() gin.HandlerFunc {
+       return func(c *gin.Context) {
+               // add Access-Control-Allow-Origin
+               c.Header("Access-Control-Allow-Origin", "*")
+               c.Header("Access-Control-Allow-Headers", "Content-Type")
+               c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
+               if c.Request.Method == "OPTIONS" {
+                       c.AbortWithStatus(http.StatusOK)
+                       return
+               }
+
+               c.Set(serverLabel, s)
+               c.Next()
+       }
+}
+
+type handlerFun interface{}
+
+func handlerMiddleware(handleFunc interface{}) func(*gin.Context) {
+       if err := validateFuncType(handleFunc); err != nil {
+               panic(err)
+       }
+
+       return func(context *gin.Context) {
+               handleRequest(context, handleFunc)
+       }
+}
+
+func validateFuncType(fun handlerFun) error {
+       ft := reflect.TypeOf(fun)
+       if ft.Kind() != reflect.Func || ft.IsVariadic() {
+               return errors.New("need nonvariadic func in " + ft.String())
+       }
+
+       if ft.NumIn() < 1 || ft.NumIn() > 3 {
+               return errors.New("need one or two or three parameters in " + ft.String())
+       }
+
+       if ft.In(0) != contextType {
+               return errors.New("the first parameter must point of context in " + ft.String())
+       }
+
+       if ft.NumIn() == 2 && ft.In(1).Kind() != reflect.Ptr {
+               return errors.New("the second parameter must point in " + ft.String())
+       }
+
+       if ft.NumIn() == 3 && ft.In(2) != paginationQueryType {
+               return errors.New("the third parameter of pagination must point of paginationQuery in " + ft.String())
+       }
+
+       if ft.NumOut() < 1 || ft.NumOut() > 2 {
+               return errors.New("the size of return value must one or two in " + ft.String())
+       }
+
+       // if has pagination, the first return value must slice or array
+       if ft.NumIn() == 3 && ft.Out(0).Kind() != reflect.Slice && ft.Out(0).Kind() != reflect.Array {
+               return errors.New("the first return value of pagination must slice of array in " + ft.String())
+       }
+
+       if !ft.Out(ft.NumOut() - 1).Implements(errorType) {
+               return errors.New("the last return value must error in " + ft.String())
+       }
+       return nil
+}
+
+// handleRequest get a handler function to process the request by request url
+func handleRequest(context *gin.Context, fun handlerFun) {
+       args, err := buildHandleFuncArgs(fun, context)
+       if err != nil {
+               respondErrorResp(context, err)
+               return
+       }
+
+       result := callHandleFunc(fun, args...)
+       if err := result[len(result)-1]; err != nil {
+               respondErrorResp(context, err.(error))
+               return
+       }
+
+       if exist := processPaginationIfPresent(fun, args, result, context); exist {
+               return
+       }
+
+       if len(result) == 1 {
+               respondSuccessResp(context, nil)
+               return
+       }
+
+       respondSuccessResp(context, result[0])
+}
+
+func buildHandleFuncArgs(fun handlerFun, context *gin.Context) ([]interface{}, error) {
+       args := []interface{}{context}
+
+       req, err := createHandleReqArg(fun, context)
+       if err != nil {
+               return nil, errors.Wrap(err, "createHandleReqArg")
+       }
+
+       if err := checkDisplayOrder(req); err != nil {
+               return nil, err
+       }
+
+       if req != nil {
+               args = append(args, req)
+       }
+
+       ft := reflect.TypeOf(fun)
+
+       // no pagination exists
+       if ft.NumIn() != 3 {
+               return args, nil
+       }
+
+       query, err := parsePagination(context)
+       if err != nil {
+               return nil, errors.Wrap(err, "ParsePagination")
+       }
+
+       args = append(args, query)
+       return args, nil
+}
+
+func createHandleReqArg(fun handlerFun, context *gin.Context) (interface{}, error) {
+       ft := reflect.TypeOf(fun)
+       if ft.NumIn() == 1 {
+               return nil, nil
+       }
+       argType := ft.In(1).Elem()
+
+       reqArg := reflect.New(argType).Interface()
+       if err := context.ShouldBindJSON(reqArg); err != nil {
+               return nil, errors.Wrap(err, "bind reqArg")
+       }
+
+       b, err := json.Marshal(reqArg)
+       if err != nil {
+               return nil, errors.Wrap(err, "json marshal")
+       }
+
+       context.Set(reqBodyLabel, string(b))
+
+       return reqArg, nil
+}
+
+func checkDisplayOrder(req interface{}) error {
+       if req == nil {
+               return nil
+       }
+
+       reqType := reflect.TypeOf(req).Elem()
+       reqVal := reflect.ValueOf(req).Elem()
+
+       for i := 0; i < reqType.NumField(); i++ {
+               field := reqType.Field(i)
+               if field.Type != reflect.TypeOf(Display{}) {
+                       continue
+               }
+               display := reqVal.Field(i).Interface().(Display)
+
+               order := strings.Trim(display.Sorter.Order, "")
+               if order != "desc" && order != "asc" {
+                       reqVal.Field(i).Set(reflect.ValueOf(Display{Filter: display.Filter, Sorter: Sorter{By: display.Sorter.By, Order: "desc"}}))
+               }
+       }
+       return nil
+}
+
+func callHandleFunc(fun handlerFun, args ...interface{}) []interface{} {
+       fv := reflect.ValueOf(fun)
+
+       params := make([]reflect.Value, len(args))
+       for i, arg := range args {
+               params[i] = reflect.ValueOf(arg)
+       }
+
+       rs := fv.Call(params)
+       result := make([]interface{}, len(rs))
+       for i, r := range rs {
+               result[i] = r.Interface()
+       }
+       return result
+}
index e843f9e..ccedfb1 100644 (file)
@@ -1,14 +1,12 @@
 package common
 
 const (
-       CrossTxPendingStatus uint8 = iota
-       CrossTxRejectedStatus
-       CrossTxSubmittedStatus
-       CrossTxCompletedStatus
+       MainchainName = "bytom"
+       SidechainName = "vapor"
 )
 
 const (
-       CrossTxSignPendingStatus uint8 = iota
-       CrossTxSignCompletedStatus
-       CrossTxSignRejectedStatus
+       _ uint8 = iota
+       CrossTxPendingStatus
+       CrossTxCompletedStatus
 )
index d09a394..358eee5 100644 (file)
@@ -33,14 +33,15 @@ func NewConfigWithPath(path string) *Config {
 }
 
 type Config struct {
-       GinGonic    GinGonic    `json:"gin-gonic"`
+       API         API         `json:"api"`
        MySQLConfig MySQLConfig `json:"mysql"`
        Warders     []Warder    `json:"warders"`
+       Quorum      int         `json:"quorum"`
        Mainchain   Chain       `json:"mainchain"`
        Sidechain   Chain       `json:"sidechain"`
 }
 
-type GinGonic struct {
+type API struct {
        ListeningPort uint64 `json:"listening_port"`
        IsReleaseMode bool   `json:"is_release_mode"`
 }
@@ -61,8 +62,6 @@ type MySQLConnection struct {
 type Warder struct {
        Position uint8        `json:"position"`
        XPub     chainkd.XPub `json:"xpub"`
-       HostPort string       `json:"host_port"`
-       IsLocal  bool         `json:"is_local"`
 }
 
 type Chain struct {
diff --git a/federation/database/asset_store.go b/federation/database/asset_store.go
new file mode 100644 (file)
index 0000000..6c32e95
--- /dev/null
@@ -0,0 +1,78 @@
+package database
+
+import (
+       "fmt"
+
+       "github.com/golang/groupcache/lru"
+       "github.com/jinzhu/gorm"
+
+       "github.com/vapor/errors"
+       "github.com/vapor/federation/database/orm"
+)
+
+const (
+       maxAssetCached = 1024
+
+       ormIDPrefix   = "ormID"
+       assetIDPrefix = "assetID"
+)
+
+func fmtOrmIDKey(ormID uint64) string {
+       return fmt.Sprintf("%s:%d", ormIDPrefix, ormID)
+}
+
+func fmtAssetIDKey(assetID string) string {
+       return fmt.Sprintf("%s:%s", assetIDPrefix, assetID)
+}
+
+type AssetStore struct {
+       cache *lru.Cache
+       db    *gorm.DB
+}
+
+func NewAssetStore(db *gorm.DB) *AssetStore {
+       return &AssetStore{
+               cache: lru.New(maxAssetCached),
+               db:    db,
+       }
+}
+
+func (a *AssetStore) GetByOrmID(ormID uint64) (*orm.Asset, error) {
+       if v, ok := a.cache.Get(fmtOrmIDKey(ormID)); ok {
+               return v.(*orm.Asset), nil
+       }
+
+       asset := &orm.Asset{ID: ormID}
+       if err := a.db.Where(asset).First(asset).Error; err != nil {
+               return nil, errors.Wrap(err, "asset not found by orm id")
+       }
+
+       a.cache.Add(fmtOrmIDKey(asset.ID), asset)
+       a.cache.Add(fmtAssetIDKey(asset.AssetID), asset)
+       return asset, nil
+}
+
+func (a *AssetStore) GetByAssetID(assetID string) (*orm.Asset, error) {
+       if v, ok := a.cache.Get(fmtAssetIDKey(assetID)); ok {
+               return v.(*orm.Asset), nil
+       }
+
+       asset := &orm.Asset{AssetID: assetID}
+       if err := a.db.Where(asset).First(asset).Error; err != nil {
+               return nil, errors.Wrap(err, "asset not found in memory and mysql")
+       }
+
+       a.cache.Add(fmtOrmIDKey(asset.ID), asset)
+       a.cache.Add(fmtAssetIDKey(asset.AssetID), asset)
+       return asset, nil
+}
+
+func (a *AssetStore) Add(asset *orm.Asset) error {
+       if err := a.db.Create(asset).Error; err != nil {
+               return err
+       }
+
+       a.cache.Add(fmtOrmIDKey(asset.ID), asset)
+       a.cache.Add(fmtAssetIDKey(asset.AssetID), asset)
+       return nil
+}
diff --git a/federation/database/cache.go b/federation/database/cache.go
deleted file mode 100644 (file)
index 8a4807d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package database
-
-import (
-       "github.com/golang/groupcache/lru"
-
-       "github.com/vapor/federation/database/orm"
-)
-
-const maxAssetCached = 1024
-
-type AssetCache struct {
-       lruCache *lru.Cache
-}
-
-func NewAssetCache() *AssetCache {
-       return &AssetCache{lruCache: lru.New(maxAssetCached)}
-}
-
-func (a *AssetCache) Add(assetID string, asset *orm.Asset) {
-       a.lruCache.Add(assetID, asset)
-}
-
-func (a *AssetCache) Get(assetID string) *orm.Asset {
-       if v, ok := a.lruCache.Get(assetID); ok {
-               return v.(*orm.Asset)
-       }
-
-       return nil
-}
-
-func (a *AssetCache) Remove(assetID string) {
-       a.lruCache.Remove(assetID)
-}
index 21c4bf4..a2448ba 100644 (file)
@@ -5,11 +5,11 @@ import (
 )
 
 type Asset struct {
-       ID                uint64 `gorm:"primary_key"`
-       AssetID           string
-       IssuanceProgram   string
-       VMVersion         uint64
-       RawDefinitionByte string
-       CreatedAt         types.Timestamp
-       UpdatedAt         types.Timestamp
+       ID                uint64          `gorm:"primary_key" json:"-"`
+       AssetID           string          `json:"asset_id"`
+       IssuanceProgram   string          `json:"-"`
+       VMVersion         uint64          `json:"-"`
+       RawDefinitionByte string          `json:"-"`
+       CreatedAt         types.Timestamp `json:"-"`
+       UpdatedAt         types.Timestamp `json:"-"`
 }
index ddb0841..9a70d9e 100644 (file)
@@ -7,21 +7,22 @@ import (
 )
 
 type CrossTransaction struct {
-       ID                   uint64 `gorm:"primary_key"`
-       ChainID              uint64
-       SourceBlockHeight    uint64
-       SourceBlockHash      string
-       SourceTxIndex        uint64
-       SourceMuxID          string
-       SourceTxHash         string
-       SourceRawTransaction string
-       DestBlockHeight      sql.NullInt64
-       DestBlockHash        sql.NullString
-       DestTxIndex          sql.NullInt64
-       DestTxHash           sql.NullString
-       Status               uint8
-       CreatedAt            types.Timestamp
-       UpdatedAt            types.Timestamp
+       ID                   uint64          `gorm:"primary_key" json:"-"`
+       ChainID              uint64          `json:"-"`
+       SourceBlockHeight    uint64          `json:"source_block_height"`
+       SourceBlockHash      string          `json:"source_block_hash"`
+       SourceTxIndex        uint64          `json:"source_tx_index"`
+       SourceMuxID          string          `json:"-"`
+       SourceTxHash         string          `json:"source_tx_hash"`
+       SourceRawTransaction string          `json:"-"`
+       DestBlockHeight      sql.NullInt64   `sql:"default:null" json:"dest_block_height"`
+       DestBlockHash        sql.NullString  `sql:"default:null" json:"dest_block_hash"`
+       DestTxIndex          sql.NullInt64   `sql:"default:null" json:"dest_tx_index"`
+       DestTxHash           sql.NullString  `sql:"default:null" json:"dest_tx_hash"`
+       Status               uint8           `json:"status"`
+       CreatedAt            types.Timestamp `json:"-"`
+       UpdatedAt            types.Timestamp `json:"-"`
 
-       Chain *Chain `gorm:"foreignkey:ChainID"`
+       Chain *Chain                 `gorm:"foreignkey:ChainID" json:"-"`
+       Reqs  []*CrossTransactionReq `json:"crosschain_requests"`
 }
index 8744d9b..39a487b 100644 (file)
@@ -5,15 +5,15 @@ import (
 )
 
 type CrossTransactionReq struct {
-       ID                 uint64 `gorm:"primary_key"`
-       CrossTransactionID uint64
-       SourcePos          uint64
-       AssetID            uint64
-       AssetAmount        uint64
-       Script             string
-       CreatedAt          types.Timestamp
-       UpdatedAt          types.Timestamp
+       ID                 uint64          `gorm:"primary_key" json:"-"`
+       CrossTransactionID uint64          `json:"-"`
+       SourcePos          uint64          `json:"-"`
+       AssetID            uint64          `json:"-"`
+       AssetAmount        uint64          `json:"amount"`
+       Script             string          `json:"-"`
+       CreatedAt          types.Timestamp `json:"-"`
+       UpdatedAt          types.Timestamp `json:"-"`
 
-       CrossTransaction *CrossTransaction `gorm:"foreignkey:CrossTransactionID"`
-       Asset            *Asset            `gorm:"foreignkey:AssetID"`
+       CrossTransaction *CrossTransaction `gorm:"foreignkey:CrossTransactionID" json:"-"`
+       Asset            *Asset            `gorm:"foreignkey:ID" json:"asset"`
 }
diff --git a/federation/database/orm/cross_transaction_sign.go b/federation/database/orm/cross_transaction_sign.go
deleted file mode 100644 (file)
index b60e7de..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package orm
-
-import (
-       "github.com/vapor/federation/types"
-)
-
-type CrossTransactionSign struct {
-       ID                 uint64 `gorm:"primary_key"`
-       CrossTransactionID uint64
-       WarderID           uint64
-       Signatures         string
-       Status             uint8
-       CreatedAt          types.Timestamp
-       UpdatedAt          types.Timestamp
-
-       CrossTransaction *CrossTransaction `gorm:"foreignkey:CrossTransactionID"`
-       Warder           *Warder           `gorm:"foreignkey:WarderID"`
-}
diff --git a/federation/database/orm/warder.go b/federation/database/orm/warder.go
deleted file mode 100644 (file)
index 39976db..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package orm
-
-import (
-       "github.com/vapor/federation/types"
-)
-
-type Warder struct {
-       ID        uint64
-       Pubkey    string
-       CreatedAt types.Timestamp
-       UpdatedAt types.Timestamp
-}
index 6ee376c..eb71361 100644 (file)
@@ -6,17 +6,16 @@ import (
        "github.com/vapor/errors"
        "github.com/vapor/federation/util"
        "github.com/vapor/protocol/bc"
-       "github.com/vapor/protocol/bc/types"
 )
 
 // Node can invoke the api which provide by the full node server
 type Node struct {
-       ip string
+       hostPort string
 }
 
 // Node create a api client with target server
-func NewNode(ip string) *Node {
-       return &Node{ip: ip}
+func NewNode(hostPort string) *Node {
+       return &Node{hostPort: hostPort}
 }
 
 func (n *Node) GetBlockByHash(hash string) (string, *bc.TransactionStatus, error) {
@@ -59,19 +58,15 @@ func (n *Node) getRawBlock(req *getRawBlockReq) (string, *bc.TransactionStatus,
        return res.RawBlock, res.TransactionStatus, n.request(url, payload, res)
 }
 
-type submitTxReq struct {
-       Tx *types.Tx `json:"raw_transaction"`
-}
-
 type response struct {
        Status    string          `json:"status"`
        Data      json.RawMessage `json:"data"`
        ErrDetail string          `json:"error_detail"`
 }
 
-func (n *Node) request(url string, payload []byte, respData interface{}) error {
+func (n *Node) request(path string, payload []byte, respData interface{}) error {
        resp := &response{}
-       if err := util.Post(n.ip+url, payload, resp); err != nil {
+       if err := util.Post(n.hostPort+path, payload, resp); err != nil {
                return err
        }
 
index 50f7a03..0884449 100644 (file)
@@ -13,33 +13,33 @@ import (
        "github.com/jinzhu/gorm"
        log "github.com/sirupsen/logrus"
 
-       vaporCfg "github.com/vapor/config"
        "github.com/vapor/errors"
        "github.com/vapor/federation/common"
        "github.com/vapor/federation/config"
        "github.com/vapor/federation/database"
        "github.com/vapor/federation/database/orm"
        "github.com/vapor/federation/service"
+       "github.com/vapor/federation/util"
        "github.com/vapor/protocol/bc"
 )
 
-var fedProg = vaporCfg.FederationProgrom(vaporCfg.CommonConfig)
-
 type mainchainKeeper struct {
        cfg        *config.Chain
        db         *gorm.DB
        node       *service.Node
        chainName  string
-       assetCache *database.AssetCache
+       assetStore *database.AssetStore
+       fedProg    []byte
 }
 
-func NewMainchainKeeper(db *gorm.DB, chainCfg *config.Chain) *mainchainKeeper {
+func NewMainchainKeeper(db *gorm.DB, assetStore *database.AssetStore, cfg *config.Config) *mainchainKeeper {
        return &mainchainKeeper{
-               cfg:        chainCfg,
+               cfg:        &cfg.Mainchain,
                db:         db,
-               node:       service.NewNode(chainCfg.Upstream),
-               chainName:  chainCfg.Name,
-               assetCache: database.NewAssetCache(),
+               node:       service.NewNode(cfg.Mainchain.Upstream),
+               chainName:  cfg.Mainchain.Name,
+               assetStore: assetStore,
+               fedProg:    util.SegWitWrap(util.ParseFedProg(cfg.Warders, cfg.Quorum)),
        }
 }
 
@@ -103,13 +103,13 @@ func (m *mainchainKeeper) syncBlock() (bool, error) {
 func (m *mainchainKeeper) tryAttachBlock(chain *orm.Chain, block *types.Block, txStatus *bc.TransactionStatus) error {
        blockHash := block.Hash()
        log.WithFields(log.Fields{"block_height": block.Height, "block_hash": blockHash.String()}).Info("start to attachBlock")
-       m.db.Begin()
+       dbTx := m.db.Begin()
        if err := m.processBlock(chain, block, txStatus); err != nil {
-               m.db.Rollback()
+               dbTx.Rollback()
                return err
        }
 
-       return m.db.Commit().Error
+       return dbTx.Commit().Error
 }
 
 func (m *mainchainKeeper) processBlock(chain *orm.Chain, block *types.Block, txStatus *bc.TransactionStatus) error {
@@ -136,7 +136,7 @@ func (m *mainchainKeeper) processBlock(chain *orm.Chain, block *types.Block, txS
 
 func (m *mainchainKeeper) isDepositTx(tx *types.Tx) bool {
        for _, output := range tx.Outputs {
-               if bytes.Equal(output.OutputCommitment.ControlProgram, fedProg) {
+               if bytes.Equal(output.OutputCommitment.ControlProgram, m.fedProg) {
                        return true
                }
        }
@@ -145,7 +145,7 @@ func (m *mainchainKeeper) isDepositTx(tx *types.Tx) bool {
 
 func (m *mainchainKeeper) isWithdrawalTx(tx *types.Tx) bool {
        for _, input := range tx.Inputs {
-               if bytes.Equal(input.ControlProgram(), fedProg) {
+               if bytes.Equal(input.ControlProgram(), m.fedProg) {
                        return true
                }
        }
@@ -207,10 +207,10 @@ func (m *mainchainKeeper) processDepositTx(chain *orm.Chain, block *types.Block,
 func (m *mainchainKeeper) getCrossChainReqs(crossTransactionID uint64, tx *types.Tx, statusFail bool) ([]*orm.CrossTransactionReq, error) {
        // assume inputs are from an identical owner
        script := hex.EncodeToString(tx.Inputs[0].ControlProgram())
-       inputs := []*orm.CrossTransactionReq{}
+       reqs := []*orm.CrossTransactionReq{}
        for i, rawOutput := range tx.Outputs {
                // check valid deposit
-               if !bytes.Equal(rawOutput.OutputCommitment.ControlProgram, fedProg) {
+               if !bytes.Equal(rawOutput.OutputCommitment.ControlProgram, m.fedProg) {
                        continue
                }
 
@@ -218,21 +218,21 @@ func (m *mainchainKeeper) getCrossChainReqs(crossTransactionID uint64, tx *types
                        continue
                }
 
-               asset, err := m.getAsset(rawOutput.OutputCommitment.AssetAmount.AssetId.String())
+               asset, err := m.assetStore.GetByAssetID(rawOutput.OutputCommitment.AssetAmount.AssetId.String())
                if err != nil {
                        return nil, err
                }
 
-               input := &orm.CrossTransactionReq{
+               req := &orm.CrossTransactionReq{
                        CrossTransactionID: crossTransactionID,
                        SourcePos:          uint64(i),
                        AssetID:            asset.ID,
                        AssetAmount:        rawOutput.OutputCommitment.AssetAmount.Amount,
                        Script:             script,
                }
-               inputs = append(inputs, input)
+               reqs = append(reqs, req)
        }
-       return inputs, nil
+       return reqs, nil
 }
 
 func (m *mainchainKeeper) processWithdrawalTx(chain *orm.Chain, block *types.Block, txIndex uint64, tx *types.Tx) error {
@@ -240,7 +240,7 @@ func (m *mainchainKeeper) processWithdrawalTx(chain *orm.Chain, block *types.Blo
        stmt := m.db.Model(&orm.CrossTransaction{}).Where("chain_id != ?", chain.ID).
                Where(&orm.CrossTransaction{
                        DestTxHash: sql.NullString{tx.ID.String(), true},
-                       Status:     common.CrossTxSubmittedStatus,
+                       Status:     common.CrossTxPendingStatus,
                }).UpdateColumn(&orm.CrossTransaction{
                DestBlockHeight: sql.NullInt64{int64(block.Height), true},
                DestBlockHash:   sql.NullString{blockHash.String(), true},
@@ -279,38 +279,19 @@ func (m *mainchainKeeper) processIssuing(txs []*types.Tx) error {
                        switch inp := input.TypedInput.(type) {
                        case *types.IssuanceInput:
                                assetID := inp.AssetID()
-                               if _, err := m.getAsset(assetID.String()); err == nil {
+                               if _, err := m.assetStore.GetByAssetID(assetID.String()); err == nil {
                                        continue
                                }
 
-                               asset := &orm.Asset{
+                               m.assetStore.Add(&orm.Asset{
                                        AssetID:           assetID.String(),
                                        IssuanceProgram:   hex.EncodeToString(inp.IssuanceProgram),
                                        VMVersion:         inp.VMVersion,
                                        RawDefinitionByte: hex.EncodeToString(inp.AssetDefinition),
-                               }
-                               if err := m.db.Create(asset).Error; err != nil {
-                                       return err
-                               }
-
-                               m.assetCache.Add(asset.AssetID, asset)
+                               })
                        }
                }
        }
 
        return nil
 }
-
-func (m *mainchainKeeper) getAsset(assetID string) (*orm.Asset, error) {
-       if asset := m.assetCache.Get(assetID); asset != nil {
-               return asset, nil
-       }
-
-       asset := &orm.Asset{AssetID: assetID}
-       if err := m.db.Where(asset).First(asset).Error; err != nil {
-               return nil, errors.Wrap(err, "asset not found in memory and mysql")
-       }
-
-       m.assetCache.Add(assetID, asset)
-       return asset, nil
-}
index fed458e..91058c5 100644 (file)
@@ -25,16 +25,16 @@ type sidechainKeeper struct {
        db         *gorm.DB
        node       *service.Node
        chainName  string
-       assetCache *database.AssetCache
+       assetStore *database.AssetStore
 }
 
-func NewSidechainKeeper(db *gorm.DB, chainCfg *config.Chain) *sidechainKeeper {
+func NewSidechainKeeper(db *gorm.DB, assetStore *database.AssetStore, cfg *config.Config) *sidechainKeeper {
        return &sidechainKeeper{
-               cfg:        chainCfg,
+               cfg:        &cfg.Sidechain,
                db:         db,
-               node:       service.NewNode(chainCfg.Upstream),
-               chainName:  chainCfg.Name,
-               assetCache: database.NewAssetCache(),
+               node:       service.NewNode(cfg.Sidechain.Upstream),
+               chainName:  cfg.Sidechain.Name,
+               assetStore: assetStore,
        }
 }
 
@@ -98,13 +98,13 @@ func (s *sidechainKeeper) syncBlock() (bool, error) {
 func (s *sidechainKeeper) tryAttachBlock(chain *orm.Chain, block *types.Block, txStatus *bc.TransactionStatus) error {
        blockHash := block.Hash()
        log.WithFields(log.Fields{"block_height": block.Height, "block_hash": blockHash.String()}).Info("start to attachBlock")
-       s.db.Begin()
+       dbTx := s.db.Begin()
        if err := s.processBlock(chain, block, txStatus); err != nil {
-               s.db.Rollback()
+               dbTx.Rollback()
                return err
        }
 
-       return s.db.Commit().Error
+       return dbTx.Commit().Error
 }
 
 func (s *sidechainKeeper) processBlock(chain *orm.Chain, block *types.Block, txStatus *bc.TransactionStatus) error {
@@ -148,7 +148,7 @@ func (s *sidechainKeeper) processDepositTx(chain *orm.Chain, block *types.Block,
        stmt := s.db.Model(&orm.CrossTransaction{}).Where("chain_id != ?", chain.ID).
                Where(&orm.CrossTransaction{
                        DestTxHash: sql.NullString{tx.ID.String(), true},
-                       Status:     common.CrossTxSubmittedStatus,
+                       Status:     common.CrossTxPendingStatus,
                }).UpdateColumn(&orm.CrossTransaction{
                DestBlockHeight: sql.NullInt64{int64(block.Height), true},
                DestBlockHash:   sql.NullString{blockHash.String(), true},
@@ -220,9 +220,7 @@ func (s *sidechainKeeper) processWithdrawalTx(chain *orm.Chain, block *types.Blo
 }
 
 func (s *sidechainKeeper) getCrossChainReqs(crossTransactionID uint64, tx *types.Tx, statusFail bool) ([]*orm.CrossTransactionReq, error) {
-       // assume inputs are from an identical owner
-       script := hex.EncodeToString(tx.Inputs[0].ControlProgram())
-       inputs := []*orm.CrossTransactionReq{}
+       reqs := []*orm.CrossTransactionReq{}
        for i, rawOutput := range tx.Outputs {
                // check valid withdrawal
                if rawOutput.OutputType() != types.CrossChainOutputType {
@@ -233,21 +231,21 @@ func (s *sidechainKeeper) getCrossChainReqs(crossTransactionID uint64, tx *types
                        continue
                }
 
-               asset, err := s.getAsset(rawOutput.OutputCommitment().AssetAmount.AssetId.String())
+               asset, err := s.assetStore.GetByAssetID(rawOutput.OutputCommitment().AssetAmount.AssetId.String())
                if err != nil {
                        return nil, err
                }
 
-               input := &orm.CrossTransactionReq{
+               req := &orm.CrossTransactionReq{
                        CrossTransactionID: crossTransactionID,
                        SourcePos:          uint64(i),
                        AssetID:            asset.ID,
                        AssetAmount:        rawOutput.OutputCommitment().AssetAmount.Amount,
-                       Script:             script,
+                       Script:             hex.EncodeToString(rawOutput.ControlProgram()),
                }
-               inputs = append(inputs, input)
+               reqs = append(reqs, req)
        }
-       return inputs, nil
+       return reqs, nil
 }
 
 func (s *sidechainKeeper) processChainInfo(chain *orm.Chain, block *types.Block) error {
@@ -265,17 +263,3 @@ func (s *sidechainKeeper) processChainInfo(chain *orm.Chain, block *types.Block)
 
        return nil
 }
-
-func (s *sidechainKeeper) getAsset(assetID string) (*orm.Asset, error) {
-       if asset := s.assetCache.Get(assetID); asset != nil {
-               return asset, nil
-       }
-
-       asset := &orm.Asset{AssetID: assetID}
-       if err := s.db.Where(asset).First(asset).Error; err != nil {
-               return nil, errors.Wrap(err, "asset not found in memory and mysql")
-       }
-
-       s.assetCache.Add(assetID, asset)
-       return asset, nil
-}
diff --git a/federation/util/script.go b/federation/util/script.go
new file mode 100644 (file)
index 0000000..670b980
--- /dev/null
@@ -0,0 +1,49 @@
+package util
+
+import (
+       "sort"
+
+       log "github.com/sirupsen/logrus"
+
+       "github.com/vapor/crypto"
+       "github.com/vapor/crypto/ed25519/chainkd"
+       "github.com/vapor/federation/config"
+       "github.com/vapor/protocol/vm/vmutil"
+)
+
+func SegWitWrap(script []byte) []byte {
+       scriptHash := crypto.Sha256(script)
+       wscript, err := vmutil.P2WSHProgram(scriptHash)
+       if err != nil {
+               log.Panicf("Fail converts scriptHash to witness: %v", err)
+       }
+
+       return wscript
+}
+
+func ParseFedProg(warders []config.Warder, quorum int) []byte {
+       SortWarders(warders)
+
+       xpubs := []chainkd.XPub{}
+       for _, w := range warders {
+               xpubs = append(xpubs, w.XPub)
+       }
+
+       fedScript, err := vmutil.P2SPMultiSigProgram(chainkd.XPubKeys(xpubs), quorum)
+       if err != nil {
+               log.Panicf("fail to generate federation scirpt for federation: %v", err)
+       }
+
+       return fedScript
+}
+
+type byPosition []config.Warder
+
+func (w byPosition) Len() int           { return len(w) }
+func (w byPosition) Swap(i, j int)      { w[i], w[j] = w[j], w[i] }
+func (w byPosition) Less(i, j int) bool { return w[i].Position < w[j].Position }
+
+func SortWarders(warders []config.Warder) []config.Warder {
+       sort.Sort(byPosition(warders))
+       return warders
+}
index 65f4815..0d25751 100644 (file)
@@ -250,8 +250,9 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                // check assetID
                assetID := e.AssetDefinition.ComputeAssetID()
                if *e.Value.AssetId != *consensus.BTMAssetID && *e.Value.AssetId != assetID {
-                       return errors.New("incorrect asset_id while check CrossChainInput")
+                       return errors.New("incorrect asset_id while checking CrossChainInput")
                }
+
                code := config.FederationProgrom(config.CommonConfig)
                prog := &bc.Program{
                        VmVersion: e.ControlProgram.VmVersion,
diff --git a/vendor/github.com/gin-contrib/sse/LICENSE b/vendor/github.com/gin-contrib/sse/LICENSE
new file mode 100644 (file)
index 0000000..1ff7f37
--- /dev/null
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Manuel Martínez-Almeida
+
+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.
diff --git a/vendor/github.com/gin-contrib/sse/README.md b/vendor/github.com/gin-contrib/sse/README.md
new file mode 100644 (file)
index 0000000..c9c49cf
--- /dev/null
@@ -0,0 +1,58 @@
+# Server-Sent Events
+
+[![GoDoc](https://godoc.org/github.com/gin-contrib/sse?status.svg)](https://godoc.org/github.com/gin-contrib/sse)
+[![Build Status](https://travis-ci.org/gin-contrib/sse.svg)](https://travis-ci.org/gin-contrib/sse)
+[![codecov](https://codecov.io/gh/gin-contrib/sse/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-contrib/sse)
+[![Go Report Card](https://goreportcard.com/badge/github.com/gin-contrib/sse)](https://goreportcard.com/report/github.com/gin-contrib/sse)
+
+Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is [standardized as part of HTML5[1] by the W3C](http://www.w3.org/TR/2009/WD-eventsource-20091029/).
+
+- [Read this great SSE introduction by the HTML5Rocks guys](http://www.html5rocks.com/en/tutorials/eventsource/basics/)
+- [Browser support](http://caniuse.com/#feat=eventsource)
+
+## Sample code
+
+```go
+import "github.com/gin-contrib/sse"
+
+func httpHandler(w http.ResponseWriter, req *http.Request) {
+       // data can be a primitive like a string, an integer or a float
+       sse.Encode(w, sse.Event{
+               Event: "message",
+               Data:  "some data\nmore data",
+       })
+
+       // also a complex type, like a map, a struct or a slice
+       sse.Encode(w, sse.Event{
+               Id:    "124",
+               Event: "message",
+               Data: map[string]interface{}{
+                       "user":    "manu",
+                       "date":    time.Now().Unix(),
+                       "content": "hi!",
+               },
+       })
+}
+```
+```
+event: message
+data: some data\\nmore data
+
+id: 124
+event: message
+data: {"content":"hi!","date":1431540810,"user":"manu"}
+```
+
+## Content-Type
+
+```go
+fmt.Println(sse.ContentType)
+```
+```
+text/event-stream
+```
+
+## Decoding support
+
+There is a client-side implementation of SSE coming soon.
diff --git a/vendor/github.com/gin-contrib/sse/sse-decoder.go b/vendor/github.com/gin-contrib/sse/sse-decoder.go
new file mode 100644 (file)
index 0000000..fd49b9c
--- /dev/null
@@ -0,0 +1,116 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package sse
+
+import (
+       "bytes"
+       "io"
+       "io/ioutil"
+)
+
+type decoder struct {
+       events []Event
+}
+
+func Decode(r io.Reader) ([]Event, error) {
+       var dec decoder
+       return dec.decode(r)
+}
+
+func (d *decoder) dispatchEvent(event Event, data string) {
+       dataLength := len(data)
+       if dataLength > 0 {
+               //If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer.
+               data = data[:dataLength-1]
+               dataLength--
+       }
+       if dataLength == 0 && event.Event == "" {
+               return
+       }
+       if event.Event == "" {
+               event.Event = "message"
+       }
+       event.Data = data
+       d.events = append(d.events, event)
+}
+
+func (d *decoder) decode(r io.Reader) ([]Event, error) {
+       buf, err := ioutil.ReadAll(r)
+       if err != nil {
+               return nil, err
+       }
+
+       var currentEvent Event
+       var dataBuffer *bytes.Buffer = new(bytes.Buffer)
+       // TODO (and unit tests)
+       // Lines must be separated by either a U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pair,
+       // a single U+000A LINE FEED (LF) character,
+       // or a single U+000D CARRIAGE RETURN (CR) character.
+       lines := bytes.Split(buf, []byte{'\n'})
+       for _, line := range lines {
+               if len(line) == 0 {
+                       // If the line is empty (a blank line). Dispatch the event.
+                       d.dispatchEvent(currentEvent, dataBuffer.String())
+
+                       // reset current event and data buffer
+                       currentEvent = Event{}
+                       dataBuffer.Reset()
+                       continue
+               }
+               if line[0] == byte(':') {
+                       // If the line starts with a U+003A COLON character (:), ignore the line.
+                       continue
+               }
+
+               var field, value []byte
+               colonIndex := bytes.IndexRune(line, ':')
+               if colonIndex != -1 {
+                       // If the line contains a U+003A COLON character character (:)
+                       // Collect the characters on the line before the first U+003A COLON character (:),
+                       // and let field be that string.
+                       field = line[:colonIndex]
+                       // Collect the characters on the line after the first U+003A COLON character (:),
+                       // and let value be that string.
+                       value = line[colonIndex+1:]
+                       // If value starts with a single U+0020 SPACE character, remove it from value.
+                       if len(value) > 0 && value[0] == ' ' {
+                               value = value[1:]
+                       }
+               } else {
+                       // Otherwise, the string is not empty but does not contain a U+003A COLON character character (:)
+                       // Use the whole line as the field name, and the empty string as the field value.
+                       field = line
+                       value = []byte{}
+               }
+               // The steps to process the field given a field name and a field value depend on the field name,
+               // as given in the following list. Field names must be compared literally,
+               // with no case folding performed.
+               switch string(field) {
+               case "event":
+                       // Set the event name buffer to field value.
+                       currentEvent.Event = string(value)
+               case "id":
+                       // Set the event stream's last event ID to the field value.
+                       currentEvent.Id = string(value)
+               case "retry":
+                       // If the field value consists of only characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9),
+                       // then interpret the field value as an integer in base ten, and set the event stream's reconnection time to that integer.
+                       // Otherwise, ignore the field.
+                       currentEvent.Id = string(value)
+               case "data":
+                       // Append the field value to the data buffer,
+                       dataBuffer.Write(value)
+                       // then append a single U+000A LINE FEED (LF) character to the data buffer.
+                       dataBuffer.WriteString("\n")
+               default:
+                       //Otherwise. The field is ignored.
+                       continue
+               }
+       }
+       // Once the end of the file is reached, the user agent must dispatch the event one final time.
+       d.dispatchEvent(currentEvent, dataBuffer.String())
+
+       return d.events, nil
+}
diff --git a/vendor/github.com/gin-contrib/sse/sse-encoder.go b/vendor/github.com/gin-contrib/sse/sse-encoder.go
new file mode 100644 (file)
index 0000000..f9c8087
--- /dev/null
@@ -0,0 +1,110 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package sse
+
+import (
+       "encoding/json"
+       "fmt"
+       "io"
+       "net/http"
+       "reflect"
+       "strconv"
+       "strings"
+)
+
+// Server-Sent Events
+// W3C Working Draft 29 October 2009
+// http://www.w3.org/TR/2009/WD-eventsource-20091029/
+
+const ContentType = "text/event-stream"
+
+var contentType = []string{ContentType}
+var noCache = []string{"no-cache"}
+
+var fieldReplacer = strings.NewReplacer(
+       "\n", "\\n",
+       "\r", "\\r")
+
+var dataReplacer = strings.NewReplacer(
+       "\n", "\ndata:",
+       "\r", "\\r")
+
+type Event struct {
+       Event string
+       Id    string
+       Retry uint
+       Data  interface{}
+}
+
+func Encode(writer io.Writer, event Event) error {
+       w := checkWriter(writer)
+       writeId(w, event.Id)
+       writeEvent(w, event.Event)
+       writeRetry(w, event.Retry)
+       return writeData(w, event.Data)
+}
+
+func writeId(w stringWriter, id string) {
+       if len(id) > 0 {
+               w.WriteString("id:")
+               fieldReplacer.WriteString(w, id)
+               w.WriteString("\n")
+       }
+}
+
+func writeEvent(w stringWriter, event string) {
+       if len(event) > 0 {
+               w.WriteString("event:")
+               fieldReplacer.WriteString(w, event)
+               w.WriteString("\n")
+       }
+}
+
+func writeRetry(w stringWriter, retry uint) {
+       if retry > 0 {
+               w.WriteString("retry:")
+               w.WriteString(strconv.FormatUint(uint64(retry), 10))
+               w.WriteString("\n")
+       }
+}
+
+func writeData(w stringWriter, data interface{}) error {
+       w.WriteString("data:")
+       switch kindOfData(data) {
+       case reflect.Struct, reflect.Slice, reflect.Map:
+               err := json.NewEncoder(w).Encode(data)
+               if err != nil {
+                       return err
+               }
+               w.WriteString("\n")
+       default:
+               dataReplacer.WriteString(w, fmt.Sprint(data))
+               w.WriteString("\n\n")
+       }
+       return nil
+}
+
+func (r Event) Render(w http.ResponseWriter) error {
+       r.WriteContentType(w)
+       return Encode(w, r)
+}
+
+func (r Event) WriteContentType(w http.ResponseWriter) {
+       header := w.Header()
+       header["Content-Type"] = contentType
+
+       if _, exist := header["Cache-Control"]; !exist {
+               header["Cache-Control"] = noCache
+       }
+}
+
+func kindOfData(data interface{}) reflect.Kind {
+       value := reflect.ValueOf(data)
+       valueType := value.Kind()
+       if valueType == reflect.Ptr {
+               valueType = value.Elem().Kind()
+       }
+       return valueType
+}
diff --git a/vendor/github.com/gin-contrib/sse/writer.go b/vendor/github.com/gin-contrib/sse/writer.go
new file mode 100644 (file)
index 0000000..6f9806c
--- /dev/null
@@ -0,0 +1,24 @@
+package sse
+
+import "io"
+
+type stringWriter interface {
+       io.Writer
+       WriteString(string) (int, error)
+}
+
+type stringWrapper struct {
+       io.Writer
+}
+
+func (w stringWrapper) WriteString(str string) (int, error) {
+       return w.Writer.Write([]byte(str))
+}
+
+func checkWriter(writer io.Writer) stringWriter {
+       if w, ok := writer.(stringWriter); ok {
+               return w
+       } else {
+               return stringWrapper{writer}
+       }
+}
diff --git a/vendor/github.com/gin-gonic/gin/AUTHORS.md b/vendor/github.com/gin-gonic/gin/AUTHORS.md
new file mode 100644 (file)
index 0000000..dda19bc
--- /dev/null
@@ -0,0 +1,231 @@
+List of all the awesome people working to make Gin the best Web Framework in Go.
+
+## gin 1.x series authors
+
+**Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho)
+
+## gin 0.x series authors
+
+**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
+
+People and companies, who have contributed, in alphabetical order.
+
+**@858806258 (杰哥)**
+- Fix typo in example
+
+
+**@achedeuzot (Klemen Sever)**
+- Fix newline debug printing
+
+
+**@adammck (Adam Mckaig)**
+- Add MIT license
+
+
+**@AlexanderChen1989 (Alexander)**
+- Typos in README
+
+
+**@alexanderdidenko (Aleksandr Didenko)**
+- Add support multipart/form-data
+
+
+**@alexandernyquist (Alexander Nyquist)**
+- Using template.Must to fix multiple return issue
+- ★ Added support for OPTIONS verb
+- ★ Setting response headers before calling WriteHeader
+- Improved documentation for model binding
+- ★ Added Content.Redirect()
+- ★ Added tons of Unit tests
+
+
+**@austinheap (Austin Heap)**
+- Added travis CI integration
+
+
+**@andredublin (Andre Dublin)**
+- Fix typo in comment
+
+
+**@bredov (Ludwig Valda Vasquez)**
+- Fix html templating in debug mode
+
+
+**@bluele (Jun Kimura)**
+- Fixes code examples in README
+
+
+**@chad-russell**
+- ★ Support for serializing gin.H into XML
+
+
+**@dickeyxxx (Jeff Dickey)**
+- Typos in README
+- Add example about serving static files
+
+
+**@donileo (Adonis)**
+- Add NoMethod handler
+
+
+**@dutchcoders (DutchCoders)**
+- ★ Fix security bug that allows client to spoof ip
+- Fix typo. r.HTMLTemplates -> SetHTMLTemplate
+
+
+**@el3ctro- (Joshua Loper)**
+- Fix typo in example
+
+
+**@ethankan (Ethan Kan)**
+- Unsigned integers in binding
+
+
+**(Evgeny Persienko)**
+- Validate sub structures
+
+
+**@frankbille (Frank Bille)**
+- Add support for HTTP Realm Auth
+
+
+**@fmd (Fareed Dudhia)**
+- Fix typo. SetHTTPTemplate -> SetHTMLTemplate
+
+
+**@ironiridis (Christopher Harrington)**
+- Remove old reference
+
+
+**@jammie-stackhouse (Jamie Stackhouse)**
+- Add more shortcuts for router methods
+
+
+**@jasonrhansen**
+- Fix spelling and grammar errors in documentation
+
+
+**@JasonSoft (Jason Lee)**
+- Fix typo in comment
+
+
+**@joiggama (Ignacio Galindo)**
+- Add utf-8 charset header on renders
+
+
+**@julienschmidt (Julien Schmidt)**
+- gofmt the code examples
+
+
+**@kelcecil (Kel Cecil)**
+- Fix readme typo
+
+
+**@kyledinh (Kyle Dinh)**
+- Adds RunTLS()
+
+
+**@LinusU (Linus Unnebäck)**
+- Small fixes in README
+
+
+**@loongmxbt (Saint Asky)**
+- Fix typo in example
+
+
+**@lucas-clemente (Lucas Clemente)**
+- ★ work around path.Join removing trailing slashes from routes
+
+
+**@mattn (Yasuhiro Matsumoto)**
+- Improve color logger
+
+
+**@mdigger (Dmitry Sedykh)**
+- Fixes Form binding when content-type is x-www-form-urlencoded
+- No repeat call c.Writer.Status() in gin.Logger
+- Fixes Content-Type for json render
+
+
+**@mirzac (Mirza Ceric)**
+- Fix debug printing
+
+
+**@mopemope (Yutaka Matsubara)**
+- ★ Adds Godep support (Dependencies Manager)
+- Fix variadic parameter in the flexible render API
+- Fix Corrupted plain render
+- Add Pluggable View Renderer Example
+
+**@msemenistyi (Mykyta Semenistyi)**
+- update Readme.md. Add code to String method
+
+
+**@msoedov (Sasha Myasoedov)**
+- ★ Adds tons of unit tests.
+
+
+**@ngerakines (Nick Gerakines)**
+- ★ Improves API, c.GET() doesn't panic
+- Adds MustGet() method
+
+
+**@r8k (Rajiv Kilaparti)**
+- Fix Port usage in README.
+
+
+**@rayrod2030 (Ray Rodriguez)**
+- Fix typo in example
+
+
+**@rns**
+- Fix typo in example
+
+
+**@RobAWilkinson (Robert Wilkinson)**
+- Add example of forms and params
+
+
+**@rogierlommers (Rogier Lommers)**
+- Add updated static serve example
+
+
+**@se77en (Damon Zhao)**
+- Improve color logging
+
+
+**@silasb (Silas Baronda)**
+- Fixing quotes in README
+
+
+**@SkuliOskarsson (Skuli Oskarsson)**
+- Fixes some texts in README II
+
+
+**@slimmy (Jimmy Pettersson)**
+- Added messages for required bindings
+
+
+**@smira (Andrey Smirnov)**
+- Add support for ignored/unexported fields in binding
+
+
+**@superalsrk (SRK.Lyu)**
+- Update httprouter godeps
+
+
+**@tebeka (Miki Tebeka)**
+- Use net/http constants instead of numeric values
+
+
+**@techjanitor**
+- Update context.go reserved IPs
+
+
+**@yosssi (Keiji Yoshida)**
+- Fix link in README
+
+
+**@yuyabee**
+- Fixed README
diff --git a/vendor/github.com/gin-gonic/gin/BENCHMARKS.md b/vendor/github.com/gin-gonic/gin/BENCHMARKS.md
new file mode 100644 (file)
index 0000000..9a7df86
--- /dev/null
@@ -0,0 +1,604 @@
+
+## Benchmark System
+
+**VM HOST:** DigitalOcean  
+**Machine:** 4 CPU, 8 GB RAM. Ubuntu 16.04.2 x64  
+**Date:** July 19th, 2017  
+**Go Version:** 1.8.3 linux/amd64  
+**Source:** [Go HTTP Router Benchmark](https://github.com/julienschmidt/go-http-routing-benchmark)  
+
+## Static Routes: 157
+
+```
+Gin:             30512 Bytes
+
+HttpServeMux:    17344 Bytes
+Ace:             30080 Bytes
+Bear:            30472 Bytes
+Beego:           96408 Bytes
+Bone:            37904 Bytes
+Denco:           10464 Bytes
+Echo:            73680 Bytes
+GocraftWeb:      55720 Bytes
+Goji:            27200 Bytes
+Gojiv2:         104464 Bytes
+GoJsonRest:     136472 Bytes
+GoRestful:      914904 Bytes
+GorillaMux:     675568 Bytes
+HttpRouter:      21128 Bytes
+HttpTreeMux:     73448 Bytes
+Kocha:          115072 Bytes
+LARS:            30120 Bytes
+Macaron:         37984 Bytes
+Martini:        310832 Bytes
+Pat:             20464 Bytes
+Possum:          91328 Bytes
+R2router:        23712 Bytes
+Rivet:           23880 Bytes
+Tango:           28008 Bytes
+TigerTonic:      80368 Bytes
+Traffic:        626480 Bytes
+Vulcan:         369064 Bytes
+```
+
+## GithubAPI Routes: 203
+
+```
+Gin:             52672 Bytes
+
+Ace:             48992 Bytes
+Bear:           161592 Bytes
+Beego:          147992 Bytes
+Bone:            97728 Bytes
+Denco:           36440 Bytes
+Echo:            95672 Bytes
+GocraftWeb:      95640 Bytes
+Goji:            86088 Bytes
+Gojiv2:         144392 Bytes
+GoJsonRest:     134648 Bytes
+GoRestful:     1410760 Bytes
+GorillaMux:    1509488 Bytes
+HttpRouter:      37464 Bytes
+HttpTreeMux:     78800 Bytes
+Kocha:          785408 Bytes
+LARS:            49032 Bytes
+Macaron:        132712 Bytes
+Martini:        564352 Bytes
+Pat:             21200 Bytes
+Possum:          83888 Bytes
+R2router:        47104 Bytes
+Rivet:           42840 Bytes
+Tango:           54584 Bytes
+TigerTonic:      96384 Bytes
+Traffic:       1061920 Bytes
+Vulcan:         465296 Bytes
+```
+
+## GPlusAPI Routes: 13
+
+```
+Gin:              3968 Bytes
+
+Ace:              3600 Bytes
+Bear:             7112 Bytes
+Beego:           10048 Bytes
+Bone:             6480 Bytes
+Denco:            3256 Bytes
+Echo:             9000 Bytes
+GocraftWeb:       7496 Bytes
+Goji:             2912 Bytes
+Gojiv2:           7376 Bytes
+GoJsonRest:      11544 Bytes
+GoRestful:       88776 Bytes
+GorillaMux:      71488 Bytes
+HttpRouter:       2712 Bytes
+HttpTreeMux:      7440 Bytes
+Kocha:          128880 Bytes
+LARS:             3640 Bytes
+Macaron:          8656 Bytes
+Martini:         23936 Bytes
+Pat:              1856 Bytes
+Possum:           7248 Bytes
+R2router:         3928 Bytes
+Rivet:            3064 Bytes
+Tango:            4912 Bytes
+TigerTonic:       9408 Bytes
+Traffic:         49472 Bytes
+Vulcan:          25496 Bytes
+```
+
+## ParseAPI Routes: 26
+
+```
+Gin:              6928 Bytes
+
+Ace:              6592 Bytes
+Bear:            12320 Bytes
+Beego:           18960 Bytes
+Bone:            11024 Bytes
+Denco:            4184 Bytes
+Echo:            11168 Bytes
+GocraftWeb:      12800 Bytes
+Goji:             5232 Bytes
+Gojiv2:          14464 Bytes
+GoJsonRest:      14216 Bytes
+GoRestful:      127368 Bytes
+GorillaMux:     123016 Bytes
+HttpRouter:       4976 Bytes
+HttpTreeMux:      7848 Bytes
+Kocha:          181712 Bytes
+LARS:             6632 Bytes
+Macaron:         13648 Bytes
+Martini:         45952 Bytes
+Pat:              2560 Bytes
+Possum:           9200 Bytes
+R2router:         7056 Bytes
+Rivet:            5680 Bytes
+Tango:            8664 Bytes
+TigerTonic:       9840 Bytes
+Traffic:         93480 Bytes
+Vulcan:          44504 Bytes
+```
+
+## Static Routes
+
+```
+BenchmarkGin_StaticAll                     50000             34506 ns/op               0 B/op          0 allocs/op
+
+BenchmarkAce_StaticAll                     30000             49657 ns/op               0 B/op          0 allocs/op
+BenchmarkHttpServeMux_StaticAll             2000           1183737 ns/op              96 B/op          8 allocs/op
+BenchmarkBeego_StaticAll                    5000            412621 ns/op           57776 B/op        628 allocs/op
+BenchmarkBear_StaticAll                    10000            149242 ns/op           20336 B/op        461 allocs/op
+BenchmarkBone_StaticAll                    10000            118583 ns/op               0 B/op          0 allocs/op
+BenchmarkDenco_StaticAll                  100000             13247 ns/op               0 B/op          0 allocs/op
+BenchmarkEcho_StaticAll                    20000             79914 ns/op            5024 B/op        157 allocs/op
+BenchmarkGocraftWeb_StaticAll              10000            211823 ns/op           46440 B/op        785 allocs/op
+BenchmarkGoji_StaticAll                    10000            109390 ns/op               0 B/op          0 allocs/op
+BenchmarkGojiv2_StaticAll                   3000            415533 ns/op          145696 B/op       1099 allocs/op
+BenchmarkGoJsonRest_StaticAll               5000            364403 ns/op           51653 B/op       1727 allocs/op
+BenchmarkGoRestful_StaticAll                 500           2578579 ns/op          314936 B/op       3144 allocs/op
+BenchmarkGorillaMux_StaticAll                500           2704856 ns/op          115648 B/op       1578 allocs/op
+BenchmarkHttpRouter_StaticAll             100000             18541 ns/op               0 B/op          0 allocs/op
+BenchmarkHttpTreeMux_StaticAll            100000             22332 ns/op               0 B/op          0 allocs/op
+BenchmarkKocha_StaticAll                   50000             31176 ns/op               0 B/op          0 allocs/op
+BenchmarkLARS_StaticAll                    50000             40840 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_StaticAll                  5000            517656 ns/op          120576 B/op       1413 allocs/op
+BenchmarkMartini_StaticAll                   300           4462289 ns/op          125442 B/op       1717 allocs/op
+BenchmarkPat_StaticAll                       500           2157275 ns/op          533904 B/op      11123 allocs/op
+BenchmarkPossum_StaticAll                  10000            254701 ns/op           65312 B/op        471 allocs/op
+BenchmarkR2router_StaticAll                10000            133956 ns/op           22608 B/op        628 allocs/op
+BenchmarkRivet_StaticAll                   30000             46812 ns/op               0 B/op          0 allocs/op
+BenchmarkTango_StaticAll                    5000            390613 ns/op           39225 B/op       1256 allocs/op
+BenchmarkTigerTonic_StaticAll              20000             88060 ns/op            7504 B/op        157 allocs/op
+BenchmarkTraffic_StaticAll                   500           2910236 ns/op          729736 B/op      14287 allocs/op
+BenchmarkVulcan_StaticAll                   5000            277366 ns/op           15386 B/op        471 allocs/op
+```
+
+## Micro Benchmarks
+
+```
+BenchmarkGin_Param                      20000000               113 ns/op               0 B/op          0 allocs/op
+
+BenchmarkAce_Param                       5000000               375 ns/op              32 B/op          1 allocs/op
+BenchmarkBear_Param                      1000000              1709 ns/op             456 B/op          5 allocs/op
+BenchmarkBeego_Param                     1000000              2484 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_Param                      1000000              2391 ns/op             688 B/op          5 allocs/op
+BenchmarkDenco_Param                    10000000               240 ns/op              32 B/op          1 allocs/op
+BenchmarkEcho_Param                      5000000               366 ns/op              32 B/op          1 allocs/op
+BenchmarkGocraftWeb_Param                1000000              2343 ns/op             648 B/op          8 allocs/op
+BenchmarkGoji_Param                      1000000              1197 ns/op             336 B/op          2 allocs/op
+BenchmarkGojiv2_Param                    1000000              2771 ns/op             944 B/op          8 allocs/op
+BenchmarkGoJsonRest_Param                1000000              2993 ns/op             649 B/op         13 allocs/op
+BenchmarkGoRestful_Param                  200000              8860 ns/op            2296 B/op         21 allocs/op
+BenchmarkGorillaMux_Param                 500000              4461 ns/op            1056 B/op         11 allocs/op
+BenchmarkHttpRouter_Param               10000000               175 ns/op              32 B/op          1 allocs/op
+BenchmarkHttpTreeMux_Param               1000000              1167 ns/op             352 B/op          3 allocs/op
+BenchmarkKocha_Param                     3000000               429 ns/op              56 B/op          3 allocs/op
+BenchmarkLARS_Param                     10000000               134 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_Param                    500000              4635 ns/op            1056 B/op         10 allocs/op
+BenchmarkMartini_Param                    200000              9933 ns/op            1072 B/op         10 allocs/op
+BenchmarkPat_Param                       1000000              2929 ns/op             648 B/op         12 allocs/op
+BenchmarkPossum_Param                    1000000              2503 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_Param                  1000000              1507 ns/op             432 B/op          5 allocs/op
+BenchmarkRivet_Param                     5000000               297 ns/op              48 B/op          1 allocs/op
+BenchmarkTango_Param                     1000000              1862 ns/op             248 B/op          8 allocs/op
+BenchmarkTigerTonic_Param                 500000              5660 ns/op             992 B/op         17 allocs/op
+BenchmarkTraffic_Param                    200000              8408 ns/op            1960 B/op         21 allocs/op
+BenchmarkVulcan_Param                    2000000               963 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_Param5                      2000000               740 ns/op             160 B/op          1 allocs/op
+BenchmarkBear_Param5                     1000000              2777 ns/op             501 B/op          5 allocs/op
+BenchmarkBeego_Param5                    1000000              3740 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_Param5                     1000000              2950 ns/op             736 B/op          5 allocs/op
+BenchmarkDenco_Param5                    2000000               644 ns/op             160 B/op          1 allocs/op
+BenchmarkEcho_Param5                     3000000               558 ns/op              32 B/op          1 allocs/op
+BenchmarkGin_Param5                     10000000               198 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_Param5                500000              3870 ns/op             920 B/op         11 allocs/op
+BenchmarkGoji_Param5                     1000000              1746 ns/op             336 B/op          2 allocs/op
+BenchmarkGojiv2_Param5                   1000000              3214 ns/op            1008 B/op          8 allocs/op
+BenchmarkGoJsonRest_Param5                500000              5509 ns/op            1097 B/op         16 allocs/op
+BenchmarkGoRestful_Param5                 200000             11232 ns/op            2392 B/op         21 allocs/op
+BenchmarkGorillaMux_Param5                300000              7777 ns/op            1184 B/op         11 allocs/op
+BenchmarkHttpRouter_Param5               3000000               631 ns/op             160 B/op          1 allocs/op
+BenchmarkHttpTreeMux_Param5              1000000              2800 ns/op             576 B/op          6 allocs/op
+BenchmarkKocha_Param5                    1000000              2053 ns/op             440 B/op         10 allocs/op
+BenchmarkLARS_Param5                    10000000               232 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_Param5                   500000              5888 ns/op            1056 B/op         10 allocs/op
+BenchmarkMartini_Param5                   200000             12807 ns/op            1232 B/op         11 allocs/op
+BenchmarkPat_Param5                       300000              7320 ns/op             964 B/op         32 allocs/op
+BenchmarkPossum_Param5                   1000000              2495 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_Param5                 1000000              1844 ns/op             432 B/op          5 allocs/op
+BenchmarkRivet_Param5                    2000000               935 ns/op             240 B/op          1 allocs/op
+BenchmarkTango_Param5                    1000000              2327 ns/op             360 B/op          8 allocs/op
+BenchmarkTigerTonic_Param5                100000             18514 ns/op            2551 B/op         43 allocs/op
+BenchmarkTraffic_Param5                   200000             11997 ns/op            2248 B/op         25 allocs/op
+BenchmarkVulcan_Param5                   1000000              1333 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_Param20                     1000000              2031 ns/op             640 B/op          1 allocs/op
+BenchmarkBear_Param20                     200000              7285 ns/op            1664 B/op          5 allocs/op
+BenchmarkBeego_Param20                    300000              6224 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_Param20                     200000              8023 ns/op            1903 B/op          5 allocs/op
+BenchmarkDenco_Param20                   1000000              2262 ns/op             640 B/op          1 allocs/op
+BenchmarkEcho_Param20                    1000000              1387 ns/op              32 B/op          1 allocs/op
+BenchmarkGin_Param20                     3000000               503 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_Param20               100000             14408 ns/op            3795 B/op         15 allocs/op
+BenchmarkGoji_Param20                     500000              5272 ns/op            1247 B/op          2 allocs/op
+BenchmarkGojiv2_Param20                  1000000              4163 ns/op            1248 B/op          8 allocs/op
+BenchmarkGoJsonRest_Param20               100000             17866 ns/op            4485 B/op         20 allocs/op
+BenchmarkGoRestful_Param20                100000             21022 ns/op            4724 B/op         23 allocs/op
+BenchmarkGorillaMux_Param20               100000             17055 ns/op            3547 B/op         13 allocs/op
+BenchmarkHttpRouter_Param20              1000000              1748 ns/op             640 B/op          1 allocs/op
+BenchmarkHttpTreeMux_Param20              200000             12246 ns/op            3196 B/op         10 allocs/op
+BenchmarkKocha_Param20                    300000              6861 ns/op            1808 B/op         27 allocs/op
+BenchmarkLARS_Param20                    3000000               526 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_Param20                  100000             13069 ns/op            2906 B/op         12 allocs/op
+BenchmarkMartini_Param20                  100000             23602 ns/op            3597 B/op         13 allocs/op
+BenchmarkPat_Param20                       50000             32143 ns/op            4688 B/op        111 allocs/op
+BenchmarkPossum_Param20                  1000000              2396 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_Param20                 200000              8907 ns/op            2283 B/op          7 allocs/op
+BenchmarkRivet_Param20                   1000000              3280 ns/op            1024 B/op          1 allocs/op
+BenchmarkTango_Param20                    500000              4640 ns/op             856 B/op          8 allocs/op
+BenchmarkTigerTonic_Param20                20000             67581 ns/op           10532 B/op        138 allocs/op
+BenchmarkTraffic_Param20                   50000             40313 ns/op            7941 B/op         45 allocs/op
+BenchmarkVulcan_Param20                  1000000              2264 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_ParamWrite                  3000000               532 ns/op              40 B/op          2 allocs/op
+BenchmarkBear_ParamWrite                 1000000              1778 ns/op             456 B/op          5 allocs/op
+BenchmarkBeego_ParamWrite                1000000              2596 ns/op             376 B/op          5 allocs/op
+BenchmarkBone_ParamWrite                 1000000              2519 ns/op             688 B/op          5 allocs/op
+BenchmarkDenco_ParamWrite                5000000               411 ns/op              32 B/op          1 allocs/op
+BenchmarkEcho_ParamWrite                 2000000               718 ns/op              40 B/op          2 allocs/op
+BenchmarkGin_ParamWrite                  5000000               283 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_ParamWrite           1000000              2561 ns/op             656 B/op          9 allocs/op
+BenchmarkGoji_ParamWrite                 1000000              1378 ns/op             336 B/op          2 allocs/op
+BenchmarkGojiv2_ParamWrite               1000000              3128 ns/op             976 B/op         10 allocs/op
+BenchmarkGoJsonRest_ParamWrite            500000              4446 ns/op            1128 B/op         18 allocs/op
+BenchmarkGoRestful_ParamWrite             200000             10291 ns/op            2304 B/op         22 allocs/op
+BenchmarkGorillaMux_ParamWrite            500000              5153 ns/op            1064 B/op         12 allocs/op
+BenchmarkHttpRouter_ParamWrite           5000000               263 ns/op              32 B/op          1 allocs/op
+BenchmarkHttpTreeMux_ParamWrite          1000000              1351 ns/op             352 B/op          3 allocs/op
+BenchmarkKocha_ParamWrite                3000000               538 ns/op              56 B/op          3 allocs/op
+BenchmarkLARS_ParamWrite                 5000000               316 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_ParamWrite               500000              5756 ns/op            1160 B/op         14 allocs/op
+BenchmarkMartini_ParamWrite               200000             13097 ns/op            1176 B/op         14 allocs/op
+BenchmarkPat_ParamWrite                   500000              4954 ns/op            1072 B/op         17 allocs/op
+BenchmarkPossum_ParamWrite               1000000              2499 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_ParamWrite             1000000              1531 ns/op             432 B/op          5 allocs/op
+BenchmarkRivet_ParamWrite                3000000               570 ns/op             112 B/op          2 allocs/op
+BenchmarkTango_ParamWrite                2000000               957 ns/op             136 B/op          4 allocs/op
+BenchmarkTigerTonic_ParamWrite            200000              7025 ns/op            1424 B/op         23 allocs/op
+BenchmarkTraffic_ParamWrite               200000             10112 ns/op            2384 B/op         25 allocs/op
+BenchmarkVulcan_ParamWrite               1000000              1006 ns/op              98 B/op          3 allocs/op
+```
+
+## GitHub
+
+```
+BenchmarkGin_GithubStatic               10000000               156 ns/op               0 B/op          0 allocs/op
+
+BenchmarkAce_GithubStatic                5000000               294 ns/op               0 B/op          0 allocs/op
+BenchmarkBear_GithubStatic               2000000               893 ns/op             120 B/op          3 allocs/op
+BenchmarkBeego_GithubStatic              1000000              2491 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_GithubStatic                 50000             25300 ns/op            2880 B/op         60 allocs/op
+BenchmarkDenco_GithubStatic             20000000                76.0 ns/op             0 B/op          0 allocs/op
+BenchmarkEcho_GithubStatic               2000000               516 ns/op              32 B/op          1 allocs/op
+BenchmarkGocraftWeb_GithubStatic         1000000              1448 ns/op             296 B/op          5 allocs/op
+BenchmarkGoji_GithubStatic               3000000               496 ns/op               0 B/op          0 allocs/op
+BenchmarkGojiv2_GithubStatic             1000000              2941 ns/op             928 B/op          7 allocs/op
+BenchmarkGoRestful_GithubStatic           100000             27256 ns/op            3224 B/op         22 allocs/op
+BenchmarkGoJsonRest_GithubStatic         1000000              2196 ns/op             329 B/op         11 allocs/op
+BenchmarkGorillaMux_GithubStatic           50000             31617 ns/op             736 B/op         10 allocs/op
+BenchmarkHttpRouter_GithubStatic        20000000                88.4 ns/op             0 B/op          0 allocs/op
+BenchmarkHttpTreeMux_GithubStatic       10000000               134 ns/op               0 B/op          0 allocs/op
+BenchmarkKocha_GithubStatic             20000000               113 ns/op               0 B/op          0 allocs/op
+BenchmarkLARS_GithubStatic              10000000               195 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_GithubStatic             500000              3740 ns/op             768 B/op          9 allocs/op
+BenchmarkMartini_GithubStatic              50000             27673 ns/op             768 B/op          9 allocs/op
+BenchmarkPat_GithubStatic                 100000             19470 ns/op            3648 B/op         76 allocs/op
+BenchmarkPossum_GithubStatic             1000000              1729 ns/op             416 B/op          3 allocs/op
+BenchmarkR2router_GithubStatic           2000000               879 ns/op             144 B/op          4 allocs/op
+BenchmarkRivet_GithubStatic             10000000               231 ns/op               0 B/op          0 allocs/op
+BenchmarkTango_GithubStatic              1000000              2325 ns/op             248 B/op          8 allocs/op
+BenchmarkTigerTonic_GithubStatic         3000000               610 ns/op              48 B/op          1 allocs/op
+BenchmarkTraffic_GithubStatic              20000             62973 ns/op           18904 B/op        148 allocs/op
+BenchmarkVulcan_GithubStatic             1000000              1447 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_GithubParam                 2000000               686 ns/op              96 B/op          1 allocs/op
+BenchmarkBear_GithubParam                1000000              2155 ns/op             496 B/op          5 allocs/op
+BenchmarkBeego_GithubParam               1000000              2713 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_GithubParam                 100000             15088 ns/op            1760 B/op         18 allocs/op
+BenchmarkDenco_GithubParam               2000000               629 ns/op             128 B/op          1 allocs/op
+BenchmarkEcho_GithubParam                2000000               653 ns/op              32 B/op          1 allocs/op
+BenchmarkGin_GithubParam                 5000000               255 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_GithubParam          1000000              3145 ns/op             712 B/op          9 allocs/op
+BenchmarkGoji_GithubParam                1000000              1916 ns/op             336 B/op          2 allocs/op
+BenchmarkGojiv2_GithubParam              1000000              3975 ns/op            1024 B/op         10 allocs/op
+BenchmarkGoJsonRest_GithubParam           300000              4134 ns/op             713 B/op         14 allocs/op
+BenchmarkGoRestful_GithubParam             50000             30782 ns/op            2360 B/op         21 allocs/op
+BenchmarkGorillaMux_GithubParam           100000             17148 ns/op            1088 B/op         11 allocs/op
+BenchmarkHttpRouter_GithubParam          3000000               523 ns/op              96 B/op          1 allocs/op
+BenchmarkHttpTreeMux_GithubParam         1000000              1671 ns/op             384 B/op          4 allocs/op
+BenchmarkKocha_GithubParam               1000000              1021 ns/op             128 B/op          5 allocs/op
+BenchmarkLARS_GithubParam                5000000               283 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_GithubParam              500000              4270 ns/op            1056 B/op         10 allocs/op
+BenchmarkMartini_GithubParam              100000             21728 ns/op            1152 B/op         11 allocs/op
+BenchmarkPat_GithubParam                  200000             11208 ns/op            2464 B/op         48 allocs/op
+BenchmarkPossum_GithubParam              1000000              2334 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_GithubParam            1000000              1487 ns/op             432 B/op          5 allocs/op
+BenchmarkRivet_GithubParam               2000000               782 ns/op              96 B/op          1 allocs/op
+BenchmarkTango_GithubParam               1000000              2653 ns/op             344 B/op          8 allocs/op
+BenchmarkTigerTonic_GithubParam           300000             14073 ns/op            1440 B/op         24 allocs/op
+BenchmarkTraffic_GithubParam               50000             29164 ns/op            5992 B/op         52 allocs/op
+BenchmarkVulcan_GithubParam              1000000              2529 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_GithubAll                     10000            134059 ns/op           13792 B/op        167 allocs/op
+BenchmarkBear_GithubAll                     5000            534445 ns/op           86448 B/op        943 allocs/op
+BenchmarkBeego_GithubAll                    3000            592444 ns/op           74705 B/op        812 allocs/op
+BenchmarkBone_GithubAll                      200           6957308 ns/op          698784 B/op       8453 allocs/op
+BenchmarkDenco_GithubAll                   10000            158819 ns/op           20224 B/op        167 allocs/op
+BenchmarkEcho_GithubAll                    10000            154700 ns/op            6496 B/op        203 allocs/op
+BenchmarkGin_GithubAll                     30000             48375 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_GithubAll               3000            570806 ns/op          131656 B/op       1686 allocs/op
+BenchmarkGoji_GithubAll                     2000            818034 ns/op           56112 B/op        334 allocs/op
+BenchmarkGojiv2_GithubAll                   2000           1213973 ns/op          274768 B/op       3712 allocs/op
+BenchmarkGoJsonRest_GithubAll               2000            785796 ns/op          134371 B/op       2737 allocs/op
+BenchmarkGoRestful_GithubAll                 300           5238188 ns/op          689672 B/op       4519 allocs/op
+BenchmarkGorillaMux_GithubAll                100          10257726 ns/op          211840 B/op       2272 allocs/op
+BenchmarkHttpRouter_GithubAll              20000            105414 ns/op           13792 B/op        167 allocs/op
+BenchmarkHttpTreeMux_GithubAll             10000            319934 ns/op           65856 B/op        671 allocs/op
+BenchmarkKocha_GithubAll                   10000            209442 ns/op           23304 B/op        843 allocs/op
+BenchmarkLARS_GithubAll                    20000             62565 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_GithubAll                  2000           1161270 ns/op          204194 B/op       2000 allocs/op
+BenchmarkMartini_GithubAll                   200           9991713 ns/op          226549 B/op       2325 allocs/op
+BenchmarkPat_GithubAll                       200           5590793 ns/op         1499568 B/op      27435 allocs/op
+BenchmarkPossum_GithubAll                  10000            319768 ns/op           84448 B/op        609 allocs/op
+BenchmarkR2router_GithubAll                10000            305134 ns/op           77328 B/op        979 allocs/op
+BenchmarkRivet_GithubAll                   10000            132134 ns/op           16272 B/op        167 allocs/op
+BenchmarkTango_GithubAll                    3000            552754 ns/op           63826 B/op       1618 allocs/op
+BenchmarkTigerTonic_GithubAll               1000           1439483 ns/op          239104 B/op       5374 allocs/op
+BenchmarkTraffic_GithubAll                   100          11383067 ns/op         2659329 B/op      21848 allocs/op
+BenchmarkVulcan_GithubAll                   5000            394253 ns/op           19894 B/op        609 allocs/op
+```
+
+## Google+
+
+```
+BenchmarkGin_GPlusStatic                10000000               183 ns/op               0 B/op          0 allocs/op
+
+BenchmarkAce_GPlusStatic                 5000000               276 ns/op               0 B/op          0 allocs/op
+BenchmarkBear_GPlusStatic                2000000               652 ns/op             104 B/op          3 allocs/op
+BenchmarkBeego_GPlusStatic               1000000              2239 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_GPlusStatic                5000000               380 ns/op              32 B/op          1 allocs/op
+BenchmarkDenco_GPlusStatic              30000000                45.8 ns/op             0 B/op          0 allocs/op
+BenchmarkEcho_GPlusStatic                5000000               338 ns/op              32 B/op          1 allocs/op
+BenchmarkGocraftWeb_GPlusStatic          1000000              1158 ns/op             280 B/op          5 allocs/op
+BenchmarkGoji_GPlusStatic                5000000               331 ns/op               0 B/op          0 allocs/op
+BenchmarkGojiv2_GPlusStatic              1000000              2106 ns/op             928 B/op          7 allocs/op
+BenchmarkGoJsonRest_GPlusStatic          1000000              1626 ns/op             329 B/op         11 allocs/op
+BenchmarkGoRestful_GPlusStatic            300000              7598 ns/op            1976 B/op         20 allocs/op
+BenchmarkGorillaMux_GPlusStatic          1000000              2629 ns/op             736 B/op         10 allocs/op
+BenchmarkHttpRouter_GPlusStatic         30000000                52.5 ns/op             0 B/op          0 allocs/op
+BenchmarkHttpTreeMux_GPlusStatic        20000000                85.8 ns/op             0 B/op          0 allocs/op
+BenchmarkKocha_GPlusStatic              20000000                89.2 ns/op             0 B/op          0 allocs/op
+BenchmarkLARS_GPlusStatic               10000000               162 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_GPlusStatic              500000              3479 ns/op             768 B/op          9 allocs/op
+BenchmarkMartini_GPlusStatic              200000              9092 ns/op             768 B/op          9 allocs/op
+BenchmarkPat_GPlusStatic                 3000000               493 ns/op              96 B/op          2 allocs/op
+BenchmarkPossum_GPlusStatic              1000000              1467 ns/op             416 B/op          3 allocs/op
+BenchmarkR2router_GPlusStatic            2000000               788 ns/op             144 B/op          4 allocs/op
+BenchmarkRivet_GPlusStatic              20000000               114 ns/op               0 B/op          0 allocs/op
+BenchmarkTango_GPlusStatic               1000000              1534 ns/op             200 B/op          8 allocs/op
+BenchmarkTigerTonic_GPlusStatic          5000000               282 ns/op              32 B/op          1 allocs/op
+BenchmarkTraffic_GPlusStatic              500000              3798 ns/op            1192 B/op         15 allocs/op
+BenchmarkVulcan_GPlusStatic              2000000              1125 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_GPlusParam                  3000000               528 ns/op              64 B/op          1 allocs/op
+BenchmarkBear_GPlusParam                 1000000              1570 ns/op             480 B/op          5 allocs/op
+BenchmarkBeego_GPlusParam                1000000              2369 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_GPlusParam                 1000000              2028 ns/op             688 B/op          5 allocs/op
+BenchmarkDenco_GPlusParam                5000000               385 ns/op              64 B/op          1 allocs/op
+BenchmarkEcho_GPlusParam                 3000000               441 ns/op              32 B/op          1 allocs/op
+BenchmarkGin_GPlusParam                 10000000               174 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_GPlusParam           1000000              2033 ns/op             648 B/op          8 allocs/op
+BenchmarkGoji_GPlusParam                 1000000              1399 ns/op             336 B/op          2 allocs/op
+BenchmarkGojiv2_GPlusParam               1000000              2641 ns/op             944 B/op          8 allocs/op
+BenchmarkGoJsonRest_GPlusParam           1000000              2824 ns/op             649 B/op         13 allocs/op
+BenchmarkGoRestful_GPlusParam             200000              8875 ns/op            2296 B/op         21 allocs/op
+BenchmarkGorillaMux_GPlusParam            200000              6291 ns/op            1056 B/op         11 allocs/op
+BenchmarkHttpRouter_GPlusParam           5000000               316 ns/op              64 B/op          1 allocs/op
+BenchmarkHttpTreeMux_GPlusParam          1000000              1129 ns/op             352 B/op          3 allocs/op
+BenchmarkKocha_GPlusParam                3000000               538 ns/op              56 B/op          3 allocs/op
+BenchmarkLARS_GPlusParam                10000000               198 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_GPlusParam               500000              3554 ns/op            1056 B/op         10 allocs/op
+BenchmarkMartini_GPlusParam               200000              9831 ns/op            1072 B/op         10 allocs/op
+BenchmarkPat_GPlusParam                  1000000              2706 ns/op             688 B/op         12 allocs/op
+BenchmarkPossum_GPlusParam               1000000              2297 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_GPlusParam             1000000              1318 ns/op             432 B/op          5 allocs/op
+BenchmarkRivet_GPlusParam                5000000               399 ns/op              48 B/op          1 allocs/op
+BenchmarkTango_GPlusParam                1000000              2070 ns/op             264 B/op          8 allocs/op
+BenchmarkTigerTonic_GPlusParam            500000              4853 ns/op            1056 B/op         17 allocs/op
+BenchmarkTraffic_GPlusParam               200000              8278 ns/op            1976 B/op         21 allocs/op
+BenchmarkVulcan_GPlusParam               1000000              1243 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_GPlus2Params                3000000               549 ns/op              64 B/op          1 allocs/op
+BenchmarkBear_GPlus2Params               1000000              2112 ns/op             496 B/op          5 allocs/op
+BenchmarkBeego_GPlus2Params               500000              2750 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_GPlus2Params                300000              7032 ns/op            1040 B/op          9 allocs/op
+BenchmarkDenco_GPlus2Params              3000000               502 ns/op              64 B/op          1 allocs/op
+BenchmarkEcho_GPlus2Params               3000000               641 ns/op              32 B/op          1 allocs/op
+BenchmarkGin_GPlus2Params                5000000               250 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_GPlus2Params         1000000              2681 ns/op             712 B/op          9 allocs/op
+BenchmarkGoji_GPlus2Params               1000000              1926 ns/op             336 B/op          2 allocs/op
+BenchmarkGojiv2_GPlus2Params              500000              3996 ns/op            1024 B/op         11 allocs/op
+BenchmarkGoJsonRest_GPlus2Params          500000              3886 ns/op             713 B/op         14 allocs/op
+BenchmarkGoRestful_GPlus2Params           200000             10376 ns/op            2360 B/op         21 allocs/op
+BenchmarkGorillaMux_GPlus2Params          100000             14162 ns/op            1088 B/op         11 allocs/op
+BenchmarkHttpRouter_GPlus2Params         5000000               336 ns/op              64 B/op          1 allocs/op
+BenchmarkHttpTreeMux_GPlus2Params        1000000              1523 ns/op             384 B/op          4 allocs/op
+BenchmarkKocha_GPlus2Params              2000000               970 ns/op             128 B/op          5 allocs/op
+BenchmarkLARS_GPlus2Params               5000000               238 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_GPlus2Params             500000              4016 ns/op            1056 B/op         10 allocs/op
+BenchmarkMartini_GPlus2Params             100000             21253 ns/op            1200 B/op         13 allocs/op
+BenchmarkPat_GPlus2Params                 200000              8632 ns/op            2256 B/op         34 allocs/op
+BenchmarkPossum_GPlus2Params             1000000              2171 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_GPlus2Params           1000000              1340 ns/op             432 B/op          5 allocs/op
+BenchmarkRivet_GPlus2Params              3000000               557 ns/op              96 B/op          1 allocs/op
+BenchmarkTango_GPlus2Params              1000000              2186 ns/op             344 B/op          8 allocs/op
+BenchmarkTigerTonic_GPlus2Params          200000              9060 ns/op            1488 B/op         24 allocs/op
+BenchmarkTraffic_GPlus2Params             100000             20324 ns/op            3272 B/op         31 allocs/op
+BenchmarkVulcan_GPlus2Params             1000000              2039 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_GPlusAll                     300000              6603 ns/op             640 B/op         11 allocs/op
+BenchmarkBear_GPlusAll                    100000             22363 ns/op            5488 B/op         61 allocs/op
+BenchmarkBeego_GPlusAll                    50000             38757 ns/op            4784 B/op         52 allocs/op
+BenchmarkBone_GPlusAll                     20000             54916 ns/op           10336 B/op         98 allocs/op
+BenchmarkDenco_GPlusAll                   300000              4959 ns/op             672 B/op         11 allocs/op
+BenchmarkEcho_GPlusAll                    200000              6558 ns/op             416 B/op         13 allocs/op
+BenchmarkGin_GPlusAll                     500000              2757 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_GPlusAll               50000             34615 ns/op            8040 B/op        103 allocs/op
+BenchmarkGoji_GPlusAll                    100000             16002 ns/op            3696 B/op         22 allocs/op
+BenchmarkGojiv2_GPlusAll                   50000             35060 ns/op           12624 B/op        115 allocs/op
+BenchmarkGoJsonRest_GPlusAll               50000             41479 ns/op            8117 B/op        170 allocs/op
+BenchmarkGoRestful_GPlusAll                10000            131653 ns/op           32024 B/op        275 allocs/op
+BenchmarkGorillaMux_GPlusAll               10000            101380 ns/op           13296 B/op        142 allocs/op
+BenchmarkHttpRouter_GPlusAll              500000              3711 ns/op             640 B/op         11 allocs/op
+BenchmarkHttpTreeMux_GPlusAll             100000             14438 ns/op            4032 B/op         38 allocs/op
+BenchmarkKocha_GPlusAll                   200000              8039 ns/op             976 B/op         43 allocs/op
+BenchmarkLARS_GPlusAll                    500000              2630 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_GPlusAll                  30000             51123 ns/op           13152 B/op        128 allocs/op
+BenchmarkMartini_GPlusAll                  10000            176157 ns/op           14016 B/op        145 allocs/op
+BenchmarkPat_GPlusAll                      20000             69911 ns/op           16576 B/op        298 allocs/op
+BenchmarkPossum_GPlusAll                  100000             20716 ns/op            5408 B/op         39 allocs/op
+BenchmarkR2router_GPlusAll                100000             17463 ns/op            5040 B/op         63 allocs/op
+BenchmarkRivet_GPlusAll                   300000              5142 ns/op             768 B/op         11 allocs/op
+BenchmarkTango_GPlusAll                    50000             27321 ns/op            3656 B/op        104 allocs/op
+BenchmarkTigerTonic_GPlusAll               20000             77597 ns/op           14512 B/op        288 allocs/op
+BenchmarkTraffic_GPlusAll                  10000            151406 ns/op           37360 B/op        392 allocs/op
+BenchmarkVulcan_GPlusAll                  100000             18555 ns/op            1274 B/op         39 allocs/op
+```
+
+## Parse.com
+
+```
+BenchmarkGin_ParseStatic                10000000               133 ns/op               0 B/op          0 allocs/op
+
+BenchmarkAce_ParseStatic                 5000000               241 ns/op               0 B/op          0 allocs/op
+BenchmarkBear_ParseStatic                2000000               728 ns/op             120 B/op          3 allocs/op
+BenchmarkBeego_ParseStatic               1000000              2623 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_ParseStatic                1000000              1285 ns/op             144 B/op          3 allocs/op
+BenchmarkDenco_ParseStatic              30000000                57.8 ns/op             0 B/op          0 allocs/op
+BenchmarkEcho_ParseStatic                5000000               342 ns/op              32 B/op          1 allocs/op
+BenchmarkGocraftWeb_ParseStatic          1000000              1478 ns/op             296 B/op          5 allocs/op
+BenchmarkGoji_ParseStatic                3000000               415 ns/op               0 B/op          0 allocs/op
+BenchmarkGojiv2_ParseStatic              1000000              2087 ns/op             928 B/op          7 allocs/op
+BenchmarkGoJsonRest_ParseStatic          1000000              1712 ns/op             329 B/op         11 allocs/op
+BenchmarkGoRestful_ParseStatic            200000             11072 ns/op            3224 B/op         22 allocs/op
+BenchmarkGorillaMux_ParseStatic           500000              4129 ns/op             752 B/op         11 allocs/op
+BenchmarkHttpRouter_ParseStatic         30000000                52.4 ns/op             0 B/op          0 allocs/op
+BenchmarkHttpTreeMux_ParseStatic        20000000               109 ns/op               0 B/op          0 allocs/op
+BenchmarkKocha_ParseStatic              20000000                81.8 ns/op             0 B/op          0 allocs/op
+BenchmarkLARS_ParseStatic               10000000               150 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_ParseStatic             1000000              3288 ns/op             768 B/op          9 allocs/op
+BenchmarkMartini_ParseStatic              200000              9110 ns/op             768 B/op          9 allocs/op
+BenchmarkPat_ParseStatic                 1000000              1135 ns/op             240 B/op          5 allocs/op
+BenchmarkPossum_ParseStatic              1000000              1557 ns/op             416 B/op          3 allocs/op
+BenchmarkR2router_ParseStatic            2000000               730 ns/op             144 B/op          4 allocs/op
+BenchmarkRivet_ParseStatic              10000000               121 ns/op               0 B/op          0 allocs/op
+BenchmarkTango_ParseStatic               1000000              1688 ns/op             248 B/op          8 allocs/op
+BenchmarkTigerTonic_ParseStatic          3000000               427 ns/op              48 B/op          1 allocs/op
+BenchmarkTraffic_ParseStatic              500000              5962 ns/op            1816 B/op         20 allocs/op
+BenchmarkVulcan_ParseStatic              2000000               969 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_ParseParam                  3000000               497 ns/op              64 B/op          1 allocs/op
+BenchmarkBear_ParseParam                 1000000              1473 ns/op             467 B/op          5 allocs/op
+BenchmarkBeego_ParseParam                1000000              2384 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_ParseParam                 1000000              2513 ns/op             768 B/op          6 allocs/op
+BenchmarkDenco_ParseParam                5000000               364 ns/op              64 B/op          1 allocs/op
+BenchmarkEcho_ParseParam                 5000000               418 ns/op              32 B/op          1 allocs/op
+BenchmarkGin_ParseParam                 10000000               163 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_ParseParam           1000000              2361 ns/op             664 B/op          8 allocs/op
+BenchmarkGoji_ParseParam                 1000000              1590 ns/op             336 B/op          2 allocs/op
+BenchmarkGojiv2_ParseParam               1000000              2851 ns/op             976 B/op          9 allocs/op
+BenchmarkGoJsonRest_ParseParam           1000000              2965 ns/op             649 B/op         13 allocs/op
+BenchmarkGoRestful_ParseParam             200000             12207 ns/op            3544 B/op         23 allocs/op
+BenchmarkGorillaMux_ParseParam            500000              5187 ns/op            1088 B/op         12 allocs/op
+BenchmarkHttpRouter_ParseParam           5000000               275 ns/op              64 B/op          1 allocs/op
+BenchmarkHttpTreeMux_ParseParam          1000000              1108 ns/op             352 B/op          3 allocs/op
+BenchmarkKocha_ParseParam                3000000               495 ns/op              56 B/op          3 allocs/op
+BenchmarkLARS_ParseParam                10000000               192 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_ParseParam               500000              4103 ns/op            1056 B/op         10 allocs/op
+BenchmarkMartini_ParseParam               200000              9878 ns/op            1072 B/op         10 allocs/op
+BenchmarkPat_ParseParam                   500000              3657 ns/op            1120 B/op         17 allocs/op
+BenchmarkPossum_ParseParam               1000000              2084 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_ParseParam             1000000              1251 ns/op             432 B/op          5 allocs/op
+BenchmarkRivet_ParseParam                5000000               335 ns/op              48 B/op          1 allocs/op
+BenchmarkTango_ParseParam                1000000              1854 ns/op             280 B/op          8 allocs/op
+BenchmarkTigerTonic_ParseParam            500000              4582 ns/op            1008 B/op         17 allocs/op
+BenchmarkTraffic_ParseParam               200000              8125 ns/op            2248 B/op         23 allocs/op
+BenchmarkVulcan_ParseParam               1000000              1148 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_Parse2Params                3000000               539 ns/op              64 B/op          1 allocs/op
+BenchmarkBear_Parse2Params               1000000              1778 ns/op             496 B/op          5 allocs/op
+BenchmarkBeego_Parse2Params              1000000              2519 ns/op             368 B/op          4 allocs/op
+BenchmarkBone_Parse2Params               1000000              2596 ns/op             720 B/op          5 allocs/op
+BenchmarkDenco_Parse2Params              3000000               492 ns/op              64 B/op          1 allocs/op
+BenchmarkEcho_Parse2Params               3000000               484 ns/op              32 B/op          1 allocs/op
+BenchmarkGin_Parse2Params               10000000               193 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_Parse2Params         1000000              2575 ns/op             712 B/op          9 allocs/op
+BenchmarkGoji_Parse2Params               1000000              1373 ns/op             336 B/op          2 allocs/op
+BenchmarkGojiv2_Parse2Params              500000              2416 ns/op             960 B/op          8 allocs/op
+BenchmarkGoJsonRest_Parse2Params          300000              3452 ns/op             713 B/op         14 allocs/op
+BenchmarkGoRestful_Parse2Params           100000             17719 ns/op            6008 B/op         25 allocs/op
+BenchmarkGorillaMux_Parse2Params          300000              5102 ns/op            1088 B/op         11 allocs/op
+BenchmarkHttpRouter_Parse2Params         5000000               303 ns/op              64 B/op          1 allocs/op
+BenchmarkHttpTreeMux_Parse2Params        1000000              1372 ns/op             384 B/op          4 allocs/op
+BenchmarkKocha_Parse2Params              2000000               874 ns/op             128 B/op          5 allocs/op
+BenchmarkLARS_Parse2Params              10000000               192 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_Parse2Params             500000              3871 ns/op            1056 B/op         10 allocs/op
+BenchmarkMartini_Parse2Params             200000              9954 ns/op            1152 B/op         11 allocs/op
+BenchmarkPat_Parse2Params                 500000              4194 ns/op             832 B/op         17 allocs/op
+BenchmarkPossum_Parse2Params             1000000              2121 ns/op             560 B/op          6 allocs/op
+BenchmarkR2router_Parse2Params           1000000              1415 ns/op             432 B/op          5 allocs/op
+BenchmarkRivet_Parse2Params              3000000               457 ns/op              96 B/op          1 allocs/op
+BenchmarkTango_Parse2Params              1000000              1914 ns/op             312 B/op          8 allocs/op
+BenchmarkTigerTonic_Parse2Params          300000              6895 ns/op            1408 B/op         24 allocs/op
+BenchmarkTraffic_Parse2Params             200000              8317 ns/op            2040 B/op         22 allocs/op
+BenchmarkVulcan_Parse2Params             1000000              1274 ns/op              98 B/op          3 allocs/op
+BenchmarkAce_ParseAll                     200000             10401 ns/op             640 B/op         16 allocs/op
+BenchmarkBear_ParseAll                     50000             37743 ns/op            8928 B/op        110 allocs/op
+BenchmarkBeego_ParseAll                    20000             63193 ns/op            9568 B/op        104 allocs/op
+BenchmarkBone_ParseAll                     20000             61767 ns/op           14160 B/op        131 allocs/op
+BenchmarkDenco_ParseAll                   300000              7036 ns/op             928 B/op         16 allocs/op
+BenchmarkEcho_ParseAll                    200000             11824 ns/op             832 B/op         26 allocs/op
+BenchmarkGin_ParseAll                     300000              4199 ns/op               0 B/op          0 allocs/op
+BenchmarkGocraftWeb_ParseAll               30000             51758 ns/op           13728 B/op        181 allocs/op
+BenchmarkGoji_ParseAll                     50000             29614 ns/op            5376 B/op         32 allocs/op
+BenchmarkGojiv2_ParseAll                   20000             68676 ns/op           24464 B/op        199 allocs/op
+BenchmarkGoJsonRest_ParseAll               20000             76135 ns/op           13866 B/op        321 allocs/op
+BenchmarkGoRestful_ParseAll                 5000            389487 ns/op          110928 B/op        600 allocs/op
+BenchmarkGorillaMux_ParseAll               10000            221250 ns/op           24864 B/op        292 allocs/op
+BenchmarkHttpRouter_ParseAll              200000              6444 ns/op             640 B/op         16 allocs/op
+BenchmarkHttpTreeMux_ParseAll              50000             30702 ns/op            5728 B/op         51 allocs/op
+BenchmarkKocha_ParseAll                   200000             13712 ns/op            1112 B/op         54 allocs/op
+BenchmarkLARS_ParseAll                    300000              6925 ns/op               0 B/op          0 allocs/op
+BenchmarkMacaron_ParseAll                  20000             96278 ns/op           24576 B/op        250 allocs/op
+BenchmarkMartini_ParseAll                   5000            271352 ns/op           25072 B/op        253 allocs/op
+BenchmarkPat_ParseAll                      20000             74941 ns/op           17264 B/op        343 allocs/op
+BenchmarkPossum_ParseAll                   50000             39947 ns/op           10816 B/op         78 allocs/op
+BenchmarkR2router_ParseAll                 50000             42479 ns/op            8352 B/op        120 allocs/op
+BenchmarkRivet_ParseAll                   200000              7726 ns/op             912 B/op         16 allocs/op
+BenchmarkTango_ParseAll                    30000             50014 ns/op            7168 B/op        208 allocs/op
+BenchmarkTigerTonic_ParseAll               10000            106550 ns/op           19728 B/op        379 allocs/op
+BenchmarkTraffic_ParseAll                  10000            216037 ns/op           57776 B/op        642 allocs/op
+BenchmarkVulcan_ParseAll                   50000             34379 ns/op            2548 B/op         78 allocs/op
+```
diff --git a/vendor/github.com/gin-gonic/gin/CHANGELOG.md b/vendor/github.com/gin-gonic/gin/CHANGELOG.md
new file mode 100644 (file)
index 0000000..e6a108c
--- /dev/null
@@ -0,0 +1,213 @@
+# CHANGELOG
+
+### Gin 1.3.0
+
+- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383)
+- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358)
+- [NEW] Add `Pusher()` in [`type ResponseWriter`](https://godoc.org/github.com/gin-gonic/gin#ResponseWriter) for supporting http2 push, see [#1273](https://github.com/gin-gonic/gin/pull/1273)
+- [NEW] Add [`func (*Context) DataFromReader`](https://godoc.org/github.com/gin-gonic/gin#Context.DataFromReader) for serving dynamic data, see [#1304](https://github.com/gin-gonic/gin/pull/1304)
+- [NEW] Add [`func (*Context) ShouldBindBodyWith`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindBodyWith) allowing to call binding multiple times, see [#1341](https://github.com/gin-gonic/gin/pull/1341)
+- [NEW] Support pointers in form binding, see [#1336](https://github.com/gin-gonic/gin/pull/1336)
+- [NEW] Add [`func (*Context) JSONP`](https://godoc.org/github.com/gin-gonic/gin#Context.JSONP), see [#1333](https://github.com/gin-gonic/gin/pull/1333)
+- [NEW] Support default value in form binding, see [#1138](https://github.com/gin-gonic/gin/pull/1138)
+- [NEW] Expose validator engine in [`type StructValidator`](https://godoc.org/github.com/gin-gonic/gin/binding#StructValidator), see [#1277](https://github.com/gin-gonic/gin/pull/1277)
+- [NEW] Add [`func (*Context) ShouldBind`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind), [`func (*Context) ShouldBindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindQuery) and [`func (*Context) ShouldBindJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON), see [#1047](https://github.com/gin-gonic/gin/pull/1047)
+- [NEW] Add support for `time.Time` location in form binding, see [#1117](https://github.com/gin-gonic/gin/pull/1117)
+- [NEW] Add [`func (*Context) BindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.BindQuery), see [#1029](https://github.com/gin-gonic/gin/pull/1029)
+- [NEW] Make [jsonite](https://github.com/json-iterator/go) optional with build tags, see [#1026](https://github.com/gin-gonic/gin/pull/1026)
+- [NEW] Show query string in logger, see [#999](https://github.com/gin-gonic/gin/pull/999)
+- [NEW] Add [`func (*Context) SecureJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.SecureJSON), see [#987](https://github.com/gin-gonic/gin/pull/987) and [#993](https://github.com/gin-gonic/gin/pull/993)
+- [DEPRECATE] `func (*Context) GetCookie` for [`func (*Context) Cookie`](https://godoc.org/github.com/gin-gonic/gin#Context.Cookie)
+- [FIX] Don't display color tags if [`func DisableConsoleColor`](https://godoc.org/github.com/gin-gonic/gin#DisableConsoleColor) called, see [#1072](https://github.com/gin-gonic/gin/pull/1072)
+- [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250)
+- [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460)
+
+### Gin 1.2.0
+
+- [NEW] Switch from godeps to govendor
+- [NEW] Add support for Let's Encrypt via gin-gonic/autotls
+- [NEW] Improve README examples and add extra at examples folder
+- [NEW] Improved support with App Engine
+- [NEW] Add custom template delimiters, see #860
+- [NEW] Add Template Func Maps, see #962
+- [NEW] Add \*context.Handler(), see #928
+- [NEW] Add \*context.GetRawData()
+- [NEW] Add \*context.GetHeader() (request)
+- [NEW] Add \*context.AbortWithStatusJSON() (JSON content type)
+- [NEW] Add \*context.Keys type cast helpers
+- [NEW] Add \*context.ShouldBindWith()
+- [NEW] Add \*context.MustBindWith()
+- [NEW] Add \*engine.SetFuncMap()
+- [DEPRECATE] On next release: \*context.BindWith(), see #855
+- [FIX] Refactor render
+- [FIX] Reworked tests
+- [FIX] logger now supports cygwin
+- [FIX] Use X-Forwarded-For before X-Real-Ip
+- [FIX] time.Time binding (#904)
+
+### Gin 1.1.4
+
+- [NEW] Support google appengine for IsTerminal func
+
+### Gin 1.1.3
+
+- [FIX] Reverted Logger: skip ANSI color commands
+
+### Gin 1.1
+
+- [NEW] Implement QueryArray and PostArray methods 
+- [NEW] Refactor GetQuery and GetPostForm 
+- [NEW] Add contribution guide 
+- [FIX] Corrected typos in README
+- [FIX] Removed additional Iota  
+- [FIX] Changed imports to gopkg instead of github in README (#733) 
+- [FIX] Logger: skip ANSI color commands if output is not a tty
+
+### Gin 1.0rc2 (...)
+
+- [PERFORMANCE] Fast path for writing Content-Type.
+- [PERFORMANCE] Much faster 404 routing
+- [PERFORMANCE] Allocation optimizations
+- [PERFORMANCE] Faster root tree lookup
+- [PERFORMANCE] Zero overhead, String() and JSON() rendering.
+- [PERFORMANCE] Faster ClientIP parsing
+- [PERFORMANCE] Much faster SSE implementation
+- [NEW] Benchmarks suite
+- [NEW] Bind validation can be disabled and replaced with custom validators.
+- [NEW] More flexible HTML render
+- [NEW] Multipart and PostForm bindings
+- [NEW] Adds method to return all the registered routes
+- [NEW] Context.HandlerName() returns the main handler's name
+- [NEW] Adds Error.IsType() helper
+- [FIX] Binding multipart form
+- [FIX] Integration tests
+- [FIX] Crash when binding non struct object in Context.
+- [FIX] RunTLS() implementation
+- [FIX] Logger() unit tests
+- [FIX] Adds SetHTMLTemplate() warning
+- [FIX] Context.IsAborted()
+- [FIX] More unit tests
+- [FIX] JSON, XML, HTML renders accept custom content-types
+- [FIX] gin.AbortIndex is unexported
+- [FIX] Better approach to avoid directory listing in StaticFS()
+- [FIX] Context.ClientIP() always returns the IP with trimmed spaces.
+- [FIX] Better warning when running in debug mode.
+- [FIX] Google App Engine integration. debugPrint does not use os.Stdout
+- [FIX] Fixes integer overflow in error type
+- [FIX] Error implements the json.Marshaller interface
+- [FIX] MIT license in every file
+
+
+### Gin 1.0rc1 (May 22, 2015)
+
+- [PERFORMANCE] Zero allocation router
+- [PERFORMANCE] Faster JSON, XML and text rendering
+- [PERFORMANCE] Custom hand optimized HttpRouter for Gin
+- [PERFORMANCE] Misc code optimizations. Inlining, tail call optimizations
+- [NEW] Built-in support for golang.org/x/net/context
+- [NEW] Any(path, handler). Create a route that matches any path
+- [NEW] Refactored rendering pipeline (faster and static typeded)
+- [NEW] Refactored errors API
+- [NEW] IndentedJSON() prints pretty JSON
+- [NEW] Added gin.DefaultWriter
+- [NEW] UNIX socket support
+- [NEW] RouterGroup.BasePath is exposed
+- [NEW] JSON validation using go-validate-yourself (very powerful options)
+- [NEW] Completed suite of unit tests
+- [NEW] HTTP streaming with c.Stream()
+- [NEW] StaticFile() creates a router for serving just one file.
+- [NEW] StaticFS() has an option to disable directory listing.
+- [NEW] StaticFS() for serving static files through virtual filesystems
+- [NEW] Server-Sent Events native support
+- [NEW] WrapF() and WrapH() helpers for wrapping http.HandlerFunc and http.Handler
+- [NEW] Added LoggerWithWriter() middleware
+- [NEW] Added RecoveryWithWriter() middleware
+- [NEW] Added DefaultPostFormValue()
+- [NEW] Added DefaultFormValue()
+- [NEW] Added DefaultParamValue()
+- [FIX] BasicAuth() when using custom realm
+- [FIX] Bug when serving static files in nested routing group
+- [FIX] Redirect using built-in http.Redirect()
+- [FIX] Logger when printing the requested path
+- [FIX] Documentation typos
+- [FIX] Context.Engine renamed to Context.engine
+- [FIX] Better debugging messages
+- [FIX] ErrorLogger
+- [FIX] Debug HTTP render
+- [FIX] Refactored binding and render modules
+- [FIX] Refactored Context initialization
+- [FIX] Refactored BasicAuth()
+- [FIX] NoMethod/NoRoute handlers
+- [FIX] Hijacking http
+- [FIX] Better support for Google App Engine (using log instead of fmt)
+
+
+### Gin 0.6 (Mar 9, 2015)
+
+- [NEW] Support multipart/form-data
+- [NEW] NoMethod handler
+- [NEW] Validate sub structures
+- [NEW] Support for HTTP Realm Auth
+- [FIX] Unsigned integers in binding
+- [FIX] Improve color logger
+
+
+### Gin 0.5 (Feb 7, 2015)
+
+- [NEW] Content Negotiation
+- [FIX] Solved security bug that allow a client to spoof ip
+- [FIX] Fix unexported/ignored fields in binding
+
+
+### Gin 0.4 (Aug 21, 2014)
+
+- [NEW] Development mode
+- [NEW] Unit tests
+- [NEW] Add Content.Redirect()
+- [FIX] Deferring WriteHeader()
+- [FIX] Improved documentation for model binding
+
+
+### Gin 0.3 (Jul 18, 2014)
+
+- [PERFORMANCE] Normal log and error log are printed in the same call.
+- [PERFORMANCE] Improve performance of NoRouter()
+- [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults.
+- [NEW] Flexible rendering API
+- [NEW] Add Context.File()
+- [NEW] Add shorcut RunTLS() for http.ListenAndServeTLS
+- [FIX] Rename NotFound404() to NoRoute()
+- [FIX] Errors in context are purged
+- [FIX] Adds HEAD method in Static file serving
+- [FIX] Refactors Static() file serving
+- [FIX] Using keyed initialization to fix app-engine integration
+- [FIX] Can't unmarshal JSON array, #63
+- [FIX] Renaming Context.Req to Context.Request
+- [FIX] Check application/x-www-form-urlencoded when parsing form
+
+
+### Gin 0.2b (Jul 08, 2014)
+- [PERFORMANCE] Using sync.Pool to allocatio/gc overhead
+- [NEW] Travis CI integration
+- [NEW] Completely new logger
+- [NEW] New API for serving static files. gin.Static()
+- [NEW] gin.H() can be serialized into XML
+- [NEW] Typed errors. Errors can be typed. Internet/external/custom.
+- [NEW] Support for Godeps
+- [NEW] Travis/Godocs badges in README
+- [NEW] New Bind() and BindWith() methods for parsing request body.
+- [NEW] Add Content.Copy()
+- [NEW] Add context.LastError()
+- [NEW] Add shorcut for OPTIONS HTTP method
+- [FIX] Tons of README fixes
+- [FIX] Header is written before body
+- [FIX] BasicAuth() and changes API a little bit
+- [FIX] Recovery() middleware only prints panics
+- [FIX] Context.Get() does not panic anymore. Use MustGet() instead.
+- [FIX] Multiple http.WriteHeader() in NotFound handlers
+- [FIX] Engine.Run() panics if http server can't be setted up
+- [FIX] Crash when route path doesn't start with '/'
+- [FIX] Do not update header when status code is negative
+- [FIX] Setting response headers before calling WriteHeader in context.String()
+- [FIX] Add MIT license
+- [FIX] Changes behaviour of ErrorLogger() and Logger()
diff --git a/vendor/github.com/gin-gonic/gin/CODE_OF_CONDUCT.md b/vendor/github.com/gin-gonic/gin/CODE_OF_CONDUCT.md
new file mode 100644 (file)
index 0000000..4ea14f3
--- /dev/null
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at teamgingonic@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/vendor/github.com/gin-gonic/gin/CONTRIBUTING.md b/vendor/github.com/gin-gonic/gin/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..547b777
--- /dev/null
@@ -0,0 +1,13 @@
+## Contributing 
+
+- With issues:
+  - Use the search tool before opening a new issue.
+  - Please provide source code and commit sha if you found a bug.
+  - Review existing issues and provide feedback or react to them.
+
+- With pull requests:
+  - Open your pull request against `master`
+  - Your pull request should have no more than two commits, if not you should squash them.
+  - It should pass all tests in the available continuous integrations systems such as TravisCI.
+  - You should add/modify tests to cover your proposed code changes.
+  - If your pull request contains a new feature, please document it on the README.
diff --git a/vendor/github.com/gin-gonic/gin/LICENSE b/vendor/github.com/gin-gonic/gin/LICENSE
new file mode 100644 (file)
index 0000000..1ff7f37
--- /dev/null
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Manuel Martínez-Almeida
+
+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.
diff --git a/vendor/github.com/gin-gonic/gin/Makefile b/vendor/github.com/gin-gonic/gin/Makefile
new file mode 100644 (file)
index 0000000..51b9969
--- /dev/null
@@ -0,0 +1,62 @@
+GOFMT ?= gofmt "-s"
+PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
+VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/)
+GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
+
+all: install
+
+install: deps
+       govendor sync
+
+.PHONY: test
+test:
+       sh coverage.sh
+
+.PHONY: fmt
+fmt:
+       $(GOFMT) -w $(GOFILES)
+
+.PHONY: fmt-check
+fmt-check:
+       # get all go files and run go fmt on them
+       @diff=$$($(GOFMT) -d $(GOFILES)); \
+       if [ -n "$$diff" ]; then \
+               echo "Please run 'make fmt' and commit the result:"; \
+               echo "$${diff}"; \
+               exit 1; \
+       fi;
+
+vet:
+       go vet $(VETPACKAGES)
+
+deps:
+       @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+               go get -u github.com/kardianos/govendor; \
+       fi
+       @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+               go get -u github.com/campoy/embedmd; \
+       fi
+
+embedmd:
+       embedmd -d *.md
+
+.PHONY: lint
+lint:
+       @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+               go get -u github.com/golang/lint/golint; \
+       fi
+       for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
+
+.PHONY: misspell-check
+misspell-check:
+       @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+               go get -u github.com/client9/misspell/cmd/misspell; \
+       fi
+       misspell -error $(GOFILES)
+
+.PHONY: misspell
+misspell:
+       @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
+               go get -u github.com/client9/misspell/cmd/misspell; \
+       fi
+       misspell -w $(GOFILES)
diff --git a/vendor/github.com/gin-gonic/gin/README.md b/vendor/github.com/gin-gonic/gin/README.md
new file mode 100644 (file)
index 0000000..28598ba
--- /dev/null
@@ -0,0 +1,1820 @@
+# Gin Web Framework
+
+<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
+
+[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
+[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
+[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
+[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)
+[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
+[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin)
+
+Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
+
+![Gin console logger](https://gin-gonic.github.io/gin/other/console.png)
+
+## Contents
+
+- [Installation](#installation)
+- [Prerequisite](#prerequisite)
+- [Quick start](#quick-start)
+- [Benchmarks](#benchmarks)
+- [Gin v1.stable](#gin-v1-stable)
+- [Build with jsoniter](#build-with-jsoniter)
+- [API Examples](#api-examples)
+    - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
+    - [Parameters in path](#parameters-in-path)
+    - [Querystring parameters](#querystring-parameters)
+    - [Multipart/Urlencoded Form](#multiparturlencoded-form)
+    - [Another example: query + post form](#another-example-query--post-form)
+    - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)
+    - [Upload files](#upload-files)
+    - [Grouping routes](#grouping-routes)
+    - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
+    - [Using middleware](#using-middleware)
+    - [How to write log file](#how-to-write-log-file)
+    - [Model binding and validation](#model-binding-and-validation)
+    - [Custom Validators](#custom-validators)
+    - [Only Bind Query String](#only-bind-query-string)
+    - [Bind Query String or Post Data](#bind-query-string-or-post-data)
+    - [Bind HTML checkboxes](#bind-html-checkboxes)
+    - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
+    - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering)
+    - [JSONP rendering](#jsonp)
+    - [Serving static files](#serving-static-files)
+    - [Serving data from reader](#serving-data-from-reader)
+    - [HTML rendering](#html-rendering)
+    - [Multitemplate](#multitemplate)
+    - [Redirects](#redirects)
+    - [Custom Middleware](#custom-middleware)
+    - [Using BasicAuth() middleware](#using-basicauth-middleware)
+    - [Goroutines inside a middleware](#goroutines-inside-a-middleware)
+    - [Custom HTTP configuration](#custom-http-configuration)
+    - [Support Let's Encrypt](#support-lets-encrypt)
+    - [Run multiple service using Gin](#run-multiple-service-using-gin)
+    - [Graceful restart or stop](#graceful-restart-or-stop)
+    - [Build a single binary with templates](#build-a-single-binary-with-templates)
+    - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
+    - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
+    - [http2 server push](#http2-server-push)
+- [Testing](#testing)
+- [Users](#users--)
+
+## Installation
+
+To install Gin package, you need to install Go and set your Go workspace first.
+
+1. Download and install it:
+
+```sh
+$ go get -u github.com/gin-gonic/gin
+```
+
+2. Import it in your code:
+
+```go
+import "github.com/gin-gonic/gin"
+```
+
+3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.
+
+```go
+import "net/http"
+```
+
+### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor)
+
+1. `go get` govendor
+
+```sh
+$ go get github.com/kardianos/govendor
+```
+2. Create your project folder and `cd` inside
+
+```sh
+$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
+```
+
+3. Vendor init your project and add gin
+
+```sh
+$ govendor init
+$ govendor fetch github.com/gin-gonic/gin@v1.2
+```
+
+4. Copy a starting template inside your project
+
+```sh
+$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go
+```
+
+5. Run your project
+
+```sh
+$ go run main.go
+```
+
+## Prerequisite
+
+Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
+
+## Quick start
+```sh
+# assume the following codes in example.go file
+$ cat example.go
+```
+
+```go
+package main
+
+import "github.com/gin-gonic/gin"
+
+func main() {
+       r := gin.Default()
+       r.GET("/ping", func(c *gin.Context) {
+               c.JSON(200, gin.H{
+                       "message": "pong",
+               })
+       })
+       r.Run() // listen and serve on 0.0.0.0:8080
+}
+```
+
+```
+# run example.go and visit 0.0.0.0:8080/ping on browser
+$ go run example.go
+```
+
+## Benchmarks
+
+Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)
+
+[See all benchmarks](/BENCHMARKS.md)
+
+Benchmark name                              | (1)        | (2)         | (3)               | (4)
+--------------------------------------------|-----------:|------------:|-----------:|---------:
+**BenchmarkGin_GithubAll**                  | **30000**  |  **48375**  |     **0**  |   **0**
+BenchmarkAce_GithubAll                      |   10000    |   134059    |   13792    |   167
+BenchmarkBear_GithubAll                     |    5000    |   534445    |   86448    |   943
+BenchmarkBeego_GithubAll                    |    3000    |   592444    |   74705    |   812
+BenchmarkBone_GithubAll                     |     200    |  6957308    |  698784    |  8453
+BenchmarkDenco_GithubAll                    |   10000    |   158819    |   20224    |   167
+BenchmarkEcho_GithubAll                     |   10000    |   154700    |    6496    |   203
+BenchmarkGocraftWeb_GithubAll               |    3000    |   570806    |  131656    |  1686
+BenchmarkGoji_GithubAll                     |    2000    |   818034    |   56112    |   334
+BenchmarkGojiv2_GithubAll                   |    2000    |  1213973    |  274768    |  3712
+BenchmarkGoJsonRest_GithubAll               |    2000    |   785796    |  134371    |  2737
+BenchmarkGoRestful_GithubAll                |     300    |  5238188    |  689672    |  4519
+BenchmarkGorillaMux_GithubAll               |     100    | 10257726    |  211840    |  2272
+BenchmarkHttpRouter_GithubAll               |   20000    |   105414    |   13792    |   167
+BenchmarkHttpTreeMux_GithubAll              |   10000    |   319934    |   65856    |   671
+BenchmarkKocha_GithubAll                    |   10000    |   209442    |   23304    |   843
+BenchmarkLARS_GithubAll                     |   20000    |    62565    |       0    |     0
+BenchmarkMacaron_GithubAll                  |    2000    |  1161270    |  204194    |  2000
+BenchmarkMartini_GithubAll                  |     200    |  9991713    |  226549    |  2325
+BenchmarkPat_GithubAll                      |     200    |  5590793    | 1499568    | 27435
+BenchmarkPossum_GithubAll                   |   10000    |   319768    |   84448    |   609
+BenchmarkR2router_GithubAll                 |   10000    |   305134    |   77328    |   979
+BenchmarkRivet_GithubAll                    |   10000    |   132134    |   16272    |   167
+BenchmarkTango_GithubAll                    |    3000    |   552754    |   63826    |  1618
+BenchmarkTigerTonic_GithubAll               |    1000    |  1439483    |  239104    |  5374
+BenchmarkTraffic_GithubAll                  |     100    | 11383067    | 2659329    | 21848
+BenchmarkVulcan_GithubAll                   |    5000    |   394253    |   19894    |   609
+
+- (1): Total Repetitions achieved in constant time, higher means more confident result
+- (2): Single Repetition Duration (ns/op), lower is better
+- (3): Heap Memory (B/op), lower is better
+- (4): Average Allocations per Repetition (allocs/op), lower is better
+
+## Gin v1. stable
+
+- [x] Zero allocation router.
+- [x] Still the fastest http router and framework. From routing to writing.
+- [x] Complete suite of unit tests
+- [x] Battle tested
+- [x] API frozen, new releases will not break your code.
+
+## Build with [jsoniter](https://github.com/json-iterator/go)
+
+Gin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags.
+
+```sh
+$ go build -tags=jsoniter .
+```
+
+## API Examples
+
+### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
+
+```go
+func main() {
+       // Disable Console Color
+       // gin.DisableConsoleColor()
+
+       // Creates a gin router with default middleware:
+       // logger and recovery (crash-free) middleware
+       router := gin.Default()
+
+       router.GET("/someGet", getting)
+       router.POST("/somePost", posting)
+       router.PUT("/somePut", putting)
+       router.DELETE("/someDelete", deleting)
+       router.PATCH("/somePatch", patching)
+       router.HEAD("/someHead", head)
+       router.OPTIONS("/someOptions", options)
+
+       // By default it serves on :8080 unless a
+       // PORT environment variable was defined.
+       router.Run()
+       // router.Run(":3000") for a hard coded port
+}
+```
+
+### Parameters in path
+
+```go
+func main() {
+       router := gin.Default()
+
+       // This handler will match /user/john but will not match /user/ or /user
+       router.GET("/user/:name", func(c *gin.Context) {
+               name := c.Param("name")
+               c.String(http.StatusOK, "Hello %s", name)
+       })
+
+       // However, this one will match /user/john/ and also /user/john/send
+       // If no other routers match /user/john, it will redirect to /user/john/
+       router.GET("/user/:name/*action", func(c *gin.Context) {
+               name := c.Param("name")
+               action := c.Param("action")
+               message := name + " is " + action
+               c.String(http.StatusOK, message)
+       })
+
+       router.Run(":8080")
+}
+```
+
+### Querystring parameters
+
+```go
+func main() {
+       router := gin.Default()
+
+       // Query string parameters are parsed using the existing underlying request object.
+       // The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
+       router.GET("/welcome", func(c *gin.Context) {
+               firstname := c.DefaultQuery("firstname", "Guest")
+               lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
+
+               c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
+       })
+       router.Run(":8080")
+}
+```
+
+### Multipart/Urlencoded Form
+
+```go
+func main() {
+       router := gin.Default()
+
+       router.POST("/form_post", func(c *gin.Context) {
+               message := c.PostForm("message")
+               nick := c.DefaultPostForm("nick", "anonymous")
+
+               c.JSON(200, gin.H{
+                       "status":  "posted",
+                       "message": message,
+                       "nick":    nick,
+               })
+       })
+       router.Run(":8080")
+}
+```
+
+### Another example: query + post form
+
+```
+POST /post?id=1234&page=1 HTTP/1.1
+Content-Type: application/x-www-form-urlencoded
+
+name=manu&message=this_is_great
+```
+
+```go
+func main() {
+       router := gin.Default()
+
+       router.POST("/post", func(c *gin.Context) {
+
+               id := c.Query("id")
+               page := c.DefaultQuery("page", "0")
+               name := c.PostForm("name")
+               message := c.PostForm("message")
+
+               fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
+       })
+       router.Run(":8080")
+}
+```
+
+```
+id: 1234; page: 1; name: manu; message: this_is_great
+```
+
+### Map as querystring or postform parameters
+
+```
+POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
+Content-Type: application/x-www-form-urlencoded
+
+names[first]=thinkerou&names[second]=tianou
+```
+
+```go
+func main() {
+       router := gin.Default()
+
+       router.POST("/post", func(c *gin.Context) {
+
+               ids := c.QueryMap("ids")
+               names := c.PostFormMap("names")
+
+               fmt.Printf("ids: %v; names: %v", ids, names)
+       })
+       router.Run(":8080")
+}
+```
+
+```
+ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
+```
+
+### Upload files
+
+#### Single file
+
+References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single).
+
+```go
+func main() {
+       router := gin.Default()
+       // Set a lower memory limit for multipart forms (default is 32 MiB)
+       // router.MaxMultipartMemory = 8 << 20  // 8 MiB
+       router.POST("/upload", func(c *gin.Context) {
+               // single file
+               file, _ := c.FormFile("file")
+               log.Println(file.Filename)
+
+               // Upload the file to specific dst.
+               // c.SaveUploadedFile(file, dst)
+
+               c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
+       })
+       router.Run(":8080")
+}
+```
+
+How to `curl`:
+
+```bash
+curl -X POST http://localhost:8080/upload \
+  -F "file=@/Users/appleboy/test.zip" \
+  -H "Content-Type: multipart/form-data"
+```
+
+#### Multiple files
+
+See the detail [example code](examples/upload-file/multiple).
+
+```go
+func main() {
+       router := gin.Default()
+       // Set a lower memory limit for multipart forms (default is 32 MiB)
+       // router.MaxMultipartMemory = 8 << 20  // 8 MiB
+       router.POST("/upload", func(c *gin.Context) {
+               // Multipart form
+               form, _ := c.MultipartForm()
+               files := form.File["upload[]"]
+
+               for _, file := range files {
+                       log.Println(file.Filename)
+
+                       // Upload the file to specific dst.
+                       // c.SaveUploadedFile(file, dst)
+               }
+               c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
+       })
+       router.Run(":8080")
+}
+```
+
+How to `curl`:
+
+```bash
+curl -X POST http://localhost:8080/upload \
+  -F "upload[]=@/Users/appleboy/test1.zip" \
+  -F "upload[]=@/Users/appleboy/test2.zip" \
+  -H "Content-Type: multipart/form-data"
+```
+
+### Grouping routes
+
+```go
+func main() {
+       router := gin.Default()
+
+       // Simple group: v1
+       v1 := router.Group("/v1")
+       {
+               v1.POST("/login", loginEndpoint)
+               v1.POST("/submit", submitEndpoint)
+               v1.POST("/read", readEndpoint)
+       }
+
+       // Simple group: v2
+       v2 := router.Group("/v2")
+       {
+               v2.POST("/login", loginEndpoint)
+               v2.POST("/submit", submitEndpoint)
+               v2.POST("/read", readEndpoint)
+       }
+
+       router.Run(":8080")
+}
+```
+
+### Blank Gin without middleware by default
+
+Use
+
+```go
+r := gin.New()
+```
+
+instead of
+
+```go
+// Default With the Logger and Recovery middleware already attached
+r := gin.Default()
+```
+
+
+### Using middleware
+```go
+func main() {
+       // Creates a router without any middleware by default
+       r := gin.New()
+
+       // Global middleware
+       // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
+       // By default gin.DefaultWriter = os.Stdout
+       r.Use(gin.Logger())
+
+       // Recovery middleware recovers from any panics and writes a 500 if there was one.
+       r.Use(gin.Recovery())
+
+       // Per route middleware, you can add as many as you desire.
+       r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
+
+       // Authorization group
+       // authorized := r.Group("/", AuthRequired())
+       // exactly the same as:
+       authorized := r.Group("/")
+       // per group middleware! in this case we use the custom created
+       // AuthRequired() middleware just in the "authorized" group.
+       authorized.Use(AuthRequired())
+       {
+               authorized.POST("/login", loginEndpoint)
+               authorized.POST("/submit", submitEndpoint)
+               authorized.POST("/read", readEndpoint)
+
+               // nested group
+               testing := authorized.Group("testing")
+               testing.GET("/analytics", analyticsEndpoint)
+       }
+
+       // Listen and serve on 0.0.0.0:8080
+       r.Run(":8080")
+}
+```
+
+### How to write log file
+```go
+func main() {
+    // Disable Console Color, you don't need console color when writing the logs to file.
+    gin.DisableConsoleColor()
+
+    // Logging to a file.
+    f, _ := os.Create("gin.log")
+    gin.DefaultWriter = io.MultiWriter(f)
+
+    // Use the following code if you need to write the logs to file and console at the same time.
+    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
+
+    router := gin.Default()
+    router.GET("/ping", func(c *gin.Context) {
+        c.String(200, "pong")
+    })
+
+    router.Run(":8080")
+}
+```
+
+### Model binding and validation
+
+To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).
+
+Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags).
+
+Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
+
+Also, Gin provides two sets of methods for binding:
+- **Type** - Must bind
+  - **Methods** - `Bind`, `BindJSON`, `BindQuery`
+  - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
+- **Type** - Should bind
+  - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery`
+  - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
+
+When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
+
+You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned.
+
+```go
+// Binding from JSON
+type Login struct {
+       User     string `form:"user" json:"user" binding:"required"`
+       Password string `form:"password" json:"password" binding:"required"`
+}
+
+func main() {
+       router := gin.Default()
+
+       // Example for binding JSON ({"user": "manu", "password": "123"})
+       router.POST("/loginJSON", func(c *gin.Context) {
+               var json Login
+               if err := c.ShouldBindJSON(&json); err == nil {
+                       if json.User == "manu" && json.Password == "123" {
+                               c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
+                       } else {
+                               c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
+                       }
+               } else {
+                       c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               }
+       })
+
+       // Example for binding a HTML form (user=manu&password=123)
+       router.POST("/loginForm", func(c *gin.Context) {
+               var form Login
+               // This will infer what binder to use depending on the content-type header.
+               if err := c.ShouldBind(&form); err == nil {
+                       if form.User == "manu" && form.Password == "123" {
+                               c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
+                       } else {
+                               c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
+                       }
+               } else {
+                       c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+               }
+       })
+
+       // Listen and serve on 0.0.0.0:8080
+       router.Run(":8080")
+}
+```
+
+**Sample request**
+```shell
+$ curl -v -X POST \
+  http://localhost:8080/loginJSON \
+  -H 'content-type: application/json' \
+  -d '{ "user": "manu" }'
+> POST /loginJSON HTTP/1.1
+> Host: localhost:8080
+> User-Agent: curl/7.51.0
+> Accept: */*
+> content-type: application/json
+> Content-Length: 18
+>
+* upload completely sent off: 18 out of 18 bytes
+< HTTP/1.1 400 Bad Request
+< Content-Type: application/json; charset=utf-8
+< Date: Fri, 04 Aug 2017 03:51:31 GMT
+< Content-Length: 100
+<
+{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
+```
+
+**Skip validate**
+
+When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
+
+### Custom Validators
+
+It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go).
+
+[embedmd]:# (examples/custom-validation/server.go go)
+```go
+package main
+
+import (
+       "net/http"
+       "reflect"
+       "time"
+
+       "github.com/gin-gonic/gin"
+       "github.com/gin-gonic/gin/binding"
+       "gopkg.in/go-playground/validator.v8"
+)
+
+type Booking struct {
+       CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
+       CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
+}
+
+func bookableDate(
+       v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
+       field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
+) bool {
+       if date, ok := field.Interface().(time.Time); ok {
+               today := time.Now()
+               if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
+                       return false
+               }
+       }
+       return true
+}
+
+func main() {
+       route := gin.Default()
+
+       if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
+               v.RegisterValidation("bookabledate", bookableDate)
+       }
+
+       route.GET("/bookable", getBookable)
+       route.Run(":8085")
+}
+
+func getBookable(c *gin.Context) {
+       var b Booking
+       if err := c.ShouldBindWith(&b, binding.Query); err == nil {
+               c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
+       } else {
+               c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+       }
+}
+```
+
+```console
+$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
+{"message":"Booking dates are valid!"}
+
+$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
+{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
+```
+
+[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way.
+See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more.
+
+### Only Bind Query String
+
+`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
+
+```go
+package main
+
+import (
+       "log"
+
+       "github.com/gin-gonic/gin"
+)
+
+type Person struct {
+       Name    string `form:"name"`
+       Address string `form:"address"`
+}
+
+func main() {
+       route := gin.Default()
+       route.Any("/testing", startPage)
+       route.Run(":8085")
+}
+
+func startPage(c *gin.Context) {
+       var person Person
+       if c.ShouldBindQuery(&person) == nil {
+               log.Println("====== Only Bind By Query String ======")
+               log.Println(person.Name)
+               log.Println(person.Address)
+       }
+       c.String(200, "Success")
+}
+
+```
+
+### Bind Query String or Post Data
+
+See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292).
+
+```go
+package main
+
+import "log"
+import "github.com/gin-gonic/gin"
+import "time"
+
+type Person struct {
+       Name     string    `form:"name"`
+       Address  string    `form:"address"`
+       Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
+}
+
+func main() {
+       route := gin.Default()
+       route.GET("/testing", startPage)
+       route.Run(":8085")
+}
+
+func startPage(c *gin.Context) {
+       var person Person
+       // If `GET`, only `Form` binding engine (`query`) used.
+       // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
+       // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
+       if c.ShouldBind(&person) == nil {
+               log.Println(person.Name)
+               log.Println(person.Address)
+               log.Println(person.Birthday)
+       }
+
+       c.String(200, "Success")
+}
+```
+
+Test it with:
+```sh
+$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
+```
+
+### Bind HTML checkboxes
+
+See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
+
+main.go
+
+```go
+...
+
+type myForm struct {
+    Colors []string `form:"colors[]"`
+}
+
+...
+
+func formHandler(c *gin.Context) {
+    var fakeForm myForm
+    c.ShouldBind(&fakeForm)
+    c.JSON(200, gin.H{"color": fakeForm.Colors})
+}
+
+...
+
+```
+
+form.html
+
+```html
+<form action="/" method="POST">
+    <p>Check some colors</p>
+    <label for="red">Red</label>
+    <input type="checkbox" name="colors[]" value="red" id="red" />
+    <label for="green">Green</label>
+    <input type="checkbox" name="colors[]" value="green" id="green" />
+    <label for="blue">Blue</label>
+    <input type="checkbox" name="colors[]" value="blue" id="blue" />
+    <input type="submit" />
+</form>
+```
+
+result:
+
+```
+{"color":["red","green","blue"]}
+```
+
+### Multipart/Urlencoded binding
+
+```go
+package main
+
+import (
+       "github.com/gin-gonic/gin"
+)
+
+type LoginForm struct {
+       User     string `form:"user" binding:"required"`
+       Password string `form:"password" binding:"required"`
+}
+
+func main() {
+       router := gin.Default()
+       router.POST("/login", func(c *gin.Context) {
+               // you can bind multipart form with explicit binding declaration:
+               // c.ShouldBindWith(&form, binding.Form)
+               // or you can simply use autobinding with ShouldBind method:
+               var form LoginForm
+               // in this case proper binding will be automatically selected
+               if c.ShouldBind(&form) == nil {
+                       if form.User == "user" && form.Password == "password" {
+                               c.JSON(200, gin.H{"status": "you are logged in"})
+                       } else {
+                               c.JSON(401, gin.H{"status": "unauthorized"})
+                       }
+               }
+       })
+       router.Run(":8080")
+}
+```
+
+Test it with:
+```sh
+$ curl -v --form user=user --form password=password http://localhost:8080/login
+```
+
+### XML, JSON and YAML rendering
+
+```go
+func main() {
+       r := gin.Default()
+
+       // gin.H is a shortcut for map[string]interface{}
+       r.GET("/someJSON", func(c *gin.Context) {
+               c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
+       })
+
+       r.GET("/moreJSON", func(c *gin.Context) {
+               // You also can use a struct
+               var msg struct {
+                       Name    string `json:"user"`
+                       Message string
+                       Number  int
+               }
+               msg.Name = "Lena"
+               msg.Message = "hey"
+               msg.Number = 123
+               // Note that msg.Name becomes "user" in the JSON
+               // Will output  :   {"user": "Lena", "Message": "hey", "Number": 123}
+               c.JSON(http.StatusOK, msg)
+       })
+
+       r.GET("/someXML", func(c *gin.Context) {
+               c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
+       })
+
+       r.GET("/someYAML", func(c *gin.Context) {
+               c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
+       })
+
+       // Listen and serve on 0.0.0.0:8080
+       r.Run(":8080")
+}
+```
+
+#### SecureJSON
+
+Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values.
+
+```go
+func main() {
+       r := gin.Default()
+
+       // You can also use your own secure json prefix
+       // r.SecureJsonPrefix(")]}',\n")
+
+       r.GET("/someJSON", func(c *gin.Context) {
+               names := []string{"lena", "austin", "foo"}
+
+               // Will output  :   while(1);["lena","austin","foo"]
+               c.SecureJSON(http.StatusOK, names)
+       })
+
+       // Listen and serve on 0.0.0.0:8080
+       r.Run(":8080")
+}
+```
+#### JSONP
+
+Using JSONP to request data from a server  in a different domain. Add callback to response body if the query parameter callback exists.
+
+```go
+func main() {
+       r := gin.Default()
+
+       r.GET("/JSONP?callback=x", func(c *gin.Context) {
+               data := map[string]interface{}{
+                       "foo": "bar",
+               }
+               
+               //callback is x
+               // Will output  :   x({\"foo\":\"bar\"})
+               c.JSONP(http.StatusOK, data)
+       })
+
+       // Listen and serve on 0.0.0.0:8080
+       r.Run(":8080")
+}
+```
+
+#### AsciiJSON
+
+Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters.
+
+```go
+func main() {
+       r := gin.Default()
+
+       r.GET("/someJSON", func(c *gin.Context) {
+               data := map[string]interface{}{
+                       "lang": "GO语言",
+                       "tag":  "<br>",
+               }
+
+               // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
+               c.AsciiJSON(http.StatusOK, data)
+       })
+
+       // Listen and serve on 0.0.0.0:8080
+       r.Run(":8080")
+}
+```
+
+### Serving static files
+
+```go
+func main() {
+       router := gin.Default()
+       router.Static("/assets", "./assets")
+       router.StaticFS("/more_static", http.Dir("my_file_system"))
+       router.StaticFile("/favicon.ico", "./resources/favicon.ico")
+
+       // Listen and serve on 0.0.0.0:8080
+       router.Run(":8080")
+}
+```
+
+### Serving data from reader
+
+```go
+func main() {
+       router := gin.Default()
+       router.GET("/someDataFromReader", func(c *gin.Context) {
+               response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
+               if err != nil || response.StatusCode != http.StatusOK {
+                       c.Status(http.StatusServiceUnavailable)
+                       return
+               }
+
+               reader := response.Body
+               contentLength := response.ContentLength
+               contentType := response.Header.Get("Content-Type")
+
+               extraHeaders := map[string]string{
+                       "Content-Disposition": `attachment; filename="gopher.png"`,
+               }
+
+               c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
+       })
+       router.Run(":8080")
+}
+```
+
+### HTML rendering
+
+Using LoadHTMLGlob() or LoadHTMLFiles()
+
+```go
+func main() {
+       router := gin.Default()
+       router.LoadHTMLGlob("templates/*")
+       //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
+       router.GET("/index", func(c *gin.Context) {
+               c.HTML(http.StatusOK, "index.tmpl", gin.H{
+                       "title": "Main website",
+               })
+       })
+       router.Run(":8080")
+}
+```
+
+templates/index.tmpl
+
+```html
+<html>
+       <h1>
+               {{ .title }}
+       </h1>
+</html>
+```
+
+Using templates with same name in different directories
+
+```go
+func main() {
+       router := gin.Default()
+       router.LoadHTMLGlob("templates/**/*")
+       router.GET("/posts/index", func(c *gin.Context) {
+               c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
+                       "title": "Posts",
+               })
+       })
+       router.GET("/users/index", func(c *gin.Context) {
+               c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
+                       "title": "Users",
+               })
+       })
+       router.Run(":8080")
+}
+```
+
+templates/posts/index.tmpl
+
+```html
+{{ define "posts/index.tmpl" }}
+<html><h1>
+       {{ .title }}
+</h1>
+<p>Using posts/index.tmpl</p>
+</html>
+{{ end }}
+```
+
+templates/users/index.tmpl
+
+```html
+{{ define "users/index.tmpl" }}
+<html><h1>
+       {{ .title }}
+</h1>
+<p>Using users/index.tmpl</p>
+</html>
+{{ end }}
+```
+
+#### Custom Template renderer
+
+You can also use your own html template render
+
+```go
+import "html/template"
+
+func main() {
+       router := gin.Default()
+       html := template.Must(template.ParseFiles("file1", "file2"))
+       router.SetHTMLTemplate(html)
+       router.Run(":8080")
+}
+```
+
+#### Custom Delimiters
+
+You may use custom delims
+
+```go
+       r := gin.Default()
+       r.Delims("{[{", "}]}")
+       r.LoadHTMLGlob("/path/to/templates"))
+```
+
+#### Custom Template Funcs
+
+See the detail [example code](examples/template).
+
+main.go
+
+```go
+import (
+    "fmt"
+    "html/template"
+    "net/http"
+    "time"
+
+    "github.com/gin-gonic/gin"
+)
+
+func formatAsDate(t time.Time) string {
+    year, month, day := t.Date()
+    return fmt.Sprintf("%d%02d/%02d", year, month, day)
+}
+
+func main() {
+    router := gin.Default()
+    router.Delims("{[{", "}]}")
+    router.SetFuncMap(template.FuncMap{
+        "formatAsDate": formatAsDate,
+    })
+    router.LoadHTMLFiles("./testdata/template/raw.tmpl")
+
+    router.GET("/raw", func(c *gin.Context) {
+        c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
+            "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
+        })
+    })
+
+    router.Run(":8080")
+}
+
+```
+
+raw.tmpl
+
+```html
+Date: {[{.now | formatAsDate}]}
+```
+
+Result:
+```
+Date: 2017/07/01
+```
+
+### Multitemplate
+
+Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
+
+### Redirects
+
+Issuing a HTTP redirect is easy. Both internal and external locations are supported.
+
+```go
+r.GET("/test", func(c *gin.Context) {
+       c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
+})
+```
+
+
+Issuing a Router redirect, use `HandleContext` like below.
+
+``` go
+r.GET("/test", func(c *gin.Context) {
+    c.Request.URL.Path = "/test2"
+    r.HandleContext(c)
+})
+r.GET("/test2", func(c *gin.Context) {
+    c.JSON(200, gin.H{"hello": "world"})
+})
+```
+
+
+### Custom Middleware
+
+```go
+func Logger() gin.HandlerFunc {
+       return func(c *gin.Context) {
+               t := time.Now()
+
+               // Set example variable
+               c.Set("example", "12345")
+
+               // before request
+
+               c.Next()
+
+               // after request
+               latency := time.Since(t)
+               log.Print(latency)
+
+               // access the status we are sending
+               status := c.Writer.Status()
+               log.Println(status)
+       }
+}
+
+func main() {
+       r := gin.New()
+       r.Use(Logger())
+
+       r.GET("/test", func(c *gin.Context) {
+               example := c.MustGet("example").(string)
+
+               // it would print: "12345"
+               log.Println(example)
+       })
+
+       // Listen and serve on 0.0.0.0:8080
+       r.Run(":8080")
+}
+```
+
+### Using BasicAuth() middleware
+
+```go
+// simulate some private data
+var secrets = gin.H{
+       "foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
+       "austin": gin.H{"email": "austin@example.com", "phone": "666"},
+       "lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
+}
+
+func main() {
+       r := gin.Default()
+
+       // Group using gin.BasicAuth() middleware
+       // gin.Accounts is a shortcut for map[string]string
+       authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
+               "foo":    "bar",
+               "austin": "1234",
+               "lena":   "hello2",
+               "manu":   "4321",
+       }))
+
+       // /admin/secrets endpoint
+       // hit "localhost:8080/admin/secrets
+       authorized.GET("/secrets", func(c *gin.Context) {
+               // get user, it was set by the BasicAuth middleware
+               user := c.MustGet(gin.AuthUserKey).(string)
+               if secret, ok := secrets[user]; ok {
+                       c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
+               } else {
+                       c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
+               }
+       })
+
+       // Listen and serve on 0.0.0.0:8080
+       r.Run(":8080")
+}
+```
+
+### Goroutines inside a middleware
+
+When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
+
+```go
+func main() {
+       r := gin.Default()
+
+       r.GET("/long_async", func(c *gin.Context) {
+               // create copy to be used inside the goroutine
+               cCp := c.Copy()
+               go func() {
+                       // simulate a long task with time.Sleep(). 5 seconds
+                       time.Sleep(5 * time.Second)
+
+                       // note that you are using the copied context "cCp", IMPORTANT
+                       log.Println("Done! in path " + cCp.Request.URL.Path)
+               }()
+       })
+
+       r.GET("/long_sync", func(c *gin.Context) {
+               // simulate a long task with time.Sleep(). 5 seconds
+               time.Sleep(5 * time.Second)
+
+               // since we are NOT using a goroutine, we do not have to copy the context
+               log.Println("Done! in path " + c.Request.URL.Path)
+       })
+
+       // Listen and serve on 0.0.0.0:8080
+       r.Run(":8080")
+}
+```
+
+### Custom HTTP configuration
+
+Use `http.ListenAndServe()` directly, like this:
+
+```go
+func main() {
+       router := gin.Default()
+       http.ListenAndServe(":8080", router)
+}
+```
+or
+
+```go
+func main() {
+       router := gin.Default()
+
+       s := &http.Server{
+               Addr:           ":8080",
+               Handler:        router,
+               ReadTimeout:    10 * time.Second,
+               WriteTimeout:   10 * time.Second,
+               MaxHeaderBytes: 1 << 20,
+       }
+       s.ListenAndServe()
+}
+```
+
+### Support Let's Encrypt
+
+example for 1-line LetsEncrypt HTTPS servers.
+
+[embedmd]:# (examples/auto-tls/example1/main.go go)
+```go
+package main
+
+import (
+       "log"
+
+       "github.com/gin-gonic/autotls"
+       "github.com/gin-gonic/gin"
+)
+
+func main() {
+       r := gin.Default()
+
+       // Ping handler
+       r.GET("/ping", func(c *gin.Context) {
+               c.String(200, "pong")
+       })
+
+       log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
+}
+```
+
+example for custom autocert manager.
+
+[embedmd]:# (examples/auto-tls/example2/main.go go)
+```go
+package main
+
+import (
+       "log"
+
+       "github.com/gin-gonic/autotls"
+       "github.com/gin-gonic/gin"
+       "golang.org/x/crypto/acme/autocert"
+)
+
+func main() {
+       r := gin.Default()
+
+       // Ping handler
+       r.GET("/ping", func(c *gin.Context) {
+               c.String(200, "pong")
+       })
+
+       m := autocert.Manager{
+               Prompt:     autocert.AcceptTOS,
+               HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
+               Cache:      autocert.DirCache("/var/www/.cache"),
+       }
+
+       log.Fatal(autotls.RunWithManager(r, &m))
+}
+```
+
+### Run multiple service using Gin
+
+See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example:
+
+[embedmd]:# (examples/multiple-service/main.go go)
+```go
+package main
+
+import (
+       "log"
+       "net/http"
+       "time"
+
+       "github.com/gin-gonic/gin"
+       "golang.org/x/sync/errgroup"
+)
+
+var (
+       g errgroup.Group
+)
+
+func router01() http.Handler {
+       e := gin.New()
+       e.Use(gin.Recovery())
+       e.GET("/", func(c *gin.Context) {
+               c.JSON(
+                       http.StatusOK,
+                       gin.H{
+                               "code":  http.StatusOK,
+                               "error": "Welcome server 01",
+                       },
+               )
+       })
+
+       return e
+}
+
+func router02() http.Handler {
+       e := gin.New()
+       e.Use(gin.Recovery())
+       e.GET("/", func(c *gin.Context) {
+               c.JSON(
+                       http.StatusOK,
+                       gin.H{
+                               "code":  http.StatusOK,
+                               "error": "Welcome server 02",
+                       },
+               )
+       })
+
+       return e
+}
+
+func main() {
+       server01 := &http.Server{
+               Addr:         ":8080",
+               Handler:      router01(),
+               ReadTimeout:  5 * time.Second,
+               WriteTimeout: 10 * time.Second,
+       }
+
+       server02 := &http.Server{
+               Addr:         ":8081",
+               Handler:      router02(),
+               ReadTimeout:  5 * time.Second,
+               WriteTimeout: 10 * time.Second,
+       }
+
+       g.Go(func() error {
+               return server01.ListenAndServe()
+       })
+
+       g.Go(func() error {
+               return server02.ListenAndServe()
+       })
+
+       if err := g.Wait(); err != nil {
+               log.Fatal(err)
+       }
+}
+```
+
+### Graceful restart or stop
+
+Do you want to graceful restart or stop your web server?
+There are some ways this can be done.
+
+We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.
+
+```go
+router := gin.Default()
+router.GET("/", handler)
+// [...]
+endless.ListenAndServe(":4242", router)
+```
+
+An alternative to endless:
+
+* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
+* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.
+* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers.
+
+If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](./examples/graceful-shutdown) example with gin.
+
+[embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go)
+```go
+// +build go1.8
+
+package main
+
+import (
+       "context"
+       "log"
+       "net/http"
+       "os"
+       "os/signal"
+       "time"
+
+       "github.com/gin-gonic/gin"
+)
+
+func main() {
+       router := gin.Default()
+       router.GET("/", func(c *gin.Context) {
+               time.Sleep(5 * time.Second)
+               c.String(http.StatusOK, "Welcome Gin Server")
+       })
+
+       srv := &http.Server{
+               Addr:    ":8080",
+               Handler: router,
+       }
+
+       go func() {
+               // service connections
+               if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+                       log.Fatalf("listen: %s\n", err)
+               }
+       }()
+
+       // Wait for interrupt signal to gracefully shutdown the server with
+       // a timeout of 5 seconds.
+       quit := make(chan os.Signal)
+       signal.Notify(quit, os.Interrupt)
+       <-quit
+       log.Println("Shutdown Server ...")
+
+       ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+       defer cancel()
+       if err := srv.Shutdown(ctx); err != nil {
+               log.Fatal("Server Shutdown:", err)
+       }
+       log.Println("Server exiting")
+}
+```
+
+### Build a single binary with templates
+
+You can build a server into a single binary containing templates by using [go-assets][].
+
+[go-assets]: https://github.com/jessevdk/go-assets
+
+```go
+func main() {
+       r := gin.New()
+
+       t, err := loadTemplate()
+       if err != nil {
+               panic(err)
+       }
+       r.SetHTMLTemplate(t)
+
+       r.GET("/", func(c *gin.Context) {
+               c.HTML(http.StatusOK, "/html/index.tmpl",nil)
+       })
+       r.Run(":8080")
+}
+
+// loadTemplate loads templates embedded by go-assets-builder
+func loadTemplate() (*template.Template, error) {
+       t := template.New("")
+       for name, file := range Assets.Files {
+               if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
+                       continue
+               }
+               h, err := ioutil.ReadAll(file)
+               if err != nil {
+                       return nil, err
+               }
+               t, err = t.New(name).Parse(string(h))
+               if err != nil {
+                       return nil, err
+               }
+       }
+       return t, nil
+}
+```
+
+See a complete example in the `examples/assets-in-binary` directory.
+
+### Bind form-data request with custom struct
+
+The follow example using custom struct:
+
+```go
+type StructA struct {
+    FieldA string `form:"field_a"`
+}
+
+type StructB struct {
+    NestedStruct StructA
+    FieldB string `form:"field_b"`
+}
+
+type StructC struct {
+    NestedStructPointer *StructA
+    FieldC string `form:"field_c"`
+}
+
+type StructD struct {
+    NestedAnonyStruct struct {
+        FieldX string `form:"field_x"`
+    }
+    FieldD string `form:"field_d"`
+}
+
+func GetDataB(c *gin.Context) {
+    var b StructB
+    c.Bind(&b)
+    c.JSON(200, gin.H{
+        "a": b.NestedStruct,
+        "b": b.FieldB,
+    })
+}
+
+func GetDataC(c *gin.Context) {
+    var b StructC
+    c.Bind(&b)
+    c.JSON(200, gin.H{
+        "a": b.NestedStructPointer,
+        "c": b.FieldC,
+    })
+}
+
+func GetDataD(c *gin.Context) {
+    var b StructD
+    c.Bind(&b)
+    c.JSON(200, gin.H{
+        "x": b.NestedAnonyStruct,
+        "d": b.FieldD,
+    })
+}
+
+func main() {
+    r := gin.Default()
+    r.GET("/getb", GetDataB)
+    r.GET("/getc", GetDataC)
+    r.GET("/getd", GetDataD)
+
+    r.Run()
+}
+```
+
+Using the command `curl` command result:
+
+```
+$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
+{"a":{"FieldA":"hello"},"b":"world"}
+$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
+{"a":{"FieldA":"hello"},"c":"world"}
+$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
+{"d":"world","x":{"FieldX":"hello"}}
+```
+
+**NOTE**: NOT support the follow style struct:
+
+```go
+type StructX struct {
+    X struct {} `form:"name_x"` // HERE have form
+}
+
+type StructY struct {
+    Y StructX `form:"name_y"` // HERE hava form
+}
+
+type StructZ struct {
+    Z *StructZ `form:"name_z"` // HERE hava form
+}
+```
+
+In a word, only support nested custom struct which have no `form` now.
+
+### Try to bind body into different structs
+
+The normal methods for binding request body consumes `c.Request.Body` and they
+cannot be called multiple times.
+
+```go
+type formA struct {
+  Foo string `json:"foo" xml:"foo" binding:"required"`
+}
+
+type formB struct {
+  Bar string `json:"bar" xml:"bar" binding:"required"`
+}
+
+func SomeHandler(c *gin.Context) {
+  objA := formA{}
+  objB := formB{}
+  // This c.ShouldBind consumes c.Request.Body and it cannot be reused.
+  if errA := c.ShouldBind(&objA); errA == nil {
+    c.String(http.StatusOK, `the body should be formA`)
+  // Always an error is occurred by this because c.Request.Body is EOF now.
+  } else if errB := c.ShouldBind(&objB); errB == nil {
+    c.String(http.StatusOK, `the body should be formB`)
+  } else {
+    ...
+  }
+}
+```
+
+For this, you can use `c.ShouldBindBodyWith`.
+
+```go
+func SomeHandler(c *gin.Context) {
+  objA := formA{}
+  objB := formB{}
+  // This reads c.Request.Body and stores the result into the context.
+  if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
+    c.String(http.StatusOK, `the body should be formA`)
+  // At this time, it reuses body stored in the context.
+  } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
+    c.String(http.StatusOK, `the body should be formB JSON`)
+  // And it can accepts other formats
+  } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
+    c.String(http.StatusOK, `the body should be formB XML`)
+  } else {
+    ...
+  }
+}
+```
+
+* `c.ShouldBindBodyWith` stores body into the context before binding. This has
+a slight impact to performance, so you should not use this method if you are
+enough to call binding at once.
+* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`,
+`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`,
+can be called by `c.ShouldBind()` multiple times without any damage to
+performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
+
+### http2 server push
+
+http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.
+
+[embedmd]:# (examples/http-pusher/main.go go)
+```go
+package main
+
+import (
+       "html/template"
+       "log"
+
+       "github.com/gin-gonic/gin"
+)
+
+var html = template.Must(template.New("https").Parse(`
+<html>
+<head>
+  <title>Https Test</title>
+  <script src="/assets/app.js"></script>
+</head>
+<body>
+  <h1 style="color:red;">Welcome, Ginner!</h1>
+</body>
+</html>
+`))
+
+func main() {
+       r := gin.Default()
+       r.Static("/assets", "./assets")
+       r.SetHTMLTemplate(html)
+
+       r.GET("/", func(c *gin.Context) {
+               if pusher := c.Writer.Pusher(); pusher != nil {
+                       // use pusher.Push() to do server push
+                       if err := pusher.Push("/assets/app.js", nil); err != nil {
+                               log.Printf("Failed to push: %v", err)
+                       }
+               }
+               c.HTML(200, "https", gin.H{
+                       "status": "success",
+               })
+       })
+
+       // Listen and Server in https://127.0.0.1:8080
+       r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
+}
+```
+
+## Testing
+
+The `net/http/httptest` package is preferable way for HTTP testing.
+
+```go
+package main
+
+func setupRouter() *gin.Engine {
+       r := gin.Default()
+       r.GET("/ping", func(c *gin.Context) {
+               c.String(200, "pong")
+       })
+       return r
+}
+
+func main() {
+       r := setupRouter()
+       r.Run(":8080")
+}
+```
+
+Test for code example above:
+
+```go
+package main
+
+import (
+       "net/http"
+       "net/http/httptest"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+func TestPingRoute(t *testing.T) {
+       router := setupRouter()
+
+       w := httptest.NewRecorder()
+       req, _ := http.NewRequest("GET", "/ping", nil)
+       router.ServeHTTP(w, req)
+
+       assert.Equal(t, 200, w.Code)
+       assert.Equal(t, "pong", w.Body.String())
+}
+```
+
+## Users
+
+Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
+
+* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go
+* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
diff --git a/vendor/github.com/gin-gonic/gin/auth.go b/vendor/github.com/gin-gonic/gin/auth.go
new file mode 100644 (file)
index 0000000..9ed81b5
--- /dev/null
@@ -0,0 +1,96 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package gin
+
+import (
+       "crypto/subtle"
+       "encoding/base64"
+       "net/http"
+       "strconv"
+)
+
+// AuthUserKey is the cookie name for user credential in basic auth.
+const AuthUserKey = "user"
+
+// Accounts defines a key/value for user/pass list of authorized logins.
+type Accounts map[string]string
+
+type authPair struct {
+       value string
+       user  string
+}
+
+type authPairs []authPair
+
+func (a authPairs) searchCredential(authValue string) (string, bool) {
+       if authValue == "" {
+               return "", false
+       }
+       for _, pair := range a {
+               if pair.value == authValue {
+                       return pair.user, true
+               }
+       }
+       return "", false
+}
+
+// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
+// the key is the user name and the value is the password, as well as the name of the Realm.
+// If the realm is empty, "Authorization Required" will be used by default.
+// (see http://tools.ietf.org/html/rfc2617#section-1.2)
+func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
+       if realm == "" {
+               realm = "Authorization Required"
+       }
+       realm = "Basic realm=" + strconv.Quote(realm)
+       pairs := processAccounts(accounts)
+       return func(c *Context) {
+               // Search user in the slice of allowed credentials
+               user, found := pairs.searchCredential(c.requestHeader("Authorization"))
+               if !found {
+                       // Credentials doesn't match, we return 401 and abort handlers chain.
+                       c.Header("WWW-Authenticate", realm)
+                       c.AbortWithStatus(http.StatusUnauthorized)
+                       return
+               }
+
+               // The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
+               // c.MustGet(gin.AuthUserKey).
+               c.Set(AuthUserKey, user)
+       }
+}
+
+// BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
+// the key is the user name and the value is the password.
+func BasicAuth(accounts Accounts) HandlerFunc {
+       return BasicAuthForRealm(accounts, "")
+}
+
+func processAccounts(accounts Accounts) authPairs {
+       assert1(len(accounts) > 0, "Empty list of authorized credentials")
+       pairs := make(authPairs, 0, len(accounts))
+       for user, password := range accounts {
+               assert1(user != "", "User can not be empty")
+               value := authorizationHeader(user, password)
+               pairs = append(pairs, authPair{
+                       value: value,
+                       user:  user,
+               })
+       }
+       return pairs
+}
+
+func authorizationHeader(user, password string) string {
+       base := user + ":" + password
+       return "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
+}
+
+func secureCompare(given, actual string) bool {
+       if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
+               return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
+       }
+       // Securely compare actual to itself to keep constant time, but always return false.
+       return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/binding.go b/vendor/github.com/gin-gonic/gin/binding/binding.go
new file mode 100644 (file)
index 0000000..3a2aad9
--- /dev/null
@@ -0,0 +1,99 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import "net/http"
+
+// Content-Type MIME of the most common data formats.
+const (
+       MIMEJSON              = "application/json"
+       MIMEHTML              = "text/html"
+       MIMEXML               = "application/xml"
+       MIMEXML2              = "text/xml"
+       MIMEPlain             = "text/plain"
+       MIMEPOSTForm          = "application/x-www-form-urlencoded"
+       MIMEMultipartPOSTForm = "multipart/form-data"
+       MIMEPROTOBUF          = "application/x-protobuf"
+       MIMEMSGPACK           = "application/x-msgpack"
+       MIMEMSGPACK2          = "application/msgpack"
+)
+
+// Binding describes the interface which needs to be implemented for binding the
+// data present in the request such as JSON request body, query parameters or
+// the form POST.
+type Binding interface {
+       Name() string
+       Bind(*http.Request, interface{}) error
+}
+
+// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
+// but it reads the body from supplied bytes instead of req.Body.
+type BindingBody interface {
+       Binding
+       BindBody([]byte, interface{}) error
+}
+
+// StructValidator is the minimal interface which needs to be implemented in
+// order for it to be used as the validator engine for ensuring the correctness
+// of the reqest. Gin provides a default implementation for this using
+// https://github.com/go-playground/validator/tree/v8.18.2.
+type StructValidator interface {
+       // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
+       // If the received type is not a struct, any validation should be skipped and nil must be returned.
+       // If the received type is a struct or pointer to a struct, the validation should be performed.
+       // If the struct is not valid or the validation itself fails, a descriptive error should be returned.
+       // Otherwise nil must be returned.
+       ValidateStruct(interface{}) error
+
+       // Engine returns the underlying validator engine which powers the
+       // StructValidator implementation.
+       Engine() interface{}
+}
+
+// Validator is the default validator which implements the StructValidator
+// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
+// under the hood.
+var Validator StructValidator = &defaultValidator{}
+
+// These implement the Binding interface and can be used to bind the data
+// present in the request to struct instances.
+var (
+       JSON          = jsonBinding{}
+       XML           = xmlBinding{}
+       Form          = formBinding{}
+       Query         = queryBinding{}
+       FormPost      = formPostBinding{}
+       FormMultipart = formMultipartBinding{}
+       ProtoBuf      = protobufBinding{}
+       MsgPack       = msgpackBinding{}
+)
+
+// Default returns the appropriate Binding instance based on the HTTP method
+// and the content type.
+func Default(method, contentType string) Binding {
+       if method == "GET" {
+               return Form
+       }
+
+       switch contentType {
+       case MIMEJSON:
+               return JSON
+       case MIMEXML, MIMEXML2:
+               return XML
+       case MIMEPROTOBUF:
+               return ProtoBuf
+       case MIMEMSGPACK, MIMEMSGPACK2:
+               return MsgPack
+       default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
+               return Form
+       }
+}
+
+func validate(obj interface{}) error {
+       if Validator == nil {
+               return nil
+       }
+       return Validator.ValidateStruct(obj)
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/default_validator.go b/vendor/github.com/gin-gonic/gin/binding/default_validator.go
new file mode 100644 (file)
index 0000000..e7a302d
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright 2017 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+       "reflect"
+       "sync"
+
+       "gopkg.in/go-playground/validator.v8"
+)
+
+type defaultValidator struct {
+       once     sync.Once
+       validate *validator.Validate
+}
+
+var _ StructValidator = &defaultValidator{}
+
+// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
+func (v *defaultValidator) ValidateStruct(obj interface{}) error {
+       value := reflect.ValueOf(obj)
+       valueType := value.Kind()
+       if valueType == reflect.Ptr {
+               valueType = value.Elem().Kind()
+       }
+       if valueType == reflect.Struct {
+               v.lazyinit()
+               if err := v.validate.Struct(obj); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+// Engine returns the underlying validator engine which powers the default
+// Validator instance. This is useful if you want to register custom validations
+// or struct level validations. See validator GoDoc for more info -
+// https://godoc.org/gopkg.in/go-playground/validator.v8
+func (v *defaultValidator) Engine() interface{} {
+       v.lazyinit()
+       return v.validate
+}
+
+func (v *defaultValidator) lazyinit() {
+       v.once.Do(func() {
+               config := &validator.Config{TagName: "binding"}
+               v.validate = validator.New(config)
+       })
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/form.go b/vendor/github.com/gin-gonic/gin/binding/form.go
new file mode 100644 (file)
index 0000000..0be5966
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import "net/http"
+
+const defaultMemory = 32 * 1024 * 1024
+
+type formBinding struct{}
+type formPostBinding struct{}
+type formMultipartBinding struct{}
+
+func (formBinding) Name() string {
+       return "form"
+}
+
+func (formBinding) Bind(req *http.Request, obj interface{}) error {
+       if err := req.ParseForm(); err != nil {
+               return err
+       }
+       req.ParseMultipartForm(defaultMemory)
+       if err := mapForm(obj, req.Form); err != nil {
+               return err
+       }
+       return validate(obj)
+}
+
+func (formPostBinding) Name() string {
+       return "form-urlencoded"
+}
+
+func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
+       if err := req.ParseForm(); err != nil {
+               return err
+       }
+       if err := mapForm(obj, req.PostForm); err != nil {
+               return err
+       }
+       return validate(obj)
+}
+
+func (formMultipartBinding) Name() string {
+       return "multipart/form-data"
+}
+
+func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
+       if err := req.ParseMultipartForm(defaultMemory); err != nil {
+               return err
+       }
+       if err := mapForm(obj, req.MultipartForm.Value); err != nil {
+               return err
+       }
+       return validate(obj)
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/form_mapping.go b/vendor/github.com/gin-gonic/gin/binding/form_mapping.go
new file mode 100644 (file)
index 0000000..3f6b9bf
--- /dev/null
@@ -0,0 +1,209 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+       "errors"
+       "reflect"
+       "strconv"
+       "strings"
+       "time"
+)
+
+func mapForm(ptr interface{}, form map[string][]string) error {
+       typ := reflect.TypeOf(ptr).Elem()
+       val := reflect.ValueOf(ptr).Elem()
+       for i := 0; i < typ.NumField(); i++ {
+               typeField := typ.Field(i)
+               structField := val.Field(i)
+               if !structField.CanSet() {
+                       continue
+               }
+
+               structFieldKind := structField.Kind()
+               inputFieldName := typeField.Tag.Get("form")
+               inputFieldNameList := strings.Split(inputFieldName, ",")
+               inputFieldName = inputFieldNameList[0]
+               var defaultValue string
+               if len(inputFieldNameList) > 1 {
+                       defaultList := strings.SplitN(inputFieldNameList[1], "=", 2)
+                       if defaultList[0] == "default" {
+                               defaultValue = defaultList[1]
+                       }
+               }
+               if inputFieldName == "" {
+                       inputFieldName = typeField.Name
+
+                       // if "form" tag is nil, we inspect if the field is a struct or struct pointer.
+                       // this would not make sense for JSON parsing but it does for a form
+                       // since data is flatten
+                       if structFieldKind == reflect.Ptr {
+                               if !structField.Elem().IsValid() {
+                                       structField.Set(reflect.New(structField.Type().Elem()))
+                               }
+                               structField = structField.Elem()
+                               structFieldKind = structField.Kind()
+                       }
+                       if structFieldKind == reflect.Struct {
+                               err := mapForm(structField.Addr().Interface(), form)
+                               if err != nil {
+                                       return err
+                               }
+                               continue
+                       }
+               }
+               inputValue, exists := form[inputFieldName]
+
+               if !exists {
+                       if defaultValue == "" {
+                               continue
+                       }
+                       inputValue = make([]string, 1)
+                       inputValue[0] = defaultValue
+               }
+
+               numElems := len(inputValue)
+               if structFieldKind == reflect.Slice && numElems > 0 {
+                       sliceOf := structField.Type().Elem().Kind()
+                       slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
+                       for i := 0; i < numElems; i++ {
+                               if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
+                                       return err
+                               }
+                       }
+                       val.Field(i).Set(slice)
+               } else {
+                       if _, isTime := structField.Interface().(time.Time); isTime {
+                               if err := setTimeField(inputValue[0], typeField, structField); err != nil {
+                                       return err
+                               }
+                               continue
+                       }
+                       if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
+       switch valueKind {
+       case reflect.Int:
+               return setIntField(val, 0, structField)
+       case reflect.Int8:
+               return setIntField(val, 8, structField)
+       case reflect.Int16:
+               return setIntField(val, 16, structField)
+       case reflect.Int32:
+               return setIntField(val, 32, structField)
+       case reflect.Int64:
+               return setIntField(val, 64, structField)
+       case reflect.Uint:
+               return setUintField(val, 0, structField)
+       case reflect.Uint8:
+               return setUintField(val, 8, structField)
+       case reflect.Uint16:
+               return setUintField(val, 16, structField)
+       case reflect.Uint32:
+               return setUintField(val, 32, structField)
+       case reflect.Uint64:
+               return setUintField(val, 64, structField)
+       case reflect.Bool:
+               return setBoolField(val, structField)
+       case reflect.Float32:
+               return setFloatField(val, 32, structField)
+       case reflect.Float64:
+               return setFloatField(val, 64, structField)
+       case reflect.String:
+               structField.SetString(val)
+       case reflect.Ptr:
+               if !structField.Elem().IsValid() {
+                       structField.Set(reflect.New(structField.Type().Elem()))
+               }
+               structFieldElem := structField.Elem()
+               return setWithProperType(structFieldElem.Kind(), val, structFieldElem)
+       default:
+               return errors.New("Unknown type")
+       }
+       return nil
+}
+
+func setIntField(val string, bitSize int, field reflect.Value) error {
+       if val == "" {
+               val = "0"
+       }
+       intVal, err := strconv.ParseInt(val, 10, bitSize)
+       if err == nil {
+               field.SetInt(intVal)
+       }
+       return err
+}
+
+func setUintField(val string, bitSize int, field reflect.Value) error {
+       if val == "" {
+               val = "0"
+       }
+       uintVal, err := strconv.ParseUint(val, 10, bitSize)
+       if err == nil {
+               field.SetUint(uintVal)
+       }
+       return err
+}
+
+func setBoolField(val string, field reflect.Value) error {
+       if val == "" {
+               val = "false"
+       }
+       boolVal, err := strconv.ParseBool(val)
+       if err == nil {
+               field.SetBool(boolVal)
+       }
+       return err
+}
+
+func setFloatField(val string, bitSize int, field reflect.Value) error {
+       if val == "" {
+               val = "0.0"
+       }
+       floatVal, err := strconv.ParseFloat(val, bitSize)
+       if err == nil {
+               field.SetFloat(floatVal)
+       }
+       return err
+}
+
+func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
+       timeFormat := structField.Tag.Get("time_format")
+       if timeFormat == "" {
+               return errors.New("Blank time format")
+       }
+
+       if val == "" {
+               value.Set(reflect.ValueOf(time.Time{}))
+               return nil
+       }
+
+       l := time.Local
+       if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
+               l = time.UTC
+       }
+
+       if locTag := structField.Tag.Get("time_location"); locTag != "" {
+               loc, err := time.LoadLocation(locTag)
+               if err != nil {
+                       return err
+               }
+               l = loc
+       }
+
+       t, err := time.ParseInLocation(timeFormat, val, l)
+       if err != nil {
+               return err
+       }
+
+       value.Set(reflect.ValueOf(t))
+       return nil
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/json.go b/vendor/github.com/gin-gonic/gin/binding/json.go
new file mode 100644 (file)
index 0000000..fea17bb
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+       "bytes"
+       "io"
+       "net/http"
+
+       "github.com/gin-gonic/gin/json"
+)
+
+// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
+// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
+// interface{} as a Number instead of as a float64.
+var EnableDecoderUseNumber = false
+
+type jsonBinding struct{}
+
+func (jsonBinding) Name() string {
+       return "json"
+}
+
+func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
+       return decodeJSON(req.Body, obj)
+}
+
+func (jsonBinding) BindBody(body []byte, obj interface{}) error {
+       return decodeJSON(bytes.NewReader(body), obj)
+}
+
+func decodeJSON(r io.Reader, obj interface{}) error {
+       decoder := json.NewDecoder(r)
+       if EnableDecoderUseNumber {
+               decoder.UseNumber()
+       }
+       if err := decoder.Decode(obj); err != nil {
+               return err
+       }
+       return validate(obj)
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/msgpack.go b/vendor/github.com/gin-gonic/gin/binding/msgpack.go
new file mode 100644 (file)
index 0000000..b7f7319
--- /dev/null
@@ -0,0 +1,35 @@
+// Copyright 2017 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+       "bytes"
+       "io"
+       "net/http"
+
+       "github.com/ugorji/go/codec"
+)
+
+type msgpackBinding struct{}
+
+func (msgpackBinding) Name() string {
+       return "msgpack"
+}
+
+func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
+       return decodeMsgPack(req.Body, obj)
+}
+
+func (msgpackBinding) BindBody(body []byte, obj interface{}) error {
+       return decodeMsgPack(bytes.NewReader(body), obj)
+}
+
+func decodeMsgPack(r io.Reader, obj interface{}) error {
+       cdc := new(codec.MsgpackHandle)
+       if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
+               return err
+       }
+       return validate(obj)
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/protobuf.go b/vendor/github.com/gin-gonic/gin/binding/protobuf.go
new file mode 100644 (file)
index 0000000..540e9c1
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+       "io/ioutil"
+       "net/http"
+
+       "github.com/golang/protobuf/proto"
+)
+
+type protobufBinding struct{}
+
+func (protobufBinding) Name() string {
+       return "protobuf"
+}
+
+func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
+       buf, err := ioutil.ReadAll(req.Body)
+       if err != nil {
+               return err
+       }
+       return b.BindBody(buf, obj)
+}
+
+func (protobufBinding) BindBody(body []byte, obj interface{}) error {
+       if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
+               return err
+       }
+       // Here it's same to return validate(obj), but util now we cann't add
+       // `binding:""` to the struct which automatically generate by gen-proto
+       return nil
+       // return validate(obj)
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/query.go b/vendor/github.com/gin-gonic/gin/binding/query.go
new file mode 100644 (file)
index 0000000..219743f
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright 2017 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import "net/http"
+
+type queryBinding struct{}
+
+func (queryBinding) Name() string {
+       return "query"
+}
+
+func (queryBinding) Bind(req *http.Request, obj interface{}) error {
+       values := req.URL.Query()
+       if err := mapForm(obj, values); err != nil {
+               return err
+       }
+       return validate(obj)
+}
diff --git a/vendor/github.com/gin-gonic/gin/binding/xml.go b/vendor/github.com/gin-gonic/gin/binding/xml.go
new file mode 100644 (file)
index 0000000..4e90114
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+       "bytes"
+       "encoding/xml"
+       "io"
+       "net/http"
+)
+
+type xmlBinding struct{}
+
+func (xmlBinding) Name() string {
+       return "xml"
+}
+
+func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
+       return decodeXML(req.Body, obj)
+}
+
+func (xmlBinding) BindBody(body []byte, obj interface{}) error {
+       return decodeXML(bytes.NewReader(body), obj)
+}
+func decodeXML(r io.Reader, obj interface{}) error {
+       decoder := xml.NewDecoder(r)
+       if err := decoder.Decode(obj); err != nil {
+               return err
+       }
+       return validate(obj)
+}
diff --git a/vendor/github.com/gin-gonic/gin/codecov.yml b/vendor/github.com/gin-gonic/gin/codecov.yml
new file mode 100644 (file)
index 0000000..c9c9a52
--- /dev/null
@@ -0,0 +1,5 @@
+coverage:
+  notify:
+    gitter:
+      default:
+        url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165
diff --git a/vendor/github.com/gin-gonic/gin/context.go b/vendor/github.com/gin-gonic/gin/context.go
new file mode 100644 (file)
index 0000000..724ded7
--- /dev/null
@@ -0,0 +1,933 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package gin
+
+import (
+       "errors"
+       "io"
+       "io/ioutil"
+       "math"
+       "mime/multipart"
+       "net"
+       "net/http"
+       "net/url"
+       "os"
+       "strings"
+       "time"
+
+       "github.com/gin-contrib/sse"
+       "github.com/gin-gonic/gin/binding"
+       "github.com/gin-gonic/gin/render"
+)
+
+// Content-Type MIME of the most common data formats.
+const (
+       MIMEJSON              = binding.MIMEJSON
+       MIMEHTML              = binding.MIMEHTML
+       MIMEXML               = binding.MIMEXML
+       MIMEXML2              = binding.MIMEXML2
+       MIMEPlain             = binding.MIMEPlain
+       MIMEPOSTForm          = binding.MIMEPOSTForm
+       MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
+       BodyBytesKey          = "_gin-gonic/gin/bodybyteskey"
+)
+
+const abortIndex int8 = math.MaxInt8 / 2
+
+// Context is the most important part of gin. It allows us to pass variables between middleware,
+// manage the flow, validate the JSON of a request and render a JSON response for example.
+type Context struct {
+       writermem responseWriter
+       Request   *http.Request
+       Writer    ResponseWriter
+
+       Params   Params
+       handlers HandlersChain
+       index    int8
+
+       engine *Engine
+
+       // Keys is a key/value pair exclusively for the context of each request.
+       Keys map[string]interface{}
+
+       // Errors is a list of errors attached to all the handlers/middlewares who used this context.
+       Errors errorMsgs
+
+       // Accepted defines a list of manually accepted formats for content negotiation.
+       Accepted []string
+}
+
+/************************************/
+/********** CONTEXT CREATION ********/
+/************************************/
+
+func (c *Context) reset() {
+       c.Writer = &c.writermem
+       c.Params = c.Params[0:0]
+       c.handlers = nil
+       c.index = -1
+       c.Keys = nil
+       c.Errors = c.Errors[0:0]
+       c.Accepted = nil
+}
+
+// Copy returns a copy of the current context that can be safely used outside the request's scope.
+// This has to be used when the context has to be passed to a goroutine.
+func (c *Context) Copy() *Context {
+       var cp = *c
+       cp.writermem.ResponseWriter = nil
+       cp.Writer = &cp.writermem
+       cp.index = abortIndex
+       cp.handlers = nil
+       return &cp
+}
+
+// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()",
+// this function will return "main.handleGetUsers".
+func (c *Context) HandlerName() string {
+       return nameOfFunction(c.handlers.Last())
+}
+
+// Handler returns the main handler.
+func (c *Context) Handler() HandlerFunc {
+       return c.handlers.Last()
+}
+
+/************************************/
+/*********** FLOW CONTROL ***********/
+/************************************/
+
+// Next should be used only inside middleware.
+// It executes the pending handlers in the chain inside the calling handler.
+// See example in GitHub.
+func (c *Context) Next() {
+       c.index++
+       for s := int8(len(c.handlers)); c.index < s; c.index++ {
+               c.handlers[c.index](c)
+       }
+}
+
+// IsAborted returns true if the current context was aborted.
+func (c *Context) IsAborted() bool {
+       return c.index >= abortIndex
+}
+
+// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
+// Let's say you have an authorization middleware that validates that the current request is authorized.
+// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers
+// for this request are not called.
+func (c *Context) Abort() {
+       c.index = abortIndex
+}
+
+// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.
+// For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401).
+func (c *Context) AbortWithStatus(code int) {
+       c.Status(code)
+       c.Writer.WriteHeaderNow()
+       c.Abort()
+}
+
+// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
+// This method stops the chain, writes the status code and return a JSON body.
+// It also sets the Content-Type as "application/json".
+func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) {
+       c.Abort()
+       c.JSON(code, jsonObj)
+}
+
+// AbortWithError calls `AbortWithStatus()` and `Error()` internally.
+// This method stops the chain, writes the status code and pushes the specified error to `c.Errors`.
+// See Context.Error() for more details.
+func (c *Context) AbortWithError(code int, err error) *Error {
+       c.AbortWithStatus(code)
+       return c.Error(err)
+}
+
+/************************************/
+/********* ERROR MANAGEMENT *********/
+/************************************/
+
+// Error attaches an error to the current context. The error is pushed to a list of errors.
+// It's a good idea to call Error for each error that occurred during the resolution of a request.
+// A middleware can be used to collect all the errors and push them to a database together,
+// print a log, or append it in the HTTP response.
+// Error will panic if err is nil.
+func (c *Context) Error(err error) *Error {
+       if err == nil {
+               panic("err is nil")
+       }
+
+       parsedError, ok := err.(*Error)
+       if !ok {
+               parsedError = &Error{
+                       Err:  err,
+                       Type: ErrorTypePrivate,
+               }
+       }
+
+       c.Errors = append(c.Errors, parsedError)
+       return parsedError
+}
+
+/************************************/
+/******** METADATA MANAGEMENT********/
+/************************************/
+
+// Set is used to store a new key/value pair exclusively for this context.
+// It also lazy initializes  c.Keys if it was not used previously.
+func (c *Context) Set(key string, value interface{}) {
+       if c.Keys == nil {
+               c.Keys = make(map[string]interface{})
+       }
+       c.Keys[key] = value
+}
+
+// Get returns the value for the given key, ie: (value, true).
+// If the value does not exists it returns (nil, false)
+func (c *Context) Get(key string) (value interface{}, exists bool) {
+       value, exists = c.Keys[key]
+       return
+}
+
+// MustGet returns the value for the given key if it exists, otherwise it panics.
+func (c *Context) MustGet(key string) interface{} {
+       if value, exists := c.Get(key); exists {
+               return value
+       }
+       panic("Key \"" + key + "\" does not exist")
+}
+
+// GetString returns the value associated with the key as a string.
+func (c *Context) GetString(key string) (s string) {
+       if val, ok := c.Get(key); ok && val != nil {
+               s, _ = val.(string)
+       }
+       return
+}
+
+// GetBool returns the value associated with the key as a boolean.
+func (c *Context) GetBool(key string) (b bool) {
+       if val, ok := c.Get(key); ok && val != nil {
+               b, _ = val.(bool)
+       }
+       return
+}
+
+// GetInt returns the value associated with the key as an integer.
+func (c *Context) GetInt(key string) (i int) {
+       if val, ok := c.Get(key); ok && val != nil {
+               i, _ = val.(int)
+       }
+       return
+}
+
+// GetInt64 returns the value associated with the key as an integer.
+func (c *Context) GetInt64(key string) (i64 int64) {
+       if val, ok := c.Get(key); ok && val != nil {
+               i64, _ = val.(int64)
+       }
+       return
+}
+
+// GetFloat64 returns the value associated with the key as a float64.
+func (c *Context) GetFloat64(key string) (f64 float64) {
+       if val, ok := c.Get(key); ok && val != nil {
+               f64, _ = val.(float64)
+       }
+       return
+}
+
+// GetTime returns the value associated with the key as time.
+func (c *Context) GetTime(key string) (t time.Time) {
+       if val, ok := c.Get(key); ok && val != nil {
+               t, _ = val.(time.Time)
+       }
+       return
+}
+
+// GetDuration returns the value associated with the key as a duration.
+func (c *Context) GetDuration(key string) (d time.Duration) {
+       if val, ok := c.Get(key); ok && val != nil {
+               d, _ = val.(time.Duration)
+       }
+       return
+}
+
+// GetStringSlice returns the value associated with the key as a slice of strings.
+func (c *Context) GetStringSlice(key string) (ss []string) {
+       if val, ok := c.Get(key); ok && val != nil {
+               ss, _ = val.([]string)
+       }
+       return
+}
+
+// GetStringMap returns the value associated with the key as a map of interfaces.
+func (c *Context) GetStringMap(key string) (sm map[string]interface{}) {
+       if val, ok := c.Get(key); ok && val != nil {
+               sm, _ = val.(map[string]interface{})
+       }
+       return
+}
+
+// GetStringMapString returns the value associated with the key as a map of strings.
+func (c *Context) GetStringMapString(key string) (sms map[string]string) {
+       if val, ok := c.Get(key); ok && val != nil {
+               sms, _ = val.(map[string]string)
+       }
+       return
+}
+
+// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
+func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) {
+       if val, ok := c.Get(key); ok && val != nil {
+               smss, _ = val.(map[string][]string)
+       }
+       return
+}
+
+/************************************/
+/************ INPUT DATA ************/
+/************************************/
+
+// Param returns the value of the URL param.
+// It is a shortcut for c.Params.ByName(key)
+//     router.GET("/user/:id", func(c *gin.Context) {
+//         // a GET request to /user/john
+//         id := c.Param("id") // id == "john"
+//     })
+func (c *Context) Param(key string) string {
+       return c.Params.ByName(key)
+}
+
+// Query returns the keyed url query value if it exists,
+// otherwise it returns an empty string `("")`.
+// It is shortcut for `c.Request.URL.Query().Get(key)`
+//     GET /path?id=1234&name=Manu&value=
+//        c.Query("id") == "1234"
+//        c.Query("name") == "Manu"
+//        c.Query("value") == ""
+//        c.Query("wtf") == ""
+func (c *Context) Query(key string) string {
+       value, _ := c.GetQuery(key)
+       return value
+}
+
+// DefaultQuery returns the keyed url query value if it exists,
+// otherwise it returns the specified defaultValue string.
+// See: Query() and GetQuery() for further information.
+//     GET /?name=Manu&lastname=
+//     c.DefaultQuery("name", "unknown") == "Manu"
+//     c.DefaultQuery("id", "none") == "none"
+//     c.DefaultQuery("lastname", "none") == ""
+func (c *Context) DefaultQuery(key, defaultValue string) string {
+       if value, ok := c.GetQuery(key); ok {
+               return value
+       }
+       return defaultValue
+}
+
+// GetQuery is like Query(), it returns the keyed url query value
+// if it exists `(value, true)` (even when the value is an empty string),
+// otherwise it returns `("", false)`.
+// It is shortcut for `c.Request.URL.Query().Get(key)`
+//     GET /?name=Manu&lastname=
+//     ("Manu", true) == c.GetQuery("name")
+//     ("", false) == c.GetQuery("id")
+//     ("", true) == c.GetQuery("lastname")
+func (c *Context) GetQuery(key string) (string, bool) {
+       if values, ok := c.GetQueryArray(key); ok {
+               return values[0], ok
+       }
+       return "", false
+}
+
+// QueryArray returns a slice of strings for a given query key.
+// The length of the slice depends on the number of params with the given key.
+func (c *Context) QueryArray(key string) []string {
+       values, _ := c.GetQueryArray(key)
+       return values
+}
+
+// GetQueryArray returns a slice of strings for a given query key, plus
+// a boolean value whether at least one value exists for the given key.
+func (c *Context) GetQueryArray(key string) ([]string, bool) {
+       if values, ok := c.Request.URL.Query()[key]; ok && len(values) > 0 {
+               return values, true
+       }
+       return []string{}, false
+}
+
+// QueryMap returns a map for a given query key.
+func (c *Context) QueryMap(key string) map[string]string {
+       dicts, _ := c.GetQueryMap(key)
+       return dicts
+}
+
+// GetQueryMap returns a map for a given query key, plus a boolean value
+// whether at least one value exists for the given key.
+func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
+       return c.get(c.Request.URL.Query(), key)
+}
+
+// PostForm returns the specified key from a POST urlencoded form or multipart form
+// when it exists, otherwise it returns an empty string `("")`.
+func (c *Context) PostForm(key string) string {
+       value, _ := c.GetPostForm(key)
+       return value
+}
+
+// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
+// when it exists, otherwise it returns the specified defaultValue string.
+// See: PostForm() and GetPostForm() for further information.
+func (c *Context) DefaultPostForm(key, defaultValue string) string {
+       if value, ok := c.GetPostForm(key); ok {
+               return value
+       }
+       return defaultValue
+}
+
+// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded
+// form or multipart form when it exists `(value, true)` (even when the value is an empty string),
+// otherwise it returns ("", false).
+// For example, during a PATCH request to update the user's email:
+//     email=mail@example.com  -->  ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
+//        email=                  -->  ("", true) := GetPostForm("email") // set email to ""
+//                             -->  ("", false) := GetPostForm("email") // do nothing with email
+func (c *Context) GetPostForm(key string) (string, bool) {
+       if values, ok := c.GetPostFormArray(key); ok {
+               return values[0], ok
+       }
+       return "", false
+}
+
+// PostFormArray returns a slice of strings for a given form key.
+// The length of the slice depends on the number of params with the given key.
+func (c *Context) PostFormArray(key string) []string {
+       values, _ := c.GetPostFormArray(key)
+       return values
+}
+
+// GetPostFormArray returns a slice of strings for a given form key, plus
+// a boolean value whether at least one value exists for the given key.
+func (c *Context) GetPostFormArray(key string) ([]string, bool) {
+       req := c.Request
+       req.ParseForm()
+       req.ParseMultipartForm(c.engine.MaxMultipartMemory)
+       if values := req.PostForm[key]; len(values) > 0 {
+               return values, true
+       }
+       if req.MultipartForm != nil && req.MultipartForm.File != nil {
+               if values := req.MultipartForm.Value[key]; len(values) > 0 {
+                       return values, true
+               }
+       }
+       return []string{}, false
+}
+
+// PostFormMap returns a map for a given form key.
+func (c *Context) PostFormMap(key string) map[string]string {
+       dicts, _ := c.GetPostFormMap(key)
+       return dicts
+}
+
+// GetPostFormMap returns a map for a given form key, plus a boolean value
+// whether at least one value exists for the given key.
+func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
+       req := c.Request
+       req.ParseForm()
+       req.ParseMultipartForm(c.engine.MaxMultipartMemory)
+       dicts, exist := c.get(req.PostForm, key)
+
+       if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil {
+               dicts, exist = c.get(req.MultipartForm.Value, key)
+       }
+
+       return dicts, exist
+}
+
+// get is an internal method and returns a map which satisfy conditions.
+func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
+       dicts := make(map[string]string)
+       exist := false
+       for k, v := range m {
+               if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
+                       if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
+                               exist = true
+                               dicts[k[i+1:][:j]] = v[0]
+                       }
+               }
+       }
+       return dicts, exist
+}
+
+// FormFile returns the first file for the provided form key.
+func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
+       _, fh, err := c.Request.FormFile(name)
+       return fh, err
+}
+
+// MultipartForm is the parsed multipart form, including file uploads.
+func (c *Context) MultipartForm() (*multipart.Form, error) {
+       err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
+       return c.Request.MultipartForm, err
+}
+
+// SaveUploadedFile uploads the form file to specific dst.
+func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
+       src, err := file.Open()
+       if err != nil {
+               return err
+       }
+       defer src.Close()
+
+       out, err := os.Create(dst)
+       if err != nil {
+               return err
+       }
+       defer out.Close()
+
+       io.Copy(out, src)
+       return nil
+}
+
+// Bind checks the Content-Type to select a binding engine automatically,
+// Depending the "Content-Type" header different bindings are used:
+//     "application/json" --> JSON binding
+//     "application/xml"  --> XML binding
+// otherwise --> returns an error.
+// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
+// It decodes the json payload into the struct specified as a pointer.
+// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
+func (c *Context) Bind(obj interface{}) error {
+       b := binding.Default(c.Request.Method, c.ContentType())
+       return c.MustBindWith(obj, b)
+}
+
+// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
+func (c *Context) BindJSON(obj interface{}) error {
+       return c.MustBindWith(obj, binding.JSON)
+}
+
+// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
+func (c *Context) BindQuery(obj interface{}) error {
+       return c.MustBindWith(obj, binding.Query)
+}
+
+// MustBindWith binds the passed struct pointer using the specified binding engine.
+// It will abort the request with HTTP 400 if any error ocurrs.
+// See the binding package.
+func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
+       if err = c.ShouldBindWith(obj, b); err != nil {
+               c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind)
+       }
+
+       return
+}
+
+// ShouldBind checks the Content-Type to select a binding engine automatically,
+// Depending the "Content-Type" header different bindings are used:
+//     "application/json" --> JSON binding
+//     "application/xml"  --> XML binding
+// otherwise --> returns an error
+// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
+// It decodes the json payload into the struct specified as a pointer.
+// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.
+func (c *Context) ShouldBind(obj interface{}) error {
+       b := binding.Default(c.Request.Method, c.ContentType())
+       return c.ShouldBindWith(obj, b)
+}
+
+// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
+func (c *Context) ShouldBindJSON(obj interface{}) error {
+       return c.ShouldBindWith(obj, binding.JSON)
+}
+
+// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
+func (c *Context) ShouldBindQuery(obj interface{}) error {
+       return c.ShouldBindWith(obj, binding.Query)
+}
+
+// ShouldBindWith binds the passed struct pointer using the specified binding engine.
+// See the binding package.
+func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
+       return b.Bind(c.Request, obj)
+}
+
+// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request
+// body into the context, and reuse when it is called again.
+//
+// NOTE: This method reads the body before binding. So you should use
+// ShouldBindWith for better performance if you need to call only once.
+func (c *Context) ShouldBindBodyWith(
+       obj interface{}, bb binding.BindingBody,
+) (err error) {
+       var body []byte
+       if cb, ok := c.Get(BodyBytesKey); ok {
+               if cbb, ok := cb.([]byte); ok {
+                       body = cbb
+               }
+       }
+       if body == nil {
+               body, err = ioutil.ReadAll(c.Request.Body)
+               if err != nil {
+                       return err
+               }
+               c.Set(BodyBytesKey, body)
+       }
+       return bb.BindBody(body, obj)
+}
+
+// ClientIP implements a best effort algorithm to return the real client IP, it parses
+// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
+// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.
+func (c *Context) ClientIP() string {
+       if c.engine.ForwardedByClientIP {
+               clientIP := c.requestHeader("X-Forwarded-For")
+               clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0])
+               if clientIP == "" {
+                       clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
+               }
+               if clientIP != "" {
+                       return clientIP
+               }
+       }
+
+       if c.engine.AppEngine {
+               if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
+                       return addr
+               }
+       }
+
+       if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
+               return ip
+       }
+
+       return ""
+}
+
+// ContentType returns the Content-Type header of the request.
+func (c *Context) ContentType() string {
+       return filterFlags(c.requestHeader("Content-Type"))
+}
+
+// IsWebsocket returns true if the request headers indicate that a websocket
+// handshake is being initiated by the client.
+func (c *Context) IsWebsocket() bool {
+       if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") &&
+               strings.ToLower(c.requestHeader("Upgrade")) == "websocket" {
+               return true
+       }
+       return false
+}
+
+func (c *Context) requestHeader(key string) string {
+       return c.Request.Header.Get(key)
+}
+
+/************************************/
+/******** RESPONSE RENDERING ********/
+/************************************/
+
+// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.
+func bodyAllowedForStatus(status int) bool {
+       switch {
+       case status >= 100 && status <= 199:
+               return false
+       case status == http.StatusNoContent:
+               return false
+       case status == http.StatusNotModified:
+               return false
+       }
+       return true
+}
+
+// Status sets the HTTP response code.
+func (c *Context) Status(code int) {
+       c.writermem.WriteHeader(code)
+}
+
+// Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
+// It writes a header in the response.
+// If value == "", this method removes the header `c.Writer.Header().Del(key)`
+func (c *Context) Header(key, value string) {
+       if value == "" {
+               c.Writer.Header().Del(key)
+       } else {
+               c.Writer.Header().Set(key, value)
+       }
+}
+
+// GetHeader returns value from request headers.
+func (c *Context) GetHeader(key string) string {
+       return c.requestHeader(key)
+}
+
+// GetRawData return stream data.
+func (c *Context) GetRawData() ([]byte, error) {
+       return ioutil.ReadAll(c.Request.Body)
+}
+
+// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
+// The provided cookie must have a valid Name. Invalid cookies may be
+// silently dropped.
+func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
+       if path == "" {
+               path = "/"
+       }
+       http.SetCookie(c.Writer, &http.Cookie{
+               Name:     name,
+               Value:    url.QueryEscape(value),
+               MaxAge:   maxAge,
+               Path:     path,
+               Domain:   domain,
+               Secure:   secure,
+               HttpOnly: httpOnly,
+       })
+}
+
+// Cookie returns the named cookie provided in the request or
+// ErrNoCookie if not found. And return the named cookie is unescaped.
+// If multiple cookies match the given name, only one cookie will
+// be returned.
+func (c *Context) Cookie(name string) (string, error) {
+       cookie, err := c.Request.Cookie(name)
+       if err != nil {
+               return "", err
+       }
+       val, _ := url.QueryUnescape(cookie.Value)
+       return val, nil
+}
+
+func (c *Context) Render(code int, r render.Render) {
+       c.Status(code)
+
+       if !bodyAllowedForStatus(code) {
+               r.WriteContentType(c.Writer)
+               c.Writer.WriteHeaderNow()
+               return
+       }
+
+       if err := r.Render(c.Writer); err != nil {
+               panic(err)
+       }
+}
+
+// HTML renders the HTTP template specified by its file name.
+// It also updates the HTTP code and sets the Content-Type as "text/html".
+// See http://golang.org/doc/articles/wiki/
+func (c *Context) HTML(code int, name string, obj interface{}) {
+       instance := c.engine.HTMLRender.Instance(name, obj)
+       c.Render(code, instance)
+}
+
+// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
+// It also sets the Content-Type as "application/json".
+// WARNING: we recommend to use this only for development purposes since printing pretty JSON is
+// more CPU and bandwidth consuming. Use Context.JSON() instead.
+func (c *Context) IndentedJSON(code int, obj interface{}) {
+       c.Render(code, render.IndentedJSON{Data: obj})
+}
+
+// SecureJSON serializes the given struct as Secure JSON into the response body.
+// Default prepends "while(1)," to response body if the given struct is array values.
+// It also sets the Content-Type as "application/json".
+func (c *Context) SecureJSON(code int, obj interface{}) {
+       c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
+}
+
+// JSONP serializes the given struct as JSON into the response body.
+// It add padding to response body to request data from a server residing in a different domain than the client.
+// It also sets the Content-Type as "application/javascript".
+func (c *Context) JSONP(code int, obj interface{}) {
+       callback := c.DefaultQuery("callback", "")
+       if callback == "" {
+               c.Render(code, render.JSON{Data: obj})
+       } else {
+               c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
+       }
+}
+
+// JSON serializes the given struct as JSON into the response body.
+// It also sets the Content-Type as "application/json".
+func (c *Context) JSON(code int, obj interface{}) {
+       c.Render(code, render.JSON{Data: obj})
+}
+
+// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
+// It also sets the Content-Type as "application/json".
+func (c *Context) AsciiJSON(code int, obj interface{}) {
+       c.Render(code, render.AsciiJSON{Data: obj})
+}
+
+// XML serializes the given struct as XML into the response body.
+// It also sets the Content-Type as "application/xml".
+func (c *Context) XML(code int, obj interface{}) {
+       c.Render(code, render.XML{Data: obj})
+}
+
+// YAML serializes the given struct as YAML into the response body.
+func (c *Context) YAML(code int, obj interface{}) {
+       c.Render(code, render.YAML{Data: obj})
+}
+
+// String writes the given string into the response body.
+func (c *Context) String(code int, format string, values ...interface{}) {
+       c.Render(code, render.String{Format: format, Data: values})
+}
+
+// Redirect returns a HTTP redirect to the specific location.
+func (c *Context) Redirect(code int, location string) {
+       c.Render(-1, render.Redirect{
+               Code:     code,
+               Location: location,
+               Request:  c.Request,
+       })
+}
+
+// Data writes some data into the body stream and updates the HTTP code.
+func (c *Context) Data(code int, contentType string, data []byte) {
+       c.Render(code, render.Data{
+               ContentType: contentType,
+               Data:        data,
+       })
+}
+
+// DataFromReader writes the specified reader into the body stream and updates the HTTP code.
+func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) {
+       c.Render(code, render.Reader{
+               Headers:       extraHeaders,
+               ContentType:   contentType,
+               ContentLength: contentLength,
+               Reader:        reader,
+       })
+}
+
+// File writes the specified file into the body stream in a efficient way.
+func (c *Context) File(filepath string) {
+       http.ServeFile(c.Writer, c.Request, filepath)
+}
+
+// SSEvent writes a Server-Sent Event into the body stream.
+func (c *Context) SSEvent(name string, message interface{}) {
+       c.Render(-1, sse.Event{
+               Event: name,
+               Data:  message,
+       })
+}
+
+func (c *Context) Stream(step func(w io.Writer) bool) {
+       w := c.Writer
+       clientGone := w.CloseNotify()
+       for {
+               select {
+               case <-clientGone:
+                       return
+               default:
+                       keepOpen := step(w)
+                       w.Flush()
+                       if !keepOpen {
+                               return
+                       }
+               }
+       }
+}
+
+/************************************/
+/******** CONTENT NEGOTIATION *******/
+/************************************/
+
+type Negotiate struct {
+       Offered  []string
+       HTMLName string
+       HTMLData interface{}
+       JSONData interface{}
+       XMLData  interface{}
+       Data     interface{}
+}
+
+func (c *Context) Negotiate(code int, config Negotiate) {
+       switch c.NegotiateFormat(config.Offered...) {
+       case binding.MIMEJSON:
+               data := chooseData(config.JSONData, config.Data)
+               c.JSON(code, data)
+
+       case binding.MIMEHTML:
+               data := chooseData(config.HTMLData, config.Data)
+               c.HTML(code, config.HTMLName, data)
+
+       case binding.MIMEXML:
+               data := chooseData(config.XMLData, config.Data)
+               c.XML(code, data)
+
+       default:
+               c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))
+       }
+}
+
+func (c *Context) NegotiateFormat(offered ...string) string {
+       assert1(len(offered) > 0, "you must provide at least one offer")
+
+       if c.Accepted == nil {
+               c.Accepted = parseAccept(c.requestHeader("Accept"))
+       }
+       if len(c.Accepted) == 0 {
+               return offered[0]
+       }
+       for _, accepted := range c.Accepted {
+               for _, offert := range offered {
+                       if accepted == offert {
+                               return offert
+                       }
+               }
+       }
+       return ""
+}
+
+func (c *Context) SetAccepted(formats ...string) {
+       c.Accepted = formats
+}
+
+/************************************/
+/***** GOLANG.ORG/X/NET/CONTEXT *****/
+/************************************/
+
+// Deadline returns the time when work done on behalf of this context
+// should be canceled. Deadline returns ok==false when no deadline is
+// set. Successive calls to Deadline return the same results.
+func (c *Context) Deadline() (deadline time.Time, ok bool) {
+       return
+}
+
+// Done returns a channel that's closed when work done on behalf of this
+// context should be canceled. Done may return nil if this context can
+// never be canceled. Successive calls to Done return the same value.
+func (c *Context) Done() <-chan struct{} {
+       return nil
+}
+
+// Err returns a non-nil error value after Done is closed,
+// successive calls to Err return the same error.
+// If Done is not yet closed, Err returns nil.
+// If Done is closed, Err returns a non-nil error explaining why:
+// Canceled if the context was canceled
+// or DeadlineExceeded if the context's deadline passed.
+func (c *Context) Err() error {
+       return nil
+}
+
+// Value returns the value associated with this context for key, or nil
+// if no value is associated with key. Successive calls to Value with
+// the same key returns the same result.
+func (c *Context) Value(key interface{}) interface{} {
+       if key == 0 {
+               return c.Request
+       }
+       if keyAsString, ok := key.(string); ok {
+               val, _ := c.Get(keyAsString)
+               return val
+       }
+       return nil
+}
diff --git a/vendor/github.com/gin-gonic/gin/context_appengine.go b/vendor/github.com/gin-gonic/gin/context_appengine.go
new file mode 100644 (file)
index 0000000..38c189a
--- /dev/null
@@ -0,0 +1,11 @@
+// +build appengine
+
+// Copyright 2017 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package gin
+
+func init() {
+       defaultAppEngine = true
+}
diff --git a/vendor/github.com/gin-gonic/gin/coverage.sh b/vendor/github.com/gin-gonic/gin/coverage.sh
new file mode 100644 (file)
index 0000000..4d1ee03
--- /dev/null
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+set -e
+
+echo "mode: count" > coverage.out
+
+for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do
+    go test -v -covermode=count -coverprofile=profile.out $d
+    if [ -f profile.out ]; then
+        cat profile.out | grep -v "mode:" >> coverage.out
+        rm profile.out
+    fi
+done
diff --git a/vendor/github.com/gin-gonic/gin/debug.go b/vendor/github.com/gin-gonic/gin/debug.go
new file mode 100644 (file)
index 0000000..f11156b
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package gin
+
+import (
+       "bytes"
+       "html/template"
+       "log"
+)
+
+func init() {
+       log.SetFlags(0)
+}
+
+// IsDebugging returns true if the framework is running in debug mode.
+// Use SetMode(gin.ReleaseMode) to disable debug mode.
+func IsDebugging() bool {
+       return ginMode == debugCode
+}
+
+func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
+       if IsDebugging() {
+               nuHandlers := len(handlers)
+               handlerName := nameOfFunction(handlers.Last())
+               debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
+       }
+}
+
+func debugPrintLoadTemplate(tmpl *template.Template) {
+       if IsDebugging() {
+               var buf bytes.Buffer
+               for _, tmpl := range tmpl.Templates() {
+                       buf.WriteString("\t- ")
+                       buf.WriteString(tmpl.Name())
+                       buf.WriteString("\n")
+               }
+               debugPrint("Loaded HTML Templates (%d): \n%s\n", len(tmpl.Templates()), buf.String())
+       }
+}
+
+func debugPrint(format string, values ...interface{}) {
+       if IsDebugging() {
+               log.Printf("[GIN-debug] "+format, values...)
+       }
+}
+
+func debugPrintWARNINGDefault() {
+       debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
+
+`)
+       debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
+
+`)
+}
+
+func debugPrintWARNINGNew() {
+       debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
+ - using env:  export GIN_MODE=release
+ - using code: gin.SetMode(gin.ReleaseMode)
+
+`)
+}
+
+func debugPrintWARNINGSetHTMLTemplate() {
+       debugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called
+at initialization. ie. before any route is registered or the router is listening in a socket:
+
+       router := gin.Default()
+       router.SetHTMLTemplate(template) // << good place
+
+`)
+}
+
+func debugPrintError(err error) {
+       if err != nil {
+               debugPrint("[ERROR] %v\n", err)
+       }
+}
diff --git a/vendor/github.com/gin-gonic/gin/deprecated.go b/vendor/github.com/gin-gonic/gin/deprecated.go
new file mode 100644 (file)
index 0000000..ab44742
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package gin
+
+import (
+       "log"
+
+       "github.com/gin-gonic/gin/binding"
+)
+
+// BindWith binds the passed struct pointer using the specified binding engine.
+// See the binding package.
+func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
+       log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
+       be deprecated, please check issue #662 and either use MustBindWith() if you
+       want HTTP 400 to be automatically returned if any error occur, or use
+       ShouldBindWith() if you need to manage the error.`)
+       return c.MustBindWith(obj, b)
+}
diff --git a/vendor/github.com/gin-gonic/gin/doc.go b/vendor/github.com/gin-gonic/gin/doc.go
new file mode 100644 (file)
index 0000000..01ac4a9
--- /dev/null
@@ -0,0 +1,6 @@
+/*
+Package gin implements a HTTP web framework called gin.
+
+See https://gin-gonic.github.io/gin/ for more information about gin.
+*/
+package gin // import "github.com/gin-gonic/gin"
diff --git a/vendor/github.com/gin-gonic/gin/errors.go b/vendor/github.com/gin-gonic/gin/errors.go
new file mode 100644 (file)
index 0000000..dbfccd8
--- /dev/null
@@ -0,0 +1,157 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package gin
+
+import (
+       "bytes"
+       "fmt"
+       "reflect"
+
+       "github.com/gin-gonic/gin/json"
+)
+
+type ErrorType uint64
+
+const (
+       ErrorTypeBind    ErrorType = 1 << 63 // used when c.Bind() fails
+       ErrorTypeRender  ErrorType = 1 << 62 // used when c.Render() fails
+       ErrorTypePrivate ErrorType = 1 << 0
+       ErrorTypePublic  ErrorType = 1 << 1
+
+       ErrorTypeAny ErrorType = 1<<64 - 1
+       ErrorTypeNu            = 2
+)
+
+type Error struct {
+       Err  error
+       Type ErrorType
+       Meta interface{}
+}
+
+type errorMsgs []*Error
+
+var _ error = &Error{}
+
+func (msg *Error) SetType(flags ErrorType) *Error {
+       msg.Type = flags
+       return msg
+}
+
+func (msg *Error) SetMeta(data interface{}) *Error {
+       msg.Meta = data
+       return msg
+}
+
+func (msg *Error) JSON() interface{} {
+       json := H{}
+       if msg.Meta != nil {
+               value := reflect.ValueOf(msg.Meta)
+               switch value.Kind() {
+               case reflect.Struct:
+                       return msg.Meta
+               case reflect.Map:
+                       for _, key := range value.MapKeys() {
+                               json[key.String()] = value.MapIndex(key).Interface()
+                       }
+               default:
+                       json["meta"] = msg.Meta
+               }
+       }
+       if _, ok := json["error"]; !ok {
+               json["error"] = msg.Error()
+       }
+       return json
+}
+
+// MarshalJSON implements the json.Marshaller interface.
+func (msg *Error) MarshalJSON() ([]byte, error) {
+       return json.Marshal(msg.JSON())
+}
+
+// Error implements the error interface
+func (msg Error) Error() string {
+       return msg.Err.Error()
+}
+
+func (msg *Error) IsType(flags ErrorType) bool {
+       return (msg.Type & flags) > 0
+}
+
+// ByType returns a readonly copy filtered the byte.
+// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.
+func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
+       if len(a) == 0 {
+               return nil
+       }
+       if typ == ErrorTypeAny {
+               return a
+       }
+       var result errorMsgs
+       for _, msg := range a {
+               if msg.IsType(typ) {
+                       result = append(result, msg)
+               }
+       }
+       return result
+}
+
+// Last returns the last error in the slice. It returns nil if the array is empty.
+// Shortcut for errors[len(errors)-1].
+func (a errorMsgs) Last() *Error {
+       if length := len(a); length > 0 {
+               return a[length-1]
+       }
+       return nil
+}
+
+// Errors returns an array will all the error messages.
+// Example:
+//             c.Error(errors.New("first"))
+//             c.Error(errors.New("second"))
+//             c.Error(errors.New("third"))
+//             c.Errors.Errors() // == []string{"first", "second", "third"}
+func (a errorMsgs) Errors() []string {
+       if len(a) == 0 {
+               return nil
+       }
+       errorStrings := make([]string, len(a))
+       for i, err := range a {
+               errorStrings[i] = err.Error()
+       }
+       return errorStrings
+}
+
+func (a errorMsgs) JSON() interface{} {
+       switch len(a) {
+       case 0:
+               return nil
+       case 1:
+               return a.Last().JSON()
+       default:
+               json := make([]interface{}, len(a))
+               for i, err := range a {
+                       json[i] = err.JSON()
+               }
+               return json
+       }
+}
+
+func (a errorMsgs) MarshalJSON() ([]byte, error) {
+       return json.Marshal(a.JSON())
+}
+
+func (a errorMsgs) String() string {
+       if len(a) == 0 {
+               return ""
+       }
+       var buffer bytes.Buffer
+       for i, msg := range a {
+               fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
+               if msg.Meta != nil {
+                       fmt.Fprintf(&buffer, "     Meta: %v\n", msg.Meta)
+               }
+       }
+       return buffer.String()
+}
diff --git a/vendor/github.com/gin-gonic/gin/fs.go b/vendor/github.com/gin-gonic/gin/fs.go
new file mode 100644 (file)
index 0000000..7a6738a
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright 2017 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package gin
+
+import (
+       "net/http"
+       "os"
+)
+
+type onlyfilesFS struct {
+       fs http.FileSystem
+}
+
+type neuteredReaddirFile struct {
+       http.File
+}
+
+// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally
+// in router.Static().
+// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
+// a filesystem that prevents http.FileServer() to list the directory files.
+func Dir(root string, listDirectory bool) http.FileSystem {
+       fs := http.Dir(root)
+       if listDirectory {
+               return fs
+       }
+       return &onlyfilesFS{fs}
+}
+
+// Open conforms to http.Filesystem.
+func (fs onlyfilesFS) Open(name string) (http.File, error) {
+       f, err := fs.fs.Open(name)
+       if err != nil {
+               return nil, err
+       }
+       return neuteredReaddirFile{f}, nil
+}
+
+// Readdir overrides the http.File default implementation.
+func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
+       // this disables directory listing
+       return nil, nil
+}
diff --git a/vendor/github.com/gin-gonic/gin/gin.go b/vendor/github.com/gin-gonic/gin/gin.go
new file mode 100644 (file)
index 0000000..aa62e01
--- /dev/null
@@ -0,0 +1,443 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package gin
+
+import (
+       "html/template"
+       "net"
+       "net/http"
+       "os"
+       "sync"
+
+       "github.com/gin-gonic/gin/render"
+)
+
+const (
+       // Version is Framework's version.
+       Version                = "v1.3.0"
+       defaultMultipartMemory = 32 << 20 // 32 MB
+)
+
+var (
+       default404Body   = []byte("404 page not found")
+       default405Body   = []byte("405 method not allowed")
+       defaultAppEngine bool
+)
+
+type HandlerFunc func(*Context)
+type HandlersChain []HandlerFunc
+
+// Last returns the last handler in the chain. ie. the last handler is the main own.
+func (c HandlersChain) Last() HandlerFunc {
+       if length := len(c); length > 0 {
+               return c[length-1]
+       }
+       return nil
+}
+
+type RouteInfo struct {
+       Method  string
+       Path    string
+       Handler string
+}
+
+type RoutesInfo []RouteInfo
+
+// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
+// Create an instance of Engine, by using New() or Default()
+type Engine struct {
+       RouterGroup
+
+       // 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
+       ForwardedByClientIP    bool
+
+       // #726 #755 If enabled, it will thrust some headers starting with
+       // 'X-AppEngine...' for better integration with that PaaS.
+       AppEngine bool
+
+       // If enabled, the url.RawPath will be used to find parameters.
+       UseRawPath bool
+
+       // If true, the path value will be unescaped.
+       // If UseRawPath is false (by default), the UnescapePathValues effectively is true,
+       // as url.Path gonna be used, which is already unescaped.
+       UnescapePathValues bool
+
+       // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
+       // method call.
+       MaxMultipartMemory int64
+
+       delims           render.Delims
+       secureJsonPrefix string
+       HTMLRender       render.HTMLRender
+       FuncMap          template.FuncMap
+       allNoRoute       HandlersChain
+       allNoMethod      HandlersChain
+       noRoute          HandlersChain
+       noMethod         HandlersChain
+       pool             sync.Pool
+       trees            methodTrees
+}
+
+var _ IRouter = &Engine{}
+
+// New returns a new blank Engine instance without any middleware attached.
+// By default the configuration is:
+// - RedirectTrailingSlash:  true
+// - RedirectFixedPath:      false
+// - HandleMethodNotAllowed: false
+// - ForwardedByClientIP:    true
+// - UseRawPath:             false
+// - UnescapePathValues:     true
+func New() *Engine {
+       debugPrintWARNINGNew()
+       engine := &Engine{
+               RouterGroup: RouterGroup{
+                       Handlers: nil,
+                       basePath: "/",
+                       root:     true,
+               },
+               FuncMap:                template.FuncMap{},
+               RedirectTrailingSlash:  true,
+               RedirectFixedPath:      false,
+               HandleMethodNotAllowed: false,
+               ForwardedByClientIP:    true,
+               AppEngine:              defaultAppEngine,
+               UseRawPath:             false,
+               UnescapePathValues:     true,
+               MaxMultipartMemory:     defaultMultipartMemory,
+               trees:                  make(methodTrees, 0, 9),
+               delims:                 render.Delims{Left: "{{", Right: "}}"},
+               secureJsonPrefix:       "while(1);",
+       }
+       engine.RouterGroup.engine = engine
+       engine.pool.New = func() interface{} {
+               return engine.allocateContext()
+       }
+       return engine
+}
+
+// Default returns an Engine instance with the Logger and Recovery middleware already attached.
+func Default() *Engine {
+       debugPrintWARNINGDefault()
+       engine := New()
+       engine.Use(Logger(), Recovery())
+       return engine
+}
+
+func (engine *Engine) allocateContext() *Context {
+       return &Context{engine: engine}
+}
+
+func (engine *Engine) Delims(left, right string) *Engine {
+       engine.delims = render.Delims{Left: left, Right: right}
+       return engine
+}
+
+// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
+func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
+       engine.secureJsonPrefix = prefix
+       return engine
+}
+
+// LoadHTMLGlob loads HTML files identified by glob pattern
+// and associates the result with HTML renderer.
+func (engine *Engine) LoadHTMLGlob(pattern string) {
+       left := engine.delims.Left
+       right := engine.delims.Right
+       templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
+
+       if IsDebugging() {
+               debugPrintLoadTemplate(templ)
+               engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
+               return
+       }
+
+       engine.SetHTMLTemplate(templ)
+}
+
+// LoadHTMLFiles loads a slice of HTML files
+// and associates the result with HTML renderer.
+func (engine *Engine) LoadHTMLFiles(files ...string) {
+       if IsDebugging() {
+               engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
+               return
+       }
+
+       templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
+       engine.SetHTMLTemplate(templ)
+}
+
+// SetHTMLTemplate associate a template with HTML renderer.
+func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
+       if len(engine.trees) > 0 {
+               debugPrintWARNINGSetHTMLTemplate()
+       }
+
+       engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
+}
+
+// SetFuncMap sets the FuncMap used for template.FuncMap.
+func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
+       engine.FuncMap = funcMap
+}
+
+// NoRoute adds handlers for NoRoute. It return a 404 code by default.
+func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
+       engine.noRoute = handlers
+       engine.rebuild404Handlers()
+}
+
+// NoMethod sets the handlers called when... TODO.
+func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
+       engine.noMethod = handlers
+       engine.rebuild405Handlers()
+}
+
+// Use attachs a global middleware to the router. ie. the middleware attached though Use() will be
+// included in the handlers chain for every single request. Even 404, 405, static files...
+// For example, this is the right place for a logger or error management middleware.
+func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
+       engine.RouterGroup.Use(middleware...)
+       engine.rebuild404Handlers()
+       engine.rebuild405Handlers()
+       return engine
+}
+
+func (engine *Engine) rebuild404Handlers() {
+       engine.allNoRoute = engine.combineHandlers(engine.noRoute)
+}
+
+func (engine *Engine) rebuild405Handlers() {
+       engine.allNoMethod = engine.combineHandlers(engine.noMethod)
+}
+
+func (engine *Engine) addRoute(method,&nb