summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/labstack/echo
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/labstack/echo')
-rw-r--r--vendor/github.com/labstack/echo/LICENSE21
-rw-r--r--vendor/github.com/labstack/echo/bind.go259
-rw-r--r--vendor/github.com/labstack/echo/context.go551
-rw-r--r--vendor/github.com/labstack/echo/cookbook/auto-tls/server.go26
-rw-r--r--vendor/github.com/labstack/echo/cookbook/cors/server.go38
-rw-r--r--vendor/github.com/labstack/echo/cookbook/crud/server.go75
-rw-r--r--vendor/github.com/labstack/echo/cookbook/embed-resources/server.go21
-rw-r--r--vendor/github.com/labstack/echo/cookbook/file-upload/multiple/server.go65
-rw-r--r--vendor/github.com/labstack/echo/cookbook/file-upload/single/server.go59
-rw-r--r--vendor/github.com/labstack/echo/cookbook/google-app-engine/app-engine.go17
-rw-r--r--vendor/github.com/labstack/echo/cookbook/google-app-engine/app-managed.go25
-rw-r--r--vendor/github.com/labstack/echo/cookbook/google-app-engine/app-standalone.go24
-rw-r--r--vendor/github.com/labstack/echo/cookbook/google-app-engine/app.go4
-rw-r--r--vendor/github.com/labstack/echo/cookbook/google-app-engine/users.go54
-rw-r--r--vendor/github.com/labstack/echo/cookbook/google-app-engine/welcome.go31
-rw-r--r--vendor/github.com/labstack/echo/cookbook/graceful-shutdown/grace/server.go20
-rw-r--r--vendor/github.com/labstack/echo/cookbook/graceful-shutdown/graceful/server.go21
-rw-r--r--vendor/github.com/labstack/echo/cookbook/hello-world/server.go25
-rw-r--r--vendor/github.com/labstack/echo/cookbook/http2/server.go42
-rw-r--r--vendor/github.com/labstack/echo/cookbook/jsonp/server.go35
-rw-r--r--vendor/github.com/labstack/echo/cookbook/jwt/custom-claims/server.go86
-rw-r--r--vendor/github.com/labstack/echo/cookbook/jwt/map-claims/server.go69
-rw-r--r--vendor/github.com/labstack/echo/cookbook/middleware/server.go82
-rw-r--r--vendor/github.com/labstack/echo/cookbook/streaming-response/server.go45
-rw-r--r--vendor/github.com/labstack/echo/cookbook/subdomains/server.go78
-rw-r--r--vendor/github.com/labstack/echo/cookbook/twitter/handler/handler.go14
-rw-r--r--vendor/github.com/labstack/echo/cookbook/twitter/handler/post.go73
-rw-r--r--vendor/github.com/labstack/echo/cookbook/twitter/handler/user.go97
-rw-r--r--vendor/github.com/labstack/echo/cookbook/twitter/model/post.go12
-rw-r--r--vendor/github.com/labstack/echo/cookbook/twitter/model/user.go13
-rw-r--r--vendor/github.com/labstack/echo/cookbook/twitter/server.go52
-rw-r--r--vendor/github.com/labstack/echo/cookbook/websocket/gorilla/server.go47
-rw-r--r--vendor/github.com/labstack/echo/cookbook/websocket/net/server.go41
-rw-r--r--vendor/github.com/labstack/echo/echo.go666
-rw-r--r--vendor/github.com/labstack/echo/group.go104
-rw-r--r--vendor/github.com/labstack/echo/log.go40
-rw-r--r--vendor/github.com/labstack/echo/middleware/basic_auth.go86
-rw-r--r--vendor/github.com/labstack/echo/middleware/body_limit.go116
-rw-r--r--vendor/github.com/labstack/echo/middleware/compress.go121
-rw-r--r--vendor/github.com/labstack/echo/middleware/cors.go139
-rw-r--r--vendor/github.com/labstack/echo/middleware/csrf.go210
-rw-r--r--vendor/github.com/labstack/echo/middleware/jwt.go189
-rw-r--r--vendor/github.com/labstack/echo/middleware/key_auth.go133
-rw-r--r--vendor/github.com/labstack/echo/middleware/logger.go191
-rw-r--r--vendor/github.com/labstack/echo/middleware/method_override.go88
-rw-r--r--vendor/github.com/labstack/echo/middleware/middleware.go14
-rw-r--r--vendor/github.com/labstack/echo/middleware/recover.go85
-rw-r--r--vendor/github.com/labstack/echo/middleware/redirect.go215
-rw-r--r--vendor/github.com/labstack/echo/middleware/secure.go116
-rw-r--r--vendor/github.com/labstack/echo/middleware/slash.go119
-rw-r--r--vendor/github.com/labstack/echo/middleware/static.go118
-rw-r--r--vendor/github.com/labstack/echo/response.go89
-rw-r--r--vendor/github.com/labstack/echo/router.go436
53 files changed, 5397 insertions, 0 deletions
diff --git a/vendor/github.com/labstack/echo/LICENSE b/vendor/github.com/labstack/echo/LICENSE
new file mode 100644
index 00000000..b5b006b4
--- /dev/null
+++ b/vendor/github.com/labstack/echo/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 LabStack
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/labstack/echo/bind.go b/vendor/github.com/labstack/echo/bind.go
new file mode 100644
index 00000000..f2393ea6
--- /dev/null
+++ b/vendor/github.com/labstack/echo/bind.go
@@ -0,0 +1,259 @@
+package echo
+
+import (
+ "encoding/json"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "net/http"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+type (
+ // Binder is the interface that wraps the Bind method.
+ Binder interface {
+ Bind(i interface{}, c Context) error
+ }
+
+ // DefaultBinder is the default implementation of the Binder interface.
+ DefaultBinder struct{}
+
+ // BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
+ BindUnmarshaler interface {
+ // UnmarshalParam decodes and assigns a value from an form or query param.
+ UnmarshalParam(param string) error
+ }
+)
+
+// Bind implements the `Binder#Bind` function.
+func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
+ req := c.Request()
+ if req.Method == GET {
+ if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
+ return NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+ return
+ }
+ ctype := req.Header.Get(HeaderContentType)
+ if req.ContentLength == 0 {
+ return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
+ }
+ switch {
+ case strings.HasPrefix(ctype, MIMEApplicationJSON):
+ if err = json.NewDecoder(req.Body).Decode(i); err != nil {
+ if ute, ok := err.(*json.UnmarshalTypeError); ok {
+ return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, offset=%v", ute.Type, ute.Value, ute.Offset))
+ } else if se, ok := err.(*json.SyntaxError); ok {
+ return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error()))
+ } else {
+ return NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+ }
+ case strings.HasPrefix(ctype, MIMEApplicationXML):
+ if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
+ if ute, ok := err.(*xml.UnsupportedTypeError); ok {
+ return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
+ } else if se, ok := err.(*xml.SyntaxError); ok {
+ return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error()))
+ } else {
+ return NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+ }
+ case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
+ params, err := c.FormParams()
+ if err != nil {
+ return NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+ if err = b.bindData(i, params, "form"); err != nil {
+ return NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+ default:
+ return ErrUnsupportedMediaType
+ }
+ return
+}
+
+func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
+ typ := reflect.TypeOf(ptr).Elem()
+ val := reflect.ValueOf(ptr).Elem()
+
+ if typ.Kind() != reflect.Struct {
+ return errors.New("Binding element must be a struct")
+ }
+
+ for i := 0; i < typ.NumField(); i++ {
+ typeField := typ.Field(i)
+ structField := val.Field(i)
+ if !structField.CanSet() {
+ continue
+ }
+ structFieldKind := structField.Kind()
+ inputFieldName := typeField.Tag.Get(tag)
+
+ if inputFieldName == "" {
+ inputFieldName = typeField.Name
+ // If tag is nil, we inspect if the field is a struct.
+ if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
+ err := b.bindData(structField.Addr().Interface(), data, tag)
+ if err != nil {
+ return err
+ }
+ continue
+ }
+ }
+ inputValue, exists := data[inputFieldName]
+ if !exists {
+ continue
+ }
+
+ // Call this first, in case we're dealing with an alias to an array type
+ if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
+ if err != nil {
+ return err
+ }
+ continue
+ }
+
+ numElems := len(inputValue)
+ if structFieldKind == reflect.Slice && numElems > 0 {
+ sliceOf := structField.Type().Elem().Kind()
+ slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
+ for j := 0; j < numElems; j++ {
+ if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
+ return err
+ }
+ }
+ val.Field(i).Set(slice)
+ } else {
+ if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
+ // But also call it here, in case we're dealing with an array of BindUnmarshalers
+ if ok, err := unmarshalField(valueKind, val, structField); ok {
+ return err
+ }
+
+ switch valueKind {
+ case reflect.Int:
+ return setIntField(val, 0, structField)
+ case reflect.Int8:
+ return setIntField(val, 8, structField)
+ case reflect.Int16:
+ return setIntField(val, 16, structField)
+ case reflect.Int32:
+ return setIntField(val, 32, structField)
+ case reflect.Int64:
+ return setIntField(val, 64, structField)
+ case reflect.Uint:
+ return setUintField(val, 0, structField)
+ case reflect.Uint8:
+ return setUintField(val, 8, structField)
+ case reflect.Uint16:
+ return setUintField(val, 16, structField)
+ case reflect.Uint32:
+ return setUintField(val, 32, structField)
+ case reflect.Uint64:
+ return setUintField(val, 64, structField)
+ case reflect.Bool:
+ return setBoolField(val, structField)
+ case reflect.Float32:
+ return setFloatField(val, 32, structField)
+ case reflect.Float64:
+ return setFloatField(val, 64, structField)
+ case reflect.String:
+ structField.SetString(val)
+ default:
+ return errors.New("unknown type")
+ }
+ return nil
+}
+
+func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
+ switch valueKind {
+ case reflect.Ptr:
+ return unmarshalFieldPtr(val, field)
+ default:
+ return unmarshalFieldNonPtr(val, field)
+ }
+}
+
+// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
+func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) {
+ ptr := reflect.New(field.Type())
+ if ptr.CanInterface() {
+ iface := ptr.Interface()
+ if unmarshaler, ok := iface.(BindUnmarshaler); ok {
+ return unmarshaler, ok
+ }
+ }
+ return nil, false
+}
+
+func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
+ if unmarshaler, ok := bindUnmarshaler(field); ok {
+ err := unmarshaler.UnmarshalParam(value)
+ field.Set(reflect.ValueOf(unmarshaler).Elem())
+ return true, err
+ }
+ return false, nil
+}
+
+func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
+ if field.IsNil() {
+ // Initialize the pointer to a nil value
+ field.Set(reflect.New(field.Type().Elem()))
+ }
+ return unmarshalFieldNonPtr(value, field.Elem())
+}
+
+func setIntField(value string, bitSize int, field reflect.Value) error {
+ if value == "" {
+ value = "0"
+ }
+ intVal, err := strconv.ParseInt(value, 10, bitSize)
+ if err == nil {
+ field.SetInt(intVal)
+ }
+ return err
+}
+
+func setUintField(value string, bitSize int, field reflect.Value) error {
+ if value == "" {
+ value = "0"
+ }
+ uintVal, err := strconv.ParseUint(value, 10, bitSize)
+ if err == nil {
+ field.SetUint(uintVal)
+ }
+ return err
+}
+
+func setBoolField(value string, field reflect.Value) error {
+ if value == "" {
+ value = "false"
+ }
+ boolVal, err := strconv.ParseBool(value)
+ if err == nil {
+ field.SetBool(boolVal)
+ }
+ return err
+}
+
+func setFloatField(value string, bitSize int, field reflect.Value) error {
+ if value == "" {
+ value = "0.0"
+ }
+ floatVal, err := strconv.ParseFloat(value, bitSize)
+ if err == nil {
+ field.SetFloat(floatVal)
+ }
+ return err
+}
diff --git a/vendor/github.com/labstack/echo/context.go b/vendor/github.com/labstack/echo/context.go
new file mode 100644
index 00000000..1a6ebf47
--- /dev/null
+++ b/vendor/github.com/labstack/echo/context.go
@@ -0,0 +1,551 @@
+package echo
+
+import (
+ "bytes"
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+type (
+ // Context represents the context of the current HTTP request. It holds request and
+ // response objects, path, path parameters, data and registered handler.
+ Context interface {
+ // Request returns `*http.Request`.
+ Request() *http.Request
+
+ // SetRequest sets `*http.Request`.
+ SetRequest(r *http.Request)
+
+ // Response returns `*Response`.
+ Response() *Response
+
+ // IsTLS returns true if HTTP connection is TLS otherwise false.
+ IsTLS() bool
+
+ // Scheme returns the HTTP protocol scheme, `http` or `https`.
+ Scheme() string
+
+ // RealIP returns the client's network address based on `X-Forwarded-For`
+ // or `X-Real-IP` request header.
+ RealIP() string
+
+ // Path returns the registered path for the handler.
+ Path() string
+
+ // SetPath sets the registered path for the handler.
+ SetPath(p string)
+
+ // Param returns path parameter by name.
+ Param(name string) string
+
+ // ParamNames returns path parameter names.
+ ParamNames() []string
+
+ // SetParamNames sets path parameter names.
+ SetParamNames(names ...string)
+
+ // ParamValues returns path parameter values.
+ ParamValues() []string
+
+ // SetParamValues sets path parameter values.
+ SetParamValues(values ...string)
+
+ // QueryParam returns the query param for the provided name.
+ QueryParam(name string) string
+
+ // QueryParams returns the query parameters as `url.Values`.
+ QueryParams() url.Values
+
+ // QueryString returns the URL query string.
+ QueryString() string
+
+ // FormValue returns the form field value for the provided name.
+ FormValue(name string) string
+
+ // FormParams returns the form parameters as `url.Values`.
+ FormParams() (url.Values, error)
+
+ // FormFile returns the multipart form file for the provided name.
+ FormFile(name string) (*multipart.FileHeader, error)
+
+ // MultipartForm returns the multipart form.
+ MultipartForm() (*multipart.Form, error)
+
+ // Cookie returns the named cookie provided in the request.
+ Cookie(name string) (*http.Cookie, error)
+
+ // SetCookie adds a `Set-Cookie` header in HTTP response.
+ SetCookie(cookie *http.Cookie)
+
+ // Cookies returns the HTTP cookies sent with the request.
+ Cookies() []*http.Cookie
+
+ // Get retrieves data from the context.
+ Get(key string) interface{}
+
+ // Set saves data in the context.
+ Set(key string, val interface{})
+
+ // Bind binds the request body into provided type `i`. The default binder
+ // does it based on Content-Type header.
+ Bind(i interface{}) error
+
+ // Validate validates provided `i`. It is usually called after `Context#Bind()`.
+ // Validator must be registered using `Echo#Validator`.
+ Validate(i interface{}) error
+
+ // Render renders a template with data and sends a text/html response with status
+ // code. Renderer must be registered using `Echo.Renderer`.
+ Render(code int, name string, data interface{}) error
+
+ // HTML sends an HTTP response with status code.
+ HTML(code int, html string) error
+
+ // HTMLBlob sends an HTTP blob response with status code.
+ HTMLBlob(code int, b []byte) error
+
+ // String sends a string response with status code.
+ String(code int, s string) error
+
+ // JSON sends a JSON response with status code.
+ JSON(code int, i interface{}) error
+
+ // JSONPretty sends a pretty-print JSON with status code.
+ JSONPretty(code int, i interface{}, indent string) error
+
+ // JSONBlob sends a JSON blob response with status code.
+ JSONBlob(code int, b []byte) error
+
+ // JSONP sends a JSONP response with status code. It uses `callback` to construct
+ // the JSONP payload.
+ JSONP(code int, callback string, i interface{}) error
+
+ // JSONPBlob sends a JSONP blob response with status code. It uses `callback`
+ // to construct the JSONP payload.
+ JSONPBlob(code int, callback string, b []byte) error
+
+ // XML sends an XML response with status code.
+ XML(code int, i interface{}) error
+
+ // XMLPretty sends a pretty-print XML with status code.
+ XMLPretty(code int, i interface{}, indent string) error
+
+ // XMLBlob sends an XML blob response with status code.
+ XMLBlob(code int, b []byte) error
+
+ // Blob sends a blob response with status code and content type.
+ Blob(code int, contentType string, b []byte) error
+
+ // Stream sends a streaming response with status code and content type.
+ Stream(code int, contentType string, r io.Reader) error
+
+ // File sends a response with the content of the file.
+ File(file string) error
+
+ // Attachment sends a response as attachment, prompting client to save the
+ // file.
+ Attachment(file string, name string) error
+
+ // Inline sends a response as inline, opening the file in the browser.
+ Inline(file string, name string) error
+
+ // NoContent sends a response with no body and a status code.
+ NoContent(code int) error
+
+ // Redirect redirects the request to a provided URL with status code.
+ Redirect(code int, url string) error
+
+ // Error invokes the registered HTTP error handler. Generally used by middleware.
+ Error(err error)
+
+ // Handler returns the matched handler by router.
+ Handler() HandlerFunc
+
+ // SetHandler sets the matched handler by router.
+ SetHandler(h HandlerFunc)
+
+ // Logger returns the `Logger` instance.
+ Logger() Logger
+
+ // Echo returns the `Echo` instance.
+ Echo() *Echo
+
+ // Reset resets the context after request completes. It must be called along
+ // with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
+ // See `Echo#ServeHTTP()`
+ Reset(r *http.Request, w http.ResponseWriter)
+ }
+
+ context struct {
+ request *http.Request
+ response *Response
+ path string
+ pnames []string
+ pvalues []string
+ query url.Values
+ handler HandlerFunc
+ store Map
+ echo *Echo
+ }
+)
+
+const (
+ defaultMemory = 32 << 20 // 32 MB
+ indexPage = "index.html"
+)
+
+func (c *context) Request() *http.Request {
+ return c.request
+}
+
+func (c *context) SetRequest(r *http.Request) {
+ c.request = r
+}
+
+func (c *context) Response() *Response {
+ return c.response
+}
+
+func (c *context) IsTLS() bool {
+ return c.request.TLS != nil
+}
+
+func (c *context) Scheme() string {
+ // Can't use `r.Request.URL.Scheme`
+ // See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
+ if c.IsTLS() {
+ return "https"
+ }
+ return "http"
+}
+
+func (c *context) RealIP() string {
+ ra := c.request.RemoteAddr
+ if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
+ ra = ip
+ } else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
+ ra = ip
+ } else {
+ ra, _, _ = net.SplitHostPort(ra)
+ }
+ return ra
+}
+
+func (c *context) Path() string {
+ return c.path
+}
+
+func (c *context) SetPath(p string) {
+ c.path = p
+}
+
+func (c *context) Param(name string) string {
+ for i, n := range c.pnames {
+ if i < len(c.pvalues) {
+ if n == name {
+ return c.pvalues[i]
+ }
+
+ // Param name with aliases
+ for _, p := range strings.Split(n, ",") {
+ if p == name {
+ return c.pvalues[i]
+ }
+ }
+ }
+ }
+ return ""
+}
+
+func (c *context) ParamNames() []string {
+ return c.pnames
+}
+
+func (c *context) SetParamNames(names ...string) {
+ c.pnames = names
+}
+
+func (c *context) ParamValues() []string {
+ return c.pvalues
+}
+
+func (c *context) SetParamValues(values ...string) {
+ c.pvalues = values
+}
+
+func (c *context) QueryParam(name string) string {
+ if c.query == nil {
+ c.query = c.request.URL.Query()
+ }
+ return c.query.Get(name)
+}
+
+func (c *context) QueryParams() url.Values {
+ if c.query == nil {
+ c.query = c.request.URL.Query()
+ }
+ return c.query
+}
+
+func (c *context) QueryString() string {
+ return c.request.URL.RawQuery
+}
+
+func (c *context) FormValue(name string) string {
+ return c.request.FormValue(name)
+}
+
+func (c *context) FormParams() (url.Values, error) {
+ if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
+ if err := c.request.ParseMultipartForm(defaultMemory); err != nil {
+ return nil, err
+ }
+ } else {
+ if err := c.request.ParseForm(); err != nil {
+ return nil, err
+ }
+ }
+ return c.request.Form, nil
+}
+
+func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
+ _, fh, err := c.request.FormFile(name)
+ return fh, err
+}
+
+func (c *context) MultipartForm() (*multipart.Form, error) {
+ err := c.request.ParseMultipartForm(defaultMemory)
+ return c.request.MultipartForm, err
+}
+
+func (c *context) Cookie(name string) (*http.Cookie, error) {
+ return c.request.Cookie(name)
+}
+
+func (c *context) SetCookie(cookie *http.Cookie) {
+ http.SetCookie(c.Response(), cookie)
+}
+
+func (c *context) Cookies() []*http.Cookie {
+ return c.request.Cookies()
+}
+
+func (c *context) Get(key string) interface{} {
+ return c.store[key]
+}
+
+func (c *context) Set(key string, val interface{}) {
+ if c.store == nil {
+ c.store = make(Map)
+ }
+ c.store[key] = val
+}
+
+func (c *context) Bind(i interface{}) error {
+ return c.echo.Binder.Bind(i, c)
+}
+
+func (c *context) Validate(i interface{}) error {
+ if c.echo.Validator == nil {
+ return ErrValidatorNotRegistered
+ }
+ return c.echo.Validator.Validate(i)
+}
+
+func (c *context) Render(code int, name string, data interface{}) (err error) {
+ if c.echo.Renderer == nil {
+ return ErrRendererNotRegistered
+ }
+ buf := new(bytes.Buffer)
+ if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
+ return
+ }
+ return c.HTMLBlob(code, buf.Bytes())
+}
+
+func (c *context) HTML(code int, html string) (err error) {
+ return c.HTMLBlob(code, []byte(html))
+}
+
+func (c *context) HTMLBlob(code int, b []byte) (err error) {
+ return c.Blob(code, MIMETextHTMLCharsetUTF8, b)
+}
+
+func (c *context) String(code int, s string) (err error) {
+ return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
+}
+
+func (c *context) JSON(code int, i interface{}) (err error) {
+ if c.echo.Debug {
+ return c.JSONPretty(code, i, " ")
+ }
+ b, err := json.Marshal(i)
+ if err != nil {
+ return
+ }
+ return c.JSONBlob(code, b)
+}
+
+func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
+ b, err := json.MarshalIndent(i, "", indent)
+ if err != nil {
+ return
+ }
+ return c.JSONBlob(code, b)
+}
+
+func (c *context) JSONBlob(code int, b []byte) (err error) {
+ return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
+}
+
+func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
+ b, err := json.Marshal(i)
+ if err != nil {
+ return
+ }
+ return c.JSONPBlob(code, callback, b)
+}
+
+func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
+ c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8)
+ c.response.WriteHeader(code)
+ if _, err = c.response.Write([]byte(callback + "(")); err != nil {
+ return
+ }
+ if _, err = c.response.Write(b); err != nil {
+ return
+ }
+ _, err = c.response.Write([]byte(");"))
+ return
+}
+
+func (c *context) XML(code int, i interface{}) (err error) {
+ if c.echo.Debug {
+ return c.XMLPretty(code, i, " ")
+ }
+ b, err := xml.Marshal(i)
+ if err != nil {
+ return
+ }
+ return c.XMLBlob(code, b)
+}
+
+func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
+ b, err := xml.MarshalIndent(i, "", indent)
+ if err != nil {
+ return
+ }
+ return c.XMLBlob(code, b)
+}
+
+func (c *context) XMLBlob(code int, b []byte) (err error) {
+ c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8)
+ c.response.WriteHeader(code)
+ if _, err = c.response.Write([]byte(xml.Header)); err != nil {
+ return
+ }
+ _, err = c.response.Write(b)
+ return
+}
+
+func (c *context) Blob(code int, contentType string, b []byte) (err error) {
+ c.response.Header().Set(HeaderContentType, contentType)
+ c.response.WriteHeader(code)
+ _, err = c.response.Write(b)
+ return
+}
+
+func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
+ c.response.Header().Set(HeaderContentType, contentType)
+ c.response.WriteHeader(code)
+ _, err = io.Copy(c.response, r)
+ return
+}
+
+func (c *context) File(file string) error {
+ f, err := os.Open(file)
+ if err != nil {
+ return ErrNotFound
+ }
+ defer f.Close()
+
+ fi, _ := f.Stat()
+ if fi.IsDir() {
+ file = filepath.Join(file, indexPage)
+ f, err = os.Open(file)
+ if err != nil {
+ return ErrNotFound
+ }
+ defer f.Close()
+ if fi, err = f.Stat(); err != nil {
+ return err
+ }
+ }
+ http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
+ return nil
+}
+
+func (c *context) Attachment(file, name string) (err error) {
+ return c.contentDisposition(file, name, "attachment")
+}
+
+func (c *context) Inline(file, name string) (err error) {
+ return c.contentDisposition(file, name, "inline")
+}
+
+func (c *context) contentDisposition(file, name, dispositionType string) (err error) {
+ c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%s", dispositionType, name))
+ c.File(file)
+ return
+}
+
+func (c *context) NoContent(code int) error {
+ c.response.WriteHeader(code)
+ return nil
+}
+
+func (c *context) Redirect(code int, url string) error {
+ if code < http.StatusMultipleChoices || code > http.StatusTemporaryRedirect {
+ return ErrInvalidRedirectCode
+ }
+ c.response.Header().Set(HeaderLocation, url)
+ c.response.WriteHeader(code)
+ return nil
+}
+
+func (c *context) Error(err error) {
+ c.echo.HTTPErrorHandler(err, c)
+}
+
+func (c *context) Echo() *Echo {
+ return c.echo
+}
+
+func (c *context) Handler() HandlerFunc {
+ return c.handler
+}
+
+func (c *context) SetHandler(h HandlerFunc) {
+ c.handler = h
+}
+
+func (c *context) Logger() Logger {
+ return c.echo.Logger
+}
+
+func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
+ c.request = r
+ c.response.reset(w)
+ c.query = nil
+ c.handler = NotFoundHandler
+ c.store = nil
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/auto-tls/server.go b/vendor/github.com/labstack/echo/cookbook/auto-tls/server.go
new file mode 100644
index 00000000..4a8bbdfd
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/auto-tls/server.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "net/http"
+
+ "golang.org/x/crypto/acme/autocert"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+func main() {
+ e := echo.New()
+ // e.AutoTLSManager.HostPolicy = autocert.HostWhitelist("<DOMAIN>")
+ // Cache certificates
+ e.AutoTLSManager.Cache = autocert.DirCache("/var/www/.cache")
+ e.Use(middleware.Recover())
+ e.Use(middleware.Logger())
+ e.GET("/", func(c echo.Context) error {
+ return c.HTML(http.StatusOK, `
+ <h1>Welcome to Echo!</h1>
+ <h3>TLS certificates automatically installed from Let's Encrypt :)</h3>
+ `)
+ })
+ e.Logger.Fatal(e.StartAutoTLS(":443"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/cors/server.go b/vendor/github.com/labstack/echo/cookbook/cors/server.go
new file mode 100644
index 00000000..0cc5c345
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/cors/server.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+var (
+ users = []string{"Joe", "Veer", "Zion"}
+)
+
+func getUsers(c echo.Context) error {
+ return c.JSON(http.StatusOK, users)
+}
+
+func main() {
+ e := echo.New()
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ // CORS default
+ // Allows requests from any origin wth GET, HEAD, PUT, POST or DELETE method.
+ // e.Use(middleware.CORS())
+
+ // CORS restricted
+ // Allows requests from any `https://labstack.com` or `https://labstack.net` origin
+ // wth GET, PUT, POST or DELETE method.
+ e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
+ AllowOrigins: []string{"https://labstack.com", "https://labstack.net"},
+ AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
+ }))
+
+ e.GET("/api/users", getUsers)
+
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/crud/server.go b/vendor/github.com/labstack/echo/cookbook/crud/server.go
new file mode 100644
index 00000000..fbb5c754
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/crud/server.go
@@ -0,0 +1,75 @@
+package main
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+type (
+ user struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ }
+)
+
+var (
+ users = map[int]*user{}
+ seq = 1
+)
+
+//----------
+// Handlers
+//----------
+
+func createUser(c echo.Context) error {
+ u := &user{
+ ID: seq,
+ }
+ if err := c.Bind(u); err != nil {
+ return err
+ }
+ users[u.ID] = u
+ seq++
+ return c.JSON(http.StatusCreated, u)
+}
+
+func getUser(c echo.Context) error {
+ id, _ := strconv.Atoi(c.Param("id"))
+ return c.JSON(http.StatusOK, users[id])
+}
+
+func updateUser(c echo.Context) error {
+ u := new(user)
+ if err := c.Bind(u); err != nil {
+ return err
+ }
+ id, _ := strconv.Atoi(c.Param("id"))
+ users[id].Name = u.Name
+ return c.JSON(http.StatusOK, users[id])
+}
+
+func deleteUser(c echo.Context) error {
+ id, _ := strconv.Atoi(c.Param("id"))
+ delete(users, id)
+ return c.NoContent(http.StatusNoContent)
+}
+
+func main() {
+ e := echo.New()
+
+ // Middleware
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ // Routes
+ e.POST("/users", createUser)
+ e.GET("/users/:id", getUser)
+ e.PUT("/users/:id", updateUser)
+ e.DELETE("/users/:id", deleteUser)
+
+ // Start server
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/embed-resources/server.go b/vendor/github.com/labstack/echo/cookbook/embed-resources/server.go
new file mode 100644
index 00000000..e5b0c3db
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/embed-resources/server.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "net/http"
+
+ rice "github.com/GeertJohan/go.rice"
+ "github.com/labstack/echo"
+)
+
+func main() {
+ e := echo.New()
+ // the file server for rice. "app" is the folder where the files come from.
+ assetHandler := http.FileServer(rice.MustFindBox("app").HTTPBox())
+ // serves the index.html from rice
+ e.GET("/", echo.WrapHandler(assetHandler))
+
+ // servers other static files
+ e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler)))
+
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/file-upload/multiple/server.go b/vendor/github.com/labstack/echo/cookbook/file-upload/multiple/server.go
new file mode 100644
index 00000000..cd0f54d3
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/file-upload/multiple/server.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+func upload(c echo.Context) error {
+ // Read form fields
+ name := c.FormValue("name")
+ email := c.FormValue("email")
+
+ //------------
+ // Read files
+ //------------
+
+ // Multipart form
+ form, err := c.MultipartForm()
+ if err != nil {
+ return err
+ }
+ files := form.File["files"]
+
+ for _, file := range files {
+ // Source
+ src, err := file.Open()
+ if err != nil {
+ return err
+ }
+ defer src.Close()
+
+ // Destination
+ dst, err := os.Create(file.Filename)
+ if err != nil {
+ return err
+ }
+ defer dst.Close()
+
+ // Copy
+ if _, err = io.Copy(dst, src); err != nil {
+ return err
+ }
+
+ }
+
+ return c.HTML(http.StatusOK, fmt.Sprintf("<p>Uploaded successfully %d files with fields name=%s and email=%s.</p>", len(files), name, email))
+}
+
+func main() {
+ e := echo.New()
+
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ e.Static("/", "public")
+ e.POST("/upload", upload)
+
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/file-upload/single/server.go b/vendor/github.com/labstack/echo/cookbook/file-upload/single/server.go
new file mode 100644
index 00000000..1b84f220
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/file-upload/single/server.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+func upload(c echo.Context) error {
+ // Read form fields
+ name := c.FormValue("name")
+ email := c.FormValue("email")
+
+ //-----------
+ // Read file
+ //-----------
+
+ // Source
+ file, err := c.FormFile("file")
+ if err != nil {
+ return err
+ }
+ src, err := file.Open()
+ if err != nil {
+ return err
+ }
+ defer src.Close()
+
+ // Destination
+ dst, err := os.Create(file.Filename)
+ if err != nil {
+ return err
+ }
+ defer dst.Close()
+
+ // Copy
+ if _, err = io.Copy(dst, src); err != nil {
+ return err
+ }
+
+ return c.HTML(http.StatusOK, fmt.Sprintf("<p>File %s uploaded successfully with fields name=%s and email=%s.</p>", file.Filename, name, email))
+}
+
+func main() {
+ e := echo.New()
+
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ e.Static("/", "public")
+ e.POST("/upload", upload)
+
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-engine.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-engine.go
new file mode 100644
index 00000000..0c1db087
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-engine.go
@@ -0,0 +1,17 @@
+// +build appengine
+
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+)
+
+func createMux() *echo.Echo {
+ e := echo.New()
+ // note: we don't need to provide the middleware or static handlers, that's taken care of by the platform
+ // app engine has it's own "main" wrapper - we just need to hook echo into the default handler
+ http.Handle("/", e)
+ return e
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-managed.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-managed.go
new file mode 100644
index 00000000..7b8eacf8
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-managed.go
@@ -0,0 +1,25 @@
+// +build appenginevm
+
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+ "google.golang.org/appengine"
+)
+
+func createMux() *echo.Echo {
+ e := echo.New()
+ // note: we don't need to provide the middleware or static handlers
+ // for the appengine vm version - that's taken care of by the platform
+ return e
+}
+
+func main() {
+ // the appengine package provides a convenient method to handle the health-check requests
+ // and also run the app on the correct port. We just need to add Echo to the default handler
+ e := echo.New(":8080")
+ http.Handle("/", e)
+ appengine.Main()
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-standalone.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-standalone.go
new file mode 100644
index 00000000..c3b44dc0
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app-standalone.go
@@ -0,0 +1,24 @@
+// +build !appengine,!appenginevm
+
+package main
+
+import (
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+func createMux() *echo.Echo {
+ e := echo.New()
+
+ e.Use(middleware.Recover())
+ e.Use(middleware.Logger())
+ e.Use(middleware.Gzip())
+
+ e.Static("/", "public")
+
+ return e
+}
+
+func main() {
+ e.Logger.Fatal(e.Start(":8080"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/app.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app.go
new file mode 100644
index 00000000..8d4d97a2
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/app.go
@@ -0,0 +1,4 @@
+package main
+
+// reference our echo instance and create it early
+var e = createMux()
diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/users.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/users.go
new file mode 100644
index 00000000..19533e51
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/users.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+type (
+ user struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ }
+)
+
+var (
+ users map[string]user
+)
+
+func init() {
+ users = map[string]user{
+ "1": user{
+ ID: "1",
+ Name: "Wreck-It Ralph",
+ },
+ }
+
+ // hook into the echo instance to create an endpoint group
+ // and add specific middleware to it plus handlers
+ g := e.Group("/users")
+ g.Use(middleware.CORS())
+
+ g.POST("", createUser)
+ g.GET("", getUsers)
+ g.GET("/:id", getUser)
+}
+
+func createUser(c echo.Context) error {
+ u := new(user)
+ if err := c.Bind(u); err != nil {
+ return err
+ }
+ users[u.ID] = *u
+ return c.JSON(http.StatusCreated, u)
+}
+
+func getUsers(c echo.Context) error {
+ return c.JSON(http.StatusOK, users)
+}
+
+func getUser(c echo.Context) error {
+ return c.JSON(http.StatusOK, users[c.Param("id")])
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/google-app-engine/welcome.go b/vendor/github.com/labstack/echo/cookbook/google-app-engine/welcome.go
new file mode 100644
index 00000000..2639b209
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/google-app-engine/welcome.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "html/template"
+ "io"
+ "net/http"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ Template struct {
+ templates *template.Template
+ }
+)
+
+func init() {
+ t := &Template{
+ templates: template.Must(template.ParseFiles("templates/welcome.html")),
+ }
+ e.Renderer = t
+ e.GET("/welcome", welcome)
+}
+
+func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
+ return t.templates.ExecuteTemplate(w, name, data)
+}
+
+func welcome(c echo.Context) error {
+ return c.Render(http.StatusOK, "welcome", "Joe")
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/grace/server.go b/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/grace/server.go
new file mode 100644
index 00000000..1f9937b0
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/grace/server.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "net/http"
+
+ "github.com/facebookgo/grace/gracehttp"
+ "github.com/labstack/echo"
+)
+
+func main() {
+ // Setup
+ e := echo.New()
+ e.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "Six sick bricks tick")
+ })
+ e.Server.Addr = ":1323"
+
+ // Serve it like a boss
+ e.Logger.Fatal(gracehttp.Serve(e.Server))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/graceful/server.go b/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/graceful/server.go
new file mode 100644
index 00000000..39e7b634
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/graceful-shutdown/graceful/server.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/labstack/echo"
+ "github.com/tylerb/graceful"
+)
+
+func main() {
+ // Setup
+ e := echo.New()
+ e.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "Sue sews rose on slow joe crows nose")
+ })
+ e.Server.Addr = ":1323"
+
+ // Serve it like a boss
+ graceful.ListenAndServe(e.Server, 5*time.Second)
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/hello-world/server.go b/vendor/github.com/labstack/echo/cookbook/hello-world/server.go
new file mode 100644
index 00000000..06e0718b
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/hello-world/server.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+func main() {
+ // Echo instance
+ e := echo.New()
+
+ // Middleware
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ // Route => handler
+ e.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "Hello, World!\n")
+ })
+
+ // Start server
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/http2/server.go b/vendor/github.com/labstack/echo/cookbook/http2/server.go
new file mode 100644
index 00000000..8db989c4
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/http2/server.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/labstack/echo"
+)
+
+func request(c echo.Context) error {
+ req := c.Request()
+ format := "<pre><strong>Request Information</strong>\n\n<code>Protocol: %s\nHost: %s\nRemote Address: %s\nMethod: %s\nPath: %s\n</code></pre>"
+ return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path))
+}
+
+func stream(c echo.Context) error {
+ res := c.Response()
+ gone := res.CloseNotify()
+ res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
+ res.WriteHeader(http.StatusOK)
+ ticker := time.NewTicker(1 * time.Second)
+ defer ticker.Stop()
+
+ fmt.Fprint(res, "<pre><strong>Clock Stream</strong>\n\n<code>")
+ for {
+ fmt.Fprintf(res, "%v\n", time.Now())
+ res.Flush()
+ select {
+ case <-ticker.C:
+ case <-gone:
+ break
+ }
+ }
+}
+
+func main() {
+ e := echo.New()
+ e.GET("/request", request)
+ e.GET("/stream", stream)
+ e.Logger.Fatal(e.StartTLS(":1323", "cert.pem", "key.pem"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/jsonp/server.go b/vendor/github.com/labstack/echo/cookbook/jsonp/server.go
new file mode 100644
index 00000000..ba46bab0
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/jsonp/server.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+ "math/rand"
+ "net/http"
+ "time"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+func main() {
+ e := echo.New()
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ e.Static("/", "public")
+
+ // JSONP
+ e.GET("/jsonp", func(c echo.Context) error {
+ callback := c.QueryParam("callback")
+ var content struct {
+ Response string `json:"response"`
+ Timestamp time.Time `json:"timestamp"`
+ Random int `json:"random"`
+ }
+ content.Response = "Sent via JSONP"
+ content.Timestamp = time.Now().UTC()
+ content.Random = rand.Intn(1000)
+ return c.JSONP(http.StatusOK, callback, &content)
+ })
+
+ // Start server
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/jwt/custom-claims/server.go b/vendor/github.com/labstack/echo/cookbook/jwt/custom-claims/server.go
new file mode 100644
index 00000000..b3a13205
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/jwt/custom-claims/server.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+ "net/http"
+ "time"
+
+ jwt "github.com/dgrijalva/jwt-go"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+// jwtCustomClaims are custom claims extending default ones.
+type jwtCustomClaims struct {
+ Name string `json:"name"`
+ Admin bool `json:"admin"`
+ jwt.StandardClaims
+}
+
+func login(c echo.Context) error {
+ username := c.FormValue("username")
+ password := c.FormValue("password")
+
+ if username == "jon" && password == "shhh!" {
+
+ // Set custom claims
+ claims := &jwtCustomClaims{
+ "Jon Snow",
+ true,
+ jwt.StandardClaims{
+ ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
+ },
+ }
+
+ // Create token with claims
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+
+ // Generate encoded token and send it as response.
+ t, err := token.SignedString([]byte("secret"))
+ if err != nil {
+ return err
+ }
+ return c.JSON(http.StatusOK, echo.Map{
+ "token": t,
+ })
+ }
+
+ return echo.ErrUnauthorized
+}
+
+func accessible(c echo.Context) error {
+ return c.String(http.StatusOK, "Accessible")
+}
+
+func restricted(c echo.Context) error {
+ user := c.Get("user").(*jwt.Token)
+ claims := user.Claims.(*jwtCustomClaims)
+ name := claims.Name
+ return c.String(http.StatusOK, "Welcome "+name+"!")
+}
+
+func main() {
+ e := echo.New()
+
+ // Middleware
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ // Login route
+ e.POST("/login", login)
+
+ // Unauthenticated route
+ e.GET("/", accessible)
+
+ // Restricted group
+ r := e.Group("/restricted")
+
+ // Configure middleware with the custom claims type
+ config := middleware.JWTConfig{
+ Claims: &jwtCustomClaims{},
+ SigningKey: []byte("secret"),
+ }
+ r.Use(middleware.JWTWithConfig(config))
+ r.GET("", restricted)
+
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/jwt/map-claims/server.go b/vendor/github.com/labstack/echo/cookbook/jwt/map-claims/server.go
new file mode 100644
index 00000000..678be490
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/jwt/map-claims/server.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "net/http"
+ "time"
+
+ jwt "github.com/dgrijalva/jwt-go"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+func login(c echo.Context) error {
+ username := c.FormValue("username")
+ password := c.FormValue("password")
+
+ if username == "jon" && password == "shhh!" {
+ // Create token
+ token := jwt.New(jwt.SigningMethodHS256)
+
+ // Set claims
+ claims := token.Claims.(jwt.MapClaims)
+ claims["name"] = "Jon Snow"
+ claims["admin"] = true
+ claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
+
+ // Generate encoded token and send it as response.
+ t, err := token.SignedString([]byte("secret"))
+ if err != nil {
+ return err
+ }
+ return c.JSON(http.StatusOK, map[string]string{
+ "token": t,
+ })
+ }
+
+ return echo.ErrUnauthorized
+}
+
+func accessible(c echo.Context) error {
+ return c.String(http.StatusOK, "Accessible")
+}
+
+func restricted(c echo.Context) error {
+ user := c.Get("user").(*jwt.Token)
+ claims := user.Claims.(jwt.MapClaims)
+ name := claims["name"].(string)
+ return c.String(http.StatusOK, "Welcome "+name+"!")
+}
+
+func main() {
+ e := echo.New()
+
+ // Middleware
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ // Login route
+ e.POST("/login", login)
+
+ // Unauthenticated route
+ e.GET("/", accessible)
+
+ // Restricted group
+ r := e.Group("/restricted")
+ r.Use(middleware.JWT([]byte("secret")))
+ r.GET("", restricted)
+
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/middleware/server.go b/vendor/github.com/labstack/echo/cookbook/middleware/server.go
new file mode 100644
index 00000000..2f21df50
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/middleware/server.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+ "net/http"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ Stats struct {
+ Uptime time.Time `json:"uptime"`
+ RequestCount uint64 `json:"requestCount"`
+ Statuses map[string]int `json:"statuses"`
+ mutex sync.RWMutex
+ }
+)
+
+func NewStats() *Stats {
+ return &Stats{
+ Uptime: time.Now(),
+ Statuses: make(map[string]int),
+ }
+}
+
+// Process is the middleware function.
+func (s *Stats) Process(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if err := next(c); err != nil {
+ c.Error(err)
+ }
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ s.RequestCount++
+ status := strconv.Itoa(c.Response().Status)
+ s.Statuses[status]++
+ return nil
+ }
+}
+
+// Handle is the endpoint to get stats.
+func (s *Stats) Handle(c echo.Context) error {
+ s.mutex.RLock()
+ defer s.mutex.RUnlock()
+ return c.JSON(http.StatusOK, s)
+}
+
+// ServerHeader middleware adds a `Server` header to the response.
+func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ c.Response().Header().Set(echo.HeaderServer, "Echo/3.0")
+ return next(c)
+ }
+}
+
+func main() {
+ e := echo.New()
+
+ // Debug mode
+ e.Debug = true
+
+ //-------------------
+ // Custom middleware
+ //-------------------
+ // Stats
+ s := NewStats()
+ e.Use(s.Process)
+ e.GET("/stats", s.Handle) // Endpoint to get stats
+
+ // Server header
+ e.Use(ServerHeader)
+
+ // Handler
+ e.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "Hello, World!")
+ })
+
+ // Start server
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/streaming-response/server.go b/vendor/github.com/labstack/echo/cookbook/streaming-response/server.go
new file mode 100644
index 00000000..a3a679ef
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/streaming-response/server.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "net/http"
+ "time"
+
+ "encoding/json"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ Geolocation struct {
+ Altitude float64
+ Latitude float64
+ Longitude float64
+ }
+)
+
+var (
+ locations = []Geolocation{
+ {-97, 37.819929, -122.478255},
+ {1899, 39.096849, -120.032351},
+ {2619, 37.865101, -119.538329},
+ {42, 33.812092, -117.918974},
+ {15, 37.77493, -122.419416},
+ }
+)
+
+func main() {
+ e := echo.New()
+ e.GET("/", func(c echo.Context) error {
+ c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+ c.Response().WriteHeader(http.StatusOK)
+ for _, l := range locations {
+ if err := json.NewEncoder(c.Response()).Encode(l); err != nil {
+ return err
+ }
+ c.Response().Flush()
+ time.Sleep(1 * time.Second)
+ }
+ return nil
+ })
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/subdomains/server.go b/vendor/github.com/labstack/echo/cookbook/subdomains/server.go
new file mode 100644
index 00000000..ef4f65f9
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/subdomains/server.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+)
+
+type (
+ Host struct {
+ Echo *echo.Echo
+ }
+)
+
+func main() {
+ // Hosts
+ hosts := make(map[string]*Host)
+
+ //-----
+ // API
+ //-----
+
+ api := echo.New()
+ api.Use(middleware.Logger())
+ api.Use(middleware.Recover())
+
+ hosts["api.localhost:1323"] = &Host{api}
+
+ api.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "API")
+ })
+
+ //------
+ // Blog
+ //------
+
+ blog := echo.New()
+ blog.Use(middleware.Logger())
+ blog.Use(middleware.Recover())
+
+ hosts["blog.localhost:1323"] = &Host{blog}
+
+ blog.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "Blog")
+ })
+
+ //---------
+ // Website
+ //---------
+
+ site := echo.New()
+ site.Use(middleware.Logger())
+ site.Use(middleware.Recover())
+
+ hosts["localhost:1323"] = &Host{site}
+
+ site.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "Website")
+ })
+
+ // Server
+ e := echo.New()
+ e.Any("/*", func(c echo.Context) (err error) {
+ req := c.Request()
+ res := c.Response()
+ host := hosts[req.Host]
+
+ if host == nil {
+ err = echo.ErrNotFound
+ } else {
+ host.Echo.ServeHTTP(res, req)
+ }
+
+ return
+ })
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/handler/handler.go b/vendor/github.com/labstack/echo/cookbook/twitter/handler/handler.go
new file mode 100644
index 00000000..263c5e21
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/handler/handler.go
@@ -0,0 +1,14 @@
+package handler
+
+import mgo "gopkg.in/mgo.v2"
+
+type (
+ Handler struct {
+ DB *mgo.Session
+ }
+)
+
+const (
+ // Key (Should come from somewhere else).
+ Key = "secret"
+)
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/handler/post.go b/vendor/github.com/labstack/echo/cookbook/twitter/handler/post.go
new file mode 100644
index 00000000..b1428a30
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/handler/post.go
@@ -0,0 +1,73 @@
+package handler
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/cookbook/twitter/model"
+ mgo "gopkg.in/mgo.v2"
+ "gopkg.in/mgo.v2/bson"
+)
+
+func (h *Handler) CreatePost(c echo.Context) (err error) {
+ u := &model.User{
+ ID: bson.ObjectIdHex(userIDFromToken(c)),
+ }
+ p := &model.Post{
+ ID: bson.NewObjectId(),
+ From: u.ID.Hex(),
+ }
+ if err = c.Bind(p); err != nil {
+ return
+ }
+
+ // Validation
+ if p.To == "" || p.Message == "" {
+ return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid to or message fields"}
+ }
+
+ // Find user from database
+ db := h.DB.Clone()
+ defer db.Close()
+ if err = db.DB("twitter").C("users").FindId(u.ID).One(u); err != nil {
+ if err == mgo.ErrNotFound {
+ return echo.ErrNotFound
+ }
+ return
+ }
+
+ // Save post in database
+ if err = db.DB("twitter").C("posts").Insert(p); err != nil {
+ return
+ }
+ return c.JSON(http.StatusCreated, p)
+}
+
+func (h *Handler) FetchPost(c echo.Context) (err error) {
+ userID := userIDFromToken(c)
+ page, _ := strconv.Atoi(c.QueryParam("page"))
+ limit, _ := strconv.Atoi(c.QueryParam("limit"))
+
+ // Defaults
+ if page == 0 {
+ page = 1
+ }
+ if limit == 0 {
+ limit = 100
+ }
+
+ // Retrieve posts from database
+ posts := []*model.Post{}
+ db := h.DB.Clone()
+ if err = db.DB("twitter").C("posts").
+ Find(bson.M{"to": userID}).
+ Skip((page - 1) * limit).
+ Limit(limit).
+ All(&posts); err != nil {
+ return
+ }
+ defer db.Close()
+
+ return c.JSON(http.StatusOK, posts)
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/handler/user.go b/vendor/github.com/labstack/echo/cookbook/twitter/handler/user.go
new file mode 100644
index 00000000..a34d2f4e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/handler/user.go
@@ -0,0 +1,97 @@
+package handler
+
+import (
+ "net/http"
+ "time"
+
+ jwt "github.com/dgrijalva/jwt-go"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/cookbook/twitter/model"
+ mgo "gopkg.in/mgo.v2"
+ "gopkg.in/mgo.v2/bson"
+)
+
+func (h *Handler) Signup(c echo.Context) (err error) {
+ // Bind
+ u := &model.User{ID: bson.NewObjectId()}
+ if err = c.Bind(u); err != nil {
+ return
+ }
+
+ // Validate
+ if u.Email == "" || u.Password == "" {
+ return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid email or password"}
+ }
+
+ // Save user
+ db := h.DB.Clone()
+ defer db.Close()
+ if err = db.DB("twitter").C("users").Insert(u); err != nil {
+ return
+ }
+
+ return c.JSON(http.StatusCreated, u)
+}
+
+func (h *Handler) Login(c echo.Context) (err error) {
+ // Bind
+ u := new(model.User)
+ if err = c.Bind(u); err != nil {
+ return
+ }
+
+ // Find user
+ db := h.DB.Clone()
+ defer db.Close()
+ if err = db.DB("twitter").C("users").
+ Find(bson.M{"email": u.Email, "password": u.Password}).One(u); err != nil {
+ if err == mgo.ErrNotFound {
+ return &echo.HTTPError{Code: http.StatusUnauthorized, Message: "invalid email or password"}
+ }
+ return
+ }
+
+ //-----
+ // JWT
+ //-----
+
+ // Create token
+ token := jwt.New(jwt.SigningMethodHS256)
+
+ // Set claims
+ claims := token.Claims.(jwt.MapClaims)
+ claims["id"] = u.ID
+ claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
+
+ // Generate encoded token and send it as response
+ u.Token, err = token.SignedString([]byte(Key))
+ if err != nil {
+ return err
+ }
+
+ u.Password = "" // Don't send password
+ return c.JSON(http.StatusOK, u)
+}
+
+func (h *Handler) Follow(c echo.Context) (err error) {
+ userID := userIDFromToken(c)
+ id := c.Param("id")
+
+ // Add a follower to user
+ db := h.DB.Clone()
+ defer db.Close()
+ if err = db.DB("twitter").C("users").
+ UpdateId(bson.ObjectIdHex(id), bson.M{"$addToSet": bson.M{"followers": userID}}); err != nil {
+ if err == mgo.ErrNotFound {
+ return echo.ErrNotFound
+ }
+ }
+
+ return
+}
+
+func userIDFromToken(c echo.Context) string {
+ user := c.Get("user").(*jwt.Token)
+ claims := user.Claims.(jwt.MapClaims)
+ return claims["id"].(string)
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/model/post.go b/vendor/github.com/labstack/echo/cookbook/twitter/model/post.go
new file mode 100644
index 00000000..7344296e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/model/post.go
@@ -0,0 +1,12 @@
+package model
+
+import "gopkg.in/mgo.v2/bson"
+
+type (
+ Post struct {
+ ID bson.ObjectId `json:"id" bson:"_id,omitempty"`
+ To string `json:"to" bson:"to"`
+ From string `json:"from" bson:"from"`
+ Message string `json:"message" bson:"message"`
+ }
+)
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/model/user.go b/vendor/github.com/labstack/echo/cookbook/twitter/model/user.go
new file mode 100644
index 00000000..e063c89b
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/model/user.go
@@ -0,0 +1,13 @@
+package model
+
+import "gopkg.in/mgo.v2/bson"
+
+type (
+ User struct {
+ ID bson.ObjectId `json:"id" bson:"_id,omitempty"`
+ Email string `json:"email" bson:"email"`
+ Password string `json:"password,omitempty" bson:"password"`
+ Token string `json:"token,omitempty" bson:"-"`
+ Followers []string `json:"followers,omitempty" bson:"followers,omitempty"`
+ }
+)
diff --git a/vendor/github.com/labstack/echo/cookbook/twitter/server.go b/vendor/github.com/labstack/echo/cookbook/twitter/server.go
new file mode 100644
index 00000000..22db7aa0
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/twitter/server.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/cookbook/twitter/handler"
+ "github.com/labstack/echo/middleware"
+ "github.com/labstack/gommon/log"
+ mgo "gopkg.in/mgo.v2"
+)
+
+func main() {
+ e := echo.New()
+ e.Logger.SetLevel(log.ERROR)
+ e.Use(middleware.Logger())
+ e.Use(middleware.JWTWithConfig(middleware.JWTConfig{
+ SigningKey: []byte(handler.Key),
+ Skipper: func(c echo.Context) bool {
+ // Skip authentication for and signup login requests
+ if c.Path() == "/login" || c.Path() == "/signup" {
+ return true
+ }
+ return false
+ },
+ }))
+
+ // Database connection
+ db, err := mgo.Dial("localhost")
+ if err != nil {
+ e.Logger.Fatal(err)
+ }
+
+ // Create indices
+ if err = db.Copy().DB("twitter").C("users").EnsureIndex(mgo.Index{
+ Key: []string{"email"},
+ Unique: true,
+ }); err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize handler
+ h := &handler.Handler{DB: db}
+
+ // Routes
+ e.POST("/signup", h.Signup)
+ e.POST("/login", h.Login)
+ e.POST("/follow/:id", h.Follow)
+ e.POST("/posts", h.CreatePost)
+ e.GET("/feed", h.FetchPost)
+
+ // Start server
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/websocket/gorilla/server.go b/vendor/github.com/labstack/echo/cookbook/websocket/gorilla/server.go
new file mode 100644
index 00000000..e9d52dbb
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/websocket/gorilla/server.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/labstack/echo"
+
+ "github.com/gorilla/websocket"
+ "github.com/labstack/echo/middleware"
+)
+
+var (
+ upgrader = websocket.Upgrader{}
+)
+
+func hello(c echo.Context) error {
+ ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
+ if err != nil {
+ return err
+ }
+ defer ws.Close()
+
+ for {
+ // Write
+ err := ws.WriteMessage(websocket.TextMessage, []byte("Hello, Client!"))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Read
+ _, msg, err := ws.ReadMessage()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s\n", msg)
+ }
+}
+
+func main() {
+ e := echo.New()
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+ e.Static("/", "../public")
+ e.GET("/ws", hello)
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/cookbook/websocket/net/server.go b/vendor/github.com/labstack/echo/cookbook/websocket/net/server.go
new file mode 100644
index 00000000..aa746030
--- /dev/null
+++ b/vendor/github.com/labstack/echo/cookbook/websocket/net/server.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+ "golang.org/x/net/websocket"
+)
+
+func hello(c echo.Context) error {
+ websocket.Handler(func(ws *websocket.Conn) {
+ defer ws.Close()
+ for {
+ // Write
+ err := websocket.Message.Send(ws, "Hello, Client!")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Read
+ msg := ""
+ err = websocket.Message.Receive(ws, &msg)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s\n", msg)
+ }
+ }).ServeHTTP(c.Response(), c.Request())
+ return nil
+}
+
+func main() {
+ e := echo.New()
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+ e.Static("/", "../public")
+ e.GET("/ws", hello)
+ e.Logger.Fatal(e.Start(":1323"))
+}
diff --git a/vendor/github.com/labstack/echo/echo.go b/vendor/github.com/labstack/echo/echo.go
new file mode 100644
index 00000000..f0c1b72e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/echo.go
@@ -0,0 +1,666 @@
+/*
+Package echo implements high performance, minimalist Go web framework.
+
+Example:
+
+ package main
+
+ import (
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/middleware"
+ )
+
+ // Handler
+ func hello(c echo.Context) error {
+ return c.String(http.StatusOK, "Hello, World!")
+ }
+
+ func main() {
+ // Echo instance
+ e := echo.New()
+
+ // Middleware
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+
+ // Routes
+ e.GET("/", hello)
+
+ // Start server
+ e.Logger.Fatal(e.Start(":1323"))
+ }
+
+Learn more at https://echo.labstack.com
+*/
+package echo
+
+import (
+ "bytes"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "io"
+ slog "log"
+ "net"
+ "net/http"
+ "path"
+ "reflect"
+ "runtime"
+ "sync"
+ "time"
+
+ "github.com/labstack/gommon/color"
+ "github.com/labstack/gommon/log"
+ "golang.org/x/crypto/acme/autocert"
+)
+
+type (
+ // Echo is the top-level framework instance.
+ Echo struct {
+ stdLogger *slog.Logger
+ colorer *color.Color
+ premiddleware []MiddlewareFunc
+ middleware []MiddlewareFunc
+ maxParam *int
+ router *Router
+ notFoundHandler HandlerFunc
+ pool sync.Pool
+ Server *http.Server
+ TLSServer *http.Server
+ Listener net.Listener
+ TLSListener net.Listener
+ DisableHTTP2 bool
+ Debug bool
+ HTTPErrorHandler HTTPErrorHandler
+ Binder Binder
+ Validator Validator
+ Renderer Renderer
+ AutoTLSManager autocert.Manager
+ Mutex sync.RWMutex
+ Logger Logger
+ }
+
+ // Route contains a handler and information for matching against requests.
+ Route struct {
+ Method string
+ Path string
+ Handler string
+ }
+
+ // HTTPError represents an error that occurred while handling a request.
+ HTTPError struct {
+ Code int
+ Message interface{}
+ }
+
+ // MiddlewareFunc defines a function to process middleware.
+ MiddlewareFunc func(HandlerFunc) HandlerFunc
+
+ // HandlerFunc defines a function to server HTTP requests.
+ HandlerFunc func(Context) error
+
+ // HTTPErrorHandler is a centralized HTTP error handler.
+ HTTPErrorHandler func(error, Context)
+
+ // Validator is the interface that wraps the Validate function.
+ Validator interface {
+ Validate(i interface{}) error
+ }
+
+ // Renderer is the interface that wraps the Render function.
+ Renderer interface {
+ Render(io.Writer, string, interface{}, Context) error
+ }
+
+ // Map defines a generic map of type `map[string]interface{}`.
+ Map map[string]interface{}
+
+ // i is the interface for Echo and Group.
+ i interface {
+ GET(string, HandlerFunc, ...MiddlewareFunc)
+ }
+)
+
+// HTTP methods
+const (
+ CONNECT = "CONNECT"
+ DELETE = "DELETE"
+ GET = "GET"
+ HEAD = "HEAD"
+ OPTIONS = "OPTIONS"
+ PATCH = "PATCH"
+ POST = "POST"
+ PUT = "PUT"
+ TRACE = "TRACE"
+)
+
+// MIME types
+const (
+ MIMEApplicationJSON = "application/json"
+ MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
+ MIMEApplicationJavaScript = "application/javascript"
+ MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
+ MIMEApplicationXML = "application/xml"
+ MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
+ MIMEApplicationForm = "application/x-www-form-urlencoded"
+ MIMEApplicationProtobuf = "application/protobuf"
+ MIMEApplicationMsgpack = "application/msgpack"
+ MIMETextHTML = "text/html"
+ MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
+ MIMETextPlain = "text/plain"
+ MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
+ MIMEMultipartForm = "multipart/form-data"
+ MIMEOctetStream = "application/octet-stream"
+)
+
+const (
+ charsetUTF8 = "charset=UTF-8"
+)
+
+// Headers
+const (
+ HeaderAcceptEncoding = "Accept-Encoding"
+ HeaderAllow = "Allow"
+ HeaderAuthorization = "Authorization"
+ HeaderContentDisposition = "Content-Disposition"
+ HeaderContentEncoding = "Content-Encoding"
+ HeaderContentLength = "Content-Length"
+ HeaderContentType = "Content-Type"
+ HeaderCookie = "Cookie"
+ HeaderSetCookie = "Set-Cookie"
+ HeaderIfModifiedSince = "If-Modified-Since"
+ HeaderLastModified = "Last-Modified"
+ HeaderLocation = "Location"
+ HeaderUpgrade = "Upgrade"
+ HeaderVary = "Vary"
+ HeaderWWWAuthenticate = "WWW-Authenticate"
+ HeaderXForwardedProto = "X-Forwarded-Proto"
+ HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
+ HeaderXForwardedFor = "X-Forwarded-For"
+ HeaderXRealIP = "X-Real-IP"
+ HeaderServer = "Server"
+ HeaderOrigin = "Origin"
+ HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
+ HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
+ HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
+ HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
+ HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
+ HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
+ HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
+ HeaderAccessControlMaxAge = "Access-Control-Max-Age"
+
+ // Security
+ HeaderStrictTransportSecurity = "Strict-Transport-Security"
+ HeaderXContentTypeOptions = "X-Content-Type-Options"
+ HeaderXXSSProtection = "X-XSS-Protection"
+ HeaderXFrameOptions = "X-Frame-Options"
+ HeaderContentSecurityPolicy = "Content-Security-Policy"
+ HeaderXCSRFToken = "X-CSRF-Token"
+)
+
+var (
+ methods = [...]string{
+ CONNECT,
+ DELETE,
+ GET,
+ HEAD,
+ OPTIONS,
+ PATCH,
+ POST,
+ PUT,
+ TRACE,
+ }
+)
+
+// Errors
+var (
+ ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
+ ErrNotFound = NewHTTPError(http.StatusNotFound)
+ ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
+ ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
+ ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
+ ErrValidatorNotRegistered = errors.New("Validator not registered")
+ ErrRendererNotRegistered = errors.New("Renderer not registered")
+ ErrInvalidRedirectCode = errors.New("Invalid redirect status code")
+ ErrCookieNotFound = errors.New("Cookie not found")
+)
+
+// Error handlers
+var (
+ NotFoundHandler = func(c Context) error {
+ return ErrNotFound
+ }
+
+ MethodNotAllowedHandler = func(c Context) error {
+ return ErrMethodNotAllowed
+ }
+)
+
+// New creates an instance of Echo.
+func New() (e *Echo) {
+ e = &Echo{
+ Server: new(http.Server),
+ TLSServer: new(http.Server),
+ AutoTLSManager: autocert.Manager{
+ Prompt: autocert.AcceptTOS,
+ },
+ Logger: log.New("echo"),
+ colorer: color.New(),
+ maxParam: new(int),
+ }
+ e.Server.Handler = e
+ e.TLSServer.Handler = e
+ e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
+ e.Binder = &DefaultBinder{}
+ e.Logger.SetLevel(log.OFF)
+ e.stdLogger = slog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
+ e.pool.New = func() interface{} {
+ return e.NewContext(nil, nil)
+ }
+ e.router = NewRouter(e)
+ return
+}
+
+// NewContext returns a Context instance.
+func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
+ return &context{
+ request: r,
+ response: NewResponse(w, e),
+ store: make(Map),
+ echo: e,
+ pvalues: make([]string, *e.maxParam),
+ handler: NotFoundHandler,
+ }
+}
+
+// Router returns router.
+func (e *Echo) Router() *Router {
+ return e.router
+}
+
+// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
+// with status code.
+func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
+ var (
+ code = http.StatusInternalServerError
+ msg interface{}
+ )
+
+ if he, ok := err.(*HTTPError); ok {
+ code = he.Code
+ msg = he.Message
+ } else if e.Debug {
+ msg = err.Error()
+ } else {
+ msg = http.StatusText(code)
+ }
+ if _, ok := msg.(string); ok {
+ msg = Map{"message": msg}
+ }
+
+ if !c.Response().Committed {
+ if c.Request().Method == HEAD { // Issue #608
+ if err := c.NoContent(code); err != nil {
+ goto ERROR
+ }
+ } else {
+ if err := c.JSON(code, msg); err != nil {
+ goto ERROR
+ }
+ }
+ }
+ERROR:
+ e.Logger.Error(err)
+}
+
+// Pre adds middleware to the chain which is run before router.
+func (e *Echo) Pre(middleware ...MiddlewareFunc) {
+ e.premiddleware = append(e.premiddleware, middleware...)
+}
+
+// Use adds middleware to the chain which is run after router.
+func (e *Echo) Use(middleware ...MiddlewareFunc) {
+ e.middleware = append(e.middleware, middleware...)
+}
+
+// CONNECT registers a new CONNECT route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(CONNECT, path, h, m...)
+}
+
+// DELETE registers a new DELETE route for a path with matching handler in the router
+// with optional route-level middleware.
+func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(DELETE, path, h, m...)
+}
+
+// GET registers a new GET route for a path with matching handler in the router
+// with optional route-level middleware.
+func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(GET, path, h, m...)
+}
+
+// HEAD registers a new HEAD route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(HEAD, path, h, m...)
+}
+
+// OPTIONS registers a new OPTIONS route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(OPTIONS, path, h, m...)
+}
+
+// PATCH registers a new PATCH route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(PATCH, path, h, m...)
+}
+
+// POST registers a new POST route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(POST, path, h, m...)
+}
+
+// PUT registers a new PUT route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(PUT, path, h, m...)
+}
+
+// TRACE registers a new TRACE route for a path with matching handler in the
+// router with optional route-level middleware.
+func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ e.add(TRACE, path, h, m...)
+}
+
+// Any registers a new route for all HTTP methods and path with matching handler
+// in the router with optional route-level middleware.
+func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+ for _, m := range methods {
+ e.add(m, path, handler, middleware...)
+ }
+}
+
+// Match registers a new route for multiple HTTP methods and path with matching
+// handler in the router with optional route-level middleware.
+func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+ for _, m := range methods {
+ e.add(m, path, handler, middleware...)
+ }
+}
+
+// Static registers a new route with path prefix to serve static files from the
+// provided root directory.
+func (e *Echo) Static(prefix, root string) {
+ static(e, prefix, root)
+}
+
+func static(i i, prefix, root string) {
+ h := func(c Context) error {
+ return c.File(path.Join(root, c.Param("*")))
+ }
+ i.GET(prefix, h)
+ if prefix == "/" {
+ i.GET(prefix+"*", h)
+ } else {
+ i.GET(prefix+"/*", h)
+ }
+}
+
+// File registers a new route with path to serve a static file.
+func (e *Echo) File(path, file string) {
+ e.GET(path, func(c Context) error {
+ return c.File(file)
+ })
+}
+
+func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+ name := handlerName(handler)
+ e.router.Add(method, path, func(c Context) error {
+ h := handler
+ // Chain middleware
+ for i := len(middleware) - 1; i >= 0; i-- {
+ h = middleware[i](h)
+ }
+ return h(c)
+ })
+ r := Route{
+ Method: method,
+ Path: path,
+ Handler: name,
+ }
+ e.router.routes[method+path] = r
+}
+
+// Group creates a new router group with prefix and optional group-level middleware.
+func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
+ g = &Group{prefix: prefix, echo: e}
+ g.Use(m...)
+ return
+}
+
+// URI generates a URI from handler.
+func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
+ uri := new(bytes.Buffer)
+ ln := len(params)
+ n := 0
+ name := handlerName(handler)
+ for _, r := range e.router.routes {
+ if r.Handler == name {
+ for i, l := 0, len(r.Path); i < l; i++ {
+ if r.Path[i] == ':' && n < ln {
+ for ; i < l && r.Path[i] != '/'; i++ {
+ }
+ uri.WriteString(fmt.Sprintf("%v", params[n]))
+ n++
+ }
+ if i < l {
+ uri.WriteByte(r.Path[i])
+ }
+ }
+ break
+ }
+ }
+ return uri.String()
+}
+
+// URL is an alias for `URI` function.
+func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
+ return e.URI(h, params...)
+}
+
+// Routes returns the registered routes.
+func (e *Echo) Routes() []Route {
+ routes := []Route{}
+ for _, v := range e.router.routes {
+ routes = append(routes, v)
+ }
+ return routes
+}
+
+// AcquireContext returns an empty `Context` instance from the pool.
+// You must return the context by calling `ReleaseContext()`.
+func (e *Echo) AcquireContext() Context {
+ return e.pool.Get().(Context)
+}
+
+// ReleaseContext returns the `Context` instance back to the pool.
+// You must call it after `AcquireContext()`.
+func (e *Echo) ReleaseContext(c Context) {
+ e.pool.Put(c)
+}
+
+// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
+func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ // Acquire lock
+ e.Mutex.RLock()
+ defer e.Mutex.RUnlock()
+
+ // Acquire context
+ c := e.pool.Get().(*context)
+ defer e.pool.Put(c)
+ c.Reset(r, w)
+
+ // Middleware
+ h := func(c Context) error {
+ method := r.Method
+ path := r.URL.EscapedPath()
+ e.router.Find(method, path, c)
+ h := c.Handler()
+ for i := len(e.middleware) - 1; i >= 0; i-- {
+ h = e.middleware[i](h)
+ }
+ return h(c)
+ }
+
+ // Premiddleware
+ for i := len(e.premiddleware) - 1; i >= 0; i-- {
+ h = e.premiddleware[i](h)
+ }
+
+ // Execute chain
+ if err := h(c); err != nil {
+ e.HTTPErrorHandler(err, c)
+ }
+}
+
+// Start starts an HTTP server.
+func (e *Echo) Start(address string) error {
+ e.Server.Addr = address
+ return e.StartServer(e.Server)
+}
+
+// StartTLS starts an HTTPS server.
+func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) {
+ if certFile == "" || keyFile == "" {
+ return errors.New("invalid tls configuration")
+ }
+ s := e.TLSServer
+ s.TLSConfig = new(tls.Config)
+ s.TLSConfig.Certificates = make([]tls.Certificate, 1)
+ s.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+ if err != nil {
+ return
+ }
+ return e.startTLS(address)
+}
+
+// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
+func (e *Echo) StartAutoTLS(address string) error {
+ s := e.TLSServer
+ s.TLSConfig = new(tls.Config)
+ s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
+ return e.startTLS(address)
+}
+
+func (e *Echo) startTLS(address string) error {
+ s := e.TLSServer
+ s.Addr = address
+ if !e.DisableHTTP2 {
+ s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
+ }
+ return e.StartServer(e.TLSServer)
+}
+
+// StartServer starts a custom http server.
+func (e *Echo) StartServer(s *http.Server) (err error) {
+ // Setup
+ e.colorer.SetOutput(e.Logger.Output())
+ s.Handler = e
+ s.ErrorLog = e.stdLogger
+
+ if s.TLSConfig == nil {
+ if e.Listener == nil {
+ e.Listener, err = newListener(s.Addr)
+ if err != nil {
+ return err
+ }
+ }
+ e.colorer.Printf("⇛ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
+ return s.Serve(e.Listener)
+ }
+ if e.TLSListener == nil {
+ l, err := newListener(s.Addr)
+ if err != nil {
+ return err
+ }
+ e.TLSListener = tls.NewListener(l, s.TLSConfig)
+ }
+ e.colorer.Printf("⇛ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
+ return s.Serve(e.TLSListener)
+}
+
+// NewHTTPError creates a new HTTPError instance.
+func NewHTTPError(code int, message ...interface{}) *HTTPError {
+ he := &HTTPError{Code: code, Message: http.StatusText(code)}
+ if len(message) > 0 {
+ he.Message = message[0]
+ }
+ return he
+}
+
+// Error makes it compatible with `error` interface.
+func (he *HTTPError) Error() string {
+ return fmt.Sprintf("code=%d, message=%s", he.Code, he.Message)
+}
+
+// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
+func WrapHandler(h http.Handler) HandlerFunc {
+ return func(c Context) error {
+ h.ServeHTTP(c.Response(), c.Request())
+ return nil
+ }
+}
+
+// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc`
+func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
+ return func(next HandlerFunc) HandlerFunc {
+ return func(c Context) (err error) {
+ m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c.SetRequest(r)
+ err = next(c)
+ })).ServeHTTP(c.Response(), c.Request())
+ return
+ }
+ }
+}
+
+func handlerName(h HandlerFunc) string {
+ t := reflect.ValueOf(h).Type()
+ if t.Kind() == reflect.Func {
+ return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
+ }
+ return t.String()
+}
+
+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
+// connections. It's used by ListenAndServe and ListenAndServeTLS so
+// dead TCP connections (e.g. closing laptop mid-download) eventually
+// go away.
+type tcpKeepAliveListener struct {
+ *net.TCPListener
+}
+
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
+ tc, err := ln.AcceptTCP()
+ if err != nil {
+ return
+ }
+ tc.SetKeepAlive(true)
+ tc.SetKeepAlivePeriod(3 * time.Minute)
+ return tc, nil
+}
+
+func newListener(address string) (*tcpKeepAliveListener, error) {
+ l, err := net.Listen("tcp", address)
+ if err != nil {
+ return nil, err
+ }
+ return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil
+}
diff --git a/vendor/github.com/labstack/echo/group.go b/vendor/github.com/labstack/echo/group.go
new file mode 100644
index 00000000..9767bb19
--- /dev/null
+++ b/vendor/github.com/labstack/echo/group.go
@@ -0,0 +1,104 @@
+package echo
+
+type (
+ // Group is a set of sub-routes for a specified route. It can be used for inner
+ // routes that share a common middlware or functionality that should be separate
+ // from the parent echo instance while still inheriting from it.
+ Group struct {
+ prefix string
+ middleware []MiddlewareFunc
+ echo *Echo
+ }
+)
+
+// Use implements `Echo#Use()` for sub-routes within the Group.
+func (g *Group) Use(middleware ...MiddlewareFunc) {
+ g.middleware = append(g.middleware, middleware...)
+}
+
+// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
+func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(CONNECT, path, h, m...)
+}
+
+// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
+func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(DELETE, path, h, m...)
+}
+
+// GET implements `Echo#GET()` for sub-routes within the Group.
+func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(GET, path, h, m...)
+}
+
+// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
+func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(HEAD, path, h, m...)
+}
+
+// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
+func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(OPTIONS, path, h, m...)
+}
+
+// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
+func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(PATCH, path, h, m...)
+}
+
+// POST implements `Echo#POST()` for sub-routes within the Group.
+func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(POST, path, h, m...)
+}
+
+// PUT implements `Echo#PUT()` for sub-routes within the Group.
+func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(PUT, path, h, m...)
+}
+
+// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
+func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
+ g.add(TRACE, path, h, m...)
+}
+
+// Any implements `Echo#Any()` for sub-routes within the Group.
+func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+ for _, m := range methods {
+ g.add(m, path, handler, middleware...)
+ }
+}
+
+// Match implements `Echo#Match()` for sub-routes within the Group.
+func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+ for _, m := range methods {
+ g.add(m, path, handler, middleware...)
+ }
+}
+
+// Group creates a new sub-group with prefix and optional sub-group-level middleware.
+func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
+ m := []MiddlewareFunc{}
+ m = append(m, g.middleware...)
+ m = append(m, middleware...)
+ return g.echo.Group(g.prefix+prefix, m...)
+}
+
+// Static implements `Echo#Static()` for sub-routes within the Group.
+func (g *Group) Static(prefix, root string) {
+ static(g, prefix, root)
+}
+
+// File implements `Echo#File()` for sub-routes within the Group.
+func (g *Group) File(path, file string) {
+ g.echo.File(g.prefix+path, file)
+}
+
+func (g *Group) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
+ // Combine into a new slice to avoid accidentally passing the same slice for
+ // multiple routes, which would lead to later add() calls overwriting the
+ // middleware from earlier calls.
+ m := []MiddlewareFunc{}
+ m = append(m, g.middleware...)
+ m = append(m, middleware...)
+ g.echo.add(method, g.prefix+path, handler, m...)
+}
diff --git a/vendor/github.com/labstack/echo/log.go b/vendor/github.com/labstack/echo/log.go
new file mode 100644
index 00000000..b194c39c
--- /dev/null
+++ b/vendor/github.com/labstack/echo/log.go
@@ -0,0 +1,40 @@
+package echo
+
+import (
+ "io"
+
+ "github.com/labstack/gommon/log"
+)
+
+type (
+ // Logger defines the logging interface.
+ Logger interface {
+ Output() io.Writer
+ SetOutput(w io.Writer)
+ Prefix() string
+ SetPrefix(p string)
+ Level() log.Lvl
+ SetLevel(v log.Lvl)
+ Print(i ...interface{})
+ Printf(format string, args ...interface{})
+ Printj(j log.JSON)
+ Debug(i ...interface{})
+ Debugf(format string, args ...interface{})
+ Debugj(j log.JSON)
+ Info(i ...interface{})
+ Infof(format string, args ...interface{})
+ Infoj(j log.JSON)
+ Warn(i ...interface{})
+ Warnf(format string, args ...interface{})
+ Warnj(j log.JSON)
+ Error(i ...interface{})
+ Errorf(format string, args ...interface{})
+ Errorj(j log.JSON)
+ Fatal(i ...interface{})
+ Fatalj(j log.JSON)
+ Fatalf(format string, args ...interface{})
+ Panic(i ...interface{})
+ Panicj(j log.JSON)
+ Panicf(format string, args ...interface{})
+ }
+)
diff --git a/vendor/github.com/labstack/echo/middleware/basic_auth.go b/vendor/github.com/labstack/echo/middleware/basic_auth.go
new file mode 100644
index 00000000..e98a87e3
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/basic_auth.go
@@ -0,0 +1,86 @@
+package middleware
+
+import (
+ "encoding/base64"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ // BasicAuthConfig defines the config for BasicAuth middleware.
+ BasicAuthConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Validator is a function to validate BasicAuth credentials.
+ // Required.
+ Validator BasicAuthValidator
+ }
+
+ // BasicAuthValidator defines a function to validate BasicAuth credentials.
+ BasicAuthValidator func(string, string, echo.Context) bool
+)
+
+const (
+ basic = "Basic"
+)
+
+var (
+ // DefaultBasicAuthConfig is the default BasicAuth middleware config.
+ DefaultBasicAuthConfig = BasicAuthConfig{
+ Skipper: DefaultSkipper,
+ }
+)
+
+// BasicAuth returns an BasicAuth middleware.
+//
+// For valid credentials it calls the next handler.
+// For missing or invalid credentials, it sends "401 - Unauthorized" response.
+func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc {
+ c := DefaultBasicAuthConfig
+ c.Validator = fn
+ return BasicAuthWithConfig(c)
+}
+
+// BasicAuthWithConfig returns an BasicAuth middleware with config.
+// See `BasicAuth()`.
+func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Validator == nil {
+ panic("basic-auth middleware requires a validator function")
+ }
+ if config.Skipper == nil {
+ config.Skipper = DefaultBasicAuthConfig.Skipper
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ auth := c.Request().Header.Get(echo.HeaderAuthorization)
+ l := len(basic)
+
+ if len(auth) > l+1 && auth[:l] == basic {
+ b, err := base64.StdEncoding.DecodeString(auth[l+1:])
+ if err != nil {
+ return err
+ }
+ cred := string(b)
+ for i := 0; i < len(cred); i++ {
+ if cred[i] == ':' {
+ // Verify credentials
+ if config.Validator(cred[:i], cred[i+1:], c) {
+ return next(c)
+ }
+ }
+ }
+ }
+
+ // Need to return `401` for browsers to pop-up login box.
+ c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm=Restricted")
+ return echo.ErrUnauthorized
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/body_limit.go b/vendor/github.com/labstack/echo/middleware/body_limit.go
new file mode 100644
index 00000000..a2ff8d62
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/body_limit.go
@@ -0,0 +1,116 @@
+package middleware
+
+import (
+ "fmt"
+ "io"
+ "sync"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/gommon/bytes"
+)
+
+type (
+ // BodyLimitConfig defines the config for BodyLimit middleware.
+ BodyLimitConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Maximum allowed size for a request body, it can be specified
+ // as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
+ Limit string `json:"limit"`
+ limit int64
+ }
+
+ limitedReader struct {
+ BodyLimitConfig
+ reader io.ReadCloser
+ read int64
+ context echo.Context
+ }
+)
+
+var (
+ // DefaultBodyLimitConfig is the default Gzip middleware config.
+ DefaultBodyLimitConfig = BodyLimitConfig{
+ Skipper: DefaultSkipper,
+ }
+)
+
+// BodyLimit returns a BodyLimit middleware.
+//
+// BodyLimit middleware sets the maximum allowed size for a request body, if the
+// size exceeds the configured limit, it sends "413 - Request Entity Too Large"
+// response. The BodyLimit is determined based on both `Content-Length` request
+// header and actual content read, which makes it super secure.
+// Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M,
+// G, T or P.
+func BodyLimit(limit string) echo.MiddlewareFunc {
+ c := DefaultBodyLimitConfig
+ c.Limit = limit
+ return BodyLimitWithConfig(c)
+}
+
+// BodyLimitWithConfig returns a BodyLimit middleware with config.
+// See: `BodyLimit()`.
+func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultBodyLimitConfig.Skipper
+ }
+
+ limit, err := bytes.Parse(config.Limit)
+ if err != nil {
+ panic(fmt.Errorf("invalid body-limit=%s", config.Limit))
+ }
+ config.limit = limit
+ pool := limitedReaderPool(config)
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+
+ // Based on content length
+ if req.ContentLength > config.limit {
+ return echo.ErrStatusRequestEntityTooLarge
+ }
+
+ // Based on content read
+ r := pool.Get().(*limitedReader)
+ r.Reset(req.Body, c)
+ defer pool.Put(r)
+ req.Body = r
+
+ return next(c)
+ }
+ }
+}
+
+func (r *limitedReader) Read(b []byte) (n int, err error) {
+ n, err = r.reader.Read(b)
+ r.read += int64(n)
+ if r.read > r.limit {
+ return n, echo.ErrStatusRequestEntityTooLarge
+ }
+ return
+}
+
+func (r *limitedReader) Close() error {
+ return r.reader.Close()
+}
+
+func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) {
+ r.reader = reader
+ r.context = context
+}
+
+func limitedReaderPool(c BodyLimitConfig) sync.Pool {
+ return sync.Pool{
+ New: func() interface{} {
+ return &limitedReader{BodyLimitConfig: c}
+ },
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/compress.go b/vendor/github.com/labstack/echo/middleware/compress.go
new file mode 100644
index 00000000..eee67b37
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/compress.go
@@ -0,0 +1,121 @@
+package middleware
+
+import (
+ "bufio"
+ "compress/gzip"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "strings"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ // GzipConfig defines the config for Gzip middleware.
+ GzipConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Gzip compression level.
+ // Optional. Default value -1.
+ Level int `json:"level"`
+ }
+
+ gzipResponseWriter struct {
+ io.Writer
+ http.ResponseWriter
+ }
+)
+
+const (
+ gzipScheme = "gzip"
+)
+
+var (
+ // DefaultGzipConfig is the default Gzip middleware config.
+ DefaultGzipConfig = GzipConfig{
+ Skipper: DefaultSkipper,
+ Level: -1,
+ }
+)
+
+// Gzip returns a middleware which compresses HTTP response using gzip compression
+// scheme.
+func Gzip() echo.MiddlewareFunc {
+ return GzipWithConfig(DefaultGzipConfig)
+}
+
+// GzipWithConfig return Gzip middleware with config.
+// See: `Gzip()`.
+func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultGzipConfig.Skipper
+ }
+ if config.Level == 0 {
+ config.Level = DefaultGzipConfig.Level
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ res := c.Response()
+ res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
+ if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
+ res.Header().Add(echo.HeaderContentEncoding, gzipScheme) // Issue #806
+ rw := res.Writer
+ w, err := gzip.NewWriterLevel(rw, config.Level)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if res.Size == 0 {
+ if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
+ res.Header().Del(echo.HeaderContentEncoding)
+ }
+ // We have to reset response to it's pristine state when
+ // nothing is written to body or error is returned.
+ // See issue #424, #407.
+ res.Writer = rw
+ w.Reset(ioutil.Discard)
+ }
+ w.Close()
+ }()
+ grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
+ res.Writer = grw
+ }
+ return next(c)
+ }
+ }
+}
+
+func (w *gzipResponseWriter) WriteHeader(code int) {
+ if code == http.StatusNoContent { // Issue #489
+ w.ResponseWriter.Header().Del(echo.HeaderContentEncoding)
+ }
+ w.ResponseWriter.WriteHeader(code)
+}
+
+func (w *gzipResponseWriter) Write(b []byte) (int, error) {
+ if w.Header().Get(echo.HeaderContentType) == "" {
+ w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
+ }
+ return w.Writer.Write(b)
+}
+
+func (w *gzipResponseWriter) Flush() error {
+ return w.Writer.(*gzip.Writer).Flush()
+}
+
+func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ return w.ResponseWriter.(http.Hijacker).Hijack()
+}
+
+func (w *gzipResponseWriter) CloseNotify() <-chan bool {
+ return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
+}
diff --git a/vendor/github.com/labstack/echo/middleware/cors.go b/vendor/github.com/labstack/echo/middleware/cors.go
new file mode 100644
index 00000000..c35fc36c
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/cors.go
@@ -0,0 +1,139 @@
+package middleware
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ // CORSConfig defines the config for CORS middleware.
+ CORSConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // AllowOrigin defines a list of origins that may access the resource.
+ // Optional. Default value []string{"*"}.
+ AllowOrigins []string `json:"allow_origins"`
+
+ // AllowMethods defines a list methods allowed when accessing the resource.
+ // This is used in response to a preflight request.
+ // Optional. Default value DefaultCORSConfig.AllowMethods.
+ AllowMethods []string `json:"allow_methods"`
+
+ // AllowHeaders defines a list of request headers that can be used when
+ // making the actual request. This in response to a preflight request.
+ // Optional. Default value []string{}.
+ AllowHeaders []string `json:"allow_headers"`
+
+ // AllowCredentials indicates whether or not the response to the request
+ // can be exposed when the credentials flag is true. When used as part of
+ // a response to a preflight request, this indicates whether or not the
+ // actual request can be made using credentials.
+ // Optional. Default value false.
+ AllowCredentials bool `json:"allow_credentials"`
+
+ // ExposeHeaders defines a whitelist headers that clients are allowed to
+ // access.
+ // Optional. Default value []string{}.
+ ExposeHeaders []string `json:"expose_headers"`
+
+ // MaxAge indicates how long (in seconds) the results of a preflight request
+ // can be cached.
+ // Optional. Default value 0.
+ MaxAge int `json:"max_age"`
+ }
+)
+
+var (
+ // DefaultCORSConfig is the default CORS middleware config.
+ DefaultCORSConfig = CORSConfig{
+ Skipper: DefaultSkipper,
+ AllowOrigins: []string{"*"},
+ AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
+ }
+)
+
+// CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
+// See: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
+func CORS() echo.MiddlewareFunc {
+ return CORSWithConfig(DefaultCORSConfig)
+}
+
+// CORSWithConfig returns a CORS middleware with config.
+// See: `CORS()`.
+func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultCORSConfig.Skipper
+ }
+ if len(config.AllowOrigins) == 0 {
+ config.AllowOrigins = DefaultCORSConfig.AllowOrigins
+ }
+ if len(config.AllowMethods) == 0 {
+ config.AllowMethods = DefaultCORSConfig.AllowMethods
+ }
+
+ allowMethods := strings.Join(config.AllowMethods, ",")
+ allowHeaders := strings.Join(config.AllowHeaders, ",")
+ exposeHeaders := strings.Join(config.ExposeHeaders, ",")
+ maxAge := strconv.Itoa(config.MaxAge)
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ res := c.Response()
+ origin := req.Header.Get(echo.HeaderOrigin)
+ allowOrigin := ""
+
+ // Check allowed origins
+ for _, o := range config.AllowOrigins {
+ if o == "*" || o == origin {
+ allowOrigin = o
+ break
+ }
+ }
+
+ // Simple request
+ if req.Method != echo.OPTIONS {
+ res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
+ res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
+ if config.AllowCredentials {
+ res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
+ }
+ if exposeHeaders != "" {
+ res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
+ }
+ return next(c)
+ }
+
+ // Preflight request
+ res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
+ res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
+ res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
+ res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
+ res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
+ if config.AllowCredentials {
+ res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
+ }
+ if allowHeaders != "" {
+ res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
+ } else {
+ h := req.Header.Get(echo.HeaderAccessControlRequestHeaders)
+ if h != "" {
+ res.Header().Set(echo.HeaderAccessControlAllowHeaders, h)
+ }
+ }
+ if config.MaxAge > 0 {
+ res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge)
+ }
+ return c.NoContent(http.StatusNoContent)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/csrf.go b/vendor/github.com/labstack/echo/middleware/csrf.go
new file mode 100644
index 00000000..5bbeecb4
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/csrf.go
@@ -0,0 +1,210 @@
+package middleware
+
+import (
+ "crypto/subtle"
+ "errors"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/gommon/random"
+)
+
+type (
+ // CSRFConfig defines the config for CSRF middleware.
+ CSRFConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // TokenLength is the length of the generated token.
+ TokenLength uint8 `json:"token_length"`
+ // Optional. Default value 32.
+
+ // TokenLookup is a string in the form of "<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 `json:"token_lookup"`
+
+ // Context key to store generated CSRF token into context.
+ // Optional. Default value "csrf".
+ ContextKey string `json:"context_key"`
+
+ // Name of the CSRF cookie. This cookie will store CSRF token.
+ // Optional. Default value "csrf".
+ CookieName string `json:"cookie_name"`
+
+ // Domain of the CSRF cookie.
+ // Optional. Default value none.
+ CookieDomain string `json:"cookie_domain"`
+
+ // Path of the CSRF cookie.
+ // Optional. Default value none.
+ CookiePath string `json:"cookie_path"`
+
+ // Max age (in seconds) of the CSRF cookie.
+ // Optional. Default value 86400 (24hr).
+ CookieMaxAge int `json:"cookie_max_age"`
+
+ // Indicates if CSRF cookie is secure.
+ // Optional. Default value false.
+ CookieSecure bool `json:"cookie_secure"`
+
+ // Indicates if CSRF cookie is HTTP only.
+ // Optional. Default value false.
+ CookieHTTPOnly bool `json:"cookie_http_only"`
+ }
+
+ // csrfTokenExtractor defines a function that takes `echo.Context` and returns
+ // either a token or an error.
+ csrfTokenExtractor func(echo.Context) (string, error)
+)
+
+var (
+ // DefaultCSRFConfig is the default CSRF middleware config.
+ DefaultCSRFConfig = CSRFConfig{
+ Skipper: DefaultSkipper,
+ TokenLength: 32,
+ TokenLookup: "header:" + echo.HeaderXCSRFToken,
+ ContextKey: "csrf",
+ CookieName: "_csrf",
+ CookieMaxAge: 86400,
+ }
+)
+
+// CSRF returns a Cross-Site Request Forgery (CSRF) middleware.
+// See: https://en.wikipedia.org/wiki/Cross-site_request_forgery
+func CSRF() echo.MiddlewareFunc {
+ c := DefaultCSRFConfig
+ return CSRFWithConfig(c)
+}
+
+// CSRFWithConfig returns a CSRF middleware with config.
+// See `CSRF()`.
+func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultCSRFConfig.Skipper
+ }
+ if config.TokenLength == 0 {
+ config.TokenLength = DefaultCSRFConfig.TokenLength
+ }
+ if config.TokenLookup == "" {
+ config.TokenLookup = DefaultCSRFConfig.TokenLookup
+ }
+ if config.ContextKey == "" {
+ config.ContextKey = DefaultCSRFConfig.ContextKey
+ }
+ if config.CookieName == "" {
+ config.CookieName = DefaultCSRFConfig.CookieName
+ }
+ if config.CookieMaxAge == 0 {
+ config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
+ }
+
+ // Initialize
+ parts := strings.Split(config.TokenLookup, ":")
+ extractor := csrfTokenFromHeader(parts[1])
+ switch parts[0] {
+ case "form":
+ extractor = csrfTokenFromForm(parts[1])
+ case "query":
+ extractor = csrfTokenFromQuery(parts[1])
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ k, err := c.Cookie(config.CookieName)
+ token := ""
+
+ if err != nil {
+ // Generate token
+ token = random.String(config.TokenLength)
+ } else {
+ // Reuse token
+ token = k.Value
+ }
+
+ switch req.Method {
+ case echo.GET, echo.HEAD, echo.OPTIONS, echo.TRACE:
+ default:
+ // Validate token only for requests which are not defined as 'safe' by RFC7231
+ clientToken, err := extractor(c)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+ if !validateCSRFToken(token, clientToken) {
+ return echo.NewHTTPError(http.StatusForbidden, "Invalid csrf token")
+ }
+ }
+
+ // Set CSRF cookie
+ cookie := new(http.Cookie)
+ cookie.Name = config.CookieName
+ cookie.Value = token
+ if config.CookiePath != "" {
+ cookie.Path = config.CookiePath
+ }
+ if config.CookieDomain != "" {
+ cookie.Domain = config.CookieDomain
+ }
+ cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
+ cookie.Secure = config.CookieSecure
+ cookie.HttpOnly = config.CookieHTTPOnly
+ c.SetCookie(cookie)
+
+ // Store token in the context
+ c.Set(config.ContextKey, token)
+
+ // Protect clients from caching the response
+ c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie)
+
+ return next(c)
+ }
+ }
+}
+
+// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
+// provided request header.
+func csrfTokenFromHeader(header string) csrfTokenExtractor {
+ return func(c echo.Context) (string, error) {
+ return c.Request().Header.Get(header), nil
+ }
+}
+
+// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
+// provided form parameter.
+func csrfTokenFromForm(param string) csrfTokenExtractor {
+ return func(c echo.Context) (string, error) {
+ token := c.FormValue(param)
+ if token == "" {
+ return "", errors.New("Missing csrf token in the form parameter")
+ }
+ return token, nil
+ }
+}
+
+// csrfTokenFromQuery returns a `csrfTokenExtractor` that extracts token from the
+// provided query parameter.
+func csrfTokenFromQuery(param string) csrfTokenExtractor {
+ return func(c echo.Context) (string, error) {
+ token := c.QueryParam(param)
+ if token == "" {
+ return "", errors.New("Missing csrf token in the query string")
+ }
+ return token, nil
+ }
+}
+
+func validateCSRFToken(token, clientToken string) bool {
+ return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
+}
diff --git a/vendor/github.com/labstack/echo/middleware/jwt.go b/vendor/github.com/labstack/echo/middleware/jwt.go
new file mode 100644
index 00000000..b2658739
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/jwt.go
@@ -0,0 +1,189 @@
+package middleware
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "reflect"
+ "strings"
+
+ "github.com/dgrijalva/jwt-go"
+ "github.com/labstack/echo"
+)
+
+type (
+ // JWTConfig defines the config for JWT middleware.
+ JWTConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Signing key to validate token.
+ // Required.
+ SigningKey interface{}
+
+ // Signing method, used to check token signing method.
+ // Optional. Default value HS256.
+ SigningMethod string
+
+ // Context key to store user information from the token into context.
+ // Optional. Default value "user".
+ ContextKey string
+
+ // Claims are extendable claims data defining token content.
+ // Optional. Default value jwt.MapClaims
+ Claims jwt.Claims
+
+ // TokenLookup is a string in the form of "<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
+ }
+
+ jwtExtractor func(echo.Context) (string, error)
+)
+
+// Algorithms
+const (
+ AlgorithmHS256 = "HS256"
+)
+
+var (
+ // DefaultJWTConfig is the default JWT auth middleware config.
+ DefaultJWTConfig = JWTConfig{
+ Skipper: DefaultSkipper,
+ SigningMethod: AlgorithmHS256,
+ ContextKey: "user",
+ TokenLookup: "header:" + echo.HeaderAuthorization,
+ AuthScheme: "Bearer",
+ Claims: jwt.MapClaims{},
+ }
+)
+
+// JWT returns a JSON Web Token (JWT) auth middleware.
+//
+// For valid token, it sets the user in context and calls next handler.
+// For invalid token, it returns "401 - Unauthorized" error.
+// For missing token, it returns "400 - Bad Request" error.
+//
+// See: https://jwt.io/introduction
+// See `JWTConfig.TokenLookup`
+func JWT(key []byte) echo.MiddlewareFunc {
+ c := DefaultJWTConfig
+ c.SigningKey = key
+ return JWTWithConfig(c)
+}
+
+// JWTWithConfig returns a JWT auth middleware with config.
+// See: `JWT()`.
+func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultJWTConfig.Skipper
+ }
+ if config.SigningKey == nil {
+ panic("jwt middleware requires signing key")
+ }
+ if config.SigningMethod == "" {
+ config.SigningMethod = DefaultJWTConfig.SigningMethod
+ }
+ if config.ContextKey == "" {
+ config.ContextKey = DefaultJWTConfig.ContextKey
+ }
+ if config.Claims == nil {
+ config.Claims = DefaultJWTConfig.Claims
+ }
+ if config.TokenLookup == "" {
+ config.TokenLookup = DefaultJWTConfig.TokenLookup
+ }
+ if config.AuthScheme == "" {
+ config.AuthScheme = DefaultJWTConfig.AuthScheme
+ }
+ config.keyFunc = func(t *jwt.Token) (interface{}, error) {
+ // Check the signing method
+ if t.Method.Alg() != config.SigningMethod {
+ return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"])
+ }
+ return config.SigningKey, nil
+ }
+
+ // Initialize
+ parts := strings.Split(config.TokenLookup, ":")
+ extractor := jwtFromHeader(parts[1], config.AuthScheme)
+ switch parts[0] {
+ case "query":
+ extractor = jwtFromQuery(parts[1])
+ case "cookie":
+ extractor = jwtFromCookie(parts[1])
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ auth, err := extractor(c)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+ token := new(jwt.Token)
+ // Issue #647, #656
+ if _, ok := config.Claims.(jwt.MapClaims); ok {
+ token, err = jwt.Parse(auth, config.keyFunc)
+ } else {
+ claims := reflect.ValueOf(config.Claims).Interface().(jwt.Claims)
+ token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc)
+ }
+ if err == nil && token.Valid {
+ // Store user information from token into context.
+ c.Set(config.ContextKey, token)
+ return next(c)
+ }
+ return echo.ErrUnauthorized
+ }
+ }
+}
+
+// jwtFromHeader returns a `jwtExtractor` that extracts token from the request header.
+func jwtFromHeader(header string, authScheme string) jwtExtractor {
+ return func(c echo.Context) (string, error) {
+ auth := c.Request().Header.Get(header)
+ l := len(authScheme)
+ if len(auth) > l+1 && auth[:l] == authScheme {
+ return auth[l+1:], nil
+ }
+ return "", errors.New("Missing or invalid jwt in the request header")
+ }
+}
+
+// jwtFromQuery returns a `jwtExtractor` that extracts token from the query string.
+func jwtFromQuery(param string) jwtExtractor {
+ return func(c echo.Context) (string, error) {
+ token := c.QueryParam(param)
+ if token == "" {
+ return "", errors.New("Missing jwt in the query string")
+ }
+ return token, nil
+ }
+}
+
+// jwtFromCookie returns a `jwtExtractor` that extracts token from the named cookie.
+func jwtFromCookie(name string) jwtExtractor {
+ return func(c echo.Context) (string, error) {
+ cookie, err := c.Cookie(name)
+ if err != nil {
+ return "", errors.New("Missing jwt in the cookie")
+ }
+ return cookie.Value, nil
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/key_auth.go b/vendor/github.com/labstack/echo/middleware/key_auth.go
new file mode 100644
index 00000000..4d4cb940
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/key_auth.go
@@ -0,0 +1,133 @@
+package middleware
+
+import (
+ "errors"
+ "net/http"
+ "strings"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ // KeyAuthConfig defines the config for KeyAuth middleware.
+ KeyAuthConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // KeyLookup is a string in the form of "<source>:<name>" that is used
+ // to extract key from the request.
+ // Optional. Default value "header:Authorization".
+ // Possible values:
+ // - "header:<name>"
+ // - "query:<name>"
+ KeyLookup string `json:"key_lookup"`
+
+ // AuthScheme to be used in the Authorization header.
+ // Optional. Default value "Bearer".
+ AuthScheme string
+
+ // Validator is a function to validate key.
+ // Required.
+ Validator KeyAuthValidator
+ }
+
+ // KeyAuthValidator defines a function to validate KeyAuth credentials.
+ KeyAuthValidator func(string, echo.Context) bool
+
+ keyExtractor func(echo.Context) (string, error)
+)
+
+var (
+ // DefaultKeyAuthConfig is the default KeyAuth middleware config.
+ DefaultKeyAuthConfig = KeyAuthConfig{
+ Skipper: DefaultSkipper,
+ KeyLookup: "header:" + echo.HeaderAuthorization,
+ AuthScheme: "Bearer",
+ }
+)
+
+// KeyAuth returns an KeyAuth middleware.
+//
+// For valid key it calls the next handler.
+// For invalid key, it sends "401 - Unauthorized" response.
+// For missing key, it sends "400 - Bad Request" response.
+func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc {
+ c := DefaultKeyAuthConfig
+ c.Validator = fn
+ return KeyAuthWithConfig(c)
+}
+
+// KeyAuthWithConfig returns an KeyAuth middleware with config.
+// See `KeyAuth()`.
+func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultKeyAuthConfig.Skipper
+ }
+ // Defaults
+ if config.AuthScheme == "" {
+ config.AuthScheme = DefaultKeyAuthConfig.AuthScheme
+ }
+ if config.KeyLookup == "" {
+ config.KeyLookup = DefaultKeyAuthConfig.KeyLookup
+ }
+ if config.Validator == nil {
+ panic("key-auth middleware requires a validator function")
+ }
+
+ // Initialize
+ parts := strings.Split(config.KeyLookup, ":")
+ extractor := keyFromHeader(parts[1], config.AuthScheme)
+ switch parts[0] {
+ case "query":
+ extractor = keyFromQuery(parts[1])
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ // Extract and verify key
+ key, err := extractor(c)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+ if config.Validator(key, c) {
+ return next(c)
+ }
+
+ return echo.ErrUnauthorized
+ }
+ }
+}
+
+// keyFromHeader returns a `keyExtractor` that extracts key from the request header.
+func keyFromHeader(header string, authScheme string) keyExtractor {
+ return func(c echo.Context) (string, error) {
+ auth := c.Request().Header.Get(header)
+ if auth == "" {
+ return "", errors.New("Missing key in request header")
+ }
+ if header == echo.HeaderAuthorization {
+ l := len(authScheme)
+ if len(auth) > l+1 && auth[:l] == authScheme {
+ return auth[l+1:], nil
+ }
+ return "", errors.New("Invalid key in the request header")
+ }
+ return auth, nil
+ }
+}
+
+// keyFromQuery returns a `keyExtractor` that extracts key from the query string.
+func keyFromQuery(param string) keyExtractor {
+ return func(c echo.Context) (string, error) {
+ key := c.QueryParam(param)
+ if key == "" {
+ return "", errors.New("Missing key in the query string")
+ }
+ return key, nil
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/logger.go b/vendor/github.com/labstack/echo/middleware/logger.go
new file mode 100644
index 00000000..e26b5496
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/logger.go
@@ -0,0 +1,191 @@
+package middleware
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/gommon/color"
+ "github.com/valyala/fasttemplate"
+)
+
+type (
+ // LoggerConfig defines the config for Logger middleware.
+ LoggerConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Tags to constructed the logger format.
+ //
+ // - time_unix
+ // - time_unix_nano
+ // - time_rfc3339
+ // - time_rfc3339_nano
+ // - id (Request ID - Not implemented)
+ // - remote_ip
+ // - uri
+ // - host
+ // - method
+ // - path
+ // - referer
+ // - user_agent
+ // - status
+ // - latency (In nanoseconds)
+ // - latency_human (Human readable)
+ // - bytes_in (Bytes received)
+ // - bytes_out (Bytes sent)
+ // - header:<NAME>
+ // - query:<NAME>
+ // - form:<NAME>
+ //
+ // Example "${remote_ip} ${status}"
+ //
+ // Optional. Default value DefaultLoggerConfig.Format.
+ Format string `json:"format"`
+
+ // Output is a writer where logs in JSON format are written.
+ // Optional. Default value os.Stdout.
+ Output io.Writer
+
+ template *fasttemplate.Template
+ colorer *color.Color
+ pool *sync.Pool
+ }
+)
+
+var (
+ // DefaultLoggerConfig is the default Logger middleware config.
+ DefaultLoggerConfig = LoggerConfig{
+ Skipper: DefaultSkipper,
+ Format: `{"time":"${time_rfc3339_nano}","remote_ip":"${remote_ip}","host":"${host}",` +
+ `"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
+ `"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
+ `"bytes_out":${bytes_out}}` + "\n",
+ Output: os.Stdout,
+ colorer: color.New(),
+ }
+)
+
+// Logger returns a middleware that logs HTTP requests.
+func Logger() echo.MiddlewareFunc {
+ return LoggerWithConfig(DefaultLoggerConfig)
+}
+
+// LoggerWithConfig returns a Logger middleware with config.
+// See: `Logger()`.
+func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultLoggerConfig.Skipper
+ }
+ if config.Format == "" {
+ config.Format = DefaultLoggerConfig.Format
+ }
+ if config.Output == nil {
+ config.Output = DefaultLoggerConfig.Output
+ }
+
+ config.template = fasttemplate.New(config.Format, "${", "}")
+ config.colorer = color.New()
+ config.colorer.SetOutput(config.Output)
+ config.pool = &sync.Pool{
+ New: func() interface{} {
+ return bytes.NewBuffer(make([]byte, 256))
+ },
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) (err error) {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ res := c.Response()
+ start := time.Now()
+ if err = next(c); err != nil {
+ c.Error(err)
+ }
+ stop := time.Now()
+ buf := config.pool.Get().(*bytes.Buffer)
+ buf.Reset()
+ defer config.pool.Put(buf)
+
+ if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
+ switch tag {
+ case "time_unix":
+ return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
+ case "time_unix_nano":
+ return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
+ case "time_rfc3339":
+ return buf.WriteString(time.Now().Format(time.RFC3339))
+ case "time_rfc3339_nano":
+ return buf.WriteString(time.Now().Format(time.RFC3339Nano))
+ case "remote_ip":
+ return buf.WriteString(c.RealIP())
+ case "host":
+ return buf.WriteString(req.Host)
+ case "uri":
+ return buf.WriteString(req.RequestURI)
+ case "method":
+ return buf.WriteString(req.Method)
+ case "path":
+ p := req.URL.Path
+ if p == "" {
+ p = "/"
+ }
+ return buf.WriteString(p)
+ case "referer":
+ return buf.WriteString(req.Referer())
+ case "user_agent":
+ return buf.WriteString(req.UserAgent())
+ case "status":
+ n := res.Status
+ s := config.colorer.Green(n)
+ switch {
+ case n >= 500:
+ s = config.colorer.Red(n)
+ case n >= 400:
+ s = config.colorer.Yellow(n)
+ case n >= 300:
+ s = config.colorer.Cyan(n)
+ }
+ return buf.WriteString(s)
+ case "latency":
+ l := stop.Sub(start)
+ return buf.WriteString(strconv.FormatInt(int64(l), 10))
+ case "latency_human":
+ return buf.WriteString(stop.Sub(start).String())
+ case "bytes_in":
+ cl := req.Header.Get(echo.HeaderContentLength)
+ if cl == "" {
+ cl = "0"
+ }
+ return buf.WriteString(cl)
+ case "bytes_out":
+ return buf.WriteString(strconv.FormatInt(res.Size, 10))
+ default:
+ switch {
+ case strings.HasPrefix(tag, "header:"):
+ return buf.Write([]byte(c.Request().Header.Get(tag[7:])))
+ case strings.HasPrefix(tag, "query:"):
+ return buf.Write([]byte(c.QueryParam(tag[6:])))
+ case strings.HasPrefix(tag, "form:"):
+ return buf.Write([]byte(c.FormValue(tag[5:])))
+ }
+ }
+ return 0, nil
+ }); err != nil {
+ return
+ }
+
+ _, err = config.Output.Write(buf.Bytes())
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/method_override.go b/vendor/github.com/labstack/echo/middleware/method_override.go
new file mode 100644
index 00000000..955fd11e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/method_override.go
@@ -0,0 +1,88 @@
+package middleware
+
+import "github.com/labstack/echo"
+
+type (
+ // MethodOverrideConfig defines the config for MethodOverride middleware.
+ MethodOverrideConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Getter is a function that gets overridden method from the request.
+ // Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
+ Getter MethodOverrideGetter
+ }
+
+ // MethodOverrideGetter is a function that gets overridden method from the request
+ MethodOverrideGetter func(echo.Context) string
+)
+
+var (
+ // DefaultMethodOverrideConfig is the default MethodOverride middleware config.
+ DefaultMethodOverrideConfig = MethodOverrideConfig{
+ Skipper: DefaultSkipper,
+ Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride),
+ }
+)
+
+// MethodOverride returns a MethodOverride middleware.
+// MethodOverride middleware checks for the overridden method from the request and
+// uses it instead of the original method.
+//
+// For security reasons, only `POST` method can be overridden.
+func MethodOverride() echo.MiddlewareFunc {
+ return MethodOverrideWithConfig(DefaultMethodOverrideConfig)
+}
+
+// MethodOverrideWithConfig returns a MethodOverride middleware with config.
+// See: `MethodOverride()`.
+func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultMethodOverrideConfig.Skipper
+ }
+ if config.Getter == nil {
+ config.Getter = DefaultMethodOverrideConfig.Getter
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ if req.Method == echo.POST {
+ m := config.Getter(c)
+ if m != "" {
+ req.Method = m
+ }
+ }
+ return next(c)
+ }
+ }
+}
+
+// MethodFromHeader is a `MethodOverrideGetter` that gets overridden method from
+// the request header.
+func MethodFromHeader(header string) MethodOverrideGetter {
+ return func(c echo.Context) string {
+ return c.Request().Header.Get(header)
+ }
+}
+
+// MethodFromForm is a `MethodOverrideGetter` that gets overridden method from the
+// form parameter.
+func MethodFromForm(param string) MethodOverrideGetter {
+ return func(c echo.Context) string {
+ return c.FormValue(param)
+ }
+}
+
+// MethodFromQuery is a `MethodOverrideGetter` that gets overridden method from
+// the query parameter.
+func MethodFromQuery(param string) MethodOverrideGetter {
+ return func(c echo.Context) string {
+ return c.QueryParam(param)
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/middleware.go b/vendor/github.com/labstack/echo/middleware/middleware.go
new file mode 100644
index 00000000..7edccc1d
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/middleware.go
@@ -0,0 +1,14 @@
+package middleware
+
+import "github.com/labstack/echo"
+
+type (
+ // Skipper defines a function to skip middleware. Returning true skips processing
+ // the middleware.
+ Skipper func(c echo.Context) bool
+)
+
+// DefaultSkipper returns false which processes the middleware.
+func DefaultSkipper(c echo.Context) bool {
+ return false
+}
diff --git a/vendor/github.com/labstack/echo/middleware/recover.go b/vendor/github.com/labstack/echo/middleware/recover.go
new file mode 100644
index 00000000..96fa62c9
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/recover.go
@@ -0,0 +1,85 @@
+package middleware
+
+import (
+ "fmt"
+ "runtime"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/gommon/color"
+)
+
+type (
+ // RecoverConfig defines the config for Recover middleware.
+ RecoverConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Size of the stack to be printed.
+ // Optional. Default value 4KB.
+ StackSize int `json:"stack_size"`
+
+ // DisableStackAll disables formatting stack traces of all other goroutines
+ // into buffer after the trace for the current goroutine.
+ // Optional. Default value false.
+ DisableStackAll bool `json:"disable_stack_all"`
+
+ // DisablePrintStack disables printing stack trace.
+ // Optional. Default value as false.
+ DisablePrintStack bool `json:"disable_print_stack"`
+ }
+)
+
+var (
+ // DefaultRecoverConfig is the default Recover middleware config.
+ DefaultRecoverConfig = RecoverConfig{
+ Skipper: DefaultSkipper,
+ StackSize: 4 << 10, // 4 KB
+ DisableStackAll: false,
+ DisablePrintStack: false,
+ }
+)
+
+// Recover returns a middleware which recovers from panics anywhere in the chain
+// and handles the control to the centralized HTTPErrorHandler.
+func Recover() echo.MiddlewareFunc {
+ return RecoverWithConfig(DefaultRecoverConfig)
+}
+
+// RecoverWithConfig returns a Recover middleware with config.
+// See: `Recover()`.
+func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultRecoverConfig.Skipper
+ }
+ if config.StackSize == 0 {
+ config.StackSize = DefaultRecoverConfig.StackSize
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ defer func() {
+ if r := recover(); r != nil {
+ var err error
+ switch r := r.(type) {
+ case error:
+ err = r
+ default:
+ err = fmt.Errorf("%v", r)
+ }
+ stack := make([]byte, config.StackSize)
+ length := runtime.Stack(stack, !config.DisableStackAll)
+ if !config.DisablePrintStack {
+ c.Logger().Printf("[%s] %s %s\n", color.Red("PANIC RECOVER"), err, stack[:length])
+ }
+ c.Error(err)
+ }
+ }()
+ return next(c)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/redirect.go b/vendor/github.com/labstack/echo/middleware/redirect.go
new file mode 100644
index 00000000..b87dab09
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/redirect.go
@@ -0,0 +1,215 @@
+package middleware
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ // RedirectConfig defines the config for Redirect middleware.
+ RedirectConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Status code to be used when redirecting the request.
+ // Optional. Default value http.StatusMovedPermanently.
+ Code int `json:"code"`
+ }
+)
+
+const (
+ www = "www"
+)
+
+var (
+ // DefaultRedirectConfig is the default Redirect middleware config.
+ DefaultRedirectConfig = RedirectConfig{
+ Skipper: DefaultSkipper,
+ Code: http.StatusMovedPermanently,
+ }
+)
+
+// HTTPSRedirect redirects http requests to https.
+// For example, http://labstack.com will be redirect to https://labstack.com.
+//
+// Usage `Echo#Pre(HTTPSRedirect())`
+func HTTPSRedirect() echo.MiddlewareFunc {
+ return HTTPSRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `HTTPSRedirect()`.
+func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultTrailingSlashConfig.Skipper
+ }
+ if config.Code == 0 {
+ config.Code = DefaultRedirectConfig.Code
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ host := req.Host
+ uri := req.RequestURI
+ if !c.IsTLS() {
+ return c.Redirect(config.Code, "https://"+host+uri)
+ }
+ return next(c)
+ }
+ }
+}
+
+// HTTPSWWWRedirect redirects http requests to https www.
+// For example, http://labstack.com will be redirect to https://www.labstack.com.
+//
+// Usage `Echo#Pre(HTTPSWWWRedirect())`
+func HTTPSWWWRedirect() echo.MiddlewareFunc {
+ return HTTPSWWWRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `HTTPSWWWRedirect()`.
+func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultTrailingSlashConfig.Skipper
+ }
+ if config.Code == 0 {
+ config.Code = DefaultRedirectConfig.Code
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ host := req.Host
+ uri := req.RequestURI
+ if !c.IsTLS() && host[:3] != www {
+ return c.Redirect(config.Code, "https://www."+host+uri)
+ }
+ return next(c)
+ }
+ }
+}
+
+// HTTPSNonWWWRedirect redirects http requests to https non www.
+// For example, http://www.labstack.com will be redirect to https://labstack.com.
+//
+// Usage `Echo#Pre(HTTPSNonWWWRedirect())`
+func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
+ return HTTPSNonWWWRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `HTTPSNonWWWRedirect()`.
+func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultTrailingSlashConfig.Skipper
+ }
+ if config.Code == 0 {
+ config.Code = DefaultRedirectConfig.Code
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ host := req.Host
+ uri := req.RequestURI
+ if !c.IsTLS() {
+ if host[:3] == www {
+ return c.Redirect(config.Code, "https://"+host[4:]+uri)
+ }
+ return c.Redirect(config.Code, "https://"+host+uri)
+ }
+ return next(c)
+ }
+ }
+}
+
+// WWWRedirect redirects non www requests to www.
+// For example, http://labstack.com will be redirect to http://www.labstack.com.
+//
+// Usage `Echo#Pre(WWWRedirect())`
+func WWWRedirect() echo.MiddlewareFunc {
+ return WWWRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `WWWRedirect()`.
+func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+ // Defaults
+ if config.Skipper == nil {
+ config.Skipper = DefaultTrailingSlashConfig.Skipper
+ }
+ if config.Code == 0 {
+ config.Code = DefaultRedirectConfig.Code
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ scheme := c.Scheme()
+ host := req.Host
+ if host[:3] != www {
+ uri := req.RequestURI
+ return c.Redirect(config.Code, scheme+"://www."+host+uri)
+ }
+ return next(c)
+ }
+ }
+}
+
+// NonWWWRedirect redirects www requests to non www.
+// For example, http://www.labstack.com will be redirect to http://labstack.com.
+//
+// Usage `Echo#Pre(NonWWWRedirect())`
+func NonWWWRedirect() echo.MiddlewareFunc {
+ return NonWWWRedirectWithConfig(DefaultRedirectConfig)
+}
+
+// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
+// See `NonWWWRedirect()`.
+func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
+ if config.Skipper == nil {
+ config.Skipper = DefaultTrailingSlashConfig.Skipper
+ }
+ if config.Code == 0 {
+ config.Code = DefaultRedirectConfig.Code
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ if config.Skipper(c) {
+ return next(c)
+ }
+
+ req := c.Request()
+ scheme := c.Scheme()
+ host := req.Host
+ if host[:3] == www {
+ uri := req.RequestURI
+ return c.Redirect(config.Code, scheme+"://"+host[4:]+uri)
+ }
+ return next(c)
+ }
+ }
+}
diff --git a/vendor/github.com/labstack/echo/middleware/secure.go b/vendor/github.com/labstack/echo/middleware/secure.go
new file mode 100644
index 00000000..0125e74a
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/secure.go
@@ -0,0 +1,116 @@
+package middleware
+
+import (
+ "fmt"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ // SecureConfig defines the config for Secure middleware.
+ SecureConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // XSSProtection provides protection against cross-site scripting attack (XSS)
+ // by setting the `X-XSS-Protection` header.
+ // Optional. Default value "1; mode=block".
+ XSSProtection string `json:"xss_protection"`
+
+ // ContentTypeNosniff provides protection against overriding Content-Type
+ // header by setting the `X-Content-Type-Options` header.
+ // Optional. Default value "nosniff".
+ ContentTypeNosniff string `json:"content_type_nosniff"`
+
+ // XFrameOptions can be used to indicate whether or not a browser should
+ // be allowed to render a page in a <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 `json:"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 `json:"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 `json:"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 `json:"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/middleware/slash.go b/vendor/github.com/labstack/echo/middleware/slash.go
new file mode 100644
index 00000000..1114d722
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/slash.go
@@ -0,0 +1,119 @@
+package middleware
+
+import (
+ "github.com/labstack/echo"
+)
+
+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 `json:"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/middleware/static.go b/vendor/github.com/labstack/echo/middleware/static.go
new file mode 100644
index 00000000..793c1445
--- /dev/null
+++ b/vendor/github.com/labstack/echo/middleware/static.go
@@ -0,0 +1,118 @@
+package middleware
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/labstack/echo"
+)
+
+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 `json:"root"`
+
+ // Index file for serving a directory.
+ // Optional. Default value "index.html".
+ Index string `json:"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 `json:"html5"`
+
+ // Enable directory browsing.
+ // Optional. Default value false.
+ Browse bool `json:"browse"`
+ }
+)
+
+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.Skipper == nil {
+ config.Skipper = DefaultStaticConfig.Skipper
+ }
+ if config.Index == "" {
+ config.Index = DefaultStaticConfig.Index
+ }
+
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ p := c.Param("*")
+ name := filepath.Join(config.Root, p)
+ fi, err := os.Stat(name)
+
+ if err != nil {
+ if os.IsNotExist(err) {
+ if config.HTML5 {
+ return c.File(filepath.Join(config.Root, config.Index))
+ }
+ return echo.ErrNotFound
+ }
+ return err
+ }
+
+ if fi.IsDir() {
+ if config.Browse {
+ return listDir(name, c.Response())
+ }
+ return c.File(filepath.Join(name, config.Index))
+ }
+ return c.File(name)
+ }
+ }
+}
+
+func listDir(name string, res *echo.Response) error {
+ dir, err := os.Open(name)
+ if err != nil {
+ return err
+ }
+ dirs, err := dir.Readdir(-1)
+ if err != nil {
+ return err
+ }
+
+ // Create a directory index
+ res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
+ if _, err = fmt.Fprintf(res, "<pre>\n"); err != nil {
+ return err
+ }
+ for _, d := range dirs {
+ name := d.Name()
+ color := "#212121"
+ if d.IsDir() {
+ color = "#e91e63"
+ name += "/"
+ }
+ if _, err = fmt.Fprintf(res, "<a href=\"%s\" style=\"color: %s;\">%s</a>\n", name, color, name); err != nil {
+ return err
+ }
+ }
+ _, err = fmt.Fprintf(res, "</pre>\n")
+ return err
+}
diff --git a/vendor/github.com/labstack/echo/response.go b/vendor/github.com/labstack/echo/response.go
new file mode 100644
index 00000000..2c70d213
--- /dev/null
+++ b/vendor/github.com/labstack/echo/response.go
@@ -0,0 +1,89 @@
+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 {
+ Writer http.ResponseWriter
+ Status int
+ Size int64
+ Committed bool
+ echo *Echo
+ }
+)
+
+// 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()
+}
+
+// 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
+ }
+ 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)
+ 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()
+}
+
+// CloseNotify implements the http.CloseNotifier interface to allow detecting
+// when the underlying connection has gone away.
+// This mechanism can be used to cancel long operations on the server if the
+// client has disconnected before the response is ready.
+// See [http.CloseNotifier](https://golang.org/pkg/net/http/#CloseNotifier)
+func (r *Response) CloseNotify() <-chan bool {
+ return r.Writer.(http.CloseNotifier).CloseNotify()
+}
+
+func (r *Response) reset(w http.ResponseWriter) {
+ r.Writer = w
+ r.Size = 0
+ r.Status = http.StatusOK
+ r.Committed = false
+}
diff --git a/vendor/github.com/labstack/echo/router.go b/vendor/github.com/labstack/echo/router.go
new file mode 100644
index 00000000..5cb47116
--- /dev/null
+++ b/vendor/github.com/labstack/echo/router.go
@@ -0,0 +1,436 @@
+package echo
+
+import "strings"
+
+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
+ 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: make(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
+ }
+ ppath := path // Pristine path
+ pnames := []string{} // Param names
+
+ 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, ppath, pnames)
+ } 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
+ }
+ for i, n := range pnames {
+ // Param name aliases
+ if i < len(cn.pnames) && !strings.Contains(cn.pnames[i], n) {
+ cn.pnames[i] += "," + n
+ }
+ }
+ }
+ }
+ 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 GET:
+ n.methodHandler.get = h
+ case POST:
+ n.methodHandler.post = h
+ case PUT:
+ n.methodHandler.put = h
+ case DELETE:
+ n.methodHandler.delete = h
+ case PATCH:
+ n.methodHandler.patch = h
+ case OPTIONS:
+ n.methodHandler.options = h
+ case HEAD:
+ n.methodHandler.head = h
+ case CONNECT:
+ n.methodHandler.connect = h
+ case TRACE:
+ n.methodHandler.trace = h
+ }
+}
+
+func (n *node) findHandler(method string) HandlerFunc {
+ switch method {
+ case GET:
+ return n.methodHandler.get
+ case POST:
+ return n.methodHandler.post
+ case PUT:
+ return n.methodHandler.put
+ case DELETE:
+ return n.methodHandler.delete
+ case PATCH:
+ return n.methodHandler.patch
+ case OPTIONS:
+ return n.methodHandler.options
+ case HEAD:
+ return n.methodHandler.head
+ case CONNECT:
+ return n.methodHandler.connect
+ case TRACE:
+ 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, context Context) {
+ context.SetPath(path)
+ cn := r.tree // Current node as root
+
+ var (
+ search = path
+ c *node // Child node
+ n int // Param counter
+ nk kind // Next kind
+ nn *node // Next node
+ ns string // Next search
+ pvalues = context.ParamValues()
+ )
+
+ // Search order static > param > any
+ for {
+ if search == "" {
+ goto End
+ }
+
+ 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 == "" {
+ goto End
+ }
+
+ // Static node
+ if c = cn.findChild(search[0], skind); c != nil {
+ // Save next
+ if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
+ nk = pkind
+ nn = cn
+ ns = search
+ }
+ cn = c
+ continue
+ }
+
+ // Param node
+ Param:
+ if c = cn.findChildByKind(pkind); c != 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 = c
+ 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 = nil // Next
+ search = ns
+ if nk == pkind {
+ goto Param
+ } else if nk == akind {
+ goto Any
+ }
+ }
+ // Not found
+ return
+ }
+ pvalues[len(cn.pnames)-1] = search
+ goto End
+ }
+
+End:
+ context.SetHandler(cn.findHandler(method))
+ context.SetPath(cn.ppath)
+ context.SetParamNames(cn.pnames...)
+
+ // NOTE: Slow zone...
+ if context.Handler() == nil {
+ context.SetHandler(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 {
+ context.SetHandler(h)
+ } else {
+ context.SetHandler(cn.checkMethodNotAllowed())
+ }
+ context.SetPath(cn.ppath)
+ context.SetParamNames(cn.pnames...)
+ pvalues[len(cn.pnames)-1] = ""
+ }
+
+ return
+}