From 930b639cc9cd2d2873302f30303378c0e53816a8 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 18 Feb 2017 23:00:46 +0100 Subject: Update vendor --- vendor/github.com/labstack/echo/LICENSE | 21 + vendor/github.com/labstack/echo/bind.go | 259 ++++++++ vendor/github.com/labstack/echo/context.go | 551 +++++++++++++++++ .../labstack/echo/cookbook/auto-tls/server.go | 26 + .../labstack/echo/cookbook/cors/server.go | 38 ++ .../labstack/echo/cookbook/crud/server.go | 75 +++ .../echo/cookbook/embed-resources/server.go | 21 + .../echo/cookbook/file-upload/multiple/server.go | 65 ++ .../echo/cookbook/file-upload/single/server.go | 59 ++ .../echo/cookbook/google-app-engine/app-engine.go | 17 + .../echo/cookbook/google-app-engine/app-managed.go | 25 + .../cookbook/google-app-engine/app-standalone.go | 24 + .../echo/cookbook/google-app-engine/app.go | 4 + .../echo/cookbook/google-app-engine/users.go | 54 ++ .../echo/cookbook/google-app-engine/welcome.go | 31 + .../cookbook/graceful-shutdown/grace/server.go | 20 + .../cookbook/graceful-shutdown/graceful/server.go | 21 + .../labstack/echo/cookbook/hello-world/server.go | 25 + .../labstack/echo/cookbook/http2/server.go | 42 ++ .../labstack/echo/cookbook/jsonp/server.go | 35 ++ .../echo/cookbook/jwt/custom-claims/server.go | 86 +++ .../echo/cookbook/jwt/map-claims/server.go | 69 +++ .../labstack/echo/cookbook/middleware/server.go | 82 +++ .../echo/cookbook/streaming-response/server.go | 45 ++ .../labstack/echo/cookbook/subdomains/server.go | 78 +++ .../echo/cookbook/twitter/handler/handler.go | 14 + .../labstack/echo/cookbook/twitter/handler/post.go | 73 +++ .../labstack/echo/cookbook/twitter/handler/user.go | 97 +++ .../labstack/echo/cookbook/twitter/model/post.go | 12 + .../labstack/echo/cookbook/twitter/model/user.go | 13 + .../labstack/echo/cookbook/twitter/server.go | 52 ++ .../echo/cookbook/websocket/gorilla/server.go | 47 ++ .../labstack/echo/cookbook/websocket/net/server.go | 41 ++ vendor/github.com/labstack/echo/echo.go | 666 +++++++++++++++++++++ vendor/github.com/labstack/echo/group.go | 104 ++++ vendor/github.com/labstack/echo/log.go | 40 ++ .../labstack/echo/middleware/basic_auth.go | 86 +++ .../labstack/echo/middleware/body_limit.go | 116 ++++ .../labstack/echo/middleware/compress.go | 121 ++++ vendor/github.com/labstack/echo/middleware/cors.go | 139 +++++ vendor/github.com/labstack/echo/middleware/csrf.go | 210 +++++++ vendor/github.com/labstack/echo/middleware/jwt.go | 189 ++++++ .../labstack/echo/middleware/key_auth.go | 133 ++++ .../github.com/labstack/echo/middleware/logger.go | 191 ++++++ .../labstack/echo/middleware/method_override.go | 88 +++ .../labstack/echo/middleware/middleware.go | 14 + .../github.com/labstack/echo/middleware/recover.go | 85 +++ .../labstack/echo/middleware/redirect.go | 215 +++++++ .../github.com/labstack/echo/middleware/secure.go | 116 ++++ .../github.com/labstack/echo/middleware/slash.go | 119 ++++ .../github.com/labstack/echo/middleware/static.go | 118 ++++ vendor/github.com/labstack/echo/response.go | 89 +++ vendor/github.com/labstack/echo/router.go | 436 ++++++++++++++ 53 files changed, 5397 insertions(+) create mode 100644 vendor/github.com/labstack/echo/LICENSE create mode 100644 vendor/github.com/labstack/echo/bind.go create mode 100644 vendor/github.com/labstack/echo/context.go create mode 100644 vendor/github.com/labstack/echo/cookbook/auto-tls/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/cors/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/crud/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/embed-resources/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/file-upload/multiple/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/file-upload/single/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/google-app-engine/app-engine.go create mode 100644 vendor/github.com/labstack/echo/cookbook/google-app-engine/app-managed.go create mode 100644 vendor/github.com/labstack/echo/cookbook/google-app-engine/app-standalone.go create mode 100644 vendor/github.com/labstack/echo/cookbook/google-app-engine/app.go create mode 100644 vendor/github.com/labstack/echo/cookbook/google-app-engine/users.go create mode 100644 vendor/github.com/labstack/echo/cookbook/google-app-engine/welcome.go create mode 100644 vendor/github.com/labstack/echo/cookbook/graceful-shutdown/grace/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/graceful-shutdown/graceful/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/hello-world/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/http2/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/jsonp/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/jwt/custom-claims/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/jwt/map-claims/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/middleware/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/streaming-response/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/subdomains/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/twitter/handler/handler.go create mode 100644 vendor/github.com/labstack/echo/cookbook/twitter/handler/post.go create mode 100644 vendor/github.com/labstack/echo/cookbook/twitter/handler/user.go create mode 100644 vendor/github.com/labstack/echo/cookbook/twitter/model/post.go create mode 100644 vendor/github.com/labstack/echo/cookbook/twitter/model/user.go create mode 100644 vendor/github.com/labstack/echo/cookbook/twitter/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/websocket/gorilla/server.go create mode 100644 vendor/github.com/labstack/echo/cookbook/websocket/net/server.go create mode 100644 vendor/github.com/labstack/echo/echo.go create mode 100644 vendor/github.com/labstack/echo/group.go create mode 100644 vendor/github.com/labstack/echo/log.go create mode 100644 vendor/github.com/labstack/echo/middleware/basic_auth.go create mode 100644 vendor/github.com/labstack/echo/middleware/body_limit.go create mode 100644 vendor/github.com/labstack/echo/middleware/compress.go create mode 100644 vendor/github.com/labstack/echo/middleware/cors.go create mode 100644 vendor/github.com/labstack/echo/middleware/csrf.go create mode 100644 vendor/github.com/labstack/echo/middleware/jwt.go create mode 100644 vendor/github.com/labstack/echo/middleware/key_auth.go create mode 100644 vendor/github.com/labstack/echo/middleware/logger.go create mode 100644 vendor/github.com/labstack/echo/middleware/method_override.go create mode 100644 vendor/github.com/labstack/echo/middleware/middleware.go create mode 100644 vendor/github.com/labstack/echo/middleware/recover.go create mode 100644 vendor/github.com/labstack/echo/middleware/redirect.go create mode 100644 vendor/github.com/labstack/echo/middleware/secure.go create mode 100644 vendor/github.com/labstack/echo/middleware/slash.go create mode 100644 vendor/github.com/labstack/echo/middleware/static.go create mode 100644 vendor/github.com/labstack/echo/response.go create mode 100644 vendor/github.com/labstack/echo/router.go (limited to 'vendor/github.com/labstack/echo') diff --git a/vendor/github.com/labstack/echo/LICENSE b/vendor/github.com/labstack/echo/LICENSE new file mode 100644 index 00000000..b5b006b4 --- /dev/null +++ b/vendor/github.com/labstack/echo/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 LabStack + +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/labstack/echo/bind.go b/vendor/github.com/labstack/echo/bind.go new file mode 100644 index 00000000..f2393ea6 --- /dev/null +++ b/vendor/github.com/labstack/echo/bind.go @@ -0,0 +1,259 @@ +package echo + +import ( + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "net/http" + "reflect" + "strconv" + "strings" +) + +type ( + // Binder is the interface that wraps the Bind method. + Binder interface { + Bind(i interface{}, c Context) error + } + + // DefaultBinder is the default implementation of the Binder interface. + DefaultBinder struct{} + + // BindUnmarshaler is the interface used to wrap the UnmarshalParam method. + BindUnmarshaler interface { + // UnmarshalParam decodes and assigns a value from an form or query param. + UnmarshalParam(param string) error + } +) + +// Bind implements the `Binder#Bind` function. +func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { + req := c.Request() + if req.Method == GET { + if err = b.bindData(i, c.QueryParams(), "query"); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()) + } + return + } + ctype := req.Header.Get(HeaderContentType) + if req.ContentLength == 0 { + return NewHTTPError(http.StatusBadRequest, "Request body can't be empty") + } + switch { + case strings.HasPrefix(ctype, MIMEApplicationJSON): + if err = json.NewDecoder(req.Body).Decode(i); err != nil { + if ute, ok := err.(*json.UnmarshalTypeError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, offset=%v", ute.Type, ute.Value, ute.Offset)) + } else if se, ok := err.(*json.SyntaxError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())) + } else { + return NewHTTPError(http.StatusBadRequest, err.Error()) + } + } + case strings.HasPrefix(ctype, MIMEApplicationXML): + if err = xml.NewDecoder(req.Body).Decode(i); err != nil { + if ute, ok := err.(*xml.UnsupportedTypeError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())) + } else if se, ok := err.(*xml.SyntaxError); ok { + return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())) + } else { + return NewHTTPError(http.StatusBadRequest, err.Error()) + } + } + case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm): + params, err := c.FormParams() + if err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()) + } + if err = b.bindData(i, params, "form"); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()) + } + default: + return ErrUnsupportedMediaType + } + return +} + +func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error { + typ := reflect.TypeOf(ptr).Elem() + val := reflect.ValueOf(ptr).Elem() + + if typ.Kind() != reflect.Struct { + return errors.New("Binding element must be a struct") + } + + 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(tag) + + if inputFieldName == "" { + inputFieldName = typeField.Name + // If tag is nil, we inspect if the field is a struct. + if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct { + err := b.bindData(structField.Addr().Interface(), data, tag) + if err != nil { + return err + } + continue + } + } + inputValue, exists := data[inputFieldName] + if !exists { + continue + } + + // Call this first, in case we're dealing with an alias to an array type + if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok { + if err != nil { + return err + } + continue + } + + numElems := len(inputValue) + if structFieldKind == reflect.Slice && numElems > 0 { + sliceOf := structField.Type().Elem().Kind() + slice := reflect.MakeSlice(structField.Type(), numElems, numElems) + for j := 0; j < numElems; j++ { + if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil { + return err + } + } + val.Field(i).Set(slice) + } else { + 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 { + // But also call it here, in case we're dealing with an array of BindUnmarshalers + if ok, err := unmarshalField(valueKind, val, structField); ok { + return err + } + + 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) + default: + return errors.New("unknown type") + } + return nil +} + +func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) { + switch valueKind { + case reflect.Ptr: + return unmarshalFieldPtr(val, field) + default: + return unmarshalFieldNonPtr(val, field) + } +} + +// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler +func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) { + ptr := reflect.New(field.Type()) + if ptr.CanInterface() { + iface := ptr.Interface() + if unmarshaler, ok := iface.(BindUnmarshaler); ok { + return unmarshaler, ok + } + } + return nil, false +} + +func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) { + if unmarshaler, ok := bindUnmarshaler(field); ok { + err := unmarshaler.UnmarshalParam(value) + field.Set(reflect.ValueOf(unmarshaler).Elem()) + return true, err + } + return false, nil +} + +func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) { + if field.IsNil() { + // Initialize the pointer to a nil value + field.Set(reflect.New(field.Type().Elem())) + } + return unmarshalFieldNonPtr(value, field.Elem()) +} + +func setIntField(value string, bitSize int, field reflect.Value) error { + if value == "" { + value = "0" + } + intVal, err := strconv.ParseInt(value, 10, bitSize) + if err == nil { + field.SetInt(intVal) + } + return err +} + +func setUintField(value string, bitSize int, field reflect.Value) error { + if value == "" { + value = "0" + } + uintVal, err := strconv.ParseUint(value, 10, bitSize) + if err == nil { + field.SetUint(uintVal) + } + return err +} + +func setBoolField(value string, field reflect.Value) error { + if value == "" { + value = "false" + } + boolVal, err := strconv.ParseBool(value) + if err == nil { + field.SetBool(boolVal) + } + return err +} + +func setFloatField(value string, bitSize int, field reflect.Value) error { + if value == "" { + value = "0.0" + } + floatVal, err := strconv.ParseFloat(value, bitSize) + if err == nil { + field.SetFloat(floatVal) + } + return err +} diff --git a/vendor/github.com/labstack/echo/context.go b/vendor/github.com/labstack/echo/context.go new file mode 100644 index 00000000..1a6ebf47 --- /dev/null +++ b/vendor/github.com/labstack/echo/context.go @@ -0,0 +1,551 @@ +package echo + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" + "io" + "mime/multipart" + "net" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" +) + +type ( + // Context represents the context of the current HTTP request. It holds request and + // response objects, path, path parameters, data and registered handler. + Context interface { + // Request returns `*http.Request`. + Request() *http.Request + + // SetRequest sets `*http.Request`. + SetRequest(r *http.Request) + + // Response returns `*Response`. + Response() *Response + + // IsTLS returns true if HTTP connection is TLS otherwise false. + IsTLS() bool + + // Scheme returns the HTTP protocol scheme, `http` or `https`. + Scheme() string + + // RealIP returns the client's network address based on `X-Forwarded-For` + // or `X-Real-IP` request header. + RealIP() string + + // Path returns the registered path for the handler. + Path() string + + // SetPath sets the registered path for the handler. + SetPath(p string) + + // Param returns path parameter by name. + Param(name string) string + + // ParamNames returns path parameter names. + ParamNames() []string + + // SetParamNames sets path parameter names. + SetParamNames(names ...string) + + // ParamValues returns path parameter values. + ParamValues() []string + + // SetParamValues sets path parameter values. + SetParamValues(values ...string) + + // QueryParam returns the query param for the provided name. + QueryParam(name string) string + + // QueryParams returns the query parameters as `url.Values`. + QueryParams() url.Values + + // QueryString returns the URL query string. + QueryString() string + + // FormValue returns the form field value for the provided name. + FormValue(name string) string + + // FormParams returns the form parameters as `url.Values`. + FormParams() (url.Values, error) + + // FormFile returns the multipart form file for the provided name. + FormFile(name string) (*multipart.FileHeader, error) + + // MultipartForm returns the multipart form. + MultipartForm() (*multipart.Form, error) + + // Cookie returns the named cookie provided in the request. + Cookie(name string) (*http.Cookie, error) + + // SetCookie adds a `Set-Cookie` header in HTTP response. + SetCookie(cookie *http.Cookie) + + // Cookies returns the HTTP cookies sent with the request. + Cookies() []*http.Cookie + + // Get retrieves data from the context. + Get(key string) interface{} + + // Set saves data in the context. + Set(key string, val interface{}) + + // Bind binds the request body into provided type `i`. The default binder + // does it based on Content-Type header. + Bind(i interface{}) error + + // Validate validates provided `i`. It is usually called after `Context#Bind()`. + // Validator must be registered using `Echo#Validator`. + Validate(i interface{}) error + + // Render renders a template with data and sends a text/html response with status + // code. Renderer must be registered using `Echo.Renderer`. + Render(code int, name string, data interface{}) error + + // HTML sends an HTTP response with status code. + HTML(code int, html string) error + + // HTMLBlob sends an HTTP blob response with status code. + HTMLBlob(code int, b []byte) error + + // String sends a string response with status code. + String(code int, s string) error + + // JSON sends a JSON response with status code. + JSON(code int, i interface{}) error + + // JSONPretty sends a pretty-print JSON with status code. + JSONPretty(code int, i interface{}, indent string) error + + // JSONBlob sends a JSON blob response with status code. + JSONBlob(code int, b []byte) error + + // JSONP sends a JSONP response with status code. It uses `callback` to construct + // the JSONP payload. + JSONP(code int, callback string, i interface{}) error + + // JSONPBlob sends a JSONP blob response with status code. It uses `callback` + // to construct the JSONP payload. + JSONPBlob(code int, callback string, b []byte) error + + // XML sends an XML response with status code. + XML(code int, i interface{}) error + + // XMLPretty sends a pretty-print XML with status code. + XMLPretty(code int, i interface{}, indent string) error + + // XMLBlob sends an XML blob response with status code. + XMLBlob(code int, b []byte) error + + // Blob sends a blob response with status code and content type. + Blob(code int, contentType string, b []byte) error + + // Stream sends a streaming response with status code and content type. + Stream(code int, contentType string, r io.Reader) error + + // File sends a response with the content of the file. + File(file string) error + + // Attachment sends a response as attachment, prompting client to save the + // file. + Attachment(file string, name string) error + + // Inline sends a response as inline, opening the file in the browser. + Inline(file string, name string) error + + // NoContent sends a response with no body and a status code. + NoContent(code int) error + + // Redirect redirects the request to a provided URL with status code. + Redirect(code int, url string) error + + // Error invokes the registered HTTP error handler. Generally used by middleware. + Error(err error) + + // Handler returns the matched handler by router. + Handler() HandlerFunc + + // SetHandler sets the matched handler by router. + SetHandler(h HandlerFunc) + + // Logger returns the `Logger` instance. + Logger() Logger + + // Echo returns the `Echo` instance. + Echo() *Echo + + // Reset resets the context after request completes. It must be called along + // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`. + // See `Echo#ServeHTTP()` + Reset(r *http.Request, w http.ResponseWriter) + } + + context struct { + request *http.Request + response *Response + path string + pnames []string + pvalues []string + query url.Values + handler HandlerFunc + store Map + echo *Echo + } +) + +const ( + defaultMemory = 32 << 20 // 32 MB + indexPage = "index.html" +) + +func (c *context) Request() *http.Request { + return c.request +} + +func (c *context) SetRequest(r *http.Request) { + c.request = r +} + +func (c *context) Response() *Response { + return c.response +} + +func (c *context) IsTLS() bool { + return c.request.TLS != nil +} + +func (c *context) Scheme() string { + // Can't use `r.Request.URL.Scheme` + // See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0 + if c.IsTLS() { + return "https" + } + return "http" +} + +func (c *context) RealIP() string { + ra := c.request.RemoteAddr + if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" { + ra = ip + } else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { + ra = ip + } else { + ra, _, _ = net.SplitHostPort(ra) + } + return ra +} + +func (c *context) Path() string { + return c.path +} + +func (c *context) SetPath(p string) { + c.path = p +} + +func (c *context) Param(name string) string { + for i, n := range c.pnames { + if i < len(c.pvalues) { + if n == name { + return c.pvalues[i] + } + + // Param name with aliases + for _, p := range strings.Split(n, ",") { + if p == name { + return c.pvalues[i] + } + } + } + } + return "" +} + +func (c *context) ParamNames() []string { + return c.pnames +} + +func (c *context) SetParamNames(names ...string) { + c.pnames = names +} + +func (c *context) ParamValues() []string { + return c.pvalues +} + +func (c *context) SetParamValues(values ...string) { + c.pvalues = values +} + +func (c *context) QueryParam(name string) string { + if c.query == nil { + c.query = c.request.URL.Query() + } + return c.query.Get(name) +} + +func (c *context) QueryParams() url.Values { + if c.query == nil { + c.query = c.request.URL.Query() + } + return c.query +} + +func (c *context) QueryString() string { + return c.request.URL.RawQuery +} + +func (c *context) FormValue(name string) string { + return c.request.FormValue(name) +} + +func (c *context) FormParams() (url.Values, error) { + if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) { + if err := c.request.ParseMultipartForm(defaultMemory); err != nil { + return nil, err + } + } else { + if err := c.request.ParseForm(); err != nil { + return nil, err + } + } + return c.request.Form, nil +} + +func (c *context) FormFile(name string) (*multipart.FileHeader, error) { + _, fh, err := c.request.FormFile(name) + return fh, err +} + +func (c *context) MultipartForm() (*multipart.Form, error) { + err := c.request.ParseMultipartForm(defaultMemory) + return c.request.MultipartForm, err +} + +func (c *context) Cookie(name string) (*http.Cookie, error) { + return c.request.Cookie(name) +} + +func (c *context) SetCookie(cookie *http.Cookie) { + http.SetCookie(c.Response(), cookie) +} + +func (c *context) Cookies() []*http.Cookie { + return c.request.Cookies() +} + +func (c *context) Get(key string) interface{} { + return c.store[key] +} + +func (c *context) Set(key string, val interface{}) { + if c.store == nil { + c.store = make(Map) + } + c.store[key] = val +} + +func (c *context) Bind(i interface{}) error { + return c.echo.Binder.Bind(i, c) +} + +func (c *context) Validate(i interface{}) error { + if c.echo.Validator == nil { + return ErrValidatorNotRegistered + } + return c.echo.Validator.Validate(i) +} + +func (c *context) Render(code int, name string, data interface{}) (err error) { + if c.echo.Renderer == nil { + return ErrRendererNotRegistered + } + buf := new(bytes.Buffer) + if err = c.echo.Renderer.Render(buf, name, data, c); err != nil { + return + } + return c.HTMLBlob(code, buf.Bytes()) +} + +func (c *context) HTML(code int, html string) (err error) { + return c.HTMLBlob(code, []byte(html)) +} + +func (c *context) HTMLBlob(code int, b []byte) (err error) { + return c.Blob(code, MIMETextHTMLCharsetUTF8, b) +} + +func (c *context) String(code int, s string) (err error) { + return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s)) +} + +func (c *context) JSON(code int, i interface{}) (err error) { + if c.echo.Debug { + return c.JSONPretty(code, i, " ") + } + b, err := json.Marshal(i) + if err != nil { + return + } + return c.JSONBlob(code, b) +} + +func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) { + b, err := json.MarshalIndent(i, "", indent) + if err != nil { + return + } + return c.JSONBlob(code, b) +} + +func (c *context) JSONBlob(code int, b []byte) (err error) { + return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b) +} + +func (c *context) JSONP(code int, callback string, i interface{}) (err error) { + b, err := json.Marshal(i) + if err != nil { + return + } + return c.JSONPBlob(code, callback, b) +} + +func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) { + c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8) + c.response.WriteHeader(code) + if _, err = c.response.Write([]byte(callback + "(")); err != nil { + return + } + if _, err = c.response.Write(b); err != nil { + return + } + _, err = c.response.Write([]byte(");")) + return +} + +func (c *context) XML(code int, i interface{}) (err error) { + if c.echo.Debug { + return c.XMLPretty(code, i, " ") + } + b, err := xml.Marshal(i) + if err != nil { + return + } + return c.XMLBlob(code, b) +} + +func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) { + b, err := xml.MarshalIndent(i, "", indent) + if err != nil { + return + } + return c.XMLBlob(code, b) +} + +func (c *context) XMLBlob(code int, b []byte) (err error) { + c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8) + c.response.WriteHeader(code) + if _, err = c.response.Write([]byte(xml.Header)); err != nil { + return + } + _, err = c.response.Write(b) + return +} + +func (c *context) Blob(code int, contentType string, b []byte) (err error) { + c.response.Header().Set(HeaderContentType, contentType) + c.response.WriteHeader(code) + _, err = c.response.Write(b) + return +} + +func (c *context) Stream(code int, contentType string, r io.Reader) (err error) { + c.response.Header().Set(HeaderContentType, contentType) + c.response.WriteHeader(code) + _, err = io.Copy(c.response, r) + return +} + +func (c *context) File(file string) error { + f, err := os.Open(file) + if err != nil { + return ErrNotFound + } + defer f.Close() + + fi, _ := f.Stat() + if fi.IsDir() { + file = filepath.Join(file, indexPage) + f, err = os.Open(file) + if err != nil { + return ErrNotFound + } + defer f.Close() + if fi, err = f.Stat(); err != nil { + return err + } + } + http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f) + return nil +} + +func (c *context) Attachment(file, name string) (err error) { + return c.contentDisposition(file, name, "attachment") +} + +func (c *context) Inline(file, name string) (err error) { + return c.contentDisposition(file, name, "inline") +} + +func (c *context) contentDisposition(file, name, dispositionType string) (err error) { + c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%s", dispositionType, name)) + c.File(file) + return +} + +func (c *context) NoContent(code int) error { + c.response.WriteHeader(code) + return nil +} + +func (c *context) Redirect(code int, url string) error { + if code < http.StatusMultipleChoices || code > http.StatusTemporaryRedirect { + return ErrInvalidRedirectCode + } + c.response.Header().Set(HeaderLocation, url) + c.response.WriteHeader(code) + return nil +} + +func (c *context) Error(err error) { + c.echo.HTTPErrorHandler(err, c) +} + +func (c *context) Echo() *Echo { + return c.echo +} + +func (c *context) Handler() HandlerFunc { + return c.handler +} + +func (c *context) SetHandler(h HandlerFunc) { + c.handler = h +} + +func (c *context) Logger() Logger { + return c.echo.Logger +} + +func (c *context) Reset(r *http.Request, w http.ResponseWriter) { + c.request = r + c.response.reset(w) + c.query = nil + c.handler = NotFoundHandler + c.store = nil +} diff --git a/vendor/github.com/labstack/echo/cookbook/auto-tls/server.go b/vendor/github.com/labstack/echo/cookbook/auto-tls/server.go new file mode 100644 index 00000000..4a8bbdfd --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/auto-tls/server.go @@ -0,0 +1,26 @@ +package main + +import ( + "net/http" + + "golang.org/x/crypto/acme/autocert" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +func main() { + e := echo.New() + // e.AutoTLSManager.HostPolicy = autocert.HostWhitelist("") + // Cache certificates + e.AutoTLSManager.Cache = autocert.DirCache("/var/www/.cache") + e.Use(middleware.Recover()) + e.Use(middleware.Logger()) + e.GET("/", func(c echo.Context) error { + return c.HTML(http.StatusOK, ` +

Welcome to Echo!

+

TLS certificates automatically installed from Let's Encrypt :)

+ `) + }) + e.Logger.Fatal(e.StartAutoTLS(":443")) +} diff --git a/vendor/github.com/labstack/echo/cookbook/cors/server.go b/vendor/github.com/labstack/echo/cookbook/cors/server.go new file mode 100644 index 00000000..0cc5c345 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/cors/server.go @@ -0,0 +1,38 @@ +package main + +import ( + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +var ( + users = []string{"Joe", "Veer", "Zion"} +) + +func getUsers(c echo.Context) error { + return c.JSON(http.StatusOK, users) +} + +func main() { + e := echo.New() + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + // CORS default + // Allows requests from any origin wth GET, HEAD, PUT, POST or DELETE method. + // e.Use(middleware.CORS()) + + // CORS restricted + // Allows requests from any `https://labstack.com` or `https://labstack.net` origin + // wth GET, PUT, POST or DELETE method. + e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: []string{"https://labstack.com", "https://labstack.net"}, + AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE}, + })) + + e.GET("/api/users", getUsers) + + e.Logger.Fatal(e.Start(":1323")) +} diff --git a/vendor/github.com/labstack/echo/cookbook/crud/server.go b/vendor/github.com/labstack/echo/cookbook/crud/server.go new file mode 100644 index 00000000..fbb5c754 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/crud/server.go @@ -0,0 +1,75 @@ +package main + +import ( + "net/http" + "strconv" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +type ( + user struct { + ID int `json:"id"` + Name string `json:"name"` + } +) + +var ( + users = map[int]*user{} + seq = 1 +) + +//---------- +// Handlers +//---------- + +func createUser(c echo.Context) error { + u := &user{ + ID: seq, + } + if err := c.Bind(u); err != nil { + return err + } + users[u.ID] = u + seq++ + return c.JSON(http.StatusCreated, u) +} + +func getUser(c echo.Context) error { + id, _ := strconv.Atoi(c.Param("id")) + return c.JSON(http.StatusOK, users[id]) +} + +func updateUser(c echo.Context) error { + u := new(user) + if err := c.Bind(u); err != nil { + return err + } + id, _ := strconv.Atoi(c.Param("id")) + users[id].Name = u.Name + return c.JSON(http.StatusOK, users[id]) +} + +func deleteUser(c echo.Context) error { + id, _ := strconv.Atoi(c.Param("id")) + delete(users, id) + return c.NoContent(http.StatusNoContent) +} + +func main() { + e := echo.New() + + // Middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + // Routes + e.POST("/users", createUser) + e.GET("/users/:id", getUser) + e.PUT("/users/:id", updateUser) + e.DELETE("/users/:id", deleteUser) + + // Start server + e.Logger.Fatal(e.Start(":1323")) +} diff --git a/vendor/github.com/labstack/echo/cookbook/embed-resources/server.go b/vendor/github.com/labstack/echo/cookbook/embed-resources/server.go new file mode 100644 index 00000000..e5b0c3db --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/embed-resources/server.go @@ -0,0 +1,21 @@ +package main + +import ( + "net/http" + + rice "github.com/GeertJohan/go.rice" + "github.com/labstack/echo" +) + +func main() { + e := echo.New() + // the file server for rice. "app" is the folder where the files come from. + assetHandler := http.FileServer(rice.MustFindBox("app").HTTPBox()) + // serves the index.html from rice + e.GET("/", echo.WrapHandler(assetHandler)) + + // servers other static files + e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler))) + + e.Logger.Fatal(e.Start(":1323")) +} diff --git a/vendor/github.com/labstack/echo/cookbook/file-upload/multiple/server.go b/vendor/github.com/labstack/echo/cookbook/file-upload/multiple/server.go new file mode 100644 index 00000000..cd0f54d3 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/file-upload/multiple/server.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "io" + "os" + + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +func upload(c echo.Context) error { + // Read form fields + name := c.FormValue("name") + email := c.FormValue("email") + + //------------ + // Read files + //------------ + + // Multipart form + form, err := c.MultipartForm() + if err != nil { + return err + } + files := form.File["files"] + + for _, file := range files { + // Source + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + // Destination + dst, err := os.Create(file.Filename) + if err != nil { + return err + } + defer dst.Close() + + // Copy + if _, err = io.Copy(dst, src); err != nil { + return err + } + + } + + return c.HTML(http.StatusOK, fmt.Sprintf("

Uploaded successfully %d files with fields name=%s and email=%s.

", len(files), name, email)) +} + +func main() { + e := echo.New() + + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + e.Static("/", "public") + e.POST("/upload", upload) + + e.Logger.Fatal(e.Start(":1323")) +} diff --git a/vendor/github.com/labstack/echo/cookbook/file-upload/single/server.go b/vendor/github.com/labstack/echo/cookbook/file-upload/single/server.go new file mode 100644 index 00000000..1b84f220 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/file-upload/single/server.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "io" + "os" + + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +func upload(c echo.Context) error { + // Read form fields + name := c.FormValue("name") + email := c.FormValue("email") + + //----------- + // Read file + //----------- + + // Source + file, err := c.FormFile("file") + if err != nil { + return err + } + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + // Destination + dst, err := os.Create(file.Filename) + if err != nil { + return err + } + defer dst.Close() + + // Copy + if _, err = io.Copy(dst, src); err != nil { + return err + } + + return c.HTML(http.StatusOK, fmt.Sprintf("

File %s uploaded successfully with fields name=%s and email=%s.

", file.Filename, name, email)) +} + +func main() { + e := echo.New() + + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + e.Static("/", "public") + e.POST("/upload", upload) + + e.Logger.Fatal(e.Start(":1323")) +} diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-engine.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-engine.go new file mode 100644 index 00000000..0c1db087 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-engine.go @@ -0,0 +1,17 @@ +// +build appengine + +package main + +import ( + "net/http" + + "github.com/labstack/echo" +) + +func createMux() *echo.Echo { + e := echo.New() + // note: we don't need to provide the middleware or static handlers, that's taken care of by the platform + // app engine has it's own "main" wrapper - we just need to hook echo into the default handler + http.Handle("/", e) + return e +} diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-managed.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-managed.go new file mode 100644 index 00000000..7b8eacf8 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-managed.go @@ -0,0 +1,25 @@ +// +build appenginevm + +package main + +import ( + "net/http" + + "github.com/labstack/echo" + "google.golang.org/appengine" +) + +func createMux() *echo.Echo { + e := echo.New() + // note: we don't need to provide the middleware or static handlers + // for the appengine vm version - that's taken care of by the platform + return e +} + +func main() { + // the appengine package provides a convenient method to handle the health-check requests + // and also run the app on the correct port. We just need to add Echo to the default handler + e := echo.New(":8080") + http.Handle("/", e) + appengine.Main() +} diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-standalone.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-standalone.go new file mode 100644 index 00000000..c3b44dc0 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-standalone.go @@ -0,0 +1,24 @@ +// +build !appengine,!appenginevm + +package main + +import ( + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +func createMux() *echo.Echo { + e := echo.New() + + e.Use(middleware.Recover()) + e.Use(middleware.Logger()) + e.Use(middleware.Gzip()) + + e.Static("/", "public") + + return e +} + +func main() { + e.Logger.Fatal(e.Start(":8080")) +} diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/app.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app.go new file mode 100644 index 00000000..8d4d97a2 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app.go @@ -0,0 +1,4 @@ +package main + +// reference our echo instance and create it early +var e = createMux() diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/users.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/users.go new file mode 100644 index 00000000..19533e51 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/users.go @@ -0,0 +1,54 @@ +package main + +import ( + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +type ( + user struct { + ID string `json:"id"` + Name string `json:"name"` + } +) + +var ( + users map[string]user +) + +func init() { + users = map[string]user{ + "1": user{ + ID: "1", + Name: "Wreck-It Ralph", + }, + } + + // hook into the echo instance to create an endpoint group + // and add specific middleware to it plus handlers + g := e.Group("/users") + g.Use(middleware.CORS()) + + g.POST("", createUser) + g.GET("", getUsers) + g.GET("/:id", getUser) +} + +func createUser(c echo.Context) error { + u := new(user) + if err := c.Bind(u); err != nil { + return err + } + users[u.ID] = *u + return c.JSON(http.StatusCreated, u) +} + +func getUsers(c echo.Context) error { + return c.JSON(http.StatusOK, users) +} + +func getUser(c echo.Context) error { + return c.JSON(http.StatusOK, users[c.Param("id")]) +} diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/welcome.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/welcome.go new file mode 100644 index 00000000..2639b209 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/welcome.go @@ -0,0 +1,31 @@ +package main + +import ( + "html/template" + "io" + "net/http" + + "github.com/labstack/echo" +) + +type ( + Template struct { + templates *template.Template + } +) + +func init() { + t := &Template{ + templates: template.Must(template.ParseFiles("templates/welcome.html")), + } + e.Renderer = t + e.GET("/welcome", welcome) +} + +func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + return t.templates.ExecuteTemplate(w, name, data) +} + +func welcome(c echo.Context) error { + return c.Render(http.StatusOK, "welcome", "Joe") +} diff --git a/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/grace/server.go b/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/grace/server.go new file mode 100644 index 00000000..1f9937b0 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/grace/server.go @@ -0,0 +1,20 @@ +package main + +import ( + "net/http" + + "github.com/facebookgo/grace/gracehttp" + "github.com/labstack/echo" +) + +func main() { + // Setup + e := echo.New() + e.GET("/", func(c echo.Context) error { + return c.String(http.StatusOK, "Six sick bricks tick") + }) + e.Server.Addr = ":1323" + + // Serve it like a boss + e.Logger.Fatal(gracehttp.Serve(e.Server)) +} diff --git a/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/graceful/server.go b/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/graceful/server.go new file mode 100644 index 00000000..39e7b634 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/graceful/server.go @@ -0,0 +1,21 @@ +package main + +import ( + "net/http" + "time" + + "github.com/labstack/echo" + "github.com/tylerb/graceful" +) + +func main() { + // Setup + e := echo.New() + e.GET("/", func(c echo.Context) error { + return c.String(http.StatusOK, "Sue sews rose on slow joe crows nose") + }) + e.Server.Addr = ":1323" + + // Serve it like a boss + graceful.ListenAndServe(e.Server, 5*time.Second) +} diff --git a/vendor/github.com/labstack/echo/cookbook/hello-world/server.go b/vendor/github.com/labstack/echo/cookbook/hello-world/server.go new file mode 100644 index 00000000..06e0718b --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/hello-world/server.go @@ -0,0 +1,25 @@ +package main + +import ( + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +func main() { + // Echo instance + e := echo.New() + + // Middleware + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + // Route => handler + e.GET("/", func(c echo.Context) error { + return c.String(http.StatusOK, "Hello, World!\n") + }) + + // Start server + e.Logger.Fatal(e.Start(":1323")) +} diff --git a/vendor/github.com/labstack/echo/cookbook/http2/server.go b/vendor/github.com/labstack/echo/cookbook/http2/server.go new file mode 100644 index 00000000..8db989c4 --- /dev/null +++ b/vendor/github.com/labstack/echo/cookbook/http2/server.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "net/http" + "time" + + "github.com/labstack/echo" +) + +func request(c echo.Context) error { + req := c.Request() + format := "
Request Information\n\nProtocol: %s\nHost: %s\nRemote Address: %s\nMethod: %s\nPath: %s\n
" + return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path)) +} + +func stream(c echo.Context) error { + res := c.Response() + gone := res.CloseNotify() + res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8) + res.WriteHeader(http.StatusOK) + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + fmt.Fprint(res, "
Clock Stream\n\n")
+	for {
+		fmt.Fprintf(res, "%v\n", time.Now())
+		res.Flush()
+		select {
+		case <-ticker.C:
+		case <-gone:
+			break
+		}
+	}
+}
+
+func main() {
+	e := echo.New()
+	e.GET("/request", request)
+	e.GET("/stream", stream)
+	e.Logger.Fatal(e.StartTLS(":1323", "cert.pem", "key.pem"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/jsonp/server.go b/vendor/github.com/labstack/echo/cookbook/jsonp/server.go
new file mode 100644
index 00000000..ba46bab0
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/jsonp/server.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+	"math/rand"
+	"net/http"
+	"time"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/middleware"
+)
+
+func main() {
+	e := echo.New()
+	e.Use(middleware.Logger())
+	e.Use(middleware.Recover())
+
+	e.Static("/", "public")
+
+	// JSONP
+	e.GET("/jsonp", func(c echo.Context) error {
+		callback := c.QueryParam("callback")
+		var content struct {
+			Response  string    `json:"response"`
+			Timestamp time.Time `json:"timestamp"`
+			Random    int       `json:"random"`
+		}
+		content.Response = "Sent via JSONP"
+		content.Timestamp = time.Now().UTC()
+		content.Random = rand.Intn(1000)
+		return c.JSONP(http.StatusOK, callback, &content)
+	})
+
+	// Start server
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/jwt/custom-claims/server.go b/vendor/github.com/labstack/echo/cookbook/jwt/custom-claims/server.go
new file mode 100644
index 00000000..b3a13205
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/jwt/custom-claims/server.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+	"net/http"
+	"time"
+
+	jwt "github.com/dgrijalva/jwt-go"
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/middleware"
+)
+
+// jwtCustomClaims are custom claims extending default ones.
+type jwtCustomClaims struct {
+	Name  string `json:"name"`
+	Admin bool   `json:"admin"`
+	jwt.StandardClaims
+}
+
+func login(c echo.Context) error {
+	username := c.FormValue("username")
+	password := c.FormValue("password")
+
+	if username == "jon" && password == "shhh!" {
+
+		// Set custom claims
+		claims := &jwtCustomClaims{
+			"Jon Snow",
+			true,
+			jwt.StandardClaims{
+				ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
+			},
+		}
+
+		// Create token with claims
+		token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+
+		// Generate encoded token and send it as response.
+		t, err := token.SignedString([]byte("secret"))
+		if err != nil {
+			return err
+		}
+		return c.JSON(http.StatusOK, echo.Map{
+			"token": t,
+		})
+	}
+
+	return echo.ErrUnauthorized
+}
+
+func accessible(c echo.Context) error {
+	return c.String(http.StatusOK, "Accessible")
+}
+
+func restricted(c echo.Context) error {
+	user := c.Get("user").(*jwt.Token)
+	claims := user.Claims.(*jwtCustomClaims)
+	name := claims.Name
+	return c.String(http.StatusOK, "Welcome "+name+"!")
+}
+
+func main() {
+	e := echo.New()
+
+	// Middleware
+	e.Use(middleware.Logger())
+	e.Use(middleware.Recover())
+
+	// Login route
+	e.POST("/login", login)
+
+	// Unauthenticated route
+	e.GET("/", accessible)
+
+	// Restricted group
+	r := e.Group("/restricted")
+
+	// Configure middleware with the custom claims type
+	config := middleware.JWTConfig{
+		Claims:     &jwtCustomClaims{},
+		SigningKey: []byte("secret"),
+	}
+	r.Use(middleware.JWTWithConfig(config))
+	r.GET("", restricted)
+
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/jwt/map-claims/server.go b/vendor/github.com/labstack/echo/cookbook/jwt/map-claims/server.go
new file mode 100644
index 00000000..678be490
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/jwt/map-claims/server.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+	"net/http"
+	"time"
+
+	jwt "github.com/dgrijalva/jwt-go"
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/middleware"
+)
+
+func login(c echo.Context) error {
+	username := c.FormValue("username")
+	password := c.FormValue("password")
+
+	if username == "jon" && password == "shhh!" {
+		// Create token
+		token := jwt.New(jwt.SigningMethodHS256)
+
+		// Set claims
+		claims := token.Claims.(jwt.MapClaims)
+		claims["name"] = "Jon Snow"
+		claims["admin"] = true
+		claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
+
+		// Generate encoded token and send it as response.
+		t, err := token.SignedString([]byte("secret"))
+		if err != nil {
+			return err
+		}
+		return c.JSON(http.StatusOK, map[string]string{
+			"token": t,
+		})
+	}
+
+	return echo.ErrUnauthorized
+}
+
+func accessible(c echo.Context) error {
+	return c.String(http.StatusOK, "Accessible")
+}
+
+func restricted(c echo.Context) error {
+	user := c.Get("user").(*jwt.Token)
+	claims := user.Claims.(jwt.MapClaims)
+	name := claims["name"].(string)
+	return c.String(http.StatusOK, "Welcome "+name+"!")
+}
+
+func main() {
+	e := echo.New()
+
+	// Middleware
+	e.Use(middleware.Logger())
+	e.Use(middleware.Recover())
+
+	// Login route
+	e.POST("/login", login)
+
+	// Unauthenticated route
+	e.GET("/", accessible)
+
+	// Restricted group
+	r := e.Group("/restricted")
+	r.Use(middleware.JWT([]byte("secret")))
+	r.GET("", restricted)
+
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/middleware/server.go b/vendor/github.com/labstack/echo/cookbook/middleware/server.go
new file mode 100644
index 00000000..2f21df50
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/middleware/server.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+	"net/http"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/labstack/echo"
+)
+
+type (
+	Stats struct {
+		Uptime       time.Time      `json:"uptime"`
+		RequestCount uint64         `json:"requestCount"`
+		Statuses     map[string]int `json:"statuses"`
+		mutex        sync.RWMutex
+	}
+)
+
+func NewStats() *Stats {
+	return &Stats{
+		Uptime:   time.Now(),
+		Statuses: make(map[string]int),
+	}
+}
+
+// Process is the middleware function.
+func (s *Stats) Process(next echo.HandlerFunc) echo.HandlerFunc {
+	return func(c echo.Context) error {
+		if err := next(c); err != nil {
+			c.Error(err)
+		}
+		s.mutex.Lock()
+		defer s.mutex.Unlock()
+		s.RequestCount++
+		status := strconv.Itoa(c.Response().Status)
+		s.Statuses[status]++
+		return nil
+	}
+}
+
+// Handle is the endpoint to get stats.
+func (s *Stats) Handle(c echo.Context) error {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	return c.JSON(http.StatusOK, s)
+}
+
+// ServerHeader middleware adds a `Server` header to the response.
+func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc {
+	return func(c echo.Context) error {
+		c.Response().Header().Set(echo.HeaderServer, "Echo/3.0")
+		return next(c)
+	}
+}
+
+func main() {
+	e := echo.New()
+
+	// Debug mode
+	e.Debug = true
+
+	//-------------------
+	// Custom middleware
+	//-------------------
+	// Stats
+	s := NewStats()
+	e.Use(s.Process)
+	e.GET("/stats", s.Handle) // Endpoint to get stats
+
+	// Server header
+	e.Use(ServerHeader)
+
+	// Handler
+	e.GET("/", func(c echo.Context) error {
+		return c.String(http.StatusOK, "Hello, World!")
+	})
+
+	// Start server
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/streaming-response/server.go b/vendor/github.com/labstack/echo/cookbook/streaming-response/server.go
new file mode 100644
index 00000000..a3a679ef
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/streaming-response/server.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+	"net/http"
+	"time"
+
+	"encoding/json"
+
+	"github.com/labstack/echo"
+)
+
+type (
+	Geolocation struct {
+		Altitude  float64
+		Latitude  float64
+		Longitude float64
+	}
+)
+
+var (
+	locations = []Geolocation{
+		{-97, 37.819929, -122.478255},
+		{1899, 39.096849, -120.032351},
+		{2619, 37.865101, -119.538329},
+		{42, 33.812092, -117.918974},
+		{15, 37.77493, -122.419416},
+	}
+)
+
+func main() {
+	e := echo.New()
+	e.GET("/", func(c echo.Context) error {
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		c.Response().WriteHeader(http.StatusOK)
+		for _, l := range locations {
+			if err := json.NewEncoder(c.Response()).Encode(l); err != nil {
+				return err
+			}
+			c.Response().Flush()
+			time.Sleep(1 * time.Second)
+		}
+		return nil
+	})
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/subdomains/server.go b/vendor/github.com/labstack/echo/cookbook/subdomains/server.go
new file mode 100644
index 00000000..ef4f65f9
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/subdomains/server.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/middleware"
+)
+
+type (
+	Host struct {
+		Echo *echo.Echo
+	}
+)
+
+func main() {
+	// Hosts
+	hosts := make(map[string]*Host)
+
+	//-----
+	// API
+	//-----
+
+	api := echo.New()
+	api.Use(middleware.Logger())
+	api.Use(middleware.Recover())
+
+	hosts["api.localhost:1323"] = &Host{api}
+
+	api.GET("/", func(c echo.Context) error {
+		return c.String(http.StatusOK, "API")
+	})
+
+	//------
+	// Blog
+	//------
+
+	blog := echo.New()
+	blog.Use(middleware.Logger())
+	blog.Use(middleware.Recover())
+
+	hosts["blog.localhost:1323"] = &Host{blog}
+
+	blog.GET("/", func(c echo.Context) error {
+		return c.String(http.StatusOK, "Blog")
+	})
+
+	//---------
+	// Website
+	//---------
+
+	site := echo.New()
+	site.Use(middleware.Logger())
+	site.Use(middleware.Recover())
+
+	hosts["localhost:1323"] = &Host{site}
+
+	site.GET("/", func(c echo.Context) error {
+		return c.String(http.StatusOK, "Website")
+	})
+
+	// Server
+	e := echo.New()
+	e.Any("/*", func(c echo.Context) (err error) {
+		req := c.Request()
+		res := c.Response()
+		host := hosts[req.Host]
+
+		if host == nil {
+			err = echo.ErrNotFound
+		} else {
+			host.Echo.ServeHTTP(res, req)
+		}
+
+		return
+	})
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/handler/handler.go b/vendor/github.com/labstack/echo/cookbook/twitter/handler/handler.go
new file mode 100644
index 00000000..263c5e21
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/handler/handler.go
@@ -0,0 +1,14 @@
+package handler
+
+import mgo "gopkg.in/mgo.v2"
+
+type (
+	Handler struct {
+		DB *mgo.Session
+	}
+)
+
+const (
+	// Key (Should come from somewhere else).
+	Key = "secret"
+)
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/handler/post.go b/vendor/github.com/labstack/echo/cookbook/twitter/handler/post.go
new file mode 100644
index 00000000..b1428a30
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/handler/post.go
@@ -0,0 +1,73 @@
+package handler
+
+import (
+	"net/http"
+	"strconv"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/cookbook/twitter/model"
+	mgo "gopkg.in/mgo.v2"
+	"gopkg.in/mgo.v2/bson"
+)
+
+func (h *Handler) CreatePost(c echo.Context) (err error) {
+	u := &model.User{
+		ID: bson.ObjectIdHex(userIDFromToken(c)),
+	}
+	p := &model.Post{
+		ID:   bson.NewObjectId(),
+		From: u.ID.Hex(),
+	}
+	if err = c.Bind(p); err != nil {
+		return
+	}
+
+	// Validation
+	if p.To == "" || p.Message == "" {
+		return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid to or message fields"}
+	}
+
+	// Find user from database
+	db := h.DB.Clone()
+	defer db.Close()
+	if err = db.DB("twitter").C("users").FindId(u.ID).One(u); err != nil {
+		if err == mgo.ErrNotFound {
+			return echo.ErrNotFound
+		}
+		return
+	}
+
+	// Save post in database
+	if err = db.DB("twitter").C("posts").Insert(p); err != nil {
+		return
+	}
+	return c.JSON(http.StatusCreated, p)
+}
+
+func (h *Handler) FetchPost(c echo.Context) (err error) {
+	userID := userIDFromToken(c)
+	page, _ := strconv.Atoi(c.QueryParam("page"))
+	limit, _ := strconv.Atoi(c.QueryParam("limit"))
+
+	// Defaults
+	if page == 0 {
+		page = 1
+	}
+	if limit == 0 {
+		limit = 100
+	}
+
+	// Retrieve posts from database
+	posts := []*model.Post{}
+	db := h.DB.Clone()
+	if err = db.DB("twitter").C("posts").
+		Find(bson.M{"to": userID}).
+		Skip((page - 1) * limit).
+		Limit(limit).
+		All(&posts); err != nil {
+		return
+	}
+	defer db.Close()
+
+	return c.JSON(http.StatusOK, posts)
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/handler/user.go b/vendor/github.com/labstack/echo/cookbook/twitter/handler/user.go
new file mode 100644
index 00000000..a34d2f4e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/handler/user.go
@@ -0,0 +1,97 @@
+package handler
+
+import (
+	"net/http"
+	"time"
+
+	jwt "github.com/dgrijalva/jwt-go"
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/cookbook/twitter/model"
+	mgo "gopkg.in/mgo.v2"
+	"gopkg.in/mgo.v2/bson"
+)
+
+func (h *Handler) Signup(c echo.Context) (err error) {
+	// Bind
+	u := &model.User{ID: bson.NewObjectId()}
+	if err = c.Bind(u); err != nil {
+		return
+	}
+
+	// Validate
+	if u.Email == "" || u.Password == "" {
+		return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid email or password"}
+	}
+
+	// Save user
+	db := h.DB.Clone()
+	defer db.Close()
+	if err = db.DB("twitter").C("users").Insert(u); err != nil {
+		return
+	}
+
+	return c.JSON(http.StatusCreated, u)
+}
+
+func (h *Handler) Login(c echo.Context) (err error) {
+	// Bind
+	u := new(model.User)
+	if err = c.Bind(u); err != nil {
+		return
+	}
+
+	// Find user
+	db := h.DB.Clone()
+	defer db.Close()
+	if err = db.DB("twitter").C("users").
+		Find(bson.M{"email": u.Email, "password": u.Password}).One(u); err != nil {
+		if err == mgo.ErrNotFound {
+			return &echo.HTTPError{Code: http.StatusUnauthorized, Message: "invalid email or password"}
+		}
+		return
+	}
+
+	//-----
+	// JWT
+	//-----
+
+	// Create token
+	token := jwt.New(jwt.SigningMethodHS256)
+
+	// Set claims
+	claims := token.Claims.(jwt.MapClaims)
+	claims["id"] = u.ID
+	claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
+
+	// Generate encoded token and send it as response
+	u.Token, err = token.SignedString([]byte(Key))
+	if err != nil {
+		return err
+	}
+
+	u.Password = "" // Don't send password
+	return c.JSON(http.StatusOK, u)
+}
+
+func (h *Handler) Follow(c echo.Context) (err error) {
+	userID := userIDFromToken(c)
+	id := c.Param("id")
+
+	// Add a follower to user
+	db := h.DB.Clone()
+	defer db.Close()
+	if err = db.DB("twitter").C("users").
+		UpdateId(bson.ObjectIdHex(id), bson.M{"$addToSet": bson.M{"followers": userID}}); err != nil {
+		if err == mgo.ErrNotFound {
+			return echo.ErrNotFound
+		}
+	}
+
+	return
+}
+
+func userIDFromToken(c echo.Context) string {
+	user := c.Get("user").(*jwt.Token)
+	claims := user.Claims.(jwt.MapClaims)
+	return claims["id"].(string)
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/model/post.go b/vendor/github.com/labstack/echo/cookbook/twitter/model/post.go
new file mode 100644
index 00000000..7344296e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/model/post.go
@@ -0,0 +1,12 @@
+package model
+
+import "gopkg.in/mgo.v2/bson"
+
+type (
+	Post struct {
+		ID      bson.ObjectId `json:"id" bson:"_id,omitempty"`
+		To      string        `json:"to" bson:"to"`
+		From    string        `json:"from" bson:"from"`
+		Message string        `json:"message" bson:"message"`
+	}
+)
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/model/user.go b/vendor/github.com/labstack/echo/cookbook/twitter/model/user.go
new file mode 100644
index 00000000..e063c89b
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/model/user.go
@@ -0,0 +1,13 @@
+package model
+
+import "gopkg.in/mgo.v2/bson"
+
+type (
+	User struct {
+		ID        bson.ObjectId `json:"id" bson:"_id,omitempty"`
+		Email     string        `json:"email" bson:"email"`
+		Password  string        `json:"password,omitempty" bson:"password"`
+		Token     string        `json:"token,omitempty" bson:"-"`
+		Followers []string      `json:"followers,omitempty" bson:"followers,omitempty"`
+	}
+)
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/server.go b/vendor/github.com/labstack/echo/cookbook/twitter/server.go
new file mode 100644
index 00000000..22db7aa0
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/server.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/cookbook/twitter/handler"
+	"github.com/labstack/echo/middleware"
+	"github.com/labstack/gommon/log"
+	mgo "gopkg.in/mgo.v2"
+)
+
+func main() {
+	e := echo.New()
+	e.Logger.SetLevel(log.ERROR)
+	e.Use(middleware.Logger())
+	e.Use(middleware.JWTWithConfig(middleware.JWTConfig{
+		SigningKey: []byte(handler.Key),
+		Skipper: func(c echo.Context) bool {
+			// Skip authentication for and signup login requests
+			if c.Path() == "/login" || c.Path() == "/signup" {
+				return true
+			}
+			return false
+		},
+	}))
+
+	// Database connection
+	db, err := mgo.Dial("localhost")
+	if err != nil {
+		e.Logger.Fatal(err)
+	}
+
+	// Create indices
+	if err = db.Copy().DB("twitter").C("users").EnsureIndex(mgo.Index{
+		Key:    []string{"email"},
+		Unique: true,
+	}); err != nil {
+		log.Fatal(err)
+	}
+
+	// Initialize handler
+	h := &handler.Handler{DB: db}
+
+	// Routes
+	e.POST("/signup", h.Signup)
+	e.POST("/login", h.Login)
+	e.POST("/follow/:id", h.Follow)
+	e.POST("/posts", h.CreatePost)
+	e.GET("/feed", h.FetchPost)
+
+	// Start server
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/websocket/gorilla/server.go b/vendor/github.com/labstack/echo/cookbook/websocket/gorilla/server.go
new file mode 100644
index 00000000..e9d52dbb
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/websocket/gorilla/server.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+	"fmt"
+	"log"
+
+	"github.com/labstack/echo"
+
+	"github.com/gorilla/websocket"
+	"github.com/labstack/echo/middleware"
+)
+
+var (
+	upgrader = websocket.Upgrader{}
+)
+
+func hello(c echo.Context) error {
+	ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
+	if err != nil {
+		return err
+	}
+	defer ws.Close()
+
+	for {
+		// Write
+		err := ws.WriteMessage(websocket.TextMessage, []byte("Hello, Client!"))
+		if err != nil {
+			log.Fatal(err)
+		}
+
+		// Read
+		_, msg, err := ws.ReadMessage()
+		if err != nil {
+			log.Fatal(err)
+		}
+		fmt.Printf("%s\n", msg)
+	}
+}
+
+func main() {
+	e := echo.New()
+	e.Use(middleware.Logger())
+	e.Use(middleware.Recover())
+	e.Static("/", "../public")
+	e.GET("/ws", hello)
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/websocket/net/server.go b/vendor/github.com/labstack/echo/cookbook/websocket/net/server.go
new file mode 100644
index 00000000..aa746030
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/websocket/net/server.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+	"fmt"
+	"log"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/middleware"
+	"golang.org/x/net/websocket"
+)
+
+func hello(c echo.Context) error {
+	websocket.Handler(func(ws *websocket.Conn) {
+		defer ws.Close()
+		for {
+			// Write
+			err := websocket.Message.Send(ws, "Hello, Client!")
+			if err != nil {
+				log.Fatal(err)
+			}
+
+			// Read
+			msg := ""
+			err = websocket.Message.Receive(ws, &msg)
+			if err != nil {
+				log.Fatal(err)
+			}
+			fmt.Printf("%s\n", msg)
+		}
+	}).ServeHTTP(c.Response(), c.Request())
+	return nil
+}
+
+func main() {
+	e := echo.New()
+	e.Use(middleware.Logger())
+	e.Use(middleware.Recover())
+	e.Static("/", "../public")
+	e.GET("/ws", hello)
+	e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/echo.go b/vendor/github.com/labstack/echo/echo.go
new file mode 100644
index 00000000..f0c1b72e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/echo.go
@@ -0,0 +1,666 @@
+/*
+Package echo implements high performance, minimalist Go web framework.
+
+Example:
+
+  package main
+
+  import (
+    "net/http"
+
+    "github.com/labstack/echo"
+    "github.com/labstack/echo/middleware"
+  )
+
+  // Handler
+  func hello(c echo.Context) error {
+    return c.String(http.StatusOK, "Hello, World!")
+  }
+
+  func main() {
+    // Echo instance
+    e := echo.New()
+
+    // Middleware
+    e.Use(middleware.Logger())
+    e.Use(middleware.Recover())
+
+    // Routes
+    e.GET("/", hello)
+
+    // Start server
+    e.Logger.Fatal(e.Start(":1323"))
+  }
+
+Learn more at https://echo.labstack.com
+*/
+package echo
+
+import (
+	"bytes"
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"io"
+	slog "log"
+	"net"
+	"net/http"
+	"path"
+	"reflect"
+	"runtime"
+	"sync"
+	"time"
+
+	"github.com/labstack/gommon/color"
+	"github.com/labstack/gommon/log"
+	"golang.org/x/crypto/acme/autocert"
+)
+
+type (
+	// Echo is the top-level framework instance.
+	Echo struct {
+		stdLogger        *slog.Logger
+		colorer          *color.Color
+		premiddleware    []MiddlewareFunc
+		middleware       []MiddlewareFunc
+		maxParam         *int
+		router           *Router
+		notFoundHandler  HandlerFunc
+		pool             sync.Pool
+		Server           *http.Server
+		TLSServer        *http.Server
+		Listener         net.Listener
+		TLSListener      net.Listener
+		DisableHTTP2     bool
+		Debug            bool
+		HTTPErrorHandler HTTPErrorHandler
+		Binder           Binder
+		Validator        Validator
+		Renderer         Renderer
+		AutoTLSManager   autocert.Manager
+		Mutex            sync.RWMutex
+		Logger           Logger
+	}
+
+	// Route contains a handler and information for matching against requests.
+	Route struct {
+		Method  string
+		Path    string
+		Handler string
+	}
+
+	// HTTPError represents an error that occurred while handling a request.
+	HTTPError struct {
+		Code    int
+		Message interface{}
+	}
+
+	// MiddlewareFunc defines a function to process middleware.
+	MiddlewareFunc func(HandlerFunc) HandlerFunc
+
+	// HandlerFunc defines a function to server HTTP requests.
+	HandlerFunc func(Context) error
+
+	// HTTPErrorHandler is a centralized HTTP error handler.
+	HTTPErrorHandler func(error, Context)
+
+	// Validator is the interface that wraps the Validate function.
+	Validator interface {
+		Validate(i interface{}) error
+	}
+
+	// Renderer is the interface that wraps the Render function.
+	Renderer interface {
+		Render(io.Writer, string, interface{}, Context) error
+	}
+
+	// Map defines a generic map of type `map[string]interface{}`.
+	Map map[string]interface{}
+
+	// i is the interface for Echo and Group.
+	i interface {
+		GET(string, HandlerFunc, ...MiddlewareFunc)
+	}
+)
+
+// HTTP methods
+const (
+	CONNECT = "CONNECT"
+	DELETE  = "DELETE"
+	GET     = "GET"
+	HEAD    = "HEAD"
+	OPTIONS = "OPTIONS"
+	PATCH   = "PATCH"
+	POST    = "POST"
+	PUT     = "PUT"
+	TRACE   = "TRACE"
+)
+
+// MIME types
+const (
+	MIMEApplicationJSON                  = "application/json"
+	MIMEApplicationJSONCharsetUTF8       = MIMEApplicationJSON + "; " + charsetUTF8
+	MIMEApplicationJavaScript            = "application/javascript"
+	MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
+	MIMEApplicationXML                   = "application/xml"
+	MIMEApplicationXMLCharsetUTF8        = MIMEApplicationXML + "; " + charsetUTF8
+	MIMEApplicationForm                  = "application/x-www-form-urlencoded"
+	MIMEApplicationProtobuf              = "application/protobuf"
+	MIMEApplicationMsgpack               = "application/msgpack"
+	MIMETextHTML                         = "text/html"
+	MIMETextHTMLCharsetUTF8              = MIMETextHTML + "; " + charsetUTF8
+	MIMETextPlain                        = "text/plain"
+	MIMETextPlainCharsetUTF8             = MIMETextPlain + "; " + charsetUTF8
+	MIMEMultipartForm                    = "multipart/form-data"
+	MIMEOctetStream                      = "application/octet-stream"
+)
+
+const (
+	charsetUTF8 = "charset=UTF-8"
+)
+
+// Headers
+const (
+	HeaderAcceptEncoding                = "Accept-Encoding"
+	HeaderAllow                         = "Allow"
+	HeaderAuthorization                 = "Authorization"
+	HeaderContentDisposition            = "Content-Disposition"
+	HeaderContentEncoding               = "Content-Encoding"
+	HeaderContentLength                 = "Content-Length"
+	HeaderContentType                   = "Content-Type"
+	HeaderCookie                        = "Cookie"
+	HeaderSetCookie                     = "Set-Cookie"
+	HeaderIfModifiedSince               = "If-Modified-Since"
+	HeaderLastModified                  = "Last-Modified"
+	HeaderLocation                      = "Location"
+	HeaderUpgrade                       = "Upgrade"
+	HeaderVary                          = "Vary"
+	HeaderWWWAuthenticate               = "WWW-Authenticate"
+	HeaderXForwardedProto               = "X-Forwarded-Proto"
+	HeaderXHTTPMethodOverride           = "X-HTTP-Method-Override"
+	HeaderXForwardedFor                 = "X-Forwarded-For"
+	HeaderXRealIP                       = "X-Real-IP"
+	HeaderServer                        = "Server"
+	HeaderOrigin                        = "Origin"
+	HeaderAccessControlRequestMethod    = "Access-Control-Request-Method"
+	HeaderAccessControlRequestHeaders   = "Access-Control-Request-Headers"
+	HeaderAccessControlAllowOrigin      = "Access-Control-Allow-Origin"
+	HeaderAccessControlAllowMethods     = "Access-Control-Allow-Methods"
+	HeaderAccessControlAllowHeaders     = "Access-Control-Allow-Headers"
+	HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
+	HeaderAccessControlExposeHeaders    = "Access-Control-Expose-Headers"
+	HeaderAccessControlMaxAge           = "Access-Control-Max-Age"
+
+	// Security
+	HeaderStrictTransportSecurity = "Strict-Transport-Security"
+	HeaderXContentTypeOptions     = "X-Content-Type-Options"
+	HeaderXXSSProtection          = "X-XSS-Protection"
+	HeaderXFrameOptions           = "X-Frame-Options"
+	HeaderContentSecurityPolicy   = "Content-Security-Policy"
+	HeaderXCSRFToken              = "X-CSRF-Token"
+)
+
+var (
+	methods = [...]string{
+		CONNECT,
+		DELETE,
+		GET,
+		HEAD,
+		OPTIONS,
+		PATCH,
+		POST,
+		PUT,
+		TRACE,
+	}
+)
+
+// Errors
+var (
+	ErrUnsupportedMediaType        = NewHTTPError(http.StatusUnsupportedMediaType)
+	ErrNotFound                    = NewHTTPError(http.StatusNotFound)
+	ErrUnauthorized                = NewHTTPError(http.StatusUnauthorized)
+	ErrMethodNotAllowed            = NewHTTPError(http.StatusMethodNotAllowed)
+	ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
+	ErrValidatorNotRegistered      = errors.New("Validator not registered")
+	ErrRendererNotRegistered       = errors.New("Renderer not registered")
+	ErrInvalidRedirectCode         = errors.New("Invalid redirect status code")
+	ErrCookieNotFound              = errors.New("Cookie not found")
+)
+
+// Error handlers
+var (
+	NotFoundHandler = func(c Context) error {
+		return ErrNotFound
+	}
+
+	MethodNotAllowedHandler = func(c Context) error {
+		return ErrMethodNotAllowed
+	}
+)
+
+// New creates an instance of Echo.
+func New() (e *Echo) {
+	e = &Echo{
+		Server:    new(http.Server),
+		TLSServer: new(http.Server),
+		AutoTLSManager: autocert.Manager{
+			Prompt: autocert.AcceptTOS,
+		},
+		Logger:   log.New("echo"),
+		colorer:  color.New(),
+		maxParam: new(int),
+	}
+	e.Server.Handler = e
+	e.TLSServer.Handler = e
+	e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
+	e.Binder = &DefaultBinder{}
+	e.Logger.SetLevel(log.OFF)
+	e.stdLogger = slog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
+	e.pool.New = func() interface{} {
+		return e.NewContext(nil, nil)
+	}
+	e.router = NewRouter(e)
+	return
+}
+
+// NewContext returns a Context instance.
+func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
+	return &context{
+		request:  r,
+		response: NewResponse(w, e),
+		store:    make(Map),
+		echo:     e,
+		pvalues:  make([]string, *e.maxParam),
+		handler:  NotFoundHandler,
+	}
+}
+
+// Router returns router.
+func (e *Echo) Router() *Router {
+	return e.router
+}
+
+// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
+// with status code.
+func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
+	var (
+		code = http.StatusInternalServerError
+		msg  interface{}
+	)
+
+	if he, ok := err.(*HTTPError); ok {
+		code = he.Code
+		msg = he.Message
+	} else if e.Debug {
+		msg = err.Error()
+	} else {
+		msg = http.StatusText(code)
+	}
+	if _, ok := msg.(string); ok {
+		msg = Map{"message": msg}
+	}
+
+	if !c.Response().Committed {
+		if c.Request().Method == HEAD { // Issue #608
+			if err := c.NoContent(code); err != nil {
+				goto ERROR
+			}
+		} else {
+			if err := c.JSON(code, msg); err != nil {
+				goto ERROR
+			}
+		}
+	}
+ERROR:
+	e.Logger.Error(err)
+}
+
+// Pre adds middleware to the chain which is run before router.
+func (e *Echo) Pre(middleware ...MiddlewareFunc) {
+	e.premiddleware = append(e.premiddleware, middleware...)
+}
+
+// Use adds middleware to the chain which is run after router.
+func (e *Echo) Use(middleware ...MiddlewareFunc) {
+	e.middleware = append(e.middleware, middleware...)
+}
+
+// CONNECT registers a new CONNECT route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(CONNECT, path, h, m...)
+}
+
+// DELETE registers a new DELETE route for a path with matching handler in the router
+// with optional route-level middleware.
+func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(DELETE, path, h, m...)
+}
+
+// GET registers a new GET route for a path with matching handler in the router
+// with optional route-level middleware.
+func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(GET, path, h, m...)
+}
+
+// HEAD registers a new HEAD route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(HEAD, path, h, m...)
+}
+
+// OPTIONS registers a new OPTIONS route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(OPTIONS, path, h, m...)
+}
+
+// PATCH registers a new PATCH route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(PATCH, path, h, m...)
+}
+
+// POST registers a new POST route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(POST, path, h, m...)
+}
+
+// PUT registers a new PUT route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(PUT, path, h, m...)
+}
+
+// TRACE registers a new TRACE route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	e.add(TRACE, path, h, m...)
+}
+
+// Any registers a new route for all HTTP methods and path with matching handler
+// in the router with optional route-level middleware.
+func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+	for _, m := range methods {
+		e.add(m, path, handler, middleware...)
+	}
+}
+
+// Match registers a new route for multiple HTTP methods and path with matching
+// handler in the router with optional route-level middleware.
+func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+	for _, m := range methods {
+		e.add(m, path, handler, middleware...)
+	}
+}
+
+// Static registers a new route with path prefix to serve static files from the
+// provided root directory.
+func (e *Echo) Static(prefix, root string) {
+	static(e, prefix, root)
+}
+
+func static(i i, prefix, root string) {
+	h := func(c Context) error {
+		return c.File(path.Join(root, c.Param("*")))
+	}
+	i.GET(prefix, h)
+	if prefix == "/" {
+		i.GET(prefix+"*", h)
+	} else {
+		i.GET(prefix+"/*", h)
+	}
+}
+
+// File registers a new route with path to serve a static file.
+func (e *Echo) File(path, file string) {
+	e.GET(path, func(c Context) error {
+		return c.File(file)
+	})
+}
+
+func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+	name := handlerName(handler)
+	e.router.Add(method, path, func(c Context) error {
+		h := handler
+		// Chain middleware
+		for i := len(middleware) - 1; i >= 0; i-- {
+			h = middleware[i](h)
+		}
+		return h(c)
+	})
+	r := Route{
+		Method:  method,
+		Path:    path,
+		Handler: name,
+	}
+	e.router.routes[method+path] = r
+}
+
+// Group creates a new router group with prefix and optional group-level middleware.
+func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
+	g = &Group{prefix: prefix, echo: e}
+	g.Use(m...)
+	return
+}
+
+// URI generates a URI from handler.
+func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
+	uri := new(bytes.Buffer)
+	ln := len(params)
+	n := 0
+	name := handlerName(handler)
+	for _, r := range e.router.routes {
+		if r.Handler == name {
+			for i, l := 0, len(r.Path); i < l; i++ {
+				if r.Path[i] == ':' && n < ln {
+					for ; i < l && r.Path[i] != '/'; i++ {
+					}
+					uri.WriteString(fmt.Sprintf("%v", params[n]))
+					n++
+				}
+				if i < l {
+					uri.WriteByte(r.Path[i])
+				}
+			}
+			break
+		}
+	}
+	return uri.String()
+}
+
+// URL is an alias for `URI` function.
+func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
+	return e.URI(h, params...)
+}
+
+// Routes returns the registered routes.
+func (e *Echo) Routes() []Route {
+	routes := []Route{}
+	for _, v := range e.router.routes {
+		routes = append(routes, v)
+	}
+	return routes
+}
+
+// AcquireContext returns an empty `Context` instance from the pool.
+// You must return the context by calling `ReleaseContext()`.
+func (e *Echo) AcquireContext() Context {
+	return e.pool.Get().(Context)
+}
+
+// ReleaseContext returns the `Context` instance back to the pool.
+// You must call it after `AcquireContext()`.
+func (e *Echo) ReleaseContext(c Context) {
+	e.pool.Put(c)
+}
+
+// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
+func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	// Acquire lock
+	e.Mutex.RLock()
+	defer e.Mutex.RUnlock()
+
+	// Acquire context
+	c := e.pool.Get().(*context)
+	defer e.pool.Put(c)
+	c.Reset(r, w)
+
+	// Middleware
+	h := func(c Context) error {
+		method := r.Method
+		path := r.URL.EscapedPath()
+		e.router.Find(method, path, c)
+		h := c.Handler()
+		for i := len(e.middleware) - 1; i >= 0; i-- {
+			h = e.middleware[i](h)
+		}
+		return h(c)
+	}
+
+	// Premiddleware
+	for i := len(e.premiddleware) - 1; i >= 0; i-- {
+		h = e.premiddleware[i](h)
+	}
+
+	// Execute chain
+	if err := h(c); err != nil {
+		e.HTTPErrorHandler(err, c)
+	}
+}
+
+// Start starts an HTTP server.
+func (e *Echo) Start(address string) error {
+	e.Server.Addr = address
+	return e.StartServer(e.Server)
+}
+
+// StartTLS starts an HTTPS server.
+func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) {
+	if certFile == "" || keyFile == "" {
+		return errors.New("invalid tls configuration")
+	}
+	s := e.TLSServer
+	s.TLSConfig = new(tls.Config)
+	s.TLSConfig.Certificates = make([]tls.Certificate, 1)
+	s.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+	if err != nil {
+		return
+	}
+	return e.startTLS(address)
+}
+
+// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
+func (e *Echo) StartAutoTLS(address string) error {
+	s := e.TLSServer
+	s.TLSConfig = new(tls.Config)
+	s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
+	return e.startTLS(address)
+}
+
+func (e *Echo) startTLS(address string) error {
+	s := e.TLSServer
+	s.Addr = address
+	if !e.DisableHTTP2 {
+		s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
+	}
+	return e.StartServer(e.TLSServer)
+}
+
+// StartServer starts a custom http server.
+func (e *Echo) StartServer(s *http.Server) (err error) {
+	// Setup
+	e.colorer.SetOutput(e.Logger.Output())
+	s.Handler = e
+	s.ErrorLog = e.stdLogger
+
+	if s.TLSConfig == nil {
+		if e.Listener == nil {
+			e.Listener, err = newListener(s.Addr)
+			if err != nil {
+				return err
+			}
+		}
+		e.colorer.Printf("⇛ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
+		return s.Serve(e.Listener)
+	}
+	if e.TLSListener == nil {
+		l, err := newListener(s.Addr)
+		if err != nil {
+			return err
+		}
+		e.TLSListener = tls.NewListener(l, s.TLSConfig)
+	}
+	e.colorer.Printf("⇛ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
+	return s.Serve(e.TLSListener)
+}
+
+// NewHTTPError creates a new HTTPError instance.
+func NewHTTPError(code int, message ...interface{}) *HTTPError {
+	he := &HTTPError{Code: code, Message: http.StatusText(code)}
+	if len(message) > 0 {
+		he.Message = message[0]
+	}
+	return he
+}
+
+// Error makes it compatible with `error` interface.
+func (he *HTTPError) Error() string {
+	return fmt.Sprintf("code=%d, message=%s", he.Code, he.Message)
+}
+
+// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
+func WrapHandler(h http.Handler) HandlerFunc {
+	return func(c Context) error {
+		h.ServeHTTP(c.Response(), c.Request())
+		return nil
+	}
+}
+
+// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc`
+func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
+	return func(next HandlerFunc) HandlerFunc {
+		return func(c Context) (err error) {
+			m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				c.SetRequest(r)
+				err = next(c)
+			})).ServeHTTP(c.Response(), c.Request())
+			return
+		}
+	}
+}
+
+func handlerName(h HandlerFunc) string {
+	t := reflect.ValueOf(h).Type()
+	if t.Kind() == reflect.Func {
+		return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
+	}
+	return t.String()
+}
+
+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
+// connections. It's used by ListenAndServe and ListenAndServeTLS so
+// dead TCP connections (e.g. closing laptop mid-download) eventually
+// go away.
+type tcpKeepAliveListener struct {
+	*net.TCPListener
+}
+
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
+	tc, err := ln.AcceptTCP()
+	if err != nil {
+		return
+	}
+	tc.SetKeepAlive(true)
+	tc.SetKeepAlivePeriod(3 * time.Minute)
+	return tc, nil
+}
+
+func newListener(address string) (*tcpKeepAliveListener, error) {
+	l, err := net.Listen("tcp", address)
+	if err != nil {
+		return nil, err
+	}
+	return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil
+}
diff --git a/vendor/github.com/labstack/echo/group.go b/vendor/github.com/labstack/echo/group.go
new file mode 100644
index 00000000..9767bb19
--- /dev/null
+++ b/vendor/github.com/labstack/echo/group.go
@@ -0,0 +1,104 @@
+package echo
+
+type (
+	// Group is a set of sub-routes for a specified route. It can be used for inner
+	// routes that share a common middlware or functionality that should be separate
+	// from the parent echo instance while still inheriting from it.
+	Group struct {
+		prefix     string
+		middleware []MiddlewareFunc
+		echo       *Echo
+	}
+)
+
+// Use implements `Echo#Use()` for sub-routes within the Group.
+func (g *Group) Use(middleware ...MiddlewareFunc) {
+	g.middleware = append(g.middleware, middleware...)
+}
+
+// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
+func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(CONNECT, path, h, m...)
+}
+
+// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
+func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(DELETE, path, h, m...)
+}
+
+// GET implements `Echo#GET()` for sub-routes within the Group.
+func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(GET, path, h, m...)
+}
+
+// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
+func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(HEAD, path, h, m...)
+}
+
+// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
+func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(OPTIONS, path, h, m...)
+}
+
+// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
+func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(PATCH, path, h, m...)
+}
+
+// POST implements `Echo#POST()` for sub-routes within the Group.
+func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(POST, path, h, m...)
+}
+
+// PUT implements `Echo#PUT()` for sub-routes within the Group.
+func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(PUT, path, h, m...)
+}
+
+// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
+func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
+	g.add(TRACE, path, h, m...)
+}
+
+// Any implements `Echo#Any()` for sub-routes within the Group.
+func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+	for _, m := range methods {
+		g.add(m, path, handler, middleware...)
+	}
+}
+
+// Match implements `Echo#Match()` for sub-routes within the Group.
+func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+	for _, m := range methods {
+		g.add(m, path, handler, middleware...)
+	}
+}
+
+// Group creates a new sub-group with prefix and optional sub-group-level middleware.
+func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
+	m := []MiddlewareFunc{}
+	m = append(m, g.middleware...)
+	m = append(m, middleware...)
+	return g.echo.Group(g.prefix+prefix, m...)
+}
+
+// Static implements `Echo#Static()` for sub-routes within the Group.
+func (g *Group) Static(prefix, root string) {
+	static(g, prefix, root)
+}
+
+// File implements `Echo#File()` for sub-routes within the Group.
+func (g *Group) File(path, file string) {
+	g.echo.File(g.prefix+path, file)
+}
+
+func (g *Group) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+	// Combine into a new slice to avoid accidentally passing the same slice for
+	// multiple routes, which would lead to later add() calls overwriting the
+	// middleware from earlier calls.
+	m := []MiddlewareFunc{}
+	m = append(m, g.middleware...)
+	m = append(m, middleware...)
+	g.echo.add(method, g.prefix+path, handler, m...)
+}
diff --git a/vendor/github.com/labstack/echo/log.go b/vendor/github.com/labstack/echo/log.go
new file mode 100644
index 00000000..b194c39c
--- /dev/null
+++ b/vendor/github.com/labstack/echo/log.go
@@ -0,0 +1,40 @@
+package echo
+
+import (
+	"io"
+
+	"github.com/labstack/gommon/log"
+)
+
+type (
+	// Logger defines the logging interface.
+	Logger interface {
+		Output() io.Writer
+		SetOutput(w io.Writer)
+		Prefix() string
+		SetPrefix(p string)
+		Level() log.Lvl
+		SetLevel(v log.Lvl)
+		Print(i ...interface{})
+		Printf(format string, args ...interface{})
+		Printj(j log.JSON)
+		Debug(i ...interface{})
+		Debugf(format string, args ...interface{})
+		Debugj(j log.JSON)
+		Info(i ...interface{})
+		Infof(format string, args ...interface{})
+		Infoj(j log.JSON)
+		Warn(i ...interface{})
+		Warnf(format string, args ...interface{})
+		Warnj(j log.JSON)
+		Error(i ...interface{})
+		Errorf(format string, args ...interface{})
+		Errorj(j log.JSON)
+		Fatal(i ...interface{})
+		Fatalj(j log.JSON)
+		Fatalf(format string, args ...interface{})
+		Panic(i ...interface{})
+		Panicj(j log.JSON)
+		Panicf(format string, args ...interface{})
+	}
+)
diff --git a/vendor/github.com/labstack/echo/middleware/basic_auth.go b/vendor/github.com/labstack/echo/middleware/basic_auth.go
new file mode 100644
index 00000000..e98a87e3
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/basic_auth.go
@@ -0,0 +1,86 @@
+package middleware
+
+import (
+	"encoding/base64"
+
+	"github.com/labstack/echo"
+)
+
+type (
+	// BasicAuthConfig defines the config for BasicAuth middleware.
+	BasicAuthConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// Validator is a function to validate BasicAuth credentials.
+		// Required.
+		Validator BasicAuthValidator
+	}
+
+	// BasicAuthValidator defines a function to validate BasicAuth credentials.
+	BasicAuthValidator func(string, string, echo.Context) bool
+)
+
+const (
+	basic = "Basic"
+)
+
+var (
+	// DefaultBasicAuthConfig is the default BasicAuth middleware config.
+	DefaultBasicAuthConfig = BasicAuthConfig{
+		Skipper: DefaultSkipper,
+	}
+)
+
+// BasicAuth returns an BasicAuth middleware.
+//
+// For valid credentials it calls the next handler.
+// For missing or invalid credentials, it sends "401 - Unauthorized" response.
+func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc {
+	c := DefaultBasicAuthConfig
+	c.Validator = fn
+	return BasicAuthWithConfig(c)
+}
+
+// BasicAuthWithConfig returns an BasicAuth middleware with config.
+// See `BasicAuth()`.
+func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Validator == nil {
+		panic("basic-auth middleware requires a validator function")
+	}
+	if config.Skipper == nil {
+		config.Skipper = DefaultBasicAuthConfig.Skipper
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			auth := c.Request().Header.Get(echo.HeaderAuthorization)
+			l := len(basic)
+
+			if len(auth) > l+1 && auth[:l] == basic {
+				b, err := base64.StdEncoding.DecodeString(auth[l+1:])
+				if err != nil {
+					return err
+				}
+				cred := string(b)
+				for i := 0; i < len(cred); i++ {
+					if cred[i] == ':' {
+						// Verify credentials
+						if config.Validator(cred[:i], cred[i+1:], c) {
+							return next(c)
+						}
+					}
+				}
+			}
+
+			// Need to return `401` for browsers to pop-up login box.
+			c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm=Restricted")
+			return echo.ErrUnauthorized
+		}
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/body_limit.go b/vendor/github.com/labstack/echo/middleware/body_limit.go
new file mode 100644
index 00000000..a2ff8d62
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/body_limit.go
@@ -0,0 +1,116 @@
+package middleware
+
+import (
+	"fmt"
+	"io"
+	"sync"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/gommon/bytes"
+)
+
+type (
+	// BodyLimitConfig defines the config for BodyLimit middleware.
+	BodyLimitConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// Maximum allowed size for a request body, it can be specified
+		// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
+		Limit string `json:"limit"`
+		limit int64
+	}
+
+	limitedReader struct {
+		BodyLimitConfig
+		reader  io.ReadCloser
+		read    int64
+		context echo.Context
+	}
+)
+
+var (
+	// DefaultBodyLimitConfig is the default Gzip middleware config.
+	DefaultBodyLimitConfig = BodyLimitConfig{
+		Skipper: DefaultSkipper,
+	}
+)
+
+// BodyLimit returns a BodyLimit middleware.
+//
+// BodyLimit middleware sets the maximum allowed size for a request body, if the
+// size exceeds the configured limit, it sends "413 - Request Entity Too Large"
+// response. The BodyLimit is determined based on both `Content-Length` request
+// header and actual content read, which makes it super secure.
+// Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M,
+// G, T or P.
+func BodyLimit(limit string) echo.MiddlewareFunc {
+	c := DefaultBodyLimitConfig
+	c.Limit = limit
+	return BodyLimitWithConfig(c)
+}
+
+// BodyLimitWithConfig returns a BodyLimit middleware with config.
+// See: `BodyLimit()`.
+func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultBodyLimitConfig.Skipper
+	}
+
+	limit, err := bytes.Parse(config.Limit)
+	if err != nil {
+		panic(fmt.Errorf("invalid body-limit=%s", config.Limit))
+	}
+	config.limit = limit
+	pool := limitedReaderPool(config)
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+
+			// Based on content length
+			if req.ContentLength > config.limit {
+				return echo.ErrStatusRequestEntityTooLarge
+			}
+
+			// Based on content read
+			r := pool.Get().(*limitedReader)
+			r.Reset(req.Body, c)
+			defer pool.Put(r)
+			req.Body = r
+
+			return next(c)
+		}
+	}
+}
+
+func (r *limitedReader) Read(b []byte) (n int, err error) {
+	n, err = r.reader.Read(b)
+	r.read += int64(n)
+	if r.read > r.limit {
+		return n, echo.ErrStatusRequestEntityTooLarge
+	}
+	return
+}
+
+func (r *limitedReader) Close() error {
+	return r.reader.Close()
+}
+
+func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) {
+	r.reader = reader
+	r.context = context
+}
+
+func limitedReaderPool(c BodyLimitConfig) sync.Pool {
+	return sync.Pool{
+		New: func() interface{} {
+			return &limitedReader{BodyLimitConfig: c}
+		},
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/compress.go b/vendor/github.com/labstack/echo/middleware/compress.go
new file mode 100644
index 00000000..eee67b37
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/compress.go
@@ -0,0 +1,121 @@
+package middleware
+
+import (
+	"bufio"
+	"compress/gzip"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"strings"
+
+	"github.com/labstack/echo"
+)
+
+type (
+	// GzipConfig defines the config for Gzip middleware.
+	GzipConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// Gzip compression level.
+		// Optional. Default value -1.
+		Level int `json:"level"`
+	}
+
+	gzipResponseWriter struct {
+		io.Writer
+		http.ResponseWriter
+	}
+)
+
+const (
+	gzipScheme = "gzip"
+)
+
+var (
+	// DefaultGzipConfig is the default Gzip middleware config.
+	DefaultGzipConfig = GzipConfig{
+		Skipper: DefaultSkipper,
+		Level:   -1,
+	}
+)
+
+// Gzip returns a middleware which compresses HTTP response using gzip compression
+// scheme.
+func Gzip() echo.MiddlewareFunc {
+	return GzipWithConfig(DefaultGzipConfig)
+}
+
+// GzipWithConfig return Gzip middleware with config.
+// See: `Gzip()`.
+func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultGzipConfig.Skipper
+	}
+	if config.Level == 0 {
+		config.Level = DefaultGzipConfig.Level
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			res := c.Response()
+			res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
+			if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
+				res.Header().Add(echo.HeaderContentEncoding, gzipScheme) // Issue #806
+				rw := res.Writer
+				w, err := gzip.NewWriterLevel(rw, config.Level)
+				if err != nil {
+					return err
+				}
+				defer func() {
+					if res.Size == 0 {
+						if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
+							res.Header().Del(echo.HeaderContentEncoding)
+						}
+						// We have to reset response to it's pristine state when
+						// nothing is written to body or error is returned.
+						// See issue #424, #407.
+						res.Writer = rw
+						w.Reset(ioutil.Discard)
+					}
+					w.Close()
+				}()
+				grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
+				res.Writer = grw
+			}
+			return next(c)
+		}
+	}
+}
+
+func (w *gzipResponseWriter) WriteHeader(code int) {
+	if code == http.StatusNoContent { // Issue #489
+		w.ResponseWriter.Header().Del(echo.HeaderContentEncoding)
+	}
+	w.ResponseWriter.WriteHeader(code)
+}
+
+func (w *gzipResponseWriter) Write(b []byte) (int, error) {
+	if w.Header().Get(echo.HeaderContentType) == "" {
+		w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
+	}
+	return w.Writer.Write(b)
+}
+
+func (w *gzipResponseWriter) Flush() error {
+	return w.Writer.(*gzip.Writer).Flush()
+}
+
+func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+	return w.ResponseWriter.(http.Hijacker).Hijack()
+}
+
+func (w *gzipResponseWriter) CloseNotify() <-chan bool {
+	return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
+}
diff --git a/vendor/github.com/labstack/echo/middleware/cors.go b/vendor/github.com/labstack/echo/middleware/cors.go
new file mode 100644
index 00000000..c35fc36c
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/cors.go
@@ -0,0 +1,139 @@
+package middleware
+
+import (
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/labstack/echo"
+)
+
+type (
+	// CORSConfig defines the config for CORS middleware.
+	CORSConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// AllowOrigin defines a list of origins that may access the resource.
+		// Optional. Default value []string{"*"}.
+		AllowOrigins []string `json:"allow_origins"`
+
+		// AllowMethods defines a list methods allowed when accessing the resource.
+		// This is used in response to a preflight request.
+		// Optional. Default value DefaultCORSConfig.AllowMethods.
+		AllowMethods []string `json:"allow_methods"`
+
+		// AllowHeaders defines a list of request headers that can be used when
+		// making the actual request. This in response to a preflight request.
+		// Optional. Default value []string{}.
+		AllowHeaders []string `json:"allow_headers"`
+
+		// AllowCredentials indicates whether or not the response to the request
+		// can be exposed when the credentials flag is true. When used as part of
+		// a response to a preflight request, this indicates whether or not the
+		// actual request can be made using credentials.
+		// Optional. Default value false.
+		AllowCredentials bool `json:"allow_credentials"`
+
+		// ExposeHeaders defines a whitelist headers that clients are allowed to
+		// access.
+		// Optional. Default value []string{}.
+		ExposeHeaders []string `json:"expose_headers"`
+
+		// MaxAge indicates how long (in seconds) the results of a preflight request
+		// can be cached.
+		// Optional. Default value 0.
+		MaxAge int `json:"max_age"`
+	}
+)
+
+var (
+	// DefaultCORSConfig is the default CORS middleware config.
+	DefaultCORSConfig = CORSConfig{
+		Skipper:      DefaultSkipper,
+		AllowOrigins: []string{"*"},
+		AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
+	}
+)
+
+// CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
+// See: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
+func CORS() echo.MiddlewareFunc {
+	return CORSWithConfig(DefaultCORSConfig)
+}
+
+// CORSWithConfig returns a CORS middleware with config.
+// See: `CORS()`.
+func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultCORSConfig.Skipper
+	}
+	if len(config.AllowOrigins) == 0 {
+		config.AllowOrigins = DefaultCORSConfig.AllowOrigins
+	}
+	if len(config.AllowMethods) == 0 {
+		config.AllowMethods = DefaultCORSConfig.AllowMethods
+	}
+
+	allowMethods := strings.Join(config.AllowMethods, ",")
+	allowHeaders := strings.Join(config.AllowHeaders, ",")
+	exposeHeaders := strings.Join(config.ExposeHeaders, ",")
+	maxAge := strconv.Itoa(config.MaxAge)
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			res := c.Response()
+			origin := req.Header.Get(echo.HeaderOrigin)
+			allowOrigin := ""
+
+			// Check allowed origins
+			for _, o := range config.AllowOrigins {
+				if o == "*" || o == origin {
+					allowOrigin = o
+					break
+				}
+			}
+
+			// Simple request
+			if req.Method != echo.OPTIONS {
+				res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
+				res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
+				if config.AllowCredentials {
+					res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
+				}
+				if exposeHeaders != "" {
+					res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
+				}
+				return next(c)
+			}
+
+			// Preflight request
+			res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
+			res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
+			res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
+			res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
+			res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
+			if config.AllowCredentials {
+				res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
+			}
+			if allowHeaders != "" {
+				res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
+			} else {
+				h := req.Header.Get(echo.HeaderAccessControlRequestHeaders)
+				if h != "" {
+					res.Header().Set(echo.HeaderAccessControlAllowHeaders, h)
+				}
+			}
+			if config.MaxAge > 0 {
+				res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge)
+			}
+			return c.NoContent(http.StatusNoContent)
+		}
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/csrf.go b/vendor/github.com/labstack/echo/middleware/csrf.go
new file mode 100644
index 00000000..5bbeecb4
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/csrf.go
@@ -0,0 +1,210 @@
+package middleware
+
+import (
+	"crypto/subtle"
+	"errors"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/gommon/random"
+)
+
+type (
+	// CSRFConfig defines the config for CSRF middleware.
+	CSRFConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// TokenLength is the length of the generated token.
+		TokenLength uint8 `json:"token_length"`
+		// Optional. Default value 32.
+
+		// TokenLookup is a string in the form of ":" that is used
+		// to extract token from the request.
+		// Optional. Default value "header:X-CSRF-Token".
+		// Possible values:
+		// - "header:"
+		// - "form:"
+		// - "query:"
+		TokenLookup string `json:"token_lookup"`
+
+		// Context key to store generated CSRF token into context.
+		// Optional. Default value "csrf".
+		ContextKey string `json:"context_key"`
+
+		// Name of the CSRF cookie. This cookie will store CSRF token.
+		// Optional. Default value "csrf".
+		CookieName string `json:"cookie_name"`
+
+		// Domain of the CSRF cookie.
+		// Optional. Default value none.
+		CookieDomain string `json:"cookie_domain"`
+
+		// Path of the CSRF cookie.
+		// Optional. Default value none.
+		CookiePath string `json:"cookie_path"`
+
+		// Max age (in seconds) of the CSRF cookie.
+		// Optional. Default value 86400 (24hr).
+		CookieMaxAge int `json:"cookie_max_age"`
+
+		// Indicates if CSRF cookie is secure.
+		// Optional. Default value false.
+		CookieSecure bool `json:"cookie_secure"`
+
+		// Indicates if CSRF cookie is HTTP only.
+		// Optional. Default value false.
+		CookieHTTPOnly bool `json:"cookie_http_only"`
+	}
+
+	// csrfTokenExtractor defines a function that takes `echo.Context` and returns
+	// either a token or an error.
+	csrfTokenExtractor func(echo.Context) (string, error)
+)
+
+var (
+	// DefaultCSRFConfig is the default CSRF middleware config.
+	DefaultCSRFConfig = CSRFConfig{
+		Skipper:      DefaultSkipper,
+		TokenLength:  32,
+		TokenLookup:  "header:" + echo.HeaderXCSRFToken,
+		ContextKey:   "csrf",
+		CookieName:   "_csrf",
+		CookieMaxAge: 86400,
+	}
+)
+
+// CSRF returns a Cross-Site Request Forgery (CSRF) middleware.
+// See: https://en.wikipedia.org/wiki/Cross-site_request_forgery
+func CSRF() echo.MiddlewareFunc {
+	c := DefaultCSRFConfig
+	return CSRFWithConfig(c)
+}
+
+// CSRFWithConfig returns a CSRF middleware with config.
+// See `CSRF()`.
+func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultCSRFConfig.Skipper
+	}
+	if config.TokenLength == 0 {
+		config.TokenLength = DefaultCSRFConfig.TokenLength
+	}
+	if config.TokenLookup == "" {
+		config.TokenLookup = DefaultCSRFConfig.TokenLookup
+	}
+	if config.ContextKey == "" {
+		config.ContextKey = DefaultCSRFConfig.ContextKey
+	}
+	if config.CookieName == "" {
+		config.CookieName = DefaultCSRFConfig.CookieName
+	}
+	if config.CookieMaxAge == 0 {
+		config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
+	}
+
+	// Initialize
+	parts := strings.Split(config.TokenLookup, ":")
+	extractor := csrfTokenFromHeader(parts[1])
+	switch parts[0] {
+	case "form":
+		extractor = csrfTokenFromForm(parts[1])
+	case "query":
+		extractor = csrfTokenFromQuery(parts[1])
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			k, err := c.Cookie(config.CookieName)
+			token := ""
+
+			if err != nil {
+				// Generate token
+				token = random.String(config.TokenLength)
+			} else {
+				// Reuse token
+				token = k.Value
+			}
+
+			switch req.Method {
+			case echo.GET, echo.HEAD, echo.OPTIONS, echo.TRACE:
+			default:
+				// Validate token only for requests which are not defined as 'safe' by RFC7231
+				clientToken, err := extractor(c)
+				if err != nil {
+					return echo.NewHTTPError(http.StatusBadRequest, err.Error())
+				}
+				if !validateCSRFToken(token, clientToken) {
+					return echo.NewHTTPError(http.StatusForbidden, "Invalid csrf token")
+				}
+			}
+
+			// Set CSRF cookie
+			cookie := new(http.Cookie)
+			cookie.Name = config.CookieName
+			cookie.Value = token
+			if config.CookiePath != "" {
+				cookie.Path = config.CookiePath
+			}
+			if config.CookieDomain != "" {
+				cookie.Domain = config.CookieDomain
+			}
+			cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
+			cookie.Secure = config.CookieSecure
+			cookie.HttpOnly = config.CookieHTTPOnly
+			c.SetCookie(cookie)
+
+			// Store token in the context
+			c.Set(config.ContextKey, token)
+
+			// Protect clients from caching the response
+			c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie)
+
+			return next(c)
+		}
+	}
+}
+
+// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
+// provided request header.
+func csrfTokenFromHeader(header string) csrfTokenExtractor {
+	return func(c echo.Context) (string, error) {
+		return c.Request().Header.Get(header), nil
+	}
+}
+
+// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
+// provided form parameter.
+func csrfTokenFromForm(param string) csrfTokenExtractor {
+	return func(c echo.Context) (string, error) {
+		token := c.FormValue(param)
+		if token == "" {
+			return "", errors.New("Missing csrf token in the form parameter")
+		}
+		return token, nil
+	}
+}
+
+// csrfTokenFromQuery returns a `csrfTokenExtractor` that extracts token from the
+// provided query parameter.
+func csrfTokenFromQuery(param string) csrfTokenExtractor {
+	return func(c echo.Context) (string, error) {
+		token := c.QueryParam(param)
+		if token == "" {
+			return "", errors.New("Missing csrf token in the query string")
+		}
+		return token, nil
+	}
+}
+
+func validateCSRFToken(token, clientToken string) bool {
+	return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
+}
diff --git a/vendor/github.com/labstack/echo/middleware/jwt.go b/vendor/github.com/labstack/echo/middleware/jwt.go
new file mode 100644
index 00000000..b2658739
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/jwt.go
@@ -0,0 +1,189 @@
+package middleware
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"reflect"
+	"strings"
+
+	"github.com/dgrijalva/jwt-go"
+	"github.com/labstack/echo"
+)
+
+type (
+	// JWTConfig defines the config for JWT middleware.
+	JWTConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// Signing key to validate token.
+		// Required.
+		SigningKey interface{}
+
+		// Signing method, used to check token signing method.
+		// Optional. Default value HS256.
+		SigningMethod string
+
+		// Context key to store user information from the token into context.
+		// Optional. Default value "user".
+		ContextKey string
+
+		// Claims are extendable claims data defining token content.
+		// Optional. Default value jwt.MapClaims
+		Claims jwt.Claims
+
+		// TokenLookup is a string in the form of ":" that is used
+		// to extract token from the request.
+		// Optional. Default value "header:Authorization".
+		// Possible values:
+		// - "header:"
+		// - "query:"
+		// - "cookie:"
+		TokenLookup string
+
+		// AuthScheme to be used in the Authorization header.
+		// Optional. Default value "Bearer".
+		AuthScheme string
+
+		keyFunc jwt.Keyfunc
+	}
+
+	jwtExtractor func(echo.Context) (string, error)
+)
+
+// Algorithms
+const (
+	AlgorithmHS256 = "HS256"
+)
+
+var (
+	// DefaultJWTConfig is the default JWT auth middleware config.
+	DefaultJWTConfig = JWTConfig{
+		Skipper:       DefaultSkipper,
+		SigningMethod: AlgorithmHS256,
+		ContextKey:    "user",
+		TokenLookup:   "header:" + echo.HeaderAuthorization,
+		AuthScheme:    "Bearer",
+		Claims:        jwt.MapClaims{},
+	}
+)
+
+// JWT returns a JSON Web Token (JWT) auth middleware.
+//
+// For valid token, it sets the user in context and calls next handler.
+// For invalid token, it returns "401 - Unauthorized" error.
+// For missing token, it returns "400 - Bad Request" error.
+//
+// See: https://jwt.io/introduction
+// See `JWTConfig.TokenLookup`
+func JWT(key []byte) echo.MiddlewareFunc {
+	c := DefaultJWTConfig
+	c.SigningKey = key
+	return JWTWithConfig(c)
+}
+
+// JWTWithConfig returns a JWT auth middleware with config.
+// See: `JWT()`.
+func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultJWTConfig.Skipper
+	}
+	if config.SigningKey == nil {
+		panic("jwt middleware requires signing key")
+	}
+	if config.SigningMethod == "" {
+		config.SigningMethod = DefaultJWTConfig.SigningMethod
+	}
+	if config.ContextKey == "" {
+		config.ContextKey = DefaultJWTConfig.ContextKey
+	}
+	if config.Claims == nil {
+		config.Claims = DefaultJWTConfig.Claims
+	}
+	if config.TokenLookup == "" {
+		config.TokenLookup = DefaultJWTConfig.TokenLookup
+	}
+	if config.AuthScheme == "" {
+		config.AuthScheme = DefaultJWTConfig.AuthScheme
+	}
+	config.keyFunc = func(t *jwt.Token) (interface{}, error) {
+		// Check the signing method
+		if t.Method.Alg() != config.SigningMethod {
+			return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"])
+		}
+		return config.SigningKey, nil
+	}
+
+	// Initialize
+	parts := strings.Split(config.TokenLookup, ":")
+	extractor := jwtFromHeader(parts[1], config.AuthScheme)
+	switch parts[0] {
+	case "query":
+		extractor = jwtFromQuery(parts[1])
+	case "cookie":
+		extractor = jwtFromCookie(parts[1])
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			auth, err := extractor(c)
+			if err != nil {
+				return echo.NewHTTPError(http.StatusBadRequest, err.Error())
+			}
+			token := new(jwt.Token)
+			// Issue #647, #656
+			if _, ok := config.Claims.(jwt.MapClaims); ok {
+				token, err = jwt.Parse(auth, config.keyFunc)
+			} else {
+				claims := reflect.ValueOf(config.Claims).Interface().(jwt.Claims)
+				token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc)
+			}
+			if err == nil && token.Valid {
+				// Store user information from token into context.
+				c.Set(config.ContextKey, token)
+				return next(c)
+			}
+			return echo.ErrUnauthorized
+		}
+	}
+}
+
+// jwtFromHeader returns a `jwtExtractor` that extracts token from the request header.
+func jwtFromHeader(header string, authScheme string) jwtExtractor {
+	return func(c echo.Context) (string, error) {
+		auth := c.Request().Header.Get(header)
+		l := len(authScheme)
+		if len(auth) > l+1 && auth[:l] == authScheme {
+			return auth[l+1:], nil
+		}
+		return "", errors.New("Missing or invalid jwt in the request header")
+	}
+}
+
+// jwtFromQuery returns a `jwtExtractor` that extracts token from the query string.
+func jwtFromQuery(param string) jwtExtractor {
+	return func(c echo.Context) (string, error) {
+		token := c.QueryParam(param)
+		if token == "" {
+			return "", errors.New("Missing jwt in the query string")
+		}
+		return token, nil
+	}
+}
+
+// jwtFromCookie returns a `jwtExtractor` that extracts token from the named cookie.
+func jwtFromCookie(name string) jwtExtractor {
+	return func(c echo.Context) (string, error) {
+		cookie, err := c.Cookie(name)
+		if err != nil {
+			return "", errors.New("Missing jwt in the cookie")
+		}
+		return cookie.Value, nil
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/key_auth.go b/vendor/github.com/labstack/echo/middleware/key_auth.go
new file mode 100644
index 00000000..4d4cb940
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/key_auth.go
@@ -0,0 +1,133 @@
+package middleware
+
+import (
+	"errors"
+	"net/http"
+	"strings"
+
+	"github.com/labstack/echo"
+)
+
+type (
+	// KeyAuthConfig defines the config for KeyAuth middleware.
+	KeyAuthConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// KeyLookup is a string in the form of ":" that is used
+		// to extract key from the request.
+		// Optional. Default value "header:Authorization".
+		// Possible values:
+		// - "header:"
+		// - "query:"
+		KeyLookup string `json:"key_lookup"`
+
+		// AuthScheme to be used in the Authorization header.
+		// Optional. Default value "Bearer".
+		AuthScheme string
+
+		// Validator is a function to validate key.
+		// Required.
+		Validator KeyAuthValidator
+	}
+
+	// KeyAuthValidator defines a function to validate KeyAuth credentials.
+	KeyAuthValidator func(string, echo.Context) bool
+
+	keyExtractor func(echo.Context) (string, error)
+)
+
+var (
+	// DefaultKeyAuthConfig is the default KeyAuth middleware config.
+	DefaultKeyAuthConfig = KeyAuthConfig{
+		Skipper:    DefaultSkipper,
+		KeyLookup:  "header:" + echo.HeaderAuthorization,
+		AuthScheme: "Bearer",
+	}
+)
+
+// KeyAuth returns an KeyAuth middleware.
+//
+// For valid key it calls the next handler.
+// For invalid key, it sends "401 - Unauthorized" response.
+// For missing key, it sends "400 - Bad Request" response.
+func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc {
+	c := DefaultKeyAuthConfig
+	c.Validator = fn
+	return KeyAuthWithConfig(c)
+}
+
+// KeyAuthWithConfig returns an KeyAuth middleware with config.
+// See `KeyAuth()`.
+func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultKeyAuthConfig.Skipper
+	}
+	// Defaults
+	if config.AuthScheme == "" {
+		config.AuthScheme = DefaultKeyAuthConfig.AuthScheme
+	}
+	if config.KeyLookup == "" {
+		config.KeyLookup = DefaultKeyAuthConfig.KeyLookup
+	}
+	if config.Validator == nil {
+		panic("key-auth middleware requires a validator function")
+	}
+
+	// Initialize
+	parts := strings.Split(config.KeyLookup, ":")
+	extractor := keyFromHeader(parts[1], config.AuthScheme)
+	switch parts[0] {
+	case "query":
+		extractor = keyFromQuery(parts[1])
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			// Extract and verify key
+			key, err := extractor(c)
+			if err != nil {
+				return echo.NewHTTPError(http.StatusBadRequest, err.Error())
+			}
+			if config.Validator(key, c) {
+				return next(c)
+			}
+
+			return echo.ErrUnauthorized
+		}
+	}
+}
+
+// keyFromHeader returns a `keyExtractor` that extracts key from the request header.
+func keyFromHeader(header string, authScheme string) keyExtractor {
+	return func(c echo.Context) (string, error) {
+		auth := c.Request().Header.Get(header)
+		if auth == "" {
+			return "", errors.New("Missing key in request header")
+		}
+		if header == echo.HeaderAuthorization {
+			l := len(authScheme)
+			if len(auth) > l+1 && auth[:l] == authScheme {
+				return auth[l+1:], nil
+			}
+			return "", errors.New("Invalid key in the request header")
+		}
+		return auth, nil
+	}
+}
+
+// keyFromQuery returns a `keyExtractor` that extracts key from the query string.
+func keyFromQuery(param string) keyExtractor {
+	return func(c echo.Context) (string, error) {
+		key := c.QueryParam(param)
+		if key == "" {
+			return "", errors.New("Missing key in the query string")
+		}
+		return key, nil
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/logger.go b/vendor/github.com/labstack/echo/middleware/logger.go
new file mode 100644
index 00000000..e26b5496
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/logger.go
@@ -0,0 +1,191 @@
+package middleware
+
+import (
+	"bytes"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/gommon/color"
+	"github.com/valyala/fasttemplate"
+)
+
+type (
+	// LoggerConfig defines the config for Logger middleware.
+	LoggerConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// Tags to constructed the logger format.
+		//
+		// - time_unix
+		// - time_unix_nano
+		// - time_rfc3339
+		// - time_rfc3339_nano
+		// - id (Request ID - Not implemented)
+		// - remote_ip
+		// - uri
+		// - host
+		// - method
+		// - path
+		// - referer
+		// - user_agent
+		// - status
+		// - latency (In nanoseconds)
+		// - latency_human (Human readable)
+		// - bytes_in (Bytes received)
+		// - bytes_out (Bytes sent)
+		// - header:
+		// - query:
+		// - form:
+		//
+		// Example "${remote_ip} ${status}"
+		//
+		// Optional. Default value DefaultLoggerConfig.Format.
+		Format string `json:"format"`
+
+		// Output is a writer where logs in JSON format are written.
+		// Optional. Default value os.Stdout.
+		Output io.Writer
+
+		template *fasttemplate.Template
+		colorer  *color.Color
+		pool     *sync.Pool
+	}
+)
+
+var (
+	// DefaultLoggerConfig is the default Logger middleware config.
+	DefaultLoggerConfig = LoggerConfig{
+		Skipper: DefaultSkipper,
+		Format: `{"time":"${time_rfc3339_nano}","remote_ip":"${remote_ip}","host":"${host}",` +
+			`"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
+			`"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
+			`"bytes_out":${bytes_out}}` + "\n",
+		Output:  os.Stdout,
+		colorer: color.New(),
+	}
+)
+
+// Logger returns a middleware that logs HTTP requests.
+func Logger() echo.MiddlewareFunc {
+	return LoggerWithConfig(DefaultLoggerConfig)
+}
+
+// LoggerWithConfig returns a Logger middleware with config.
+// See: `Logger()`.
+func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultLoggerConfig.Skipper
+	}
+	if config.Format == "" {
+		config.Format = DefaultLoggerConfig.Format
+	}
+	if config.Output == nil {
+		config.Output = DefaultLoggerConfig.Output
+	}
+
+	config.template = fasttemplate.New(config.Format, "${", "}")
+	config.colorer = color.New()
+	config.colorer.SetOutput(config.Output)
+	config.pool = &sync.Pool{
+		New: func() interface{} {
+			return bytes.NewBuffer(make([]byte, 256))
+		},
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) (err error) {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			res := c.Response()
+			start := time.Now()
+			if err = next(c); err != nil {
+				c.Error(err)
+			}
+			stop := time.Now()
+			buf := config.pool.Get().(*bytes.Buffer)
+			buf.Reset()
+			defer config.pool.Put(buf)
+
+			if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
+				switch tag {
+				case "time_unix":
+					return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
+				case "time_unix_nano":
+					return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
+				case "time_rfc3339":
+					return buf.WriteString(time.Now().Format(time.RFC3339))
+				case "time_rfc3339_nano":
+					return buf.WriteString(time.Now().Format(time.RFC3339Nano))
+				case "remote_ip":
+					return buf.WriteString(c.RealIP())
+				case "host":
+					return buf.WriteString(req.Host)
+				case "uri":
+					return buf.WriteString(req.RequestURI)
+				case "method":
+					return buf.WriteString(req.Method)
+				case "path":
+					p := req.URL.Path
+					if p == "" {
+						p = "/"
+					}
+					return buf.WriteString(p)
+				case "referer":
+					return buf.WriteString(req.Referer())
+				case "user_agent":
+					return buf.WriteString(req.UserAgent())
+				case "status":
+					n := res.Status
+					s := config.colorer.Green(n)
+					switch {
+					case n >= 500:
+						s = config.colorer.Red(n)
+					case n >= 400:
+						s = config.colorer.Yellow(n)
+					case n >= 300:
+						s = config.colorer.Cyan(n)
+					}
+					return buf.WriteString(s)
+				case "latency":
+					l := stop.Sub(start)
+					return buf.WriteString(strconv.FormatInt(int64(l), 10))
+				case "latency_human":
+					return buf.WriteString(stop.Sub(start).String())
+				case "bytes_in":
+					cl := req.Header.Get(echo.HeaderContentLength)
+					if cl == "" {
+						cl = "0"
+					}
+					return buf.WriteString(cl)
+				case "bytes_out":
+					return buf.WriteString(strconv.FormatInt(res.Size, 10))
+				default:
+					switch {
+					case strings.HasPrefix(tag, "header:"):
+						return buf.Write([]byte(c.Request().Header.Get(tag[7:])))
+					case strings.HasPrefix(tag, "query:"):
+						return buf.Write([]byte(c.QueryParam(tag[6:])))
+					case strings.HasPrefix(tag, "form:"):
+						return buf.Write([]byte(c.FormValue(tag[5:])))
+					}
+				}
+				return 0, nil
+			}); err != nil {
+				return
+			}
+
+			_, err = config.Output.Write(buf.Bytes())
+			return
+		}
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/method_override.go b/vendor/github.com/labstack/echo/middleware/method_override.go
new file mode 100644
index 00000000..955fd11e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/method_override.go
@@ -0,0 +1,88 @@
+package middleware
+
+import "github.com/labstack/echo"
+
+type (
+	// MethodOverrideConfig defines the config for MethodOverride middleware.
+	MethodOverrideConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// Getter is a function that gets overridden method from the request.
+		// Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
+		Getter MethodOverrideGetter
+	}
+
+	// MethodOverrideGetter is a function that gets overridden method from the request
+	MethodOverrideGetter func(echo.Context) string
+)
+
+var (
+	// DefaultMethodOverrideConfig is the default MethodOverride middleware config.
+	DefaultMethodOverrideConfig = MethodOverrideConfig{
+		Skipper: DefaultSkipper,
+		Getter:  MethodFromHeader(echo.HeaderXHTTPMethodOverride),
+	}
+)
+
+// MethodOverride returns a MethodOverride middleware.
+// MethodOverride  middleware checks for the overridden method from the request and
+// uses it instead of the original method.
+//
+// For security reasons, only `POST` method can be overridden.
+func MethodOverride() echo.MiddlewareFunc {
+	return MethodOverrideWithConfig(DefaultMethodOverrideConfig)
+}
+
+// MethodOverrideWithConfig returns a MethodOverride middleware with config.
+// See: `MethodOverride()`.
+func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultMethodOverrideConfig.Skipper
+	}
+	if config.Getter == nil {
+		config.Getter = DefaultMethodOverrideConfig.Getter
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			if req.Method == echo.POST {
+				m := config.Getter(c)
+				if m != "" {
+					req.Method = m
+				}
+			}
+			return next(c)
+		}
+	}
+}
+
+// MethodFromHeader is a `MethodOverrideGetter` that gets overridden method from
+// the request header.
+func MethodFromHeader(header string) MethodOverrideGetter {
+	return func(c echo.Context) string {
+		return c.Request().Header.Get(header)
+	}
+}
+
+// MethodFromForm is a `MethodOverrideGetter` that gets overridden method from the
+// form parameter.
+func MethodFromForm(param string) MethodOverrideGetter {
+	return func(c echo.Context) string {
+		return c.FormValue(param)
+	}
+}
+
+// MethodFromQuery is a `MethodOverrideGetter` that gets overridden method from
+// the query parameter.
+func MethodFromQuery(param string) MethodOverrideGetter {
+	return func(c echo.Context) string {
+		return c.QueryParam(param)
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/middleware.go b/vendor/github.com/labstack/echo/middleware/middleware.go
new file mode 100644
index 00000000..7edccc1d
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/middleware.go
@@ -0,0 +1,14 @@
+package middleware
+
+import "github.com/labstack/echo"
+
+type (
+	// Skipper defines a function to skip middleware. Returning true skips processing
+	// the middleware.
+	Skipper func(c echo.Context) bool
+)
+
+// DefaultSkipper returns false which processes the middleware.
+func DefaultSkipper(c echo.Context) bool {
+	return false
+}
diff --git a/vendor/github.com/labstack/echo/middleware/recover.go b/vendor/github.com/labstack/echo/middleware/recover.go
new file mode 100644
index 00000000..96fa62c9
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/recover.go
@@ -0,0 +1,85 @@
+package middleware
+
+import (
+	"fmt"
+	"runtime"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/gommon/color"
+)
+
+type (
+	// RecoverConfig defines the config for Recover middleware.
+	RecoverConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// Size of the stack to be printed.
+		// Optional. Default value 4KB.
+		StackSize int `json:"stack_size"`
+
+		// DisableStackAll disables formatting stack traces of all other goroutines
+		// into buffer after the trace for the current goroutine.
+		// Optional. Default value false.
+		DisableStackAll bool `json:"disable_stack_all"`
+
+		// DisablePrintStack disables printing stack trace.
+		// Optional. Default value as false.
+		DisablePrintStack bool `json:"disable_print_stack"`
+	}
+)
+
+var (
+	// DefaultRecoverConfig is the default Recover middleware config.
+	DefaultRecoverConfig = RecoverConfig{
+		Skipper:           DefaultSkipper,
+		StackSize:         4 << 10, // 4 KB
+		DisableStackAll:   false,
+		DisablePrintStack: false,
+	}
+)
+
+// Recover returns a middleware which recovers from panics anywhere in the chain
+// and handles the control to the centralized HTTPErrorHandler.
+func Recover() echo.MiddlewareFunc {
+	return RecoverWithConfig(DefaultRecoverConfig)
+}
+
+// RecoverWithConfig returns a Recover middleware with config.
+// See: `Recover()`.
+func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultRecoverConfig.Skipper
+	}
+	if config.StackSize == 0 {
+		config.StackSize = DefaultRecoverConfig.StackSize
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			defer func() {
+				if r := recover(); r != nil {
+					var err error
+					switch r := r.(type) {
+					case error:
+						err = r
+					default:
+						err = fmt.Errorf("%v", r)
+					}
+					stack := make([]byte, config.StackSize)
+					length := runtime.Stack(stack, !config.DisableStackAll)
+					if !config.DisablePrintStack {
+						c.Logger().Printf("[%s] %s %s\n", color.Red("PANIC RECOVER"), err, stack[:length])
+					}
+					c.Error(err)
+				}
+			}()
+			return next(c)
+		}
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/redirect.go b/vendor/github.com/labstack/echo/middleware/redirect.go
new file mode 100644
index 00000000..b87dab09
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/redirect.go
@@ -0,0 +1,215 @@
+package middleware
+
+import (
+	"net/http"
+
+	"github.com/labstack/echo"
+)
+
+type (
+	// RedirectConfig defines the config for Redirect middleware.
+	RedirectConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// Status code to be used when redirecting the request.
+		// Optional. Default value http.StatusMovedPermanently.
+		Code int `json:"code"`
+	}
+)
+
+const (
+	www = "www"
+)
+
+var (
+	// DefaultRedirectConfig is the default Redirect middleware config.
+	DefaultRedirectConfig = RedirectConfig{
+		Skipper: DefaultSkipper,
+		Code:    http.StatusMovedPermanently,
+	}
+)
+
+// HTTPSRedirect redirects http requests to https.
+// For example, http://labstack.com will be redirect to https://labstack.com.
+//
+// Usage `Echo#Pre(HTTPSRedirect())`
+func HTTPSRedirect() echo.MiddlewareFunc {
+	return HTTPSRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `HTTPSRedirect()`.
+func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultTrailingSlashConfig.Skipper
+	}
+	if config.Code == 0 {
+		config.Code = DefaultRedirectConfig.Code
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			host := req.Host
+			uri := req.RequestURI
+			if !c.IsTLS() {
+				return c.Redirect(config.Code, "https://"+host+uri)
+			}
+			return next(c)
+		}
+	}
+}
+
+// HTTPSWWWRedirect redirects http requests to https www.
+// For example, http://labstack.com will be redirect to https://www.labstack.com.
+//
+// Usage `Echo#Pre(HTTPSWWWRedirect())`
+func HTTPSWWWRedirect() echo.MiddlewareFunc {
+	return HTTPSWWWRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `HTTPSWWWRedirect()`.
+func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultTrailingSlashConfig.Skipper
+	}
+	if config.Code == 0 {
+		config.Code = DefaultRedirectConfig.Code
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			host := req.Host
+			uri := req.RequestURI
+			if !c.IsTLS() && host[:3] != www {
+				return c.Redirect(config.Code, "https://www."+host+uri)
+			}
+			return next(c)
+		}
+	}
+}
+
+// HTTPSNonWWWRedirect redirects http requests to https non www.
+// For example, http://www.labstack.com will be redirect to https://labstack.com.
+//
+// Usage `Echo#Pre(HTTPSNonWWWRedirect())`
+func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
+	return HTTPSNonWWWRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `HTTPSNonWWWRedirect()`.
+func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultTrailingSlashConfig.Skipper
+	}
+	if config.Code == 0 {
+		config.Code = DefaultRedirectConfig.Code
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			host := req.Host
+			uri := req.RequestURI
+			if !c.IsTLS() {
+				if host[:3] == www {
+					return c.Redirect(config.Code, "https://"+host[4:]+uri)
+				}
+				return c.Redirect(config.Code, "https://"+host+uri)
+			}
+			return next(c)
+		}
+	}
+}
+
+// WWWRedirect redirects non www requests to www.
+// For example, http://labstack.com will be redirect to http://www.labstack.com.
+//
+// Usage `Echo#Pre(WWWRedirect())`
+func WWWRedirect() echo.MiddlewareFunc {
+	return WWWRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `WWWRedirect()`.
+func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+	// Defaults
+	if config.Skipper == nil {
+		config.Skipper = DefaultTrailingSlashConfig.Skipper
+	}
+	if config.Code == 0 {
+		config.Code = DefaultRedirectConfig.Code
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			scheme := c.Scheme()
+			host := req.Host
+			if host[:3] != www {
+				uri := req.RequestURI
+				return c.Redirect(config.Code, scheme+"://www."+host+uri)
+			}
+			return next(c)
+		}
+	}
+}
+
+// NonWWWRedirect redirects www requests to non www.
+// For example, http://www.labstack.com will be redirect to http://labstack.com.
+//
+// Usage `Echo#Pre(NonWWWRedirect())`
+func NonWWWRedirect() echo.MiddlewareFunc {
+	return NonWWWRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `NonWWWRedirect()`.
+func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+	if config.Skipper == nil {
+		config.Skipper = DefaultTrailingSlashConfig.Skipper
+	}
+	if config.Code == 0 {
+		config.Code = DefaultRedirectConfig.Code
+	}
+
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			if config.Skipper(c) {
+				return next(c)
+			}
+
+			req := c.Request()
+			scheme := c.Scheme()
+			host := req.Host
+			if host[:3] == www {
+				uri := req.RequestURI
+				return c.Redirect(config.Code, scheme+"://"+host[4:]+uri)
+			}
+			return next(c)
+		}
+	}
+}
diff --git a/vendor/github.com/labstack/echo/middleware/secure.go b/vendor/github.com/labstack/echo/middleware/secure.go
new file mode 100644
index 00000000..0125e74a
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/secure.go
@@ -0,0 +1,116 @@
+package middleware
+
+import (
+	"fmt"
+
+	"github.com/labstack/echo"
+)
+
+type (
+	// SecureConfig defines the config for Secure middleware.
+	SecureConfig struct {
+		// Skipper defines a function to skip middleware.
+		Skipper Skipper
+
+		// XSSProtection provides protection against cross-site scripting attack (XSS)
+		// by setting the `X-XSS-Protection` header.
+		// Optional. Default value "1; mode=block".
+		XSSProtection string `json:"xss_protection"`
+
+		// ContentTypeNosniff provides protection against overriding Content-Type
+		// header by setting the `X-Content-Type-Options` header.
+		// Optional. Default value "nosniff".
+		ContentTypeNosniff string `json:"content_type_nosniff"`
+
+		// XFrameOptions can be used to indicate whether or not a browser should
+		// be allowed to render a page in a ,