Update vendor

This commit is contained in:
Wim 2017-06-06 00:01:05 +02:00
parent 2eecaccd1c
commit 3a183cb218
49 changed files with 534 additions and 1421 deletions

View File

@ -30,16 +30,16 @@ type (
// Bind implements the `Binder#Bind` function. // Bind implements the `Binder#Bind` function.
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
req := c.Request() req := c.Request()
if req.Method == GET { if req.ContentLength == 0 {
if req.Method == GET || req.Method == DELETE {
if err = b.bindData(i, c.QueryParams(), "query"); err != nil { if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()) return NewHTTPError(http.StatusBadRequest, err.Error())
} }
return return
} }
ctype := req.Header.Get(HeaderContentType)
if req.ContentLength == 0 {
return NewHTTPError(http.StatusBadRequest, "Request body can't be empty") return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
} }
ctype := req.Header.Get(HeaderContentType)
switch { switch {
case strings.HasPrefix(ctype, MIMEApplicationJSON): case strings.HasPrefix(ctype, MIMEApplicationJSON):
if err = json.NewDecoder(req.Body).Decode(i); err != nil { if err = json.NewDecoder(req.Body).Decode(i); err != nil {
@ -51,7 +51,7 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
return NewHTTPError(http.StatusBadRequest, err.Error()) return NewHTTPError(http.StatusBadRequest, err.Error())
} }
} }
case strings.HasPrefix(ctype, MIMEApplicationXML): case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
if err = xml.NewDecoder(req.Body).Decode(i); err != nil { if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*xml.UnsupportedTypeError); ok { if ute, ok := err.(*xml.UnsupportedTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())) return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
@ -142,6 +142,8 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
} }
switch valueKind { switch valueKind {
case reflect.Ptr:
return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
case reflect.Int: case reflect.Int:
return setIntField(val, 0, structField) return setIntField(val, 0, structField)
case reflect.Int8: case reflect.Int8:

View File

@ -31,6 +31,9 @@ type (
// IsTLS returns true if HTTP connection is TLS otherwise false. // IsTLS returns true if HTTP connection is TLS otherwise false.
IsTLS() bool IsTLS() bool
// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
IsWebSocket() bool
// Scheme returns the HTTP protocol scheme, `http` or `https`. // Scheme returns the HTTP protocol scheme, `http` or `https`.
Scheme() string Scheme() string
@ -219,19 +222,36 @@ func (c *context) IsTLS() bool {
return c.request.TLS != nil return c.request.TLS != nil
} }
func (c *context) IsWebSocket() bool {
upgrade := c.request.Header.Get(HeaderUpgrade)
return upgrade == "websocket" || upgrade == "Websocket"
}
func (c *context) Scheme() string { func (c *context) Scheme() string {
// Can't use `r.Request.URL.Scheme` // Can't use `r.Request.URL.Scheme`
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0 // See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
if c.IsTLS() { if c.IsTLS() {
return "https" return "https"
} }
if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
return scheme
}
if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
return scheme
}
if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
return "https"
}
if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
return scheme
}
return "http" return "http"
} }
func (c *context) RealIP() string { func (c *context) RealIP() string {
ra := c.request.RemoteAddr ra := c.request.RemoteAddr
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" { if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
ra = ip ra = strings.Split(ip, ", ")[0]
} else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { } else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
ra = ip ra = ip
} else { } else {
@ -275,7 +295,7 @@ func (c *context) SetParamNames(names ...string) {
} }
func (c *context) ParamValues() []string { func (c *context) ParamValues() []string {
return c.pvalues return c.pvalues[:len(c.pnames)]
} }
func (c *context) SetParamValues(values ...string) { func (c *context) SetParamValues(values ...string) {
@ -385,7 +405,8 @@ func (c *context) String(code int, s string) (err error) {
} }
func (c *context) JSON(code int, i interface{}) (err error) { func (c *context) JSON(code int, i interface{}) (err error) {
if c.echo.Debug { _, pretty := c.QueryParams()["pretty"]
if c.echo.Debug || pretty {
return c.JSONPretty(code, i, " ") return c.JSONPretty(code, i, " ")
} }
b, err := json.Marshal(i) b, err := json.Marshal(i)
@ -429,7 +450,8 @@ func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
} }
func (c *context) XML(code int, i interface{}) (err error) { func (c *context) XML(code int, i interface{}) (err error) {
if c.echo.Debug { _, pretty := c.QueryParams()["pretty"]
if c.echo.Debug || pretty {
return c.XMLPretty(code, i, " ") return c.XMLPretty(code, i, " ")
} }
b, err := xml.Marshal(i) b, err := xml.Marshal(i)
@ -471,7 +493,12 @@ func (c *context) Stream(code int, contentType string, r io.Reader) (err error)
return return
} }
func (c *context) File(file string) error { func (c *context) File(file string) (err error) {
file, err = url.QueryUnescape(file) // Issue #839
if err != nil {
return
}
f, err := os.Open(file) f, err := os.Open(file)
if err != nil { if err != nil {
return ErrNotFound return ErrNotFound
@ -487,11 +514,11 @@ func (c *context) File(file string) error {
} }
defer f.Close() defer f.Close()
if fi, err = f.Stat(); err != nil { if fi, err = f.Stat(); err != nil {
return err return
} }
} }
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f) http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
return nil return
} }
func (c *context) Attachment(file, name string) (err error) { func (c *context) Attachment(file, name string) (err error) {
@ -514,7 +541,7 @@ func (c *context) NoContent(code int) error {
} }
func (c *context) Redirect(code int, url string) error { func (c *context) Redirect(code int, url string) error {
if code < http.StatusMultipleChoices || code > http.StatusTemporaryRedirect { if code < 300 || code > 308 {
return ErrInvalidRedirectCode return ErrInvalidRedirectCode
} }
c.response.Header().Set(HeaderLocation, url) c.response.Header().Set(HeaderLocation, url)
@ -548,4 +575,8 @@ func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
c.query = nil c.query = nil
c.handler = NotFoundHandler c.handler = NotFoundHandler
c.store = nil c.store = nil
c.path = ""
c.pnames = nil
// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
// c.pvalues = nil
} }

View File

@ -1,26 +0,0 @@
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"))
}

View File

@ -1,38 +0,0 @@
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"))
}

View File

@ -1,75 +0,0 @@
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"))
}

View File

@ -1,21 +0,0 @@
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"))
}

View File

@ -1,65 +0,0 @@
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"))
}

View File

@ -1,59 +0,0 @@
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"))
}

View File

@ -1,17 +0,0 @@
// +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
}

View File

@ -1,25 +0,0 @@
// +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()
}

View File

@ -1,24 +0,0 @@
// +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"))
}

View File

@ -1,4 +0,0 @@
package main
// reference our echo instance and create it early
var e = createMux()

View File

@ -1,54 +0,0 @@
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")])
}

View File

@ -1,31 +0,0 @@
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")
}

View File

@ -1,20 +0,0 @@
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))
}

View File

@ -1,21 +0,0 @@
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)
}

View File

@ -1,25 +0,0 @@
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"))
}

View File

@ -1,42 +0,0 @@
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"))
}

View File

@ -1,35 +0,0 @@
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"))
}

View File

@ -1,86 +0,0 @@
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"))
}

View File

@ -1,69 +0,0 @@
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"))
}

View File

@ -1,82 +0,0 @@
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"))
}

View File

@ -1,45 +0,0 @@
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"))
}

View File

@ -1,78 +0,0 @@
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"))
}

View File

@ -1,14 +0,0 @@
package handler
import mgo "gopkg.in/mgo.v2"
type (
Handler struct {
DB *mgo.Session
}
)
const (
// Key (Should come from somewhere else).
Key = "secret"
)

View File

@ -1,73 +0,0 @@
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)
}

View File

@ -1,97 +0,0 @@
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)
}

View File

@ -1,12 +0,0 @@
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"`
}
)

View File

@ -1,13 +0,0 @@
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"`
}
)

View File

@ -1,52 +0,0 @@
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"))
}

View File

@ -1,47 +0,0 @@
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"))
}

View File

@ -1,41 +0,0 @@
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"))
}

View File

@ -42,10 +42,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
slog "log" stdLog "log"
"net" "net"
"net/http" "net/http"
"path" "path"
"path/filepath"
"reflect" "reflect"
"runtime" "runtime"
"sync" "sync"
@ -59,7 +60,7 @@ import (
type ( type (
// Echo is the top-level framework instance. // Echo is the top-level framework instance.
Echo struct { Echo struct {
stdLogger *slog.Logger stdLogger *stdLog.Logger
colorer *color.Color colorer *color.Color
premiddleware []MiddlewareFunc premiddleware []MiddlewareFunc
middleware []MiddlewareFunc middleware []MiddlewareFunc
@ -73,20 +74,21 @@ type (
TLSListener net.Listener TLSListener net.Listener
DisableHTTP2 bool DisableHTTP2 bool
Debug bool Debug bool
HideBanner bool
HTTPErrorHandler HTTPErrorHandler HTTPErrorHandler HTTPErrorHandler
Binder Binder Binder Binder
Validator Validator Validator Validator
Renderer Renderer Renderer Renderer
AutoTLSManager autocert.Manager AutoTLSManager autocert.Manager
Mutex sync.RWMutex // Mutex sync.RWMutex
Logger Logger Logger Logger
} }
// Route contains a handler and information for matching against requests. // Route contains a handler and information for matching against requests.
Route struct { Route struct {
Method string Method string `json:"method"`
Path string Path string `json:"path"`
Handler string Handler string `json:"handler"`
} }
// HTTPError represents an error that occurred while handling a request. // HTTPError represents an error that occurred while handling a request.
@ -144,6 +146,8 @@ const (
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8 MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
MIMEApplicationXML = "application/xml" MIMEApplicationXML = "application/xml"
MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8 MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
MIMETextXML = "text/xml"
MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8
MIMEApplicationForm = "application/x-www-form-urlencoded" MIMEApplicationForm = "application/x-www-form-urlencoded"
MIMEApplicationProtobuf = "application/protobuf" MIMEApplicationProtobuf = "application/protobuf"
MIMEApplicationMsgpack = "application/msgpack" MIMEApplicationMsgpack = "application/msgpack"
@ -161,6 +165,7 @@ const (
// Headers // Headers
const ( const (
HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding" HeaderAcceptEncoding = "Accept-Encoding"
HeaderAllow = "Allow" HeaderAllow = "Allow"
HeaderAuthorization = "Authorization" HeaderAuthorization = "Authorization"
@ -176,12 +181,18 @@ const (
HeaderUpgrade = "Upgrade" HeaderUpgrade = "Upgrade"
HeaderVary = "Vary" HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate" HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXForwardedFor = "X-Forwarded-For" HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXForwardedProtocol = "X-Forwarded-Protocol"
HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXRealIP = "X-Real-IP" HeaderXRealIP = "X-Real-IP"
HeaderXRequestID = "X-Request-ID"
HeaderServer = "Server" HeaderServer = "Server"
HeaderOrigin = "Origin" HeaderOrigin = "Origin"
// Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method" HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
@ -200,6 +211,22 @@ const (
HeaderXCSRFToken = "X-CSRF-Token" HeaderXCSRFToken = "X-CSRF-Token"
) )
const (
version = "3.1.0"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ %s
High performance, minimalist Go web framework
%s
____________________________________O/_______
O\
`
)
var ( var (
methods = [...]string{ methods = [...]string{
CONNECT, CONNECT,
@ -219,6 +246,7 @@ var (
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType) ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
ErrNotFound = NewHTTPError(http.StatusNotFound) ErrNotFound = NewHTTPError(http.StatusNotFound)
ErrUnauthorized = NewHTTPError(http.StatusUnauthorized) ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
ErrForbidden = NewHTTPError(http.StatusForbidden)
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed) ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
ErrValidatorNotRegistered = errors.New("Validator not registered") ErrValidatorNotRegistered = errors.New("Validator not registered")
@ -255,7 +283,7 @@ func New() (e *Echo) {
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
e.Binder = &DefaultBinder{} e.Binder = &DefaultBinder{}
e.Logger.SetLevel(log.OFF) e.Logger.SetLevel(log.OFF)
e.stdLogger = slog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0) e.stdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
e.pool.New = func() interface{} { e.pool.New = func() interface{} {
return e.NewContext(nil, nil) return e.NewContext(nil, nil)
} }
@ -398,12 +426,16 @@ func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middlew
// Static registers a new route with path prefix to serve static files from the // Static registers a new route with path prefix to serve static files from the
// provided root directory. // provided root directory.
func (e *Echo) Static(prefix, root string) { func (e *Echo) Static(prefix, root string) {
if root == "" {
root = "." // For security we want to restrict to CWD.
}
static(e, prefix, root) static(e, prefix, root)
} }
func static(i i, prefix, root string) { func static(i i, prefix, root string) {
h := func(c Context) error { h := func(c Context) error {
return c.File(path.Join(root, c.Param("*"))) name := filepath.Join(root, path.Clean("/"+c.Param("*"))) // "/"+ for security
return c.File(name)
} }
i.GET(prefix, h) i.GET(prefix, h)
if prefix == "/" { if prefix == "/" {
@ -430,7 +462,7 @@ func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...Middl
} }
return h(c) return h(c)
}) })
r := Route{ r := &Route{
Method: method, Method: method,
Path: path, Path: path,
Handler: name, Handler: name,
@ -476,8 +508,8 @@ func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
} }
// Routes returns the registered routes. // Routes returns the registered routes.
func (e *Echo) Routes() []Route { func (e *Echo) Routes() []*Route {
routes := []Route{} routes := []*Route{}
for _, v := range e.router.routes { for _, v := range e.router.routes {
routes = append(routes, v) routes = append(routes, v)
} }
@ -499,8 +531,8 @@ func (e *Echo) ReleaseContext(c Context) {
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests. // ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire lock // Acquire lock
e.Mutex.RLock() // e.Mutex.RLock()
defer e.Mutex.RUnlock() // defer e.Mutex.RUnlock()
// Acquire context // Acquire context
c := e.pool.Get().(*context) c := e.pool.Get().(*context)
@ -510,7 +542,10 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Middleware // Middleware
h := func(c Context) error { h := func(c Context) error {
method := r.Method method := r.Method
path := r.URL.EscapedPath() path := r.URL.RawPath
if path == "" {
path = r.URL.Path
}
e.router.Find(method, path, c) e.router.Find(method, path, c)
h := c.Handler() h := c.Handler()
for i := len(e.middleware) - 1; i >= 0; i-- { for i := len(e.middleware) - 1; i >= 0; i-- {
@ -572,8 +607,15 @@ func (e *Echo) startTLS(address string) error {
func (e *Echo) StartServer(s *http.Server) (err error) { func (e *Echo) StartServer(s *http.Server) (err error) {
// Setup // Setup
e.colorer.SetOutput(e.Logger.Output()) e.colorer.SetOutput(e.Logger.Output())
s.Handler = e
s.ErrorLog = e.stdLogger s.ErrorLog = e.stdLogger
s.Handler = e
if e.Debug {
e.Logger.SetLevel(log.DEBUG)
}
if !e.HideBanner {
e.colorer.Printf(banner, e.colorer.Red("v"+version), e.colorer.Blue(website))
}
if s.TLSConfig == nil { if s.TLSConfig == nil {
if e.Listener == nil { if e.Listener == nil {
@ -582,7 +624,9 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
return err return err
} }
} }
e.colorer.Printf("⇛ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) if !e.HideBanner {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
return s.Serve(e.Listener) return s.Serve(e.Listener)
} }
if e.TLSListener == nil { if e.TLSListener == nil {
@ -592,7 +636,9 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
} }
e.TLSListener = tls.NewListener(l, s.TLSConfig) e.TLSListener = tls.NewListener(l, s.TLSConfig)
} }
e.colorer.Printf("⇛ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr())) if !e.HideBanner {
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
}
return s.Serve(e.TLSListener) return s.Serve(e.TLSListener)
} }

25
vendor/github.com/labstack/echo/echo_go1.8.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
// +build go1.8
package echo
import (
stdContext "context"
)
// Close immediately stops the server.
// It internally calls `http.Server#Close()`.
func (e *Echo) Close() error {
if err := e.TLSServer.Close(); err != nil {
return err
}
return e.Server.Close()
}
// Shutdown stops server the gracefully.
// It internally calls `http.Server#Shutdown()`.
func (e *Echo) Shutdown(ctx stdContext.Context) error {
if err := e.TLSServer.Shutdown(ctx); err != nil {
return err
}
return e.Server.Shutdown(ctx)
}

View File

@ -1,8 +1,12 @@
package echo package echo
import (
"path"
)
type ( type (
// Group is a set of sub-routes for a specified route. It can be used for inner // 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 // routes that share a common middleware or functionality that should be separate
// from the parent echo instance while still inheriting from it. // from the parent echo instance while still inheriting from it.
Group struct { Group struct {
prefix string prefix string
@ -14,6 +18,11 @@ type (
// Use implements `Echo#Use()` for sub-routes within the Group. // Use implements `Echo#Use()` for sub-routes within the Group.
func (g *Group) Use(middleware ...MiddlewareFunc) { func (g *Group) Use(middleware ...MiddlewareFunc) {
g.middleware = append(g.middleware, middleware...) g.middleware = append(g.middleware, middleware...)
// Allow all requests to reach the group as they might get dropped if router
// doesn't find a match, making none of the group middleware process.
g.echo.Any(path.Clean(g.prefix+"/*"), func(c Context) error {
return ErrNotFound
}, g.middleware...)
} }
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group. // CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.

View File

@ -2,6 +2,7 @@ package middleware
import ( import (
"encoding/base64" "encoding/base64"
"strconv"
"github.com/labstack/echo" "github.com/labstack/echo"
) )
@ -15,20 +16,26 @@ type (
// Validator is a function to validate BasicAuth credentials. // Validator is a function to validate BasicAuth credentials.
// Required. // Required.
Validator BasicAuthValidator Validator BasicAuthValidator
// Realm is a string to define realm attribute of BasicAuth.
// Default value "Restricted".
Realm string
} }
// BasicAuthValidator defines a function to validate BasicAuth credentials. // BasicAuthValidator defines a function to validate BasicAuth credentials.
BasicAuthValidator func(string, string, echo.Context) bool BasicAuthValidator func(string, string, echo.Context) (bool, error)
) )
const ( const (
basic = "Basic" basic = "Basic"
defaultRealm = "Restricted"
) )
var ( var (
// DefaultBasicAuthConfig is the default BasicAuth middleware config. // DefaultBasicAuthConfig is the default BasicAuth middleware config.
DefaultBasicAuthConfig = BasicAuthConfig{ DefaultBasicAuthConfig = BasicAuthConfig{
Skipper: DefaultSkipper, Skipper: DefaultSkipper,
Realm: defaultRealm,
} }
) )
@ -52,6 +59,9 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
if config.Skipper == nil { if config.Skipper == nil {
config.Skipper = DefaultBasicAuthConfig.Skipper config.Skipper = DefaultBasicAuthConfig.Skipper
} }
if config.Realm == "" {
config.Realm = defaultRealm
}
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
@ -71,15 +81,25 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
for i := 0; i < len(cred); i++ { for i := 0; i < len(cred); i++ {
if cred[i] == ':' { if cred[i] == ':' {
// Verify credentials // Verify credentials
if config.Validator(cred[:i], cred[i+1:], c) { valid, err := config.Validator(cred[:i], cred[i+1:], c)
if err != nil {
return err
} else if valid {
return next(c) return next(c)
} }
} }
} }
} }
realm := ""
if config.Realm == defaultRealm {
realm = defaultRealm
} else {
realm = strconv.Quote(config.Realm)
}
// Need to return `401` for browsers to pop-up login box. // Need to return `401` for browsers to pop-up login box.
c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm=Restricted") c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm)
return echo.ErrUnauthorized return echo.ErrUnauthorized
} }
} }

View File

@ -108,8 +108,8 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b) return w.Writer.Write(b)
} }
func (w *gzipResponseWriter) Flush() error { func (w *gzipResponseWriter) Flush() {
return w.Writer.(*gzip.Writer).Flush() w.Writer.(*gzip.Writer).Flush()
} }
func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {

View File

@ -91,7 +91,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
config.Skipper = DefaultJWTConfig.Skipper config.Skipper = DefaultJWTConfig.Skipper
} }
if config.SigningKey == nil { if config.SigningKey == nil {
panic("jwt middleware requires signing key") panic("echo: jwt middleware requires signing key")
} }
if config.SigningMethod == "" { if config.SigningMethod == "" {
config.SigningMethod = DefaultJWTConfig.SigningMethod config.SigningMethod = DefaultJWTConfig.SigningMethod

View File

@ -32,7 +32,7 @@ type (
} }
// KeyAuthValidator defines a function to validate KeyAuth credentials. // KeyAuthValidator defines a function to validate KeyAuth credentials.
KeyAuthValidator func(string, echo.Context) bool KeyAuthValidator func(string, echo.Context) (bool, error)
keyExtractor func(echo.Context) (string, error) keyExtractor func(echo.Context) (string, error)
) )
@ -94,7 +94,10 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error()) return echo.NewHTTPError(http.StatusBadRequest, err.Error())
} }
if config.Validator(key, c) { valid, err := config.Validator(key, c)
if err != nil {
return err
} else if valid {
return next(c) return next(c)
} }

View File

@ -26,7 +26,7 @@ type (
// - time_unix_nano // - time_unix_nano
// - time_rfc3339 // - time_rfc3339
// - time_rfc3339_nano // - time_rfc3339_nano
// - id (Request ID - Not implemented) // - id (Request ID)
// - remote_ip // - remote_ip
// - uri // - uri
// - host // - host
@ -62,7 +62,7 @@ var (
// DefaultLoggerConfig is the default Logger middleware config. // DefaultLoggerConfig is the default Logger middleware config.
DefaultLoggerConfig = LoggerConfig{ DefaultLoggerConfig = LoggerConfig{
Skipper: DefaultSkipper, Skipper: DefaultSkipper,
Format: `{"time":"${time_rfc3339_nano}","remote_ip":"${remote_ip}","host":"${host}",` + Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}",` +
`"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` + `"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
`"latency_human":"${latency_human}","bytes_in":${bytes_in},` + `"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
`"bytes_out":${bytes_out}}` + "\n", `"bytes_out":${bytes_out}}` + "\n",
@ -126,6 +126,12 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
return buf.WriteString(time.Now().Format(time.RFC3339)) return buf.WriteString(time.Now().Format(time.RFC3339))
case "time_rfc3339_nano": case "time_rfc3339_nano":
return buf.WriteString(time.Now().Format(time.RFC3339Nano)) return buf.WriteString(time.Now().Format(time.RFC3339Nano))
case "id":
id := req.Header.Get(echo.HeaderXRequestID)
if id == "" {
id = res.Header().Get(echo.HeaderXRequestID)
}
return buf.WriteString(id)
case "remote_ip": case "remote_ip":
return buf.WriteString(c.RealIP()) return buf.WriteString(c.RealIP())
case "host": case "host":
@ -177,6 +183,11 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
return buf.Write([]byte(c.QueryParam(tag[6:]))) return buf.Write([]byte(c.QueryParam(tag[6:])))
case strings.HasPrefix(tag, "form:"): case strings.HasPrefix(tag, "form:"):
return buf.Write([]byte(c.FormValue(tag[5:]))) return buf.Write([]byte(c.FormValue(tag[5:])))
case strings.HasPrefix(tag, "cookie:"):
cookie, err := c.Cookie(tag[7:])
if err == nil {
return buf.Write([]byte(cookie.Value))
}
} }
} }
return 0, nil return 0, nil

View File

@ -9,6 +9,6 @@ type (
) )
// DefaultSkipper returns false which processes the middleware. // DefaultSkipper returns false which processes the middleware.
func DefaultSkipper(c echo.Context) bool { func DefaultSkipper(echo.Context) bool {
return false return false
} }

160
vendor/github.com/labstack/echo/middleware/proxy.go generated vendored Normal file
View File

@ -0,0 +1,160 @@
package middleware
import (
"errors"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/http/httputil"
"net/url"
"sync/atomic"
"time"
"github.com/labstack/echo"
)
// TODO: Handle TLS proxy
type (
// ProxyConfig defines the config for Proxy middleware.
ProxyConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Balancer defines a load balancing technique.
// Required.
// Possible values:
// - RandomBalancer
// - RoundRobinBalancer
Balancer ProxyBalancer
}
// ProxyTarget defines the upstream target.
ProxyTarget struct {
URL *url.URL
}
// RandomBalancer implements a random load balancing technique.
RandomBalancer struct {
Targets []*ProxyTarget
random *rand.Rand
}
// RoundRobinBalancer implements a round-robin load balancing technique.
RoundRobinBalancer struct {
Targets []*ProxyTarget
i uint32
}
// ProxyBalancer defines an interface to implement a load balancing technique.
ProxyBalancer interface {
Next() *ProxyTarget
}
)
func proxyHTTP(t *ProxyTarget) http.Handler {
return httputil.NewSingleHostReverseProxy(t.URL)
}
func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h, ok := w.(http.Hijacker)
if !ok {
c.Error(errors.New("proxy raw, not a hijacker"))
return
}
in, _, err := h.Hijack()
if err != nil {
c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", r.URL, err))
return
}
defer in.Close()
out, err := net.Dial("tcp", t.URL.Host)
if err != nil {
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", r.URL, err))
c.Error(he)
return
}
defer out.Close()
err = r.Write(out)
if err != nil {
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request copy error=%v, url=%s", r.URL, err))
c.Error(he)
return
}
errc := make(chan error, 2)
cp := func(dst io.Writer, src io.Reader) {
_, err := io.Copy(dst, src)
errc <- err
}
go cp(out, in)
go cp(in, out)
err = <-errc
if err != nil && err != io.EOF {
c.Logger().Errorf("proxy raw, error=%v, url=%s", r.URL, err)
}
})
}
// Next randomly returns an upstream target.
func (r *RandomBalancer) Next() *ProxyTarget {
if r.random == nil {
r.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
}
return r.Targets[r.random.Intn(len(r.Targets))]
}
// Next returns an upstream target using round-robin technique.
func (r *RoundRobinBalancer) Next() *ProxyTarget {
r.i = r.i % uint32(len(r.Targets))
t := r.Targets[r.i]
atomic.AddUint32(&r.i, 1)
return t
}
// Proxy returns an HTTP/WebSocket reverse proxy middleware.
func Proxy(config ProxyConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultLoggerConfig.Skipper
}
if config.Balancer == nil {
panic("echo: proxy middleware requires balancer")
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
req := c.Request()
res := c.Response()
tgt := config.Balancer.Next()
// Fix header
if req.Header.Get(echo.HeaderXRealIP) == "" {
req.Header.Set(echo.HeaderXRealIP, c.RealIP())
}
if req.Header.Get(echo.HeaderXForwardedProto) == "" {
req.Header.Set(echo.HeaderXForwardedProto, c.Scheme())
}
if c.IsWebSocket() && req.Header.Get(echo.HeaderXForwardedFor) == "" { // For HTTP, it is automatically set by Go HTTP reverse proxy.
req.Header.Set(echo.HeaderXForwardedFor, c.RealIP())
}
// Proxy
switch {
case c.IsWebSocket():
proxyRaw(tgt, c).ServeHTTP(res, req)
case req.Header.Get(echo.HeaderAccept) == "text/event-stream":
default:
proxyHTTP(tgt).ServeHTTP(res, req)
}
return
}
}
}

View File

@ -0,0 +1,64 @@
package middleware
import (
"github.com/labstack/echo"
"github.com/labstack/gommon/random"
)
type (
// RequestIDConfig defines the config for RequestID middleware.
RequestIDConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Generator defines a function to generate an ID.
// Optional. Default value random.String(32).
Generator func() string
}
)
var (
// DefaultRequestIDConfig is the default RequestID middleware config.
DefaultRequestIDConfig = RequestIDConfig{
Skipper: DefaultSkipper,
Generator: generator,
}
)
// RequestID returns a X-Request-ID middleware.
func RequestID() echo.MiddlewareFunc {
return RequestIDWithConfig(DefaultRequestIDConfig)
}
// RequestIDWithConfig returns a X-Request-ID middleware with config.
func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultRequestIDConfig.Skipper
}
if config.Generator == nil {
config.Generator = generator
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
res := c.Response()
rid := req.Header.Get(echo.HeaderXRequestID)
if rid == "" {
rid = config.Generator()
}
res.Header().Set(echo.HeaderXRequestID, rid)
return next(c)
}
}
}
func generator() string {
return random.String(32)
}

View File

@ -3,7 +3,9 @@ package middleware
import ( import (
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings"
"github.com/labstack/echo" "github.com/labstack/echo"
) )
@ -53,6 +55,9 @@ func Static(root string) echo.MiddlewareFunc {
// See `Static()`. // See `Static()`.
func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc { func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
// Defaults // Defaults
if config.Root == "" {
config.Root = "." // For security we want to restrict to CWD.
}
if config.Skipper == nil { if config.Skipper == nil {
config.Skipper = DefaultStaticConfig.Skipper config.Skipper = DefaultStaticConfig.Skipper
} }
@ -62,26 +67,44 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
p := c.Param("*") if config.Skipper(c) {
name := filepath.Join(config.Root, p) return next(c)
fi, err := os.Stat(name) }
p := c.Request().URL.Path
if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
p = c.Param("*")
}
name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
fi, err := os.Stat(name)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
if config.HTML5 { if config.HTML5 && path.Ext(p) == "" {
return c.File(filepath.Join(config.Root, config.Index)) return c.File(filepath.Join(config.Root, config.Index))
} }
return echo.ErrNotFound return next(c)
} }
return err return err
} }
if fi.IsDir() { if fi.IsDir() {
index := filepath.Join(name, config.Index)
fi, err = os.Stat(index)
if err != nil {
if config.Browse { if config.Browse {
return listDir(name, c.Response()) return listDir(name, c.Response())
} }
return c.File(filepath.Join(name, config.Index)) if os.IsNotExist(err) {
return next(c)
} }
return err
}
return c.File(index)
}
return c.File(name) return c.File(name)
} }
} }

View File

@ -7,7 +7,7 @@ type (
// request matching and URL path parameter parsing. // request matching and URL path parameter parsing.
Router struct { Router struct {
tree *node tree *node
routes map[string]Route routes map[string]*Route
echo *Echo echo *Echo
} }
node struct { node struct {
@ -47,7 +47,7 @@ func NewRouter(e *Echo) *Router {
tree: &node{ tree: &node{
methodHandler: new(methodHandler), methodHandler: new(methodHandler),
}, },
routes: make(map[string]Route), routes: map[string]*Route{},
echo: e, echo: e,
} }
} }
@ -101,7 +101,7 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
cn := r.tree // Current node as root cn := r.tree // Current node as root
if cn == nil { if cn == nil {
panic("echo invalid method") panic("echo: invalid method")
} }
search := path search := path
@ -296,18 +296,19 @@ func (n *node) checkMethodNotAllowed() HandlerFunc {
// - Get context from `Echo#AcquireContext()` // - Get context from `Echo#AcquireContext()`
// - Reset it `Context#Reset()` // - Reset it `Context#Reset()`
// - Return it `Echo#ReleaseContext()`. // - Return it `Echo#ReleaseContext()`.
func (r *Router) Find(method, path string, context Context) { func (r *Router) Find(method, path string, c Context) {
context.SetPath(path) ctx := c.(*context)
ctx.path = path
cn := r.tree // Current node as root cn := r.tree // Current node as root
var ( var (
search = path search = path
c *node // Child node child *node // Child node
n int // Param counter n int // Param counter
nk kind // Next kind nk kind // Next kind
nn *node // Next node nn *node // Next node
ns string // Next search ns string // Next search
pvalues = context.ParamValues() pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
) )
// Search order static > param > any // Search order static > param > any
@ -352,20 +353,20 @@ func (r *Router) Find(method, path string, context Context) {
} }
// Static node // Static node
if c = cn.findChild(search[0], skind); c != nil { if child = cn.findChild(search[0], skind); child != nil {
// Save next // Save next
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623 if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
nk = pkind nk = pkind
nn = cn nn = cn
ns = search ns = search
} }
cn = c cn = child
continue continue
} }
// Param node // Param node
Param: Param:
if c = cn.findChildByKind(pkind); c != nil { if child = cn.findChildByKind(pkind); child != nil {
// Issue #378 // Issue #378
if len(pvalues) == n { if len(pvalues) == n {
continue continue
@ -378,7 +379,7 @@ func (r *Router) Find(method, path string, context Context) {
ns = search ns = search
} }
cn = c cn = child
i, l := 0, len(search) i, l := 0, len(search)
for ; i < l && search[i] != '/'; i++ { for ; i < l && search[i] != '/'; i++ {
} }
@ -409,13 +410,13 @@ func (r *Router) Find(method, path string, context Context) {
} }
End: End:
context.SetHandler(cn.findHandler(method)) ctx.handler = cn.findHandler(method)
context.SetPath(cn.ppath) ctx.path = cn.ppath
context.SetParamNames(cn.pnames...) ctx.pnames = cn.pnames
// NOTE: Slow zone... // NOTE: Slow zone...
if context.Handler() == nil { if ctx.handler == nil {
context.SetHandler(cn.checkMethodNotAllowed()) ctx.handler = cn.checkMethodNotAllowed()
// Dig further for any, might have an empty value for *, e.g. // Dig further for any, might have an empty value for *, e.g.
// serving a directory. Issue #207. // serving a directory. Issue #207.
@ -423,12 +424,12 @@ End:
return return
} }
if h := cn.findHandler(method); h != nil { if h := cn.findHandler(method); h != nil {
context.SetHandler(h) ctx.handler = h
} else { } else {
context.SetHandler(cn.checkMethodNotAllowed()) ctx.handler = cn.checkMethodNotAllowed()
} }
context.SetPath(cn.ppath) ctx.path = cn.ppath
context.SetParamNames(cn.pnames...) ctx.pnames = cn.pnames
pvalues[len(cn.pnames)-1] = "" pvalues[len(cn.pnames)-1] = ""
} }

View File

@ -2,22 +2,24 @@ package random
import ( import (
"math/rand" "math/rand"
"strings"
"time" "time"
) )
type ( type (
Random struct { Random struct {
charset Charset
} }
Charset string
) )
// Charsets
const ( const (
Alphanumeric Charset = Alphabetic + Numeric Uppercase string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Alphabetic Charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" Lowercase = "abcdefghijklmnopqrstuvwxyz"
Numeric Charset = "0123456789" Alphabetic = Uppercase + Lowercase
Hex Charset = Numeric + "abcdef" Numeric = "0123456789"
Alphanumeric = Alphabetic + Numeric
Symbols = "`" + `~!@#$%^&*()-_+={}[]|\;:"<>,./?`
Hex = Numeric + "abcdef"
) )
var ( var (
@ -26,27 +28,21 @@ var (
func New() *Random { func New() *Random {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
return &Random{ return new(Random)
charset: Alphanumeric,
}
} }
func (r *Random) SetCharset(c Charset) { func (r *Random) String(length uint8, charsets ...string) string {
r.charset = c charset := strings.Join(charsets, "")
if charset == "" {
charset = Alphanumeric
} }
func (r *Random) String(length uint8) string {
b := make([]byte, length) b := make([]byte, length)
for i := range b { for i := range b {
b[i] = r.charset[rand.Int63()%int64(len(r.charset))] b[i] = charset[rand.Int63()%int64(len(charset))]
} }
return string(b) return string(b)
} }
func SetCharset(c Charset) { func String(length uint8, charsets ...string) string {
global.SetCharset(c) return global.String(length, charsets...)
}
func String(length uint8) string {
return global.String(length)
} }

View File

@ -1,3 +1,5 @@
// +build !appengine
package fasttemplate package fasttemplate
import ( import (

11
vendor/github.com/valyala/fasttemplate/unsafe_gae.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
// +build appengine
package fasttemplate
func unsafeBytes2String(b []byte) string {
return string(b)
}
func unsafeString2Bytes(s string) []byte {
return []byte(s)
}

14
vendor/manifest vendored
View File

@ -53,7 +53,7 @@
"importpath": "github.com/dgrijalva/jwt-go", "importpath": "github.com/dgrijalva/jwt-go",
"repository": "https://github.com/dgrijalva/jwt-go", "repository": "https://github.com/dgrijalva/jwt-go",
"vcs": "git", "vcs": "git",
"revision": "2268707a8f0843315e2004ee4f1d021dc08baedf", "revision": "6c8dedd55f8a2e41f605de6d5d66e51ed1f299fc",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },
@ -170,7 +170,7 @@
"importpath": "github.com/labstack/echo", "importpath": "github.com/labstack/echo",
"repository": "https://github.com/labstack/echo", "repository": "https://github.com/labstack/echo",
"vcs": "git", "vcs": "git",
"revision": "0b53f397ad7709a27d37500a67735c0a639b5c38", "revision": "c3887ebb131d996411cf13a9688ab02c8dba599e",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },
@ -178,7 +178,7 @@
"importpath": "github.com/labstack/gommon/bytes", "importpath": "github.com/labstack/gommon/bytes",
"repository": "https://github.com/labstack/gommon", "repository": "https://github.com/labstack/gommon",
"vcs": "git", "vcs": "git",
"revision": "f72d3c883f8ea180da8f085dd320804c41332ad1", "revision": "1121fd3e243c202482226a7afe4dcd07ffc4139a",
"branch": "master", "branch": "master",
"path": "/bytes", "path": "/bytes",
"notests": true "notests": true
@ -187,7 +187,7 @@
"importpath": "github.com/labstack/gommon/color", "importpath": "github.com/labstack/gommon/color",
"repository": "https://github.com/labstack/gommon", "repository": "https://github.com/labstack/gommon",
"vcs": "git", "vcs": "git",
"revision": "f72d3c883f8ea180da8f085dd320804c41332ad1", "revision": "1121fd3e243c202482226a7afe4dcd07ffc4139a",
"branch": "master", "branch": "master",
"path": "/color", "path": "/color",
"notests": true "notests": true
@ -196,7 +196,7 @@
"importpath": "github.com/labstack/gommon/log", "importpath": "github.com/labstack/gommon/log",
"repository": "https://github.com/labstack/gommon", "repository": "https://github.com/labstack/gommon",
"vcs": "git", "vcs": "git",
"revision": "f72d3c883f8ea180da8f085dd320804c41332ad1", "revision": "1121fd3e243c202482226a7afe4dcd07ffc4139a",
"branch": "master", "branch": "master",
"path": "/log", "path": "/log",
"notests": true "notests": true
@ -205,7 +205,7 @@
"importpath": "github.com/labstack/gommon/random", "importpath": "github.com/labstack/gommon/random",
"repository": "https://github.com/labstack/gommon", "repository": "https://github.com/labstack/gommon",
"vcs": "git", "vcs": "git",
"revision": "f72d3c883f8ea180da8f085dd320804c41332ad1", "revision": "1121fd3e243c202482226a7afe4dcd07ffc4139a",
"branch": "master", "branch": "master",
"path": "/random", "path": "/random",
"notests": true "notests": true
@ -424,7 +424,7 @@
"importpath": "github.com/valyala/fasttemplate", "importpath": "github.com/valyala/fasttemplate",
"repository": "https://github.com/valyala/fasttemplate", "repository": "https://github.com/valyala/fasttemplate",
"vcs": "git", "vcs": "git",
"revision": "d090d65668a286d9a180d43a19dfdc5dcad8fe88", "revision": "dcecefd839c4193db0d35b88ec65b4c12d360ab0",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },