diff options
Diffstat (limited to 'vendor/github.com/labstack/echo')
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 +} |