summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/labstack/echo/v4
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/labstack/echo/v4')
-rw-r--r--vendor/github.com/labstack/echo/v4/.editorconfig25
-rw-r--r--vendor/github.com/labstack/echo/v4/.gitattributes20
-rw-r--r--vendor/github.com/labstack/echo/v4/.gitignore7
-rw-r--r--vendor/github.com/labstack/echo/v4/.travis.yml16
-rw-r--r--vendor/github.com/labstack/echo/v4/LICENSE21
-rw-r--r--vendor/github.com/labstack/echo/v4/Makefile3
-rw-r--r--vendor/github.com/labstack/echo/v4/README.md114
-rw-r--r--vendor/github.com/labstack/echo/v4/bind.go273
-rw-r--r--vendor/github.com/labstack/echo/v4/context.go600
-rw-r--r--vendor/github.com/labstack/echo/v4/echo.go782
-rw-r--r--vendor/github.com/labstack/echo/v4/go.mod13
-rw-r--r--vendor/github.com/labstack/echo/v4/go.sum23
-rw-r--r--vendor/github.com/labstack/echo/v4/group.go121
-rw-r--r--vendor/github.com/labstack/echo/v4/log.go41
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/basic_auth.go106
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/body_dump.go107
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/body_limit.go117
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/compress.go118
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/cors.go143
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/csrf.go210
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/jwt.go227
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/key_auth.go150
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/logger.go217
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/method_override.go92
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/middleware.go38
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/proxy.go258
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go25
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/proxy_1_11_n.go14
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/recover.go81
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/redirect.go153
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/request_id.go64
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/rewrite.go84
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/secure.go116
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/slash.go119
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/static.go229
-rw-r--r--vendor/github.com/labstack/echo/v4/response.go101
-rw-r--r--vendor/github.com/labstack/echo/v4/router.go435
37 files changed, 5263 insertions, 0 deletions
diff --git a/vendor/github.com/labstack/echo/v4/.editorconfig b/vendor/github.com/labstack/echo/v4/.editorconfig
new file mode 100644
index 00000000..17ae50dd
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/.editorconfig
@@ -0,0 +1,25 @@
+# EditorConfig coding styles definitions. For more information about the
+# properties used in this file, please see the EditorConfig documentation:
+# http://editorconfig.org/
+
+# indicate this is the root of the project
+root = true
+
+[*]
+charset = utf-8
+
+end_of_line = LF
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+indent_style = space
+indent_size = 2
+
+[Makefile]
+indent_style = tab
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.go]
+indent_style = tab
diff --git a/vendor/github.com/labstack/echo/v4/.gitattributes b/vendor/github.com/labstack/echo/v4/.gitattributes
new file mode 100644
index 00000000..49b63e52
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/.gitattributes
@@ -0,0 +1,20 @@
+# Automatically normalize line endings for all text-based files
+# http://git-scm.com/docs/gitattributes#_end_of_line_conversion
+* text=auto
+
+# For the following file types, normalize line endings to LF on checking and
+# prevent conversion to CRLF when they are checked out (this is required in
+# order to prevent newline related issues)
+.* text eol=lf
+*.go text eol=lf
+*.yml text eol=lf
+*.html text eol=lf
+*.css text eol=lf
+*.js text eol=lf
+*.json text eol=lf
+LICENSE text eol=lf
+
+# Exclude `website` and `cookbook` from GitHub's language statistics
+# https://github.com/github/linguist#using-gitattributes
+cookbook/* linguist-documentation
+website/* linguist-documentation
diff --git a/vendor/github.com/labstack/echo/v4/.gitignore b/vendor/github.com/labstack/echo/v4/.gitignore
new file mode 100644
index 00000000..dd74acca
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+coverage.txt
+_test
+vendor
+.idea
+*.iml
+*.out
diff --git a/vendor/github.com/labstack/echo/v4/.travis.yml b/vendor/github.com/labstack/echo/v4/.travis.yml
new file mode 100644
index 00000000..30346d7f
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/.travis.yml
@@ -0,0 +1,16 @@
+language: go
+go:
+ - 1.11.x
+ - tip
+env:
+ - GO111MODULE=on
+install:
+ - go get -v golang.org/x/lint/golint
+script:
+ - golint -set_exit_status ./...
+ - go test -race -coverprofile=coverage.txt -covermode=atomic ./...
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
+matrix:
+ allow_failures:
+ - go: tip
diff --git a/vendor/github.com/labstack/echo/v4/LICENSE b/vendor/github.com/labstack/echo/v4/LICENSE
new file mode 100644
index 00000000..b5b006b4
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/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/v4/Makefile b/vendor/github.com/labstack/echo/v4/Makefile
new file mode 100644
index 00000000..dfcb6c02
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/Makefile
@@ -0,0 +1,3 @@
+tag:
+ @git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
+ @git tag|grep -v ^v
diff --git a/vendor/github.com/labstack/echo/v4/README.md b/vendor/github.com/labstack/echo/v4/README.md
new file mode 100644
index 00000000..1db5e356
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/README.md
@@ -0,0 +1,114 @@
+<a href="https://echo.labstack.com"><img height="80" src="https://cdn.labstack.com/images/echo-logo.svg"></a>
+
+[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge)
+[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo)
+[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
+[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
+[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
+[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo)
+[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://forum.labstack.com)
+[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
+[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
+
+## Supported Go versions
+
+As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
+Therefore a Go version capable of understanding /vN suffixed imports is required:
+
+- 1.9.7+
+- 1.10.3+
+- 1.11+
+
+Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
+way of using Echo going forward.
+
+For older versions, please use the latest v3 tag.
+
+## Feature Overview
+
+- Optimized HTTP router which smartly prioritize routes
+- Build robust and scalable RESTful APIs
+- Group APIs
+- Extensible middleware framework
+- Define middleware at root, group or route level
+- Data binding for JSON, XML and form payload
+- Handy functions to send variety of HTTP responses
+- Centralized HTTP error handling
+- Template rendering with any template engine
+- Define your format for the logger
+- Highly customizable
+- Automatic TLS via Let’s Encrypt
+- HTTP/2 support
+
+## Benchmarks
+
+Date: 2018/03/15<br>
+Source: https://github.com/vishr/web-framework-benchmark<br>
+Lower is better!
+
+<img src="https://i.imgur.com/I32VdMJ.png">
+
+## [Guide](https://echo.labstack.com/guide)
+
+### Example
+
+```go
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+ "github.com/labstack/echo/v4/middleware"
+)
+
+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"))
+}
+
+// Handler
+func hello(c echo.Context) error {
+ return c.String(http.StatusOK, "Hello, World!")
+}
+```
+
+## Help
+
+- [Forum](https://forum.labstack.com)
+- [Chat](https://gitter.im/labstack/echo)
+
+## Contribute
+
+**Use issues for everything**
+
+- For a small change, just send a PR.
+- For bigger changes open an issue for discussion before sending a PR.
+- PR should have:
+ - Test case
+ - Documentation
+ - Example (If it makes sense)
+- You can also contribute by:
+ - Reporting issues
+ - Suggesting new features or enhancements
+ - Improve/fix documentation
+
+## Credits
+
+- [Vishal Rana](https://github.com/vishr) - Author
+- [Nitin Rana](https://github.com/nr17) - Consultant
+- [Contributors](https://github.com/labstack/echo/graphs/contributors)
+
+## License
+
+[MIT](https://github.com/labstack/echo/blob/master/LICENSE)
diff --git a/vendor/github.com/labstack/echo/v4/bind.go b/vendor/github.com/labstack/echo/v4/bind.go
new file mode 100644
index 00000000..cb65ed2d
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/bind.go
@@ -0,0 +1,273 @@
+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.ContentLength == 0 {
+ if req.Method == http.MethodGet || req.Method == http.MethodDelete {
+ if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
+ return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
+ }
+ return
+ }
+ return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
+ }
+ ctype := req.Header.Get(HeaderContentType)
+ 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, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
+ } else if se, ok := err.(*json.SyntaxError); ok {
+ return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
+ }
+ return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
+ }
+ case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
+ 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())).SetInternal(err)
+ } else if se, ok := err.(*xml.SyntaxError); ok {
+ return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
+ }
+ return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
+ }
+ case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
+ params, err := c.FormParams()
+ if err != nil {
+ return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
+ }
+ if err = b.bindData(i, params, "form"); err != nil {
+ return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
+ }
+ 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 {
+ if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
+ return err
+ }
+ continue
+ }
+ }
+
+ inputValue, exists := data[inputFieldName]
+ if !exists {
+ // Go json.Unmarshal supports case insensitive binding. However the
+ // url params are bound case sensitive which is inconsistent. To
+ // fix this we must check all of the map values in a
+ // case-insensitive search.
+ inputFieldName = strings.ToLower(inputFieldName)
+ for k, v := range data {
+ if strings.ToLower(k) == inputFieldName {
+ inputValue = v
+ exists = true
+ break
+ }
+ }
+ }
+
+ 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.Ptr:
+ return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
+ 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/v4/context.go b/vendor/github.com/labstack/echo/v4/context.go
new file mode 100644
index 00000000..d4722700
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/context.go
@@ -0,0 +1,600 @@
+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
+
+ // IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
+ IsWebSocket() 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"
+ defaultIndent = " "
+)
+
+func (c *context) writeContentType(value string) {
+ header := c.Response().Header()
+ if header.Get(HeaderContentType) == "" {
+ header.Set(HeaderContentType, value)
+ }
+}
+
+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) IsWebSocket() bool {
+ upgrade := c.request.Header.Get(HeaderUpgrade)
+ return upgrade == "websocket" || upgrade == "Websocket"
+}
+
+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"
+ }
+ if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
+ return scheme
+ }
+ if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
+ return scheme
+ }
+ if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
+ return "https"
+ }
+ if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
+ return scheme
+ }
+ return "http"
+}
+
+func (c *context) RealIP() string {
+ if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
+ return strings.Split(ip, ", ")[0]
+ }
+ if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
+ return ip
+ }
+ ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
+ 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]
+ }
+ }
+ }
+ 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[:len(c.pnames)]
+}
+
+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) jsonPBlob(code int, callback string, i interface{}) (err error) {
+ enc := json.NewEncoder(c.response)
+ _, pretty := c.QueryParams()["pretty"]
+ if c.echo.Debug || pretty {
+ enc.SetIndent("", " ")
+ }
+ c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
+ c.response.WriteHeader(code)
+ if _, err = c.response.Write([]byte(callback + "(")); err != nil {
+ return
+ }
+ if err = enc.Encode(i); err != nil {
+ return
+ }
+ if _, err = c.response.Write([]byte(");")); err != nil {
+ return
+ }
+ return
+}
+
+func (c *context) json(code int, i interface{}, indent string) error {
+ enc := json.NewEncoder(c.response)
+ if indent != "" {
+ enc.SetIndent("", indent)
+ }
+ c.writeContentType(MIMEApplicationJSONCharsetUTF8)
+ c.response.WriteHeader(code)
+ return enc.Encode(i)
+}
+
+func (c *context) JSON(code int, i interface{}) (err error) {
+ indent := ""
+ if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
+ indent = defaultIndent
+ }
+ return c.json(code, i, indent)
+}
+
+func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
+ return c.json(code, i, indent)
+}
+
+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) {
+ return c.jsonPBlob(code, callback, i)
+}
+
+func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
+ c.writeContentType(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{}, indent string) (err error) {
+ c.writeContentType(MIMEApplicationXMLCharsetUTF8)
+ c.response.WriteHeader(code)
+ enc := xml.NewEncoder(c.response)
+ if indent != "" {
+ enc.Indent("", indent)
+ }
+ if _, err = c.response.Write([]byte(xml.Header)); err != nil {
+ return
+ }
+ return enc.Encode(i)
+}
+
+func (c *context) XML(code int, i interface{}) (err error) {
+ indent := ""
+ if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
+ indent = defaultIndent
+ }
+ return c.xml(code, i, indent)
+}
+
+func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
+ return c.xml(code, i, indent)
+}
+
+func (c *context) XMLBlob(code int, b []byte) (err error) {
+ c.writeContentType(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.writeContentType(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.writeContentType(contentType)
+ c.response.WriteHeader(code)
+ _, err = io.Copy(c.response, r)
+ return
+}
+
+func (c *context) File(file string) (err error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return NotFoundHandler(c)
+ }
+ defer f.Close()
+
+ fi, _ := f.Stat()
+ if fi.IsDir() {
+ file = filepath.Join(file, indexPage)
+ f, err = os.Open(file)
+ if err != nil {
+ return NotFoundHandler(c)
+ }
+ defer f.Close()
+ if fi, err = f.Stat(); err != nil {
+ return
+ }
+ }
+ http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
+ return
+}
+
+func (c *context) Attachment(file, name string) error {
+ return c.contentDisposition(file, name, "attachment")
+}
+
+func (c *context) Inline(file, name string) error {
+ return c.contentDisposition(file, name, "inline")
+}
+
+func (c *context) contentDisposition(file, name, dispositionType string) error {
+ c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
+ return c.File(file)
+}
+
+func (c *context) NoContent(code int) error {
+ c.response.WriteHeader(code)
+ return nil
+}
+
+func (c *context) Redirect(code int, url string) error {
+ if code < 300 || code > 308 {
+ 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
+ c.path = ""
+ c.pnames = nil
+ // NOTE: Don't reset because it has to have length c.echo.maxParam at all times
+ // c.pvalues = nil
+}
+
diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go
new file mode 100644
index 00000000..0fe6880f
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/echo.go
@@ -0,0 +1,782 @@
+/*
+Package echo implements high performance, minimalist Go web framework.
+
+Example:
+
+ package main
+
+ import (
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+ "github.com/labstack/echo/v4/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"
+ stdContext "context"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "io"
+ stdLog "log"
+ "net"
+ "net/http"
+ "net/url"
+ "path"
+ "path/filepath"
+ "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 *stdLog.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
+ AutoTLSManager autocert.Manager
+ DisableHTTP2 bool
+ Debug bool
+ HideBanner bool
+ HidePort bool
+ HTTPErrorHandler HTTPErrorHandler
+ Binder Binder
+ Validator Validator
+ Renderer Renderer
+ Logger Logger
+ }
+
+ // Route contains a handler and information for matching against requests.
+ Route struct {
+ Method string `json:"method"`
+ Path string `json:"path"`
+ Name string `json:"name"`
+ }
+
+ // HTTPError represents an error that occurred while handling a request.
+ HTTPError struct {
+ Code int
+ Message interface{}
+ Internal error // Stores the error returned by an external dependency
+ }
+
+ // MiddlewareFunc defines a function to process middleware.
+ MiddlewareFunc func(HandlerFunc) HandlerFunc
+
+ // HandlerFunc defines a function to serve 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) *Route
+ }
+)
+
+// HTTP methods
+// NOTE: Deprecated, please use the stdlib constants directly instead.
+const (
+ CONNECT = http.MethodConnect
+ DELETE = http.MethodDelete
+ GET = http.MethodGet
+ HEAD = http.MethodHead
+ OPTIONS = http.MethodOptions
+ PATCH = http.MethodPatch
+ POST = http.MethodPost
+ // PROPFIND = "PROPFIND"
+ PUT = http.MethodPut
+ TRACE = http.MethodTrace
+)
+
+// MIME types
+const (
+ MIMEApplicationJSON = "application/json"
+ MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
+ MIMEApplicationJavaScript = "application/javascript"
+ MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
+ MIMEApplicationXML = "application/xml"
+ MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
+ MIMETextXML = "text/xml"
+ MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + 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"
+ // PROPFIND Method can be used on collection and property resources.
+ PROPFIND = "PROPFIND"
+)
+
+// Headers
+const (
+ HeaderAccept = "Accept"
+ 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"
+ HeaderXForwardedFor = "X-Forwarded-For"
+ HeaderXForwardedProto = "X-Forwarded-Proto"
+ HeaderXForwardedProtocol = "X-Forwarded-Protocol"
+ HeaderXForwardedSsl = "X-Forwarded-Ssl"
+ HeaderXUrlScheme = "X-Url-Scheme"
+ HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
+ HeaderXRealIP = "X-Real-IP"
+ HeaderXRequestID = "X-Request-ID"
+ HeaderXRequestedWith = "X-Requested-With"
+ HeaderServer = "Server"
+ HeaderOrigin = "Origin"
+
+ // Access control
+ 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"
+)
+
+const (
+ // Version of Echo
+ Version = "4.0.0"
+ website = "https://echo.labstack.com"
+ // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
+ banner = `
+ ____ __
+ / __/___/ / ___
+ / _// __/ _ \/ _ \
+/___/\__/_//_/\___/ %s
+High performance, minimalist Go web framework
+%s
+____________________________________O/_______
+ O\
+`
+)
+
+var (
+ methods = [...]string{
+ http.MethodConnect,
+ http.MethodDelete,
+ http.MethodGet,
+ http.MethodHead,
+ http.MethodOptions,
+ http.MethodPatch,
+ http.MethodPost,
+ PROPFIND,
+ http.MethodPut,
+ http.MethodTrace,
+ }
+)
+
+// Errors
+var (
+ ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
+ ErrNotFound = NewHTTPError(http.StatusNotFound)
+ ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
+ ErrForbidden = NewHTTPError(http.StatusForbidden)
+ ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
+ ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
+ ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests)
+ ErrBadRequest = NewHTTPError(http.StatusBadRequest)
+ ErrBadGateway = NewHTTPError(http.StatusBadGateway)
+ ErrInternalServerError = NewHTTPError(http.StatusInternalServerError)
+ ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout)
+ ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable)
+ 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.ERROR)
+ e.StdLogger = stdLog.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
+ if he.Internal != nil {
+ err = fmt.Errorf("%v, %v", err, he.Internal)
+ }
+ } else if e.Debug {
+ msg = err.Error()
+ } else {
+ msg = http.StatusText(code)
+ }
+ if _, ok := msg.(string); ok {
+ msg = Map{"message": msg}
+ }
+
+ // Send response
+ if !c.Response().Committed {
+ if c.Request().Method == http.MethodHead { // Issue #608
+ err = c.NoContent(code)
+ } else {
+ err = c.JSON(code, msg)
+ }
+ if err != nil {
+ 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) *Route {
+ return e.Add(http.MethodConnect, 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) *Route {
+ return e.Add(http.MethodDelete, 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) *Route {
+ return e.Add(http.MethodGet, 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) *Route {
+ return e.Add(http.MethodHead, 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) *Route {
+ return e.Add(http.MethodOptions, 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) *Route {
+ return e.Add(http.MethodPatch, 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) *Route {
+ return e.Add(http.MethodPost, 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) *Route {
+ return e.Add(http.MethodPut, 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) *Route {
+ return e.Add(http.MethodTrace, 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) []*Route {
+ routes := make([]*Route, len(methods))
+ for i, m := range methods {
+ routes[i] = e.Add(m, path, handler, middleware...)
+ }
+ return routes
+}
+
+// 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) []*Route {
+ routes := make([]*Route, len(methods))
+ for i, m := range methods {
+ routes[i] = e.Add(m, path, handler, middleware...)
+ }
+ return routes
+}
+
+// Static registers a new route with path prefix to serve static files from the
+// provided root directory.
+func (e *Echo) Static(prefix, root string) *Route {
+ if root == "" {
+ root = "." // For security we want to restrict to CWD.
+ }
+ return static(e, prefix, root)
+}
+
+func static(i i, prefix, root string) *Route {
+ h := func(c Context) error {
+ p, err := url.PathUnescape(c.Param("*"))
+ if err != nil {
+ return err
+ }
+ name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
+ return c.File(name)
+ }
+ i.GET(prefix, h)
+ if prefix == "/" {
+ return i.GET(prefix+"*", h)
+ }
+
+ return i.GET(prefix+"/*", h)
+}
+
+// File registers a new route with path to serve a static file with optional route-level middleware.
+func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
+ return e.GET(path, func(c Context) error {
+ return c.File(file)
+ }, m...)
+}
+
+// Add registers a new route for an HTTP method and path with matching handler
+// in the router with optional route-level middleware.
+func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
+ 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,
+ Name: name,
+ }
+ e.router.routes[method+path] = r
+ return 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 {
+ name := handlerName(handler)
+ return e.Reverse(name, params...)
+}
+
+// URL is an alias for `URI` function.
+func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
+ return e.URI(h, params...)
+}
+
+// Reverse generates an URL from route name and provided parameters.
+func (e *Echo) Reverse(name string, params ...interface{}) string {
+ uri := new(bytes.Buffer)
+ ln := len(params)
+ n := 0
+ for _, r := range e.router.routes {
+ if r.Name == 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()
+}
+
+// Routes returns the registered routes.
+func (e *Echo) Routes() []*Route {
+ routes := make([]*Route, 0, len(e.router.routes))
+ 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 context
+ c := e.pool.Get().(*context)
+ c.Reset(r, w)
+
+ h := NotFoundHandler
+
+ if e.premiddleware == nil {
+ e.router.Find(r.Method, getPath(r), c)
+ h = c.Handler()
+ for i := len(e.middleware) - 1; i >= 0; i-- {
+ h = e.middleware[i](h)
+ }
+ } else {
+ h = func(c Context) error {
+ e.router.Find(r.Method, getPath(r), c)
+ h := c.Handler()
+ for i := len(e.middleware) - 1; i >= 0; i-- {
+ h = e.middleware[i](h)
+ }
+ return h(c)
+ }
+ 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)
+ }
+
+ // Release context
+ e.pool.Put(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.ErrorLog = e.StdLogger
+ s.Handler = e
+ if e.Debug {
+ e.Logger.SetLevel(log.DEBUG)
+ }
+
+ if !e.HideBanner {
+ e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
+ }
+
+ if s.TLSConfig == nil {
+ if e.Listener == nil {
+ e.Listener, err = newListener(s.Addr)
+ if err != nil {
+ return err
+ }
+ }
+ if !e.HidePort {
+ 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)
+ }
+ if !e.HidePort {
+ e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
+ }
+ return s.Serve(e.TLSListener)
+}
+
+// Close immediately stops the server.
+// It internally calls `http.Server#Close()`.
+func (e *Echo) Close() error {
+ if err := e.TLSServer.Close(); err != nil {
+ return err
+ }
+ return e.Server.Close()
+}
+
+// Shutdown stops the server gracefully.
+// It internally calls `http.Server#Shutdown()`.
+func (e *Echo) Shutdown(ctx stdContext.Context) error {
+ if err := e.TLSServer.Shutdown(ctx); err != nil {
+ return err
+ }
+ return e.Server.Shutdown(ctx)
+}
+
+// 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=%v", he.Code, he.Message)
+}
+
+// SetInternal sets error to HTTPError.Internal
+func (he *HTTPError) SetInternal(err error) *HTTPError {
+ he.Internal = err
+ return he
+}
+
+// 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 getPath(r *http.Request) string {
+ path := r.URL.RawPath
+ if path == "" {
+ path = r.URL.Path
+ }
+ return path
+}
+
+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()
+}
+
+// // PathUnescape is wraps `url.PathUnescape`
+// func PathUnescape(s string) (string, error) {
+// return url.PathUnescape(s)
+// }
+
+// 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/v4/go.mod b/vendor/github.com/labstack/echo/v4/go.mod
new file mode 100644
index 00000000..2853cba5
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/go.mod
@@ -0,0 +1,13 @@
+module github.com/labstack/echo/v4
+
+require (
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
+ github.com/labstack/gommon v0.2.8
+ github.com/mattn/go-colorable v0.0.9 // indirect
+ github.com/mattn/go-isatty v0.0.4 // indirect
+ github.com/stretchr/testify v1.3.0
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4
+ golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664
+ golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc // indirect
+)
diff --git a/vendor/github.com/labstack/echo/v4/go.sum b/vendor/github.com/labstack/echo/v4/go.sum
new file mode 100644
index 00000000..e6981931
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/go.sum
@@ -0,0 +1,23 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
+github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
+github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
+github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
+golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 h1:YbZJ76lQ1BqNhVe7dKTSB67wDrc2VPRR75IyGyyPDX8=
+golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ=
+golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/vendor/github.com/labstack/echo/v4/group.go b/vendor/github.com/labstack/echo/v4/group.go
new file mode 100644
index 00000000..3e3732b6
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/group.go
@@ -0,0 +1,121 @@
+package echo
+
+import (
+ "net/http"
+ "path"
+)
+
+type (
+ // Group is a set of sub-routes for a specified route. It can be used for inner
+ // routes that share a common middleware 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...)
+ // Allow all requests to reach the group as they might get dropped if router
+ // doesn't find a match, making none of the group middleware process.
+ for _, p := range []string{"", "/*"} {
+ g.echo.Any(path.Clean(g.prefix+p), func(c Context) error {
+ return NotFoundHandler(c)
+ }, g.middleware...)
+ }
+}
+
+// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
+func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodConnect, path, h, m...)
+}
+
+// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
+func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodDelete, path, h, m...)
+}
+
+// GET implements `Echo#GET()` for sub-routes within the Group.
+func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodGet, path, h, m...)
+}
+
+// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
+func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodHead, path, h, m...)
+}
+
+// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
+func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodOptions, path, h, m...)
+}
+
+// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
+func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodPatch, path, h, m...)
+}
+
+// POST implements `Echo#POST()` for sub-routes within the Group.
+func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodPost, path, h, m...)
+}
+
+// PUT implements `Echo#PUT()` for sub-routes within the Group.
+func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodPut, path, h, m...)
+}
+
+// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
+func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+ return g.Add(http.MethodTrace, path, h, m...)
+}
+
+// Any implements `Echo#Any()` for sub-routes within the Group.
+func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
+ routes := make([]*Route, len(methods))
+ for i, m := range methods {
+ routes[i] = g.Add(m, path, handler, middleware...)
+ }
+ return routes
+}
+
+// Match implements `Echo#Match()` for sub-routes within the Group.
+func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
+ routes := make([]*Route, len(methods))
+ for i, m := range methods {
+ routes[i] = g.Add(m, path, handler, middleware...)
+ }
+ return routes
+}
+
+// Group creates a new sub-group with prefix and optional sub-group-level middleware.
+func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
+ m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
+ 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)
+}
+
+// Add implements `Echo#Add()` for sub-routes within the Group.
+func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
+ // 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 := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
+ m = append(m, g.middleware...)
+ m = append(m, middleware...)
+ return g.echo.Add(method, g.prefix+path, handler, m...)
+}
diff --git a/vendor/github.com/labstack/echo/v4/log.go b/vendor/github.com/labstack/echo/v4/log.go
new file mode 100644
index 00000000..3f8de590
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/log.go
@@ -0,0 +1,41 @@
+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)
+ SetHeader(h string)
+ 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/v4/middleware/basic_auth.go b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
new file mode 100644
index 00000000..76ba2420
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
@@ -0,0 +1,106 @@
+package middleware
+
+import (
+ "encoding/base64"
+ "strconv"
+ "strings"
+
+ "github.com/labstack/echo/v4"
+)
+
+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
+
+ // Realm is a string to define realm attribute of BasicAuth.
+ // Default value "Restricted".
+ Realm string
+ }
+
+ // BasicAuthValidator defines a function to validate BasicAuth credentials.
+ BasicAuthValidator func(string, string, echo.Context) (bool, error)
+)
+
+const (
+ basic = "basic"
+ defaultRealm = "Restricted"
+)
+
+var (
+ // DefaultBasicAuthConfig is the default BasicAuth middleware config.
+ DefaultBasicAuthConfig = BasicAuthConfig{
+ Skipper: DefaultSkipper,
+ Realm: defaultRealm,
+ }
+)
+
+// 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("echo: basic-auth middleware requires a validator function")
+ }
+ if config.Skipper == nil {
+ config.Skipper = DefaultBasicAuthConfig.Skipper
+ }
+ if config.Realm == "" {
+ config.Realm = defaultRealm
+ }
+
+ 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 && strings.ToLower(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
+ valid, err := config.Validator(cred[:i], cred[i+1:], c)
+ if err != nil {
+ return err
+ } else if valid {
+ return next(c)
+ }
+ break
+ }
+ }
+ }
+
+ realm := defaultRealm
+ if config.Realm != defaultRealm {
+ realm = strconv.Quote(config.Realm)
+ }
+
+ // Need to return `401` for browsers to pop-up login box.
+ c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm)
+ return echo.ErrUnauthorized
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/body_dump.go b/vendor/github.com/labstack/echo/v4/middleware/body_dump.go
new file mode 100644
index 00000000..418d279d
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/body_dump.go
@@ -0,0 +1,107 @@
+package middleware
+
+import (
+ "bufio"
+ "bytes"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+)
+
+type (
+ // BodyDumpConfig defines the config for BodyDump middleware.
+ BodyDumpConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Handler receives request and response payload.
+ // Required.
+ Handler BodyDumpHandler
+ }
+
+ // BodyDumpHandler receives the request and response payload.
+ BodyDumpHandler func(echo.Context, []byte, []byte)
+
+ bodyDumpResponseWriter struct {
+ io.Writer
+ http.ResponseWriter
+ }
+)
+
+var (
+ // DefaultBodyDumpConfig is the default BodyDump middleware config.
+ DefaultBodyDumpConfig = BodyDumpConfig{
+ Skipper: DefaultSkipper,
+ }
+)
+
+// BodyDump returns a BodyDump middleware.
+//
+// BodyLimit middleware captures the request and response payload and calls the
+// registered handler.
+func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc {
+ c := DefaultBodyDumpConfig
+ c.Handler = handler
+ return BodyDumpWithConfig(c)
+}
+
+// BodyDumpWithConfig returns a BodyDump middleware with config.
+// See: `BodyDump()`.
+func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Handler == nil {
+ panic("echo: body-dump middleware requires a handler function")
+ }
+ if config.Skipper == nil {
+ config.Skipper = DefaultBodyDumpConfig.Skipper
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) (err error) {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ // Request
+ reqBody := []byte{}
+ if c.Request().Body != nil { // Read
+ reqBody, _ = ioutil.ReadAll(c.Request().Body)
+ }
+ c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset
+
+ // Response
+ resBody := new(bytes.Buffer)
+ mw := io.MultiWriter(c.Response().Writer, resBody)
+ writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
+ c.Response().Writer = writer
+
+ if err = next(c); err != nil {
+ c.Error(err)
+ }
+
+ // Callback
+ config.Handler(c, reqBody, resBody.Bytes())
+
+ return
+ }
+ }
+}
+
+func (w *bodyDumpResponseWriter) WriteHeader(code int) {
+ w.ResponseWriter.WriteHeader(code)
+}
+
+func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
+ return w.Writer.Write(b)
+}
+
+func (w *bodyDumpResponseWriter) Flush() {
+ w.ResponseWriter.(http.Flusher).Flush()
+}
+
+func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ return w.ResponseWriter.(http.Hijacker).Hijack()
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/body_limit.go b/vendor/github.com/labstack/echo/v4/middleware/body_limit.go
new file mode 100644
index 00000000..b436bd59
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/body_limit.go
@@ -0,0 +1,117 @@
+package middleware
+
+import (
+ "fmt"
+ "io"
+ "sync"
+
+ "github.com/labstack/echo/v4"
+ "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 `yaml:"limit"`
+ limit int64
+ }
+
+ limitedReader struct {
+ BodyLimitConfig
+ reader io.ReadCloser
+ read int64
+ context echo.Context
+ }
+)
+
+var (
+ // DefaultBodyLimitConfig is the default BodyLimit 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("echo: 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
+ r.read = 0
+}
+
+func limitedReaderPool(c BodyLimitConfig) sync.Pool {
+ return sync.Pool{
+ New: func() interface{} {
+ return &limitedReader{BodyLimitConfig: c}
+ },
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/compress.go b/vendor/github.com/labstack/echo/v4/middleware/compress.go
new file mode 100644
index 00000000..19052064
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/compress.go
@@ -0,0 +1,118 @@
+package middleware
+
+import (
+ "bufio"
+ "compress/gzip"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "strings"
+
+ "github.com/labstack/echo/v4"
+)
+
+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 `yaml:"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().Set(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.Header().Del(echo.HeaderContentLength) // Issue #444
+ 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() {
+ w.Writer.(*gzip.Writer).Flush()
+}
+
+func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ return w.ResponseWriter.(http.Hijacker).Hijack()
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/cors.go b/vendor/github.com/labstack/echo/v4/middleware/cors.go
new file mode 100644
index 00000000..c61c7125
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/cors.go
@@ -0,0 +1,143 @@
+package middleware
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/labstack/echo/v4"
+)
+
+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 `yaml:"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 `yaml:"allow_methods"`
+
+ // AllowHeaders defines a list of request headers that can be used when
+ // making the actual request. This is in response to a preflight request.
+ // Optional. Default value []string{}.
+ AllowHeaders []string `yaml:"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 `yaml:"allow_credentials"`
+
+ // ExposeHeaders defines a whitelist headers that clients are allowed to
+ // access.
+ // Optional. Default value []string{}.
+ ExposeHeaders []string `yaml:"expose_headers"`
+
+ // MaxAge indicates how long (in seconds) the results of a preflight request
+ // can be cached.
+ // Optional. Default value 0.
+ MaxAge int `yaml:"max_age"`
+ }
+)
+
+var (
+ // DefaultCORSConfig is the default CORS middleware config.
+ DefaultCORSConfig = CORSConfig{
+ Skipper: DefaultSkipper,
+ AllowOrigins: []string{"*"},
+ AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
+ }
+)
+
+// 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 == "*" && config.AllowCredentials {
+ allowOrigin = origin
+ break
+ }
+ if o == "*" || o == origin {
+ allowOrigin = o
+ break
+ }
+ }
+
+ // Simple request
+ if req.Method != http.MethodOptions {
+ 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/v4/middleware/csrf.go b/vendor/github.com/labstack/echo/v4/middleware/csrf.go
new file mode 100644
index 00000000..09a66bb6
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/csrf.go
@@ -0,0 +1,210 @@
+package middleware
+
+import (
+ "crypto/subtle"
+ "errors"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/labstack/echo/v4"
+ "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 `yaml:"token_length"`
+ // Optional. Default value 32.
+
+ // TokenLookup is a string in the form of "<source>:<key>" that is used
+ // to extract token from the request.
+ // Optional. Default value "header:X-CSRF-Token".
+ // Possible values:
+ // - "header:<name>"
+ // - "form:<name>"
+ // - "query:<name>"
+ TokenLookup string `yaml:"token_lookup"`
+
+ // Context key to store generated CSRF token into context.
+ // Optional. Default value "csrf".
+ ContextKey string `yaml:"context_key"`
+
+ // Name of the CSRF cookie. This cookie will store CSRF token.
+ // Optional. Default value "csrf".
+ CookieName string `yaml:"cookie_name"`
+
+ // Domain of the CSRF cookie.
+ // Optional. Default value none.
+ CookieDomain string `yaml:"cookie_domain"`
+
+ // Path of the CSRF cookie.
+ // Optional. Default value none.
+ CookiePath string `yaml:"cookie_path"`
+
+ // Max age (in seconds) of the CSRF cookie.
+ // Optional. Default value 86400 (24hr).
+ CookieMaxAge int `yaml:"cookie_max_age"`
+
+ // Indicates if CSRF cookie is secure.
+ // Optional. Default value false.
+ CookieSecure bool `yaml:"cookie_secure"`
+
+ // Indicates if CSRF cookie is HTTP only.
+ // Optional. Default value false.
+ CookieHTTPOnly bool `yaml:"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 := ""
+
+ // Generate token
+ if err != nil {
+ token = random.String(config.TokenLength)
+ } else {
+ // Reuse token
+ token = k.Value
+ }
+
+ switch req.Method {
+ case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
+ 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/v4/middleware/jwt.go b/vendor/github.com/labstack/echo/v4/middleware/jwt.go
new file mode 100644
index 00000000..861d3142
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/jwt.go
@@ -0,0 +1,227 @@
+package middleware
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "strings"
+
+ "github.com/dgrijalva/jwt-go"
+ "github.com/labstack/echo/v4"
+)
+
+type (
+ // JWTConfig defines the config for JWT middleware.
+ JWTConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // BeforeFunc defines a function which is executed just before the middleware.
+ BeforeFunc BeforeFunc
+
+ // SuccessHandler defines a function which is executed for a valid token.
+ SuccessHandler JWTSuccessHandler
+
+ // ErrorHandler defines a function which is executed for an invalid token.
+ // It may be used to define a custom JWT error.
+ ErrorHandler JWTErrorHandler
+
+ // 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 "<source>:<name>" that is used
+ // to extract token from the request.
+ // Optional. Default value "header:Authorization".
+ // Possible values:
+ // - "header:<name>"
+ // - "query:<name>"
+ // - "cookie:<name>"
+ TokenLookup string
+
+ // AuthScheme to be used in the Authorization header.
+ // Optional. Default value "Bearer".
+ AuthScheme string
+
+ keyFunc jwt.Keyfunc
+ }
+
+ // JWTSuccessHandler defines a function which is executed for a valid token.
+ JWTSuccessHandler func(echo.Context)
+
+ // JWTErrorHandler defines a function which is executed for an invalid token.
+ JWTErrorHandler func(error) error
+
+ jwtExtractor func(echo.Context) (string, error)
+)
+
+// Algorithms
+const (
+ AlgorithmHS256 = "HS256"
+)
+
+// Errors
+var (
+ ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
+)
+
+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 interface{}) 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("echo: 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)
+ }
+
+ if config.BeforeFunc != nil {
+ config.BeforeFunc(c)
+ }
+
+ auth, err := extractor(c)
+ if err != nil {
+ if config.ErrorHandler != nil {
+ return config.ErrorHandler(err)
+ }
+ return err
+ }
+ token := new(jwt.Token)
+ // Issue #647, #656
+ if _, ok := config.Claims.(jwt.MapClaims); ok {
+ token, err = jwt.Parse(auth, config.keyFunc)
+ } else {
+ t := reflect.ValueOf(config.Claims).Type().Elem()
+ claims := reflect.New(t).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)
+ if config.SuccessHandler != nil {
+ config.SuccessHandler(c)
+ }
+ return next(c)
+ }
+ if config.ErrorHandler != nil {
+ return config.ErrorHandler(err)
+ }
+ return &echo.HTTPError{
+ Code: http.StatusUnauthorized,
+ Message: "invalid or expired jwt",
+ Internal: err,
+ }
+ }
+ }
+}
+
+// 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 "", ErrJWTMissing
+ }
+}
+
+// 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 "", ErrJWTMissing
+ }
+ 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 "", ErrJWTMissing
+ }
+ return cookie.Value, nil
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/key_auth.go b/vendor/github.com/labstack/echo/v4/middleware/key_auth.go
new file mode 100644
index 00000000..fe01e23e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/key_auth.go
@@ -0,0 +1,150 @@
+package middleware
+
+import (
+ "errors"
+ "net/http"
+ "strings"
+
+ "github.com/labstack/echo/v4"
+)
+
+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 "<source>:<name>" that is used
+ // to extract key from the request.
+ // Optional. Default value "header:Authorization".
+ // Possible values:
+ // - "header:<name>"
+ // - "query:<name>"
+ // - "form:<name>"
+ KeyLookup string `yaml:"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, error)
+
+ 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("echo: 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])
+ case "form":
+ extractor = keyFromForm(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())
+ }
+ valid, err := config.Validator(key, c)
+ if err != nil {
+ return err
+ } else if valid {
+ 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
+ }
+}
+
+// keyFromForm returns a `keyExtractor` that extracts key from the form.
+func keyFromForm(param string) keyExtractor {
+ return func(c echo.Context) (string, error) {
+ key := c.FormValue(param)
+ if key == "" {
+ return "", errors.New("missing key in the form")
+ }
+ return key, nil
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/logger.go b/vendor/github.com/labstack/echo/v4/middleware/logger.go
new file mode 100644
index 00000000..b2e48347
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/logger.go
@@ -0,0 +1,217 @@
+package middleware
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/labstack/echo/v4"
+ "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
+ // - time_custom
+ // - id (Request ID)
+ // - remote_ip
+ // - uri
+ // - host
+ // - method
+ // - path
+ // - protocol
+ // - referer
+ // - user_agent
+ // - status
+ // - error
+ // - latency (In nanoseconds)
+ // - latency_human (Human readable)
+ // - bytes_in (Bytes received)
+ // - bytes_out (Bytes sent)
+ // - header:<NAME>
+ // - query:<NAME>
+ // - form:<NAME>
+ //
+ // Example "${remote_ip} ${status}"
+ //
+ // Optional. Default value DefaultLoggerConfig.Format.
+ Format string `yaml:"format"`
+
+ // Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
+ CustomTimeFormat string `yaml:"custom_time_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}","id":"${id}","remote_ip":"${remote_ip}",` +
+ `"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
+ `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
+ `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
+ CustomTimeFormat: "2006-01-02 15:04:05.00000",
+ 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 "time_custom":
+ return buf.WriteString(time.Now().Format(config.CustomTimeFormat))
+ case "id":
+ id := req.Header.Get(echo.HeaderXRequestID)
+ if id == "" {
+ id = res.Header().Get(echo.HeaderXRequestID)
+ }
+ return buf.WriteString(id)
+ 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 "protocol":
+ return buf.WriteString(req.Proto)
+ 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 "error":
+ if err != nil {
+ return buf.WriteString(err.Error())
+ }
+ 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:])))
+ case strings.HasPrefix(tag, "cookie:"):
+ cookie, err := c.Cookie(tag[7:])
+ if err == nil {
+ return buf.Write([]byte(cookie.Value))
+ }
+ }
+ }
+ return 0, nil
+ }); err != nil {
+ return
+ }
+
+ _, err = config.Output.Write(buf.Bytes())
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/method_override.go b/vendor/github.com/labstack/echo/v4/middleware/method_override.go
new file mode 100644
index 00000000..92b14d2e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/method_override.go
@@ -0,0 +1,92 @@
+package middleware
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+)
+
+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 == http.MethodPost {
+ 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/v4/middleware/middleware.go b/vendor/github.com/labstack/echo/v4/middleware/middleware.go
new file mode 100644
index 00000000..d0b7153c
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/middleware.go
@@ -0,0 +1,38 @@
+package middleware
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/labstack/echo/v4"
+)
+
+type (
+ // Skipper defines a function to skip middleware. Returning true skips processing
+ // the middleware.
+ Skipper func(echo.Context) bool
+
+ // BeforeFunc defines a function which is executed just before the middleware.
+ BeforeFunc func(echo.Context)
+)
+
+func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
+ groups := pattern.FindAllStringSubmatch(input, -1)
+ if groups == nil {
+ return nil
+ }
+ values := groups[0][1:]
+ replace := make([]string, 2*len(values))
+ for i, v := range values {
+ j := 2 * i
+ replace[j] = "$" + strconv.Itoa(i+1)
+ replace[j+1] = v
+ }
+ return strings.NewReplacer(replace...)
+}
+
+// DefaultSkipper returns false which processes the middleware.
+func DefaultSkipper(echo.Context) bool {
+ return false
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy.go b/vendor/github.com/labstack/echo/v4/middleware/proxy.go
new file mode 100644
index 00000000..9d67f445
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/proxy.go
@@ -0,0 +1,258 @@
+package middleware
+
+import (
+ "fmt"
+ "io"
+ "math/rand"
+ "net"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/labstack/echo/v4"
+)
+
+// TODO: Handle TLS proxy
+
+type (
+ // ProxyConfig defines the config for Proxy middleware.
+ ProxyConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Balancer defines a load balancing technique.
+ // Required.
+ Balancer ProxyBalancer
+
+ // Rewrite defines URL path rewrite rules. The values captured in asterisk can be
+ // retrieved by index e.g. $1, $2 and so on.
+ // Examples:
+ // "/old": "/new",
+ // "/api/*": "/$1",
+ // "/js/*": "/public/javascripts/$1",
+ // "/users/*/orders/*": "/user/$1/order/$2",
+ Rewrite map[string]string
+
+ // Context key to store selected ProxyTarget into context.
+ // Optional. Default value "target".
+ ContextKey string
+
+ // To customize the transport to remote.
+ // Examples: If custom TLS certificates are required.
+ Transport http.RoundTripper
+
+ rewriteRegex map[*regexp.Regexp]string
+ }
+
+ // ProxyTarget defines the upstream target.
+ ProxyTarget struct {
+ Name string
+ URL *url.URL
+ Meta echo.Map
+ }
+
+ // ProxyBalancer defines an interface to implement a load balancing technique.
+ ProxyBalancer interface {
+ AddTarget(*ProxyTarget) bool
+ RemoveTarget(string) bool
+ Next(echo.Context) *ProxyTarget
+ }
+
+ commonBalancer struct {
+ targets []*ProxyTarget
+ mutex sync.RWMutex
+ }
+
+ // RandomBalancer implements a random load balancing technique.
+ randomBalancer struct {
+ *commonBalancer
+ random *rand.Rand
+ }
+
+ // RoundRobinBalancer implements a round-robin load balancing technique.
+ roundRobinBalancer struct {
+ *commonBalancer
+ i uint32
+ }
+)
+
+var (
+ // DefaultProxyConfig is the default Proxy middleware config.
+ DefaultProxyConfig = ProxyConfig{
+ Skipper: DefaultSkipper,
+ ContextKey: "target",
+ }
+)
+
+func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ in, _, err := c.Response().Hijack()
+ if err != nil {
+ c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", t.URL, err))
+ return
+ }
+ defer in.Close()
+
+ out, err := net.Dial("tcp", t.URL.Host)
+ if err != nil {
+ he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err))
+ c.Error(he)
+ return
+ }
+ defer out.Close()
+
+ // Write header
+ err = r.Write(out)
+ if err != nil {
+ he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err))
+ c.Error(he)
+ return
+ }
+
+ errCh := make(chan error, 2)
+ cp := func(dst io.Writer, src io.Reader) {
+ _, err = io.Copy(dst, src)
+ errCh <- err
+ }
+
+ go cp(out, in)
+ go cp(in, out)
+ err = <-errCh
+ if err != nil && err != io.EOF {
+ c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err)
+ }
+ })
+}
+
+// NewRandomBalancer returns a random proxy balancer.
+func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer {
+ b := &randomBalancer{commonBalancer: new(commonBalancer)}
+ b.targets = targets
+ return b
+}
+
+// NewRoundRobinBalancer returns a round-robin proxy balancer.
+func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer {
+ b := &roundRobinBalancer{commonBalancer: new(commonBalancer)}
+ b.targets = targets
+ return b
+}
+
+// AddTarget adds an upstream target to the list.
+func (b *commonBalancer) AddTarget(target *ProxyTarget) bool {
+ for _, t := range b.targets {
+ if t.Name == target.Name {
+ return false
+ }
+ }
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+ b.targets = append(b.targets, target)
+ return true
+}
+
+// RemoveTarget removes an upstream target from the list.
+func (b *commonBalancer) RemoveTarget(name string) bool {
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+ for i, t := range b.targets {
+ if t.Name == name {
+ b.targets = append(b.targets[:i], b.targets[i+1:]...)
+ return true
+ }
+ }
+ return false
+}
+
+// Next randomly returns an upstream target.
+func (b *randomBalancer) Next(c echo.Context) *ProxyTarget {
+ if b.random == nil {
+ b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
+ }
+ b.mutex.RLock()
+ defer b.mutex.RUnlock()
+ return b.targets[b.random.Intn(len(b.targets))]
+}
+
+// Next returns an upstream target using round-robin technique.
+func (b *roundRobinBalancer) Next(c echo.Context) *ProxyTarget {
+ b.i = b.i % uint32(len(b.targets))
+ t := b.targets[b.i]
+ atomic.AddUint32(&b.i, 1)
+ return t
+}
+
+// Proxy returns a Proxy middleware.
+//
+// Proxy middleware forwards the request to upstream server using a configured load balancing technique.
+func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {
+ c := DefaultProxyConfig
+ c.Balancer = balancer
+ return ProxyWithConfig(c)
+}
+
+// ProxyWithConfig returns a Proxy middleware with config.
+// See: `Proxy()`
+func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultLoggerConfig.Skipper
+ }
+ if config.Balancer == nil {
+ panic("echo: proxy middleware requires balancer")
+ }
+ config.rewriteRegex = map[*regexp.Regexp]string{}
+
+ // Initialize
+ for k, v := range config.Rewrite {
+ k = strings.Replace(k, "*", "(\\S*)", -1)
+ config.rewriteRegex[regexp.MustCompile(k)] = v
+ }
+
+ 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()
+ tgt := config.Balancer.Next(c)
+ c.Set(config.ContextKey, tgt)
+
+ // Rewrite
+ for k, v := range config.rewriteRegex {
+ replacer := captureTokens(k, req.URL.Path)
+ if replacer != nil {
+ req.URL.Path = replacer.Replace(v)
+ }
+ }
+
+ // Fix header
+ if req.Header.Get(echo.HeaderXRealIP) == "" {
+ req.Header.Set(echo.HeaderXRealIP, c.RealIP())
+ }
+ if req.Header.Get(echo.HeaderXForwardedProto) == "" {
+ req.Header.Set(echo.HeaderXForwardedProto, c.Scheme())
+ }
+ if c.IsWebSocket() && req.Header.Get(echo.HeaderXForwardedFor) == "" { // For HTTP, it is automatically set by Go HTTP reverse proxy.
+ req.Header.Set(echo.HeaderXForwardedFor, c.RealIP())
+ }
+
+ // Proxy
+ switch {
+ case c.IsWebSocket():
+ proxyRaw(tgt, c).ServeHTTP(res, req)
+ case req.Header.Get(echo.HeaderAccept) == "text/event-stream":
+ default:
+ proxyHTTP(tgt, c, config).ServeHTTP(res, req)
+ }
+
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go
new file mode 100644
index 00000000..7784f9c6
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go
@@ -0,0 +1,25 @@
+// +build go1.11
+
+package middleware
+
+import (
+ "fmt"
+ "net/http"
+ "net/http/httputil"
+
+ "github.com/labstack/echo/v4"
+)
+
+func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
+ proxy := httputil.NewSingleHostReverseProxy(tgt.URL)
+ proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) {
+ desc := tgt.URL.String()
+ if tgt.Name != "" {
+ desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String())
+ }
+ c.Logger().Errorf("remote %s unreachable, could not forward: %v", desc, err)
+ c.Error(echo.NewHTTPError(http.StatusServiceUnavailable))
+ }
+ proxy.Transport = config.Transport
+ return proxy
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11_n.go b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11_n.go
new file mode 100644
index 00000000..9a78929f
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11_n.go
@@ -0,0 +1,14 @@
+// +build !go1.11
+
+package middleware
+
+import (
+ "net/http"
+ "net/http/httputil"
+
+ "github.com/labstack/echo/v4"
+)
+
+func proxyHTTP(t *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
+ return httputil.NewSingleHostReverseProxy(t.URL)
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/recover.go b/vendor/github.com/labstack/echo/v4/middleware/recover.go
new file mode 100644
index 00000000..e87aaf32
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/recover.go
@@ -0,0 +1,81 @@
+package middleware
+
+import (
+ "fmt"
+ "runtime"
+
+ "github.com/labstack/echo/v4"
+)
+
+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 `yaml:"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 `yaml:"disable_stack_all"`
+
+ // DisablePrintStack disables printing stack trace.
+ // Optional. Default value as false.
+ DisablePrintStack bool `yaml:"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 {
+ err, ok := r.(error)
+ if !ok {
+ err = fmt.Errorf("%v", r)
+ }
+ stack := make([]byte, config.StackSize)
+ length := runtime.Stack(stack, !config.DisableStackAll)
+ if !config.DisablePrintStack {
+ c.Logger().Printf("[PANIC RECOVER] %v %s\n", err, stack[:length])
+ }
+ c.Error(err)
+ }
+ }()
+ return next(c)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/redirect.go b/vendor/github.com/labstack/echo/v4/middleware/redirect.go
new file mode 100644
index 00000000..30a2e403
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/redirect.go
@@ -0,0 +1,153 @@
+package middleware
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+)
+
+// RedirectConfig defines the config for Redirect middleware.
+type RedirectConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper
+
+ // Status code to be used when redirecting the request.
+ // Optional. Default value http.StatusMovedPermanently.
+ Code int `yaml:"code"`
+}
+
+// redirectLogic represents a function that given a scheme, host and uri
+// can both: 1) determine if redirect is needed (will set ok accordingly) and
+// 2) return the appropriate redirect url.
+type redirectLogic func(scheme, host, uri string) (ok bool, url string)
+
+const www = "www"
+
+// DefaultRedirectConfig is the default Redirect middleware config.
+var 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 {
+ return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
+ if ok = scheme != "https"; ok {
+ url = "https://" + host + uri
+ }
+ return
+ })
+}
+
+// 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 {
+ return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
+ if ok = scheme != "https" && host[:3] != www; ok {
+ url = "https://www." + host + uri
+ }
+ return
+ })
+}
+
+// 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 {
+ return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
+ if ok = scheme != "https"; ok {
+ if host[:3] == www {
+ host = host[4:]
+ }
+ url = "https://" + host + uri
+ }
+ return
+ })
+}
+
+// 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 {
+ return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
+ if ok = host[:3] != www; ok {
+ url = scheme + "://www." + host + uri
+ }
+ return
+ })
+}
+
+// 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 {
+ return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
+ if ok = host[:3] == www; ok {
+ url = scheme + "://" + host[4:] + uri
+ }
+ return
+ })
+}
+
+func redirect(config RedirectConfig, cb redirectLogic) 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, scheme := c.Request(), c.Scheme()
+ host := req.Host
+ if ok, url := cb(scheme, host, req.RequestURI); ok {
+ return c.Redirect(config.Code, url)
+ }
+
+ return next(c)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/request_id.go b/vendor/github.com/labstack/echo/v4/middleware/request_id.go
new file mode 100644
index 00000000..21f801f3
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/request_id.go
@@ -0,0 +1,64 @@
+package middleware
+
+import (
+ "github.com/labstack/echo/v4"
+ "github.com/labstack/gommon/random"
+)
+
+type (
+ // RequestIDConfig defines the config for RequestID middleware.
+ RequestIDConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Generator defines a function to generate an ID.
+ // Optional. Default value random.String(32).
+ Generator func() string
+ }
+)
+
+var (
+ // DefaultRequestIDConfig is the default RequestID middleware config.
+ DefaultRequestIDConfig = RequestIDConfig{
+ Skipper: DefaultSkipper,
+ Generator: generator,
+ }
+)
+
+// RequestID returns a X-Request-ID middleware.
+func RequestID() echo.MiddlewareFunc {
+ return RequestIDWithConfig(DefaultRequestIDConfig)
+}
+
+// RequestIDWithConfig returns a X-Request-ID middleware with config.
+func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultRequestIDConfig.Skipper
+ }
+ if config.Generator == nil {
+ config.Generator = generator
+ }
+
+ 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()
+ rid := req.Header.Get(echo.HeaderXRequestID)
+ if rid == "" {
+ rid = config.Generator()
+ }
+ res.Header().Set(echo.HeaderXRequestID, rid)
+
+ return next(c)
+ }
+ }
+}
+
+func generator() string {
+ return random.String(32)
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/rewrite.go b/vendor/github.com/labstack/echo/v4/middleware/rewrite.go
new file mode 100644
index 00000000..a64e10bb
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/rewrite.go
@@ -0,0 +1,84 @@
+package middleware
+
+import (
+ "regexp"
+ "strings"
+
+ "github.com/labstack/echo/v4"
+)
+
+type (
+ // RewriteConfig defines the config for Rewrite middleware.
+ RewriteConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Rules defines the URL path rewrite rules. The values captured in asterisk can be
+ // retrieved by index e.g. $1, $2 and so on.
+ // Example:
+ // "/old": "/new",
+ // "/api/*": "/$1",
+ // "/js/*": "/public/javascripts/$1",
+ // "/users/*/orders/*": "/user/$1/order/$2",
+ // Required.
+ Rules map[string]string `yaml:"rules"`
+
+ rulesRegex map[*regexp.Regexp]string
+ }
+)
+
+var (
+ // DefaultRewriteConfig is the default Rewrite middleware config.
+ DefaultRewriteConfig = RewriteConfig{
+ Skipper: DefaultSkipper,
+ }
+)
+
+// Rewrite returns a Rewrite middleware.
+//
+// Rewrite middleware rewrites the URL path based on the provided rules.
+func Rewrite(rules map[string]string) echo.MiddlewareFunc {
+ c := DefaultRewriteConfig
+ c.Rules = rules
+ return RewriteWithConfig(c)
+}
+
+// RewriteWithConfig returns a Rewrite middleware with config.
+// See: `Rewrite()`.
+func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Rules == nil {
+ panic("echo: rewrite middleware requires url path rewrite rules")
+ }
+ if config.Skipper == nil {
+ config.Skipper = DefaultBodyDumpConfig.Skipper
+ }
+ config.rulesRegex = map[*regexp.Regexp]string{}
+
+ // Initialize
+ for k, v := range config.Rules {
+ k = strings.Replace(k, "*", "(.*)", -1)
+ k = k + "$"
+ config.rulesRegex[regexp.MustCompile(k)] = v
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) (err error) {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+
+ // Rewrite
+ for k, v := range config.rulesRegex {
+ replacer := captureTokens(k, req.URL.Path)
+ if replacer != nil {
+ req.URL.Path = replacer.Replace(v)
+ break
+ }
+ }
+ return next(c)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/secure.go b/vendor/github.com/labstack/echo/v4/middleware/secure.go
new file mode 100644
index 00000000..8839c879
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/secure.go
@@ -0,0 +1,116 @@
+package middleware
+
+import (
+ "fmt"
+
+ "github.com/labstack/echo/v4"
+)
+
+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 `yaml:"xss_protection"`
+
+ // ContentTypeNosniff provides protection against overriding Content-Type
+ // header by setting the `X-Content-Type-Options` header.
+ // Optional. Default value "nosniff".
+ ContentTypeNosniff string `yaml:"content_type_nosniff"`
+
+ // XFrameOptions can be used to indicate whether or not a browser should
+ // be allowed to render a page in a <frame>, <iframe> or <object> .
+ // Sites can use this to avoid clickjacking attacks, by ensuring that their
+ // content is not embedded into other sites.provides protection against
+ // clickjacking.
+ // Optional. Default value "SAMEORIGIN".
+ // Possible values:
+ // - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
+ // - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
+ // - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
+ XFrameOptions string `yaml:"x_frame_options"`
+
+ // HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
+ // long (in seconds) browsers should remember that this site is only to
+ // be accessed using HTTPS. This reduces your exposure to some SSL-stripping
+ // man-in-the-middle (MITM) attacks.
+ // Optional. Default value 0.
+ HSTSMaxAge int `yaml:"hsts_max_age"`
+
+ // HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
+ // header, excluding all subdomains from security policy. It has no effect
+ // unless HSTSMaxAge is set to a non-zero value.
+ // Optional. Default value false.
+ HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"`
+
+ // ContentSecurityPolicy sets the `Content-Security-Policy` header providing
+ // security against cross-site scripting (XSS), clickjacking and other code
+ // injection attacks resulting from execution of malicious content in the
+ // trusted web page context.
+ // Optional. Default value "".
+ ContentSecurityPolicy string `yaml:"content_security_policy"`
+ }
+)
+
+var (
+ // DefaultSecureConfig is the default Secure middleware config.
+ DefaultSecureConfig = SecureConfig{
+ Skipper: DefaultSkipper,
+ XSSProtection: "1; mode=block",
+ ContentTypeNosniff: "nosniff",
+ XFrameOptions: "SAMEORIGIN",
+ }
+)
+
+// Secure returns a Secure middleware.
+// Secure middleware provides protection against cross-site scripting (XSS) attack,
+// content type sniffing, clickjacking, insecure connection and other code injection
+// attacks.
+func Secure() echo.MiddlewareFunc {
+ return SecureWithConfig(DefaultSecureConfig)
+}
+
+// SecureWithConfig returns a Secure middleware with config.
+// See: `Secure()`.
+func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultSecureConfig.Skipper
+ }
+
+ 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()
+
+ if config.XSSProtection != "" {
+ res.Header().Set(echo.HeaderXXSSProtection, config.XSSProtection)
+ }
+ if config.ContentTypeNosniff != "" {
+ res.Header().Set(echo.HeaderXContentTypeOptions, config.ContentTypeNosniff)
+ }
+ if config.XFrameOptions != "" {
+ res.Header().Set(echo.HeaderXFrameOptions, config.XFrameOptions)
+ }
+ if (c.IsTLS() || (req.Header.Get(echo.HeaderXForwardedProto) == "https")) && config.HSTSMaxAge != 0 {
+ subdomains := ""
+ if !config.HSTSExcludeSubdomains {
+ subdomains = "; includeSubdomains"
+ }
+ res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
+ }
+ if config.ContentSecurityPolicy != "" {
+ res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
+ }
+ return next(c)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/slash.go b/vendor/github.com/labstack/echo/v4/middleware/slash.go
new file mode 100644
index 00000000..61d6e30b
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/slash.go
@@ -0,0 +1,119 @@
+package middleware
+
+import (
+ "github.com/labstack/echo/v4"
+)
+
+type (
+ // TrailingSlashConfig defines the config for TrailingSlash middleware.
+ TrailingSlashConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Status code to be used when redirecting the request.
+ // Optional, but when provided the request is redirected using this code.
+ RedirectCode int `yaml:"redirect_code"`
+ }
+)
+
+var (
+ // DefaultTrailingSlashConfig is the default TrailingSlash middleware config.
+ DefaultTrailingSlashConfig = TrailingSlashConfig{
+ Skipper: DefaultSkipper,
+ }
+)
+
+// AddTrailingSlash returns a root level (before router) middleware which adds a
+// trailing slash to the request `URL#Path`.
+//
+// Usage `Echo#Pre(AddTrailingSlash())`
+func AddTrailingSlash() echo.MiddlewareFunc {
+ return AddTrailingSlashWithConfig(DefaultTrailingSlashConfig)
+}
+
+// AddTrailingSlashWithConfig returns a AddTrailingSlash middleware with config.
+// See `AddTrailingSlash()`.
+func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultTrailingSlashConfig.Skipper
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ url := req.URL
+ path := url.Path
+ qs := c.QueryString()
+ if path != "/" && path[len(path)-1] != '/' {
+ path += "/"
+ uri := path
+ if qs != "" {
+ uri += "?" + qs
+ }
+
+ // Redirect
+ if config.RedirectCode != 0 {
+ return c.Redirect(config.RedirectCode, uri)
+ }
+
+ // Forward
+ req.RequestURI = uri
+ url.Path = path
+ }
+ return next(c)
+ }
+ }
+}
+
+// RemoveTrailingSlash returns a root level (before router) middleware which removes
+// a trailing slash from the request URI.
+//
+// Usage `Echo#Pre(RemoveTrailingSlash())`
+func RemoveTrailingSlash() echo.MiddlewareFunc {
+ return RemoveTrailingSlashWithConfig(TrailingSlashConfig{})
+}
+
+// RemoveTrailingSlashWithConfig returns a RemoveTrailingSlash middleware with config.
+// See `RemoveTrailingSlash()`.
+func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultTrailingSlashConfig.Skipper
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ url := req.URL
+ path := url.Path
+ qs := c.QueryString()
+ l := len(path) - 1
+ if l >= 0 && path != "/" && path[l] == '/' {
+ path = path[:l]
+ uri := path
+ if qs != "" {
+ uri += "?" + qs
+ }
+
+ // Redirect
+ if config.RedirectCode != 0 {
+ return c.Redirect(config.RedirectCode, uri)
+ }
+
+ // Forward
+ req.RequestURI = uri
+ url.Path = path
+ }
+ return next(c)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/static.go b/vendor/github.com/labstack/echo/v4/middleware/static.go
new file mode 100644
index 00000000..bc2087a7
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/static.go
@@ -0,0 +1,229 @@
+package middleware
+
+import (
+ "fmt"
+ "html/template"
+ "net/http"
+ "net/url"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/labstack/echo/v4"
+ "github.com/labstack/gommon/bytes"
+)
+
+type (
+ // StaticConfig defines the config for Static middleware.
+ StaticConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Root directory from where the static content is served.
+ // Required.
+ Root string `yaml:"root"`
+
+ // Index file for serving a directory.
+ // Optional. Default value "index.html".
+ Index string `yaml:"index"`
+
+ // Enable HTML5 mode by forwarding all not-found requests to root so that
+ // SPA (single-page application) can handle the routing.
+ // Optional. Default value false.
+ HTML5 bool `yaml:"html5"`
+
+ // Enable directory browsing.
+ // Optional. Default value false.
+ Browse bool `yaml:"browse"`
+ }
+)
+
+const html = `
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <title>{{ .Name }}</title>
+ <style>
+ body {
+ font-family: Menlo, Consolas, monospace;
+ padding: 48px;
+ }
+ header {
+ padding: 4px 16px;
+ font-size: 24px;
+ }
+ ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 20px 0 0 0;
+ display: flex;
+ flex-wrap: wrap;
+ }
+ li {
+ width: 300px;
+ padding: 16px;
+ }
+ li a {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ transition: opacity 0.25s;
+ }
+ li span {
+ color: #707070;
+ font-size: 12px;
+ }
+ li a:hover {
+ opacity: 0.50;
+ }
+ .dir {
+ color: #E91E63;
+ }
+ .file {
+ color: #673AB7;
+ }
+ </style>
+</head>
+<body>
+ <header>
+ {{ .Name }}
+ </header>
+ <ul>
+ {{ range .Files }}
+ <li>
+ {{ if .Dir }}
+ {{ $name := print .Name "/" }}
+ <a class="dir" href="{{ $name }}">{{ $name }}</a>
+ {{ else }}
+ <a class="file" href="{{ .Name }}">{{ .Name }}</a>
+ <span>{{ .Size }}</span>
+ {{ end }}
+ </li>
+ {{ end }}
+ </ul>
+</body>
+</html>
+`
+
+var (
+ // DefaultStaticConfig is the default Static middleware config.
+ DefaultStaticConfig = StaticConfig{
+ Skipper: DefaultSkipper,
+ Index: "index.html",
+ }
+)
+
+// Static returns a Static middleware to serves static content from the provided
+// root directory.
+func Static(root string) echo.MiddlewareFunc {
+ c := DefaultStaticConfig
+ c.Root = root
+ return StaticWithConfig(c)
+}
+
+// StaticWithConfig returns a Static middleware with config.
+// See `Static()`.
+func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Root == "" {
+ config.Root = "." // For security we want to restrict to CWD.
+ }
+ if config.Skipper == nil {
+ config.Skipper = DefaultStaticConfig.Skipper
+ }
+ if config.Index == "" {
+ config.Index = DefaultStaticConfig.Index
+ }
+
+ // Index template
+ t, err := template.New("index").Parse(html)
+ if err != nil {
+ panic(fmt.Sprintf("echo: %v", err))
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) (err error) {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ p := c.Request().URL.Path
+ if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
+ p = c.Param("*")
+ }
+ p, err = url.PathUnescape(p)
+ if err != nil {
+ return
+ }
+ name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
+
+ fi, err := os.Stat(name)
+ if err != nil {
+ if os.IsNotExist(err) {
+ if err = next(c); err != nil {
+ if he, ok := err.(*echo.HTTPError); ok {
+ if config.HTML5 && he.Code == http.StatusNotFound {
+ return c.File(filepath.Join(config.Root, config.Index))
+ }
+ }
+ return
+ }
+ }
+ return
+ }
+
+ if fi.IsDir() {
+ index := filepath.Join(name, config.Index)
+ fi, err = os.Stat(index)
+
+ if err != nil {
+ if config.Browse {
+ return listDir(t, name, c.Response())
+ }
+ if os.IsNotExist(err) {
+ return next(c)
+ }
+ return
+ }
+
+ return c.File(index)
+ }
+
+ return c.File(name)
+ }
+ }
+}
+
+func listDir(t *template.Template, name string, res *echo.Response) (err error) {
+ file, err := os.Open(name)
+ if err != nil {
+ return
+ }
+ files, err := file.Readdir(-1)
+ if err != nil {
+ return
+ }
+
+ // Create directory index
+ res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
+ data := struct {
+ Name string
+ Files []interface{}
+ }{
+ Name: name,
+ }
+ for _, f := range files {
+ data.Files = append(data.Files, struct {
+ Name string
+ Dir bool
+ Size string
+ }{f.Name(), f.IsDir(), bytes.Format(f.Size())})
+ }
+ return t.Execute(res, data)
+}
diff --git a/vendor/github.com/labstack/echo/v4/response.go b/vendor/github.com/labstack/echo/v4/response.go
new file mode 100644
index 00000000..eb2d988d
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/response.go
@@ -0,0 +1,101 @@
+package echo
+
+import (
+ "bufio"
+ "net"
+ "net/http"
+)
+
+type (
+ // Response wraps an http.ResponseWriter and implements its interface to be used
+ // by an HTTP handler to construct an HTTP response.
+ // See: https://golang.org/pkg/net/http/#ResponseWriter
+ Response struct {
+ echo *Echo
+ beforeFuncs []func()
+ afterFuncs []func()
+ Writer http.ResponseWriter
+ Status int
+ Size int64
+ Committed bool
+ }
+)
+
+// NewResponse creates a new instance of Response.
+func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
+ return &Response{Writer: w, echo: e}
+}
+
+// Header returns the header map for the writer that will be sent by
+// WriteHeader. Changing the header after a call to WriteHeader (or Write) has
+// no effect unless the modified headers were declared as trailers by setting
+// the "Trailer" header before the call to WriteHeader (see example)
+// To suppress implicit response headers, set their value to nil.
+// Example: https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
+func (r *Response) Header() http.Header {
+ return r.Writer.Header()
+}
+
+// Before registers a function which is called just before the response is written.
+func (r *Response) Before(fn func()) {
+ r.beforeFuncs = append(r.beforeFuncs, fn)
+}
+
+// After registers a function which is called just after the response is written.
+// If the `Content-Length` is unknown, none of the after function is executed.
+func (r *Response) After(fn func()) {
+ r.afterFuncs = append(r.afterFuncs, fn)
+}
+
+// WriteHeader sends an HTTP response header with status code. If WriteHeader is
+// not called explicitly, the first call to Write will trigger an implicit
+// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
+// used to send error codes.
+func (r *Response) WriteHeader(code int) {
+ if r.Committed {
+ r.echo.Logger.Warn("response already committed")
+ return
+ }
+ for _, fn := range r.beforeFuncs {
+ fn()
+ }
+ r.Status = code
+ r.Writer.WriteHeader(code)
+ r.Committed = true
+}
+
+// Write writes the data to the connection as part of an HTTP reply.
+func (r *Response) Write(b []byte) (n int, err error) {
+ if !r.Committed {
+ r.WriteHeader(http.StatusOK)
+ }
+ n, err = r.Writer.Write(b)
+ r.Size += int64(n)
+ for _, fn := range r.afterFuncs {
+ fn()
+ }
+ return
+}
+
+// Flush implements the http.Flusher interface to allow an HTTP handler to flush
+// buffered data to the client.
+// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
+func (r *Response) Flush() {
+ r.Writer.(http.Flusher).Flush()
+}
+
+// Hijack implements the http.Hijacker interface to allow an HTTP handler to
+// take over the connection.
+// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
+func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ return r.Writer.(http.Hijacker).Hijack()
+}
+
+func (r *Response) reset(w http.ResponseWriter) {
+ r.beforeFuncs = nil
+ r.afterFuncs = nil
+ r.Writer = w
+ r.Size = 0
+ r.Status = http.StatusOK
+ r.Committed = false
+}
diff --git a/vendor/github.com/labstack/echo/v4/router.go b/vendor/github.com/labstack/echo/v4/router.go
new file mode 100644
index 00000000..73f0b68b
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/router.go
@@ -0,0 +1,435 @@
+package echo
+
+import "net/http"
+
+type (
+ // Router is the registry of all registered routes for an `Echo` instance for
+ // request matching and URL path parameter parsing.
+ Router struct {
+ tree *node
+ routes map[string]*Route
+ echo *Echo
+ }
+ node struct {
+ kind kind
+ label byte
+ prefix string
+ parent *node
+ children children
+ ppath string
+ pnames []string
+ methodHandler *methodHandler
+ }
+ kind uint8
+ children []*node
+ methodHandler struct {
+ connect HandlerFunc
+ delete HandlerFunc
+ get HandlerFunc
+ head HandlerFunc
+ options HandlerFunc
+ patch HandlerFunc
+ post HandlerFunc
+ propfind HandlerFunc
+ put HandlerFunc
+ trace HandlerFunc
+ }
+)
+
+const (
+ skind kind = iota
+ pkind
+ akind
+)
+
+// NewRouter returns a new Router instance.
+func NewRouter(e *Echo) *Router {
+ return &Router{
+ tree: &node{
+ methodHandler: new(methodHandler),
+ },
+ routes: map[string]*Route{},
+ echo: e,
+ }
+}
+
+// Add registers a new route for method and path with matching handler.
+func (r *Router) Add(method, path string, h HandlerFunc) {
+ // Validate path
+ if path == "" {
+ panic("echo: path cannot be empty")
+ }
+ if path[0] != '/' {
+ path = "/" + path
+ }
+ pnames := []string{} // Param names
+ ppath := path // Pristine path
+
+ for i, l := 0, len(path); i < l; i++ {
+ if path[i] == ':' {
+ j := i + 1
+
+ r.insert(method, path[:i], nil, skind, "", nil)
+ for ; i < l && path[i] != '/'; i++ {
+ }
+
+ pnames = append(pnames, path[j:i])
+ path = path[:j] + path[i:]
+ i, l = j, len(path)
+
+ if i == l {
+ r.insert(method, path[:i], h, pkind, ppath, pnames)
+ return
+ }
+ r.insert(method, path[:i], nil, pkind, "", nil)
+ } else if path[i] == '*' {
+ r.insert(method, path[:i], nil, skind, "", nil)
+ pnames = append(pnames, "*")
+ r.insert(method, path[:i+1], h, akind, ppath, pnames)
+ return
+ }
+ }
+
+ r.insert(method, path, h, skind, ppath, pnames)
+}
+
+func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
+ // Adjust max param
+ l := len(pnames)
+ if *r.echo.maxParam < l {
+ *r.echo.maxParam = l
+ }
+
+ cn := r.tree // Current node as root
+ if cn == nil {
+ panic("echo: invalid method")
+ }
+ search := path
+
+ for {
+ sl := len(search)
+ pl := len(cn.prefix)
+ l := 0
+
+ // LCP
+ max := pl
+ if sl < max {
+ max = sl
+ }
+ for ; l < max && search[l] == cn.prefix[l]; l++ {
+ }
+
+ if l == 0 {
+ // At root node
+ cn.label = search[0]
+ cn.prefix = search
+ if h != nil {
+ cn.kind = t
+ cn.addHandler(method, h)
+ cn.ppath = ppath
+ cn.pnames = pnames
+ }
+ } else if l < pl {
+ // Split node
+ n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
+
+ // Reset parent node
+ cn.kind = skind
+ cn.label = cn.prefix[0]
+ cn.prefix = cn.prefix[:l]
+ cn.children = nil
+ cn.methodHandler = new(methodHandler)
+ cn.ppath = ""
+ cn.pnames = nil
+
+ cn.addChild(n)
+
+ if l == sl {
+ // At parent node
+ cn.kind = t
+ cn.addHandler(method, h)
+ cn.ppath = ppath
+ cn.pnames = pnames
+ } else {
+ // Create child node
+ n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)
+ n.addHandler(method, h)
+ cn.addChild(n)
+ }
+ } else if l < sl {
+ search = search[l:]
+ c := cn.findChildWithLabel(search[0])
+ if c != nil {
+ // Go deeper
+ cn = c
+ continue
+ }
+ // Create child node
+ n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)
+ n.addHandler(method, h)
+ cn.addChild(n)
+ } else {
+ // Node already exists
+ if h != nil {
+ cn.addHandler(method, h)
+ cn.ppath = ppath
+ if len(cn.pnames) == 0 { // Issue #729
+ cn.pnames = pnames
+ }
+ }
+ }
+ return
+ }
+}
+
+func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node {
+ return &node{
+ kind: t,
+ label: pre[0],
+ prefix: pre,
+ parent: p,
+ children: c,
+ ppath: ppath,
+ pnames: pnames,
+ methodHandler: mh,
+ }
+}
+
+func (n *node) addChild(c *node) {
+ n.children = append(n.children, c)
+}
+
+func (n *node) findChild(l byte, t kind) *node {
+ for _, c := range n.children {
+ if c.label == l && c.kind == t {
+ return c
+ }
+ }
+ return nil
+}
+
+func (n *node) findChildWithLabel(l byte) *node {
+ for _, c := range n.children {
+ if c.label == l {
+ return c
+ }
+ }
+ return nil
+}
+
+func (n *node) findChildByKind(t kind) *node {
+ for _, c := range n.children {
+ if c.kind == t {
+ return c
+ }
+ }
+ return nil
+}
+
+func (n *node) addHandler(method string, h HandlerFunc) {
+ switch method {
+ case http.MethodConnect:
+ n.methodHandler.connect = h
+ case http.MethodDelete:
+ n.methodHandler.delete = h
+ case http.MethodGet:
+ n.methodHandler.get = h
+ case http.MethodHead:
+ n.methodHandler.head = h
+ case http.MethodOptions:
+ n.methodHandler.options = h
+ case http.MethodPatch:
+ n.methodHandler.patch = h
+ case http.MethodPost:
+ n.methodHandler.post = h
+ case PROPFIND:
+ n.methodHandler.propfind = h
+ case http.MethodPut:
+ n.methodHandler.put = h
+ case http.MethodTrace:
+ n.methodHandler.trace = h
+ }
+}
+
+func (n *node) findHandler(method string) HandlerFunc {
+ switch method {
+ case http.MethodConnect:
+ return n.methodHandler.connect
+ case http.MethodDelete:
+ return n.methodHandler.delete
+ case http.MethodGet:
+ return n.methodHandler.get
+ case http.MethodHead:
+ return n.methodHandler.head
+ case http.MethodOptions:
+ return n.methodHandler.options
+ case http.MethodPatch:
+ return n.methodHandler.patch
+ case http.MethodPost:
+ return n.methodHandler.post
+ case PROPFIND:
+ return n.methodHandler.propfind
+ case http.MethodPut:
+ return n.methodHandler.put
+ case http.MethodTrace:
+ return n.methodHandler.trace
+ default:
+ return nil
+ }
+}
+
+func (n *node) checkMethodNotAllowed() HandlerFunc {
+ for _, m := range methods {
+ if h := n.findHandler(m); h != nil {
+ return MethodNotAllowedHandler
+ }
+ }
+ return NotFoundHandler
+}
+
+// Find lookup a handler registered for method and path. It also parses URL for path
+// parameters and load them into context.
+//
+// For performance:
+//
+// - Get context from `Echo#AcquireContext()`
+// - Reset it `Context#Reset()`
+// - Return it `Echo#ReleaseContext()`.
+func (r *Router) Find(method, path string, c Context) {
+ ctx := c.(*context)
+ ctx.path = path
+ cn := r.tree // Current node as root
+
+ var (
+ search = path
+ child *node // Child node
+ n int // Param counter
+ nk kind // Next kind
+ nn *node // Next node
+ ns string // Next search
+ pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
+ )
+
+ // Search order static > param > any
+ for {
+ if search == "" {
+ break
+ }
+
+ pl := 0 // Prefix length
+ l := 0 // LCP length
+
+ if cn.label != ':' {
+ sl := len(search)
+ pl = len(cn.prefix)
+
+ // LCP
+ max := pl
+ if sl < max {
+ max = sl
+ }
+ for ; l < max && search[l] == cn.prefix[l]; l++ {
+ }
+ }
+
+ if l == pl {
+ // Continue search
+ search = search[l:]
+ } else {
+ cn = nn
+ search = ns
+ if nk == pkind {
+ goto Param
+ } else if nk == akind {
+ goto Any
+ }
+ // Not found
+ return
+ }
+
+ if search == "" {
+ break
+ }
+
+ // Static node
+ if child = cn.findChild(search[0], skind); child != nil {
+ // Save next
+ if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
+ nk = pkind
+ nn = cn
+ ns = search
+ }
+ cn = child
+ continue
+ }
+
+ // Param node
+ Param:
+ if child = cn.findChildByKind(pkind); child != nil {
+ // Issue #378
+ if len(pvalues) == n {
+ continue
+ }
+
+ // Save next
+ if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
+ nk = akind
+ nn = cn
+ ns = search
+ }
+
+ cn = child
+ i, l := 0, len(search)
+ for ; i < l && search[i] != '/'; i++ {
+ }
+ pvalues[n] = search[:i]
+ n++
+ search = search[i:]
+ continue
+ }
+
+ // Any node
+ Any:
+ if cn = cn.findChildByKind(akind); cn == nil {
+ if nn != nil {
+ cn = nn
+ nn = cn.parent // Next (Issue #954)
+ search = ns
+ if nk == pkind {
+ goto Param
+ } else if nk == akind {
+ goto Any
+ }
+ }
+ // Not found
+ return
+ }
+ pvalues[len(cn.pnames)-1] = search
+ break
+ }
+
+ ctx.handler = cn.findHandler(method)
+ ctx.path = cn.ppath
+ ctx.pnames = cn.pnames
+
+ // NOTE: Slow zone...
+ if ctx.handler == nil {
+ ctx.handler = cn.checkMethodNotAllowed()
+
+ // Dig further for any, might have an empty value for *, e.g.
+ // serving a directory. Issue #207.
+ if cn = cn.findChildByKind(akind); cn == nil {
+ return
+ }
+ if h := cn.findHandler(method); h != nil {
+ ctx.handler = h
+ } else {
+ ctx.handler = cn.checkMethodNotAllowed()
+ }
+ ctx.path = cn.ppath
+ ctx.pnames = cn.pnames
+ pvalues[len(cn.pnames)-1] = ""
+ }
+
+ return
+}