diff --git a/recipe/cors/server.go b/recipe/cors/server.go new file mode 100644 index 00000000..e3cadb68 --- /dev/null +++ b/recipe/cors/server.go @@ -0,0 +1,38 @@ +package main + +import ( + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "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.Run(standard.New(":1323")) +} diff --git a/recipe/crud/server.go b/recipe/crud/server.go new file mode 100644 index 00000000..f92c3103 --- /dev/null +++ b/recipe/crud/server.go @@ -0,0 +1,76 @@ +package main + +import ( + "net/http" + "strconv" + + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "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.Run(standard.New(":1323")) +} diff --git a/recipe/embed-resources/.gitignore b/recipe/embed-resources/.gitignore new file mode 100644 index 00000000..9524d94f --- /dev/null +++ b/recipe/embed-resources/.gitignore @@ -0,0 +1,2 @@ +rice +app.rice-box.go diff --git a/recipe/embed-resources/app/index.html b/recipe/embed-resources/app/index.html new file mode 100644 index 00000000..66aac446 --- /dev/null +++ b/recipe/embed-resources/app/index.html @@ -0,0 +1,11 @@ + + +
+ +Uploaded successfully %d files with fields name=%s and email=%s.
", len(files), name, email)) +} + +func main() { + e := echo.New() + + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + e.Use(middleware.Static("public")) + + e.POST("/upload", upload) + + e.Run(standard.New(":1323")) +} diff --git a/recipe/file-upload/single/public/index.html b/recipe/file-upload/single/public/index.html new file mode 100644 index 00000000..99357998 --- /dev/null +++ b/recipe/file-upload/single/public/index.html @@ -0,0 +1,17 @@ + + + + +File %s uploaded successfully with fields name=%s and email=%s.
", file.Filename, name, email)) +} + +func main() { + e := echo.New() + + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + e.Use(middleware.Static("public")) + + e.POST("/upload", upload) + + e.Run(standard.New(":1323")) +} diff --git a/recipe/google-app-engine/Dockerfile b/recipe/google-app-engine/Dockerfile new file mode 100644 index 00000000..5d1c13e5 --- /dev/null +++ b/recipe/google-app-engine/Dockerfile @@ -0,0 +1,7 @@ +# Dockerfile extending the generic Go image with application files for a +# single application. +FROM gcr.io/google_appengine/golang + +COPY . /go/src/app +RUN go-wrapper download +RUN go-wrapper install -tags appenginevm \ No newline at end of file diff --git a/recipe/google-app-engine/app-engine.go b/recipe/google-app-engine/app-engine.go new file mode 100644 index 00000000..7b369770 --- /dev/null +++ b/recipe/google-app-engine/app-engine.go @@ -0,0 +1,21 @@ +// +build appengine + +package main + +import ( + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "net/http" +) + +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 + s := standard.New("") + s.SetHandler(e) + http.Handle("/", s) + + return e +} diff --git a/recipe/google-app-engine/app-engine.yaml b/recipe/google-app-engine/app-engine.yaml new file mode 100644 index 00000000..e8f5bf05 --- /dev/null +++ b/recipe/google-app-engine/app-engine.yaml @@ -0,0 +1,36 @@ +application: my-application-id # defined when you create your app using google dev console +module: default # see https://cloud.google.com/appengine/docs/go/ +version: alpha # you can run multiple versions of an app and A/B test +runtime: go # see https://cloud.google.com/appengine/docs/go/ +api_version: go1 # used when appengine supports different go versions + +default_expiration: "1d" # for CDN serving of static files (use url versioning if long!) + +handlers: +# all the static files that we normally serve ourselves are defined here and Google will handle +# serving them for us from it's own CDN / edge locations. For all the configuration options see: +# https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers +- url: / + mime_type: text/html + static_files: public/index.html + upload: public/index.html + +- url: /favicon.ico + mime_type: image/x-icon + static_files: public/favicon.ico + upload: public/favicon.ico + +- url: /scripts + mime_type: text/javascript + static_dir: public/scripts + +# static files normally don't touch the server that the app runs on but server-side template files +# needs to be readable by the app. The application_readable option makes sure they are available as +# part of the app deployment onto the instance. +- url: /templates + static_dir: /templates + application_readable: true + +# finally, we route all other requests to our application. The script name just means "the go app" +- url: /.* + script: _go_app \ No newline at end of file diff --git a/recipe/google-app-engine/app-managed.go b/recipe/google-app-engine/app-managed.go new file mode 100644 index 00000000..d6a41c81 --- /dev/null +++ b/recipe/google-app-engine/app-managed.go @@ -0,0 +1,29 @@ +// +build appenginevm + +package main + +import ( + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "google.golang.org/appengine" + "net/http" + "runtime" +) + +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 + s := standard.New(":8080") + s.SetHandler(e) + http.Handle("/", s) + appengine.Main() +} diff --git a/recipe/google-app-engine/app-managed.yaml b/recipe/google-app-engine/app-managed.yaml new file mode 100644 index 00000000..d5da4cd9 --- /dev/null +++ b/recipe/google-app-engine/app-managed.yaml @@ -0,0 +1,37 @@ +application: my-application-id # defined when you create your app using google dev console +module: default # see https://cloud.google.com/appengine/docs/go/ +version: alpha # you can run multiple versions of an app and A/B test +runtime: go # see https://cloud.google.com/appengine/docs/go/ +api_version: go1 # used when appengine supports different go versions +vm: true # for managed VMs only, remove for appengine classic + +default_expiration: "1d" # for CDN serving of static files (use url versioning if long!) + +handlers: +# all the static files that we normally serve ourselves are defined here and Google will handle +# serving them for us from it's own CDN / edge locations. For all the configuration options see: +# https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers +- url: / + mime_type: text/html + static_files: public/index.html + upload: public/index.html + +- url: /favicon.ico + mime_type: image/x-icon + static_files: public/favicon.ico + upload: public/favicon.ico + +- url: /scripts + mime_type: text/javascript + static_dir: public/scripts + +# static files normally don't touch the server that the app runs on but server-side template files +# needs to be readable by the app. The application_readable option makes sure they are available as +# part of the app deployment onto the instance. +- url: /templates + static_dir: /templates + application_readable: true + +# finally, we route all other requests to our application. The script name just means "the go app" +- url: /.* + script: _go_app \ No newline at end of file diff --git a/recipe/google-app-engine/app-standalone.go b/recipe/google-app-engine/app-standalone.go new file mode 100644 index 00000000..92615b45 --- /dev/null +++ b/recipe/google-app-engine/app-standalone.go @@ -0,0 +1,25 @@ +// +build !appengine,!appenginevm + +package main + +import ( + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "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.Use(middleware.Static("public")) + + return e +} + +func main() { + e.Run(standard.New(":8080")) +} diff --git a/recipe/google-app-engine/app.go b/recipe/google-app-engine/app.go new file mode 100644 index 00000000..8d4d97a2 --- /dev/null +++ b/recipe/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/recipe/google-app-engine/public/favicon.ico b/recipe/google-app-engine/public/favicon.ico new file mode 100644 index 00000000..d939ddca Binary files /dev/null and b/recipe/google-app-engine/public/favicon.ico differ diff --git a/recipe/google-app-engine/public/index.html b/recipe/google-app-engine/public/index.html new file mode 100644 index 00000000..aed4f466 --- /dev/null +++ b/recipe/google-app-engine/public/index.html @@ -0,0 +1,15 @@ + + + + + +Request Information\n\nProtocol: %s\nHost: %s\nRemote Address: %s\nMethod: %s\nPath: %s\n
"
+ return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path))
+}
+
+func stream(c echo.Context) error {
+ res := c.Response().(*standard.Response).ResponseWriter
+ gone := res.(http.CloseNotifier).CloseNotify()
+ res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
+ res.WriteHeader(http.StatusOK)
+ ticker := time.NewTicker(1 * time.Second)
+ defer ticker.Stop()
+
+ fmt.Fprint(res, "Clock Stream\n\n")
+ for {
+ fmt.Fprintf(res, "%v\n", time.Now())
+ res.(http.Flusher).Flush()
+ select {
+ case <-ticker.C:
+ case <-gone:
+ break
+ }
+ }
+}
+
+func main() {
+ e := echo.New()
+ e.GET("/request", request)
+ e.GET("/stream", stream)
+ e.Run(standard.WithConfig(engine.Config{
+ Address: ":1323",
+ TLSCertFile: "cert.pem",
+ TLSKeyFile: "key.pem",
+ }))
+}
diff --git a/recipe/jsonp/public/index.html b/recipe/jsonp/public/index.html
new file mode 100644
index 00000000..b26791ef
--- /dev/null
+++ b/recipe/jsonp/public/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ JSONP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/recipe/jsonp/server.go b/recipe/jsonp/server.go
new file mode 100644
index 00000000..bee3327b
--- /dev/null
+++ b/recipe/jsonp/server.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+ "math/rand"
+ "net/http"
+ "time"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+ "github.com/labstack/echo/middleware"
+)
+
+func main() {
+ e := echo.New()
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+ e.Use(middleware.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.Run(standard.New(":1323"))
+}
diff --git a/recipe/jwt/custom-claims/server.go b/recipe/jwt/custom-claims/server.go
new file mode 100644
index 00000000..d0eb5139
--- /dev/null
+++ b/recipe/jwt/custom-claims/server.go
@@ -0,0 +1,87 @@
+package main
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/dgrijalva/jwt-go"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+ "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, 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.(*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.Run(standard.New(":1323"))
+}
diff --git a/recipe/jwt/map-claims/server.go b/recipe/jwt/map-claims/server.go
new file mode 100644
index 00000000..267e045e
--- /dev/null
+++ b/recipe/jwt/map-claims/server.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/dgrijalva/jwt-go"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+ "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.Run(standard.New(":1323"))
+}
diff --git a/recipe/middleware/server.go b/recipe/middleware/server.go
new file mode 100644
index 00000000..d8bf6420
--- /dev/null
+++ b/recipe/middleware/server.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+ "net/http"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+)
+
+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/2.0")
+ return next(c)
+ }
+}
+
+func main() {
+ e := echo.New()
+
+ // Debug mode
+ e.SetDebug(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.Run(standard.New(":1323"))
+}
diff --git a/recipe/streaming-response/server.go b/recipe/streaming-response/server.go
new file mode 100644
index 00000000..ab51c9d8
--- /dev/null
+++ b/recipe/streaming-response/server.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "net/http"
+ "time"
+
+ "encoding/json"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+)
+
+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().(http.Flusher).Flush()
+ time.Sleep(1 * time.Second)
+ }
+ return nil
+ })
+ e.Run(standard.New(":1323"))
+}
diff --git a/recipe/subdomains/server.go b/recipe/subdomains/server.go
new file mode 100644
index 00000000..e16e2fb4
--- /dev/null
+++ b/recipe/subdomains/server.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+ "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(req, res)
+ }
+
+ return
+ })
+ e.Run(standard.New(":1323"))
+}
diff --git a/recipe/websocket/gorilla/server.go b/recipe/websocket/gorilla/server.go
new file mode 100644
index 00000000..a84f2494
--- /dev/null
+++ b/recipe/websocket/gorilla/server.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ "github.com/labstack/echo"
+
+ "github.com/gorilla/websocket"
+ "github.com/labstack/echo/engine/standard"
+ "github.com/labstack/echo/middleware"
+)
+
+var (
+ upgrader = websocket.Upgrader{}
+)
+
+func hello() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ c, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Print("upgrade:", err)
+ return
+ }
+ defer c.Close()
+
+ for {
+ // Write
+ err := c.WriteMessage(websocket.TextMessage, []byte("Hello, Client!"))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Read
+ _, msg, err := c.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.Use(middleware.Static("../public"))
+ e.GET("/ws", standard.WrapHandler(http.HandlerFunc(hello())))
+ e.Run(standard.New(":1323"))
+}
diff --git a/recipe/websocket/net/server.go b/recipe/websocket/net/server.go
new file mode 100644
index 00000000..f9deabfd
--- /dev/null
+++ b/recipe/websocket/net/server.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+ "github.com/labstack/echo/middleware"
+ "golang.org/x/net/websocket"
+)
+
+func hello() websocket.Handler {
+ return websocket.Handler(func(ws *websocket.Conn) {
+ 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)
+ }
+ })
+}
+
+func main() {
+ e := echo.New()
+ e.Use(middleware.Logger())
+ e.Use(middleware.Recover())
+ e.Use(middleware.Static("../public"))
+ e.GET("/ws", standard.WrapHandler(hello()))
+ e.Run(standard.New(":1323"))
+}
diff --git a/recipe/websocket/public/index.html b/recipe/websocket/public/index.html
new file mode 100644
index 00000000..33cabb1b
--- /dev/null
+++ b/recipe/websocket/public/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+ WebSocket
+
+
+
+
+
+
+
+
+
diff --git a/website/config.json b/website/config.json
new file mode 100644
index 00000000..4f48e64e
--- /dev/null
+++ b/website/config.json
@@ -0,0 +1,43 @@
+{
+ "baseurl": "https://echo.labstack.com",
+ "languageCode": "en-us",
+ "title": "Echo - Fast and unfancy HTTP server framework for Go (Golang)",
+ "canonifyurls": true,
+ "googleAnalytics": "UA-85059636-2",
+ "permalinks": {
+ "guide": "/guide/:filename",
+ "middleware": "/middleware/:filename",
+ "recipes": "/recipes/:filename"
+ },
+ "menu": {
+ "side": [{
+ "name": "Guide",
+ "pre": "",
+ "weight": 1,
+ "identifier": "guide",
+ "url": "guide"
+ },{
+ "name": "Middleware",
+ "pre": "",
+ "weight": 1,
+ "identifier": "middleware",
+ "url": "middleware"
+ }, {
+ "name": "Recipes",
+ "pre": "",
+ "weight": 2,
+ "identifier": "recipes",
+ "url": "recipes"
+ }, {
+ "name": "Godoc",
+ "pre": "",
+ "weight": 3,
+ "identifier": "godoc",
+ "url": "godoc"
+ }]
+ },
+ "params": {
+ "image": "https://echo.labstack.com/images/logo.png",
+ "description": "Echo is micro web framework for Go (Golang), high performance, minimalistic and fast."
+ }
+}
diff --git a/website/content/godoc/echo.md b/website/content/godoc/echo.md
new file mode 100644
index 00000000..6fa6ab8c
--- /dev/null
+++ b/website/content/godoc/echo.md
@@ -0,0 +1,8 @@
++++
+title = "echo"
+[menu.side]
+ name = "echo"
+ parent = "godoc"
+ weight = 1
+ url = "https://godoc.org/github.com/labstack/echo"
++++
diff --git a/website/content/godoc/engine.md b/website/content/godoc/engine.md
new file mode 100644
index 00000000..785e48b8
--- /dev/null
+++ b/website/content/godoc/engine.md
@@ -0,0 +1,8 @@
++++
+title = "engine"
+[menu.side]
+ name = "engine"
+ parent = "godoc"
+ weight = 3
+ url = "https://godoc.org/github.com/labstack/echo/engine"
++++
diff --git a/website/content/godoc/fasthttp.md b/website/content/godoc/fasthttp.md
new file mode 100644
index 00000000..0d1b9a7c
--- /dev/null
+++ b/website/content/godoc/fasthttp.md
@@ -0,0 +1,8 @@
++++
+title = "fasthttp"
+[menu.side]
+ name = "engine/fasthttp"
+ parent = "godoc"
+ weight = 5
+ url = "https://godoc.org/github.com/labstack/echo/engine/fasthttp"
++++
diff --git a/website/content/godoc/middleware.md b/website/content/godoc/middleware.md
new file mode 100644
index 00000000..b9a3a8b5
--- /dev/null
+++ b/website/content/godoc/middleware.md
@@ -0,0 +1,9 @@
++++
+title = "middleware"
+[menu.side]
+ name = "middleware"
+ identifier = "godoc-middleware"
+ parent = "godoc"
+ weight = 1
+ url = "https://godoc.org/github.com/labstack/echo/middleware"
++++
diff --git a/website/content/godoc/standard.md b/website/content/godoc/standard.md
new file mode 100644
index 00000000..19e5e92d
--- /dev/null
+++ b/website/content/godoc/standard.md
@@ -0,0 +1,8 @@
++++
+title = "standard"
+[menu.side]
+ name = "engine/standard"
+ parent = "godoc"
+ weight = 4
+ url = "https://godoc.org/github.com/labstack/echo/engine/standard"
++++
diff --git a/website/content/guide/context.md b/website/content/guide/context.md
new file mode 100644
index 00000000..eb2607aa
--- /dev/null
+++ b/website/content/guide/context.md
@@ -0,0 +1,75 @@
++++
+title = "Context"
+description = "Context in Echo"
+[menu.side]
+ name = "Context"
+ identifier = "context"
+ parent = "guide"
+ weight = 5
++++
+
+## Context
+
+`echo.Context` represents the context of the current HTTP request. It holds request and
+response reference, path, path parameters, data, registered handler and APIs to read
+request and write response. Context is 100% compatible with standard `context.Context`.
+As Context is an interface, it is easy to extend it with custom APIs.
+
+#### Extending Context
+
+**Define a custom context**
+
+```go
+type CustomContext struct {
+ echo.Context
+}
+
+func (c *CustomContext) Foo() {
+ println("foo")
+}
+
+func (c *CustomContext) Bar() {
+ println("bar")
+}
+```
+
+**Create a middleware to extend default context**
+
+```go
+e.Use(func(h echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ cc := &CustomContext{c}
+ return h(cc)
+ }
+})
+```
+
+> This middleware should be registered before any other middleware.
+
+**Use in handler**
+
+```go
+e.Get("/", func(c echo.Context) error {
+ cc := c.(*CustomContext)
+ cc.Foo()
+ cc.Bar()
+ return cc.String(200, "OK")
+})
+```
+
+### Standard Context
+
+`echo.Context` embeds standard `context.Context` interface, so all it's functions
+are available right from `echo.Context`.
+
+*Example*
+
+```go
+e.GET("/users/:name", func(c echo.Context) error) {
+ c.SetContext(context.WithValue(nil, "key", "val"))
+ // Pass it down...
+ // Use it...
+ val := c.Value("key").(string)
+ return c.String(http.StatusOK, name)
+})
+```
diff --git a/website/content/guide/cookies.md b/website/content/guide/cookies.md
new file mode 100644
index 00000000..f7d4894c
--- /dev/null
+++ b/website/content/guide/cookies.md
@@ -0,0 +1,78 @@
++++
+title = "Cookies"
+description = "Handling cookie in Echo"
+[menu.side]
+ name = "Cookies"
+ parent = "guide"
+ weight = 6
++++
+
+## Cookies
+
+Cookie is a small piece of data sent from a website and stored in the user's web
+browser while the user is browsing. Every time the user loads the website, the browser
+sends the cookie back to the server to notify the user's previous activity.
+Cookies were designed to be a reliable mechanism for websites to remember stateful
+information (such as items added in the shopping cart in an online store) or to
+record the user's browsing activity (including clicking particular buttons, logging
+in, or recording which pages were visited in the past). Cookies can also store
+passwords and form content a user has previously entered, such as a credit card
+number or an address.
+
+### Cookie Attributes
+
+Attribute | Optional
+:--- | :---
+`Name` | No
+`Value` | No
+`Path` | Yes
+`Domain` | Yes
+`Expires` | Yes
+`Secure` | Yes
+`HTTPOnly` | Yes
+
+### Create a Cookie
+
+```go
+func writeCookie(c echo.Context) error {
+ cookie := new(echo.Cookie)
+ cookie.SetName("username")
+ cookie.SetValue("jon")
+ cookie.SetExpires(time.Now().Add(24 * time.Hour))
+ c.SetCookie(cookie)
+ return c.String(http.StatusOK, "write a cookie")
+}
+```
+
+- Cookie is created using `new(echo.Cookie)`.
+- Attributes for the cookie are set using `Setter` functions.
+- Finally `c.SetCookie(cookies)` adds a `Set-Cookie` header in HTTP response.
+
+### Read a Cookie
+
+```go
+func readCookie(c echo.Context) error {
+ cookie, err := c.Cookie("username")
+ if err != nil {
+ return err
+ }
+ fmt.Println(cookie.Name())
+ fmt.Println(cookie.Value())
+ return c.String(http.StatusOK, "read a cookie")
+}
+```
+
+- Cookie is read by name using `c.Cookie("username")` from the HTTP request.
+- Cookie attributes are accessed using `Getter` function.
+
+### Read all Cookies
+
+```go
+func readAllCookies(c echo.Context) error {
+ for _, cookie := range c.Cookies() {
+ fmt.Println(cookie.Name())
+ fmt.Println(cookie.Value())
+ }
+ return c.String(http.StatusOK, "read all cookie")
+}
+```
diff --git a/website/content/guide/customization.md b/website/content/guide/customization.md
new file mode 100644
index 00000000..a555cb96
--- /dev/null
+++ b/website/content/guide/customization.md
@@ -0,0 +1,98 @@
++++
+title = "Customization"
+description = "Customizing Echo"
+[menu.side]
+ name = "Customization"
+ parent = "guide"
+ weight = 3
++++
+
+## Customization
+
+### HTTP Error Handler
+
+`Echo#SetHTTPErrorHandler(h HTTPErrorHandler)` registers a custom `Echo#HTTPErrorHandler`.
+
+Default HTTP error handler rules:
+
+- If error is of type `Echo#HTTPError` it sends HTTP response with status code `HTTPError.Code`
+and message `HTTPError.Message`.
+- Else it sends `500 - Internal Server Error`.
+- If debug mode is enabled, it uses `error.Error()` as status message.
+
+### Debug
+
+`Echo#SetDebug(on bool)` enable/disable debug mode.
+
+### Logging
+
+#### Custom Logger
+
+`Echo#SetLogger(l log.Logger)`
+
+SetLogger defines a custom logger.
+
+#### Log Output
+
+`Echo#SetLogOutput(w io.Writer)` sets the output destination for the logger. Default
+value `os.Stdout`
+
+To completely disable logs use `Echo#SetLogOutput(io.Discard)`
+
+#### Log Level
+
+`Echo#SetLogLevel(l log.Level)`
+
+SetLogLevel sets the log level for the logger. Default value `5` (OFF).
+Possible values:
+
+- `0` (DEBUG)
+- `1` (INFO)
+- `2` (WARN)
+- `3` (ERROR)
+- `4` (FATAL)
+- `5` (OFF)
+
+### HTTP Engine
+
+Echo currently supports standard and [fasthttp](https://github.com/valyala/fasthttp)
+server engines. Echo utilizes interfaces to abstract the internal implementation
+of these servers so you can seamlessly switch from one engine to another based on
+your preference.
+
+#### Running a standard HTTP server
+
+`e.Run(standard.New(":1323"))`
+
+#### Running a fasthttp server
+
+`e.Run(fasthttp.New(":1323"))`
+
+#### Running a server with TLS configuration
+
+`e.Run(.WithTLS(":1323", "", ""))`
+
+#### Running a server with engine configuration
+
+`e.Run(.WithConfig())`
+
+##### Configuration
+
+```go
+Config struct {
+ Address string // TCP address to listen on.
+ Listener net.Listener // Custom `net.Listener`. If set, server accepts connections on it.
+ TLSCertFile string // TLS certificate file path.
+ TLSKeyFile string // TLS key file path.
+ ReadTimeout time.Duration // Maximum duration before timing out read of the request.
+ WriteTimeout time.Duration // Maximum duration before timing out write of the response.
+}
+```
+
+#### Access internal server instance and configure its properties
+
+```go
+s := standard.New(":1323")
+s.MaxHeaderBytes = 1 << 20
+e.Run(s)
+```
diff --git a/website/content/guide/error-handling.md b/website/content/guide/error-handling.md
new file mode 100644
index 00000000..7aa5448f
--- /dev/null
+++ b/website/content/guide/error-handling.md
@@ -0,0 +1,48 @@
++++
+title = "Error Handling"
+description = "Error handling in Echo"
+[menu.side]
+ name = "Error Handling"
+ parent = "guide"
+ weight = 8
++++
+
+## Error Handling
+
+Echo advocates centralized HTTP error handling by returning error from middleware
+or handlers.
+
+- Log errors from a unified location
+- Send customized HTTP responses
+
+For example, when basic auth middleware finds invalid credentials it returns
+`401 - Unauthorized` error, aborting the current HTTP request.
+
+```go
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+)
+
+func main() {
+ e := echo.New()
+ e.Use(func(c echo.Context) error {
+ // Extract the credentials from HTTP request header and perform a security
+ // check
+
+ // For invalid credentials
+ return echo.NewHTTPError(http.StatusUnauthorized)
+ })
+ e.GET("/welcome", welcome)
+ e.Run(":1323")
+}
+
+func welcome(c echo.Context) error {
+ return c.String(http.StatusOK, "Welcome!")
+}
+```
+
+See how [HTTPErrorHandler](/guide/customization#http-error-handler) handles it.
diff --git a/website/content/guide/faq.md b/website/content/guide/faq.md
new file mode 100644
index 00000000..394dbb8c
--- /dev/null
+++ b/website/content/guide/faq.md
@@ -0,0 +1,90 @@
++++
+title = "FAQ"
+description = "Frequently asked questions in Echo"
+[menu.side]
+ name = "FAQ"
+ parent = "guide"
+ weight = 20
++++
+
+## FAQ
+
+Q: **How to retrieve `*http.Request` and `http.ResponseWriter` from `echo.Context`?**
+
+- `http.Request` > `c.Request().(*standard.Request).Request`
+- `http.ResponseWriter` > `c.Response()`
+
+> Standard engine only
+
+Q: **How to use standard handler `func(http.ResponseWriter, *http.Request)` with Echo?**
+
+```go
+func handler(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, "Handler!")
+}
+
+func main() {
+ e := echo.New()
+ e.GET("/", standard.WrapHandler(http.HandlerFunc(handler)))
+ e.Run(standard.New(":1323"))
+}
+```
+
+Q: **How to use fasthttp handler `func(fasthttp.RequestCtx)` with Echo?**
+
+```go
+func handler(c *fh.RequestCtx) {
+ io.WriteString(c, "Handler!")
+}
+
+func main() {
+ e := echo.New()
+ e.GET("/", fasthttp.WrapHandler(handler))
+ e.Run(fasthttp.New(":1323"))
+}
+```
+
+Q: **How to use standard middleware `func(http.Handler) http.Handler` with Echo?**
+
+```go
+func middleware(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ println("Middleware!")
+ h.ServeHTTP(w, r)
+ })
+}
+
+func main() {
+ e := echo.New()
+ e.Use(standard.WrapMiddleware(middleware))
+ e.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "OK")
+ })
+ e.Run(standard.New(":1323"))
+}
+```
+
+Q: **How to use fasthttp middleware `func(http.Handler) http.Handler` with Echo?**
+
+```go
+func middleware(h fh.RequestHandler) fh.RequestHandler {
+ return func(ctx *fh.RequestCtx) {
+ println("Middleware!")
+ h(ctx)
+ }
+}
+
+func main() {
+ e := echo.New()
+ e.Use(fasthttp.WrapMiddleware(middleware))
+ e.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "OK")
+ })
+ e.Run(fasthttp.New(":1323"))
+}
+```
+
+
diff --git a/website/content/guide/installation.md b/website/content/guide/installation.md
new file mode 100644
index 00000000..78449b9b
--- /dev/null
+++ b/website/content/guide/installation.md
@@ -0,0 +1,23 @@
++++
+title = "Installation"
+description = "Installing Echo"
+[menu.side]
+ name = "Installation"
+ parent = "guide"
+ weight = 1
++++
+
+## Installation
+
+Echo is developed and tested using Go `1.6.x` and `1.7.x`
+
+```sh
+$ go get -u github.com/labstack/echo
+```
+
+> Ideally, you should rely on a [package manager](https://github.com/avelino/awesome-go#package-management) like glide or govendor to use a specific [version](https://github.com/labstack/echo/releases) of Echo.
+
+### [Migrating from v1](/guide/migrating)
+
+Echo follows [semantic versioning](http://semver.org) managed through GitHub releases.
+Specific version of Echo can be installed using a [package manager](https://github.com/avelino/awesome-go#package-management).
diff --git a/website/content/guide/migrating.md b/website/content/guide/migrating.md
new file mode 100644
index 00000000..0baea8c1
--- /dev/null
+++ b/website/content/guide/migrating.md
@@ -0,0 +1,93 @@
++++
+title = "Migrating"
+description = "Migrating from Echo v1 to v2"
+[menu.side]
+ name = "Migrating"
+ parent = "guide"
+ weight = 2
++++
+
+## Migrating from v1
+
+### Change Log
+
+- Good news, 85% of the API remains the same.
+- `Engine` interface to abstract `HTTP` server implementation, allowing
+us to use HTTP servers beyond Go standard library. It currently supports standard and [fasthttp](https://github.com/valyala/fasthttp) server.
+- Context, Request and Response are converted to interfaces. [More...](https://github.com/labstack/echo/issues/146)
+- Handler signature is changed to `func (c echo.Context) error`.
+- Dropped auto wrapping of handler and middleware to enforce compile time check.
+- APIs to run middleware before or after the router, which doesn't require `Echo#Hook` API now.
+- Ability to define middleware at route level.
+- `Echo#HTTPError` exposed it's fields `Code` and `Message`.
+- Option to specify log format in logger middleware and default logger.
+
+#### API
+
+v1 | v2
+--- | ---
+`Context#Query()` | `Context#QueryParam()`
+`Context#Form()` | `Context#FormValue()`
+
+### FAQ
+
+Q. How to access original objects from interfaces?
+
+A. Only if you need to...
+
+```go
+// `*http.Request`
+c.Request().(*standard.Request).Request
+
+// `*http.URL`
+c.Request().URL().(*standard.URL).URL
+
+// Request `http.Header`
+c.Request().Header().(*standard.Header).Header
+
+// `http.ResponseWriter`
+c.Response().(*standard.Response).ResponseWriter
+
+// Response `http.Header`
+c.Response().Header().(*standard.Header).Header
+```
+
+Q. How to use standard handler and middleware?
+
+A.
+
+```go
+package main
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+)
+
+// Standard middleware
+func middleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ println("standard middleware")
+ next.ServeHTTP(w, r)
+ })
+}
+
+// Standard handler
+func handler(w http.ResponseWriter, r *http.Request) {
+ println("standard handler")
+}
+
+func main() {
+ e := echo.New()
+ e.Use(standard.WrapMiddleware(middleware))
+ e.GET("/", standard.WrapHandler(http.HandlerFunc(handler)))
+ e.Run(standard.New(":1323"))
+}
+```
+
+### Next?
+
+- Browse through [recipes](/recipes/hello-world) freshly converted to v2.
+- Read documentation and dig into test cases.
diff --git a/website/content/guide/request.md b/website/content/guide/request.md
new file mode 100644
index 00000000..f4ec131e
--- /dev/null
+++ b/website/content/guide/request.md
@@ -0,0 +1,98 @@
++++
+title = "HTTP Request"
+description = "Handling HTTP request in Echo"
+[menu.side]
+ name = "Request"
+ parent = "guide"
+ weight = 6
++++
+
+## Request
+
+### Bind Request Body
+
+To bind request body into a provided Go type use `Context#Bind(interface{})`.
+The default binder supports decoding application/json, application/xml and
+application/x-www-form-urlencoded payload based on Context-Type header.
+
+*Example*
+
+TODO
+
+> Custom binder can be registered via `Echo#SetBinder(Binder)`
+
+### Query Parameter
+
+Query parameter can be retrieved by name using `Context#QueryParam(name string)`.
+
+*Example*
+
+```go
+e.GET("/users", func(c echo.Context) error {
+ name := c.QueryParam("name")
+ return c.String(http.StatusOK, name)
+})
+```
+
+```sh
+$ curl -G -d "name=joe" http://localhost:1323/users
+```
+
+### Form Parameter
+
+Form parameter can be retrieved by name using `Context#FormValue(name string)`.
+
+*Example*
+
+```go
+e.POST("/users", func(c echo.Context) error {
+ name := c.FormValue("name")
+ return c.String(http.StatusOK, name)
+})
+```
+
+```sh
+$ curl -d "name=joe" http://localhost:1323/users
+```
+
+### Path Parameter
+
+Registered path parameter can be retrieved either by name `Context#Param(name string) string`
+or by index `Context#P(i int) string`. Getting parameter by index gives a slightly
+better performance.
+
+*Example*
+
+```go
+e.GET("/users/:name", func(c echo.Context) error {
+ // By name
+ name := c.Param("name")
+
+ // By index
+ name := c.P(0)
+
+ return c.String(http.StatusOK, name)
+})
+```
+
+```sh
+$ curl http://localhost:1323/users/joe
+```
+
+
+### Handler Path
+
+`Context#Path()` returns the registered path for the handler, it can be used in the
+middleware for logging purpose.
+
+*Example*
+
+```go
+e.Use(func(c echo.Context) error {
+ println(c.Path()) // Prints `/users/:name`
+ return nil
+})
+e.GET("/users/:name", func(c echo.Context) error) {
+ return c.String(http.StatusOK, name)
+})
+```
diff --git a/website/content/guide/routing.md b/website/content/guide/routing.md
new file mode 100644
index 00000000..3fa02126
--- /dev/null
+++ b/website/content/guide/routing.md
@@ -0,0 +1,115 @@
++++
+title = "HTTP Routing"
+description = "Routing HTTP request in Echo"
+[menu.side]
+ name = "Routing"
+ parent = "guide"
+ weight = 4
++++
+
+## Routing
+
+Echo's router is [fast, optimized]({{< ref "index.md#performance">}}) and
+flexible. It's based on [radix tree](http://en.wikipedia.org/wiki/Radix_tree) data
+structure which makes route lookup really fast. Router leverages [sync pool](https://golang.org/pkg/sync/#Pool)
+to reuse memory and achieve zero dynamic memory allocation with no GC overhead.
+
+Routes can be registered by specifying HTTP method, path and a matching handler.
+For example, code below registers a route for method `GET`, path `/hello` and a
+handler which sends `Hello, World!` HTTP response.
+
+```go
+// Handler
+func hello(c echo.Context) error {
+ return c.String(http.StatusOK, "Hello, World!")
+}
+
+// Route
+e.GET("/hello", hello)
+```
+
+You can use `Echo.Any(path string, h Handler)` to register a handler for all HTTP methods.
+If you want to register it for some methods use `Echo.Match(methods []string, path string, h Handler)`.
+
+Echo defines handler function as `func(echo.Context) error` where `echo.Context` primarily
+holds HTTP request and response interfaces.
+
+### Match-any
+
+Matches zero or more characters in the path. For example, pattern `/users/*` will
+match:
+
+- `/users/`
+- `/users/1`
+- `/users/1/files/1`
+- `/users/anything...`
+
+### Path matching order
+
+- Static
+- Param
+- Match any
+
+#### Example
+
+```go
+e.GET("/users/:id", func(c echo.Context) error {
+ return c.String(http.StatusOK, "/users/:id")
+})
+
+e.GET("/users/new", func(c echo.Context) error {
+ return c.String(http.StatusOK, "/users/new")
+})
+
+e.GET("/users/1/files/*", func(c echo.Context) error {
+ return c.String(http.StatusOK, "/users/1/files/*")
+})
+```
+
+Above routes would resolve in the following order:
+
+- `/users/new`
+- `/users/:id`
+- `/users/1/files/*`
+
+> Routes can be written in any order.
+
+### Group
+
+`Echo#Group(prefix string, m ...Middleware) *Group`
+
+Routes with common prefix can be grouped to define a new sub-router with optional
+middleware. In addition to specified middleware group also inherits parent middleware.
+To add middleware later in the group you can use `Group.Use(m ...Middleware)`.
+Groups can also be nested.
+
+In the code below, we create an admin group which requires basic HTTP authentication
+for routes `/admin/*`.
+
+```go
+g := e.Group("/admin")
+g.Use(middleware.BasicAuth(func(username, password string) bool {
+ if username == "joe" && password == "secret" {
+ return true
+ }
+ return false
+}))
+```
+
+### URI building
+
+`Echo.URI` can be used to generate URI for any handler with specified path parameters.
+It's helpful to centralize all your URI patterns which ease in refactoring your
+application.
+
+`e.URI(h, 1)` will generate `/users/1` for the route registered below
+
+```go
+// Handler
+h := func(c echo.Context) error {
+ return c.String(http.StatusOK, "OK")
+}
+
+// Route
+e.GET("/users/:id", h)
+```
diff --git a/website/content/guide/static-files.md b/website/content/guide/static-files.md
new file mode 100644
index 00000000..e15afc85
--- /dev/null
+++ b/website/content/guide/static-files.md
@@ -0,0 +1,60 @@
++++
+title = "Static Files"
+description = "Serving static files in Echo"
+[menu.side]
+ name = "Static Files"
+ parent = "guide"
+ weight = 3
++++
+
+Images, JavaScript, CSS, PDF, Fonts and so on...
+
+## Static Files
+
+### [Using Static Middleware]({{< ref "middleware/static.md">}})
+
+### Using `Echo#Static()`
+
+`Echo#Static(prefix, root string)` registers a new route with path prefix to serve
+static files from the provided root directory.
+
+*Usage 1*
+
+```go
+e := echo.New()
+e.Static("/static", "assets")
+```
+
+Example above will serve any file from the assets directory for path `/static/*`. For example,
+a request to `/static/js/main.js` will fetch and serve `assets/js/main.js` file.
+
+*Usage 2*
+
+```go
+e := echo.New()
+e.Static("/", "assets")
+```
+
+Example above will serve any file from the assets directory for path `/*`. For example,
+a request to `/js/main.js` will fetch and serve `assets/js/main.js` file.
+
+### Using `Echo#File()`
+
+`Echo#File(path, file string)` registers a new route with path to serve a static
+file.
+
+*Usage 1*
+
+Serving an index page from `public/index.html`
+
+```go
+e.File("/", "public/index.html")
+```
+
+*Usage 2*
+
+Serving a favicon from `images/favicon.ico`
+
+```go
+e.File("/favicon.ico", "images/favicon.ico")
+```
diff --git a/website/content/guide/templates.md b/website/content/guide/templates.md
new file mode 100644
index 00000000..4c0c7d3a
--- /dev/null
+++ b/website/content/guide/templates.md
@@ -0,0 +1,60 @@
++++
+title = "Templates"
+description = "How to use templates in Echo"
+[menu.side]
+ name = "Templates"
+ parent = "guide"
+ weight = 3
++++
+
+## Templates
+
+### Template Rendering
+
+`Context#Render(code int, name string, data interface{}) error` renders a template
+with data and sends a text/html response with status code. Templates can be registered
+using `Echo.SetRenderer()`, allowing us to use any template engine.
+
+Example below shows how to use Go `html/template`:
+
+1. Implement `echo.Renderer` interface
+
+ ```go
+ type Template struct {
+ templates *template.Template
+ }
+
+ func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
+ return t.templates.ExecuteTemplate(w, name, data)
+ }
+ ```
+
+2. Pre-compile templates
+
+ `public/views/hello.html`
+
+ ```html
+ {{define "hello"}}Hello, {{.}}!{{end}}
+ ```
+
+ ```go
+ t := &Template{
+ templates: template.Must(template.ParseGlob("public/views/*.html")),
+ }
+ ```
+
+3. Register templates
+
+ ```go
+ e := echo.New()
+ e.SetRenderer(t)
+ e.GET("/hello", Hello)
+ ```
+
+4. Render a template inside your handler
+
+ ```go
+ func Hello(c echo.Context) error {
+ return c.Render(http.StatusOK, "hello", "World")
+ }
+ ```
diff --git a/website/content/guide/testing.md b/website/content/guide/testing.md
new file mode 100644
index 00000000..5b6af23c
--- /dev/null
+++ b/website/content/guide/testing.md
@@ -0,0 +1,161 @@
++++
+title = "Testing"
+description = "Testing handler and middleware in Echo"
+[menu.side]
+ name = "Testing"
+ parent = "guide"
+ weight = 9
++++
+
+## Testing
+
+### Testing Handler
+
+`GET` `/users/:id`
+
+Handler below retrieves user by id from the database. If user is not found it returns
+`404` error with a message.
+
+#### CreateUser
+
+`POST` `/users`
+
+- Accepts JSON payload
+- On success `201 - Created`
+- On error `500 - Internal Server Error`
+
+#### GetUser
+
+`GET` `/users/:email`
+
+- On success `200 - OK`
+- On error `404 - Not Found` if user is not found otherwise `500 - Internal Server Error`
+
+`handler.go`
+
+```go
+package handler
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo"
+)
+
+type (
+ User struct {
+ Name string `json:"name" form:"name"`
+ Email string `json:"email" form:"email"`
+ }
+ handler struct {
+ db map[string]*User
+ }
+)
+
+func (h *handler) createUser(c echo.Context) error {
+ u := new(User)
+ if err := c.Bind(u); err != nil {
+ return err
+ }
+ return c.JSON(http.StatusCreated, u)
+}
+
+func (h *handler) getUser(c echo.Context) error {
+ email := c.Param("email")
+ user := h.db[email]
+ if user == nil {
+ return echo.NewHTTPError(http.StatusNotFound, "user not found")
+ }
+ return c.JSON(http.StatusOK, user)
+}
+```
+
+`handler_test.go`
+
+```go
+package handler
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+ "github.com/stretchr/testify/assert"
+)
+
+var (
+ mockDB = map[string]*User{
+ "jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"},
+ }
+ userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}`
+)
+
+func TestCreateUser(t *testing.T) {
+ // Setup
+ e := echo.New()
+ req, err := http.NewRequest(echo.POST, "/users", strings.NewReader(userJSON))
+ if assert.NoError(t, err) {
+ req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+ rec := httptest.NewRecorder()
+ c := e.NewContext(standard.NewRequest(req, e.Logger()), standard.NewResponse(rec, e.Logger()))
+ h := &handler{mockDB}
+
+ // Assertions
+ if assert.NoError(t, h.createUser(c)) {
+ assert.Equal(t, http.StatusCreated, rec.Code)
+ assert.Equal(t, userJSON, rec.Body.String())
+ }
+ }
+}
+
+func TestGetUser(t *testing.T) {
+ // Setup
+ e := echo.New()
+ req := new(http.Request)
+ rec := httptest.NewRecorder()
+ c := e.NewContext(standard.NewRequest(req, e.Logger()), standard.NewResponse(rec, e.Logger()))
+ c.SetPath("/users/:email")
+ c.SetParamNames("email")
+ c.SetParamValues("jon@labstack.com")
+ h := &handler{mockDB}
+
+ // Assertions
+ if assert.NoError(t, h.getUser(c)) {
+ assert.Equal(t, http.StatusOK, rec.Code)
+ assert.Equal(t, userJSON, rec.Body.String())
+ }
+}
+```
+
+#### Using Form Payload
+
+```go
+f := make(url.Values)
+f.Set("name", "Jon Snow")
+f.Set("email", "jon@labstack.com")
+req, err := http.NewRequest(echo.POST, "/", strings.NewReader(f.Encode()))
+```
+
+#### Setting Path Params
+
+```go
+c.SetParamNames("id", "email")
+c.SetParamValues("1", "jon@labstack.com")
+```
+
+#### Setting Query Params
+
+```go
+q := make(url.Values)
+q.Set("email", "jon@labstack.com")
+req, err := http.NewRequest(echo.POST, "/?"+q.Encode(), nil)
+```
+
+### Testing Middleware
+
+*TBD*
+
+You can looking to built-in middleware [test cases](https://github.com/labstack/echo/tree/master/middleware).
diff --git a/website/content/index.md b/website/content/index.md
new file mode 100644
index 00000000..3c911537
--- /dev/null
+++ b/website/content/index.md
@@ -0,0 +1,291 @@
++++
+title = "Index"
++++
+
+# Fast and unfancy HTTP server framework for Go (Golang).
+
+## Feature Overview
+
+- Optimized HTTP router which smartly prioritize routes
+- Build robust and scalable RESTful APIs
+- Run with standard HTTP server or FastHTTP server
+- Group APIs
+- Extensible middleware framework
+- Define middleware at root, group or route level
+- Data binding for JSON, XML and form payload
+- Handy functions to send variety of HTTP responses
+- Centralized HTTP error handling
+- Template rendering with any template engine
+- Define your format for the logger
+- Highly customizable
+
+
+## Performance
+
+- Environment:
+ - Go 1.6
+ - wrk 4.0.0
+ - 2 GB, 2 Core (DigitalOcean)
+- Test Suite: https://github.com/vishr/web-framework-benchmark
+- Date: 4/4/2016
+
+
+
+## Quick Start
+
+### Installation
+
+```sh
+$ go get -u github.com/labstack/echo
+```
+
+### Hello, World!
+
+Create `server.go`
+
+```go
+package main
+
+import (
+ "net/http"
+ "github.com/labstack/echo"
+ "github.com/labstack/echo/engine/standard"
+)
+
+func main() {
+ e := echo.New()
+ e.GET("/", func(c echo.Context) error {
+ return c.String(http.StatusOK, "Hello, World!")
+ })
+ e.Run(standard.New(":1323"))
+}
+```
+
+Start server
+
+```sh
+$ go run server.go
+```
+
+Browse to [http://localhost:1323](http://localhost:1323) and you should see
+Hello, World! on the page.
+
+### Routing
+
+```go
+e.POST("/users", saveUser)
+e.GET("/users/:id", getUser)
+e.PUT("/users/:id", updateUser)
+e.DELETE("/users/:id", deleteUser)
+```
+
+### Path Parameters
+
+```go
+func getUser(c echo.Context) error {
+ // User ID from path `users/:id`
+ id := c.Param("id")
+}
+```
+
+### Query Parameters
+
+`/show?team=x-men&member=wolverine`
+
+```go
+func show(c echo.Context) error {
+ // Get team and member from the query string
+ team := c.QueryParam("team")
+ member := c.QueryParam("member")
+}
+```
+
+### Form `application/x-www-form-urlencoded`
+
+`POST` `/save`
+
+name | value
+:--- | :---
+name | Joe Smith
+email | joe@labstack.com
+
+```go
+func save(c echo.Context) error {
+ // Get name and email
+ name := c.FormValue("name")
+ email := c.FormValue("email")
+}
+```
+
+### Form `multipart/form-data`
+
+`POST` `/save`
+
+name | value
+:--- | :---
+name | Joe Smith
+email | joe@labstack.com
+avatar | avatar
+
+```go
+func save(c echo.Context) error {
+ // Get name and email
+ name := c.FormValue("name")
+ email := c.FormValue("email")
+ // Get avatar
+ avatar, err := c.FormFile("avatar")
+ if err != nil {
+ return err
+ }
+
+ // Source
+ src, err := avatar.Open()
+ if err != nil {
+ return err
+ }
+ defer src.Close()
+
+ // Destination
+ dst, err := os.Create(avatar.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, "Thank you!")
+}
+```
+
+### Handling Request
+
+- Bind `JSON` or `XML` or `form` payload into Go struct based on `Content-Type` request header.
+- Render response as `JSON` or `XML` with status code.
+
+```go
+type User struct {
+ Name string `json:"name" xml:"name" form:"name"`
+ Email string `json:"email" xml:"email" form:"email"`
+}
+
+e.POST("/users", func(c echo.Context) error {
+ u := new(User)
+ if err := c.Bind(u); err != nil {
+ return err
+ }
+ return c.JSON(http.StatusCreated, u)
+ // or
+ // return c.XML(http.StatusCreated, u)
+})
+```
+
+### Static Content
+
+Server any file from static directory for path `/static/*`.
+
+```go
+e.Static("/static", "static")
+```
+
+##### [Learn More](https://echo.labstack.com/guide/static-files)
+
+### [Template Rendering](https://echo.labstack.com/guide/templates)
+
+### Middleware
+
+```go
+// Root level middleware
+e.Use(middleware.Logger())
+e.Use(middleware.Recover())
+
+// Group level middleware
+g := e.Group("/admin")
+g.Use(middleware.BasicAuth(func(username, password string) bool {
+ if username == "joe" && password == "secret" {
+ return true
+ }
+ return false
+}))
+
+// Route level middleware
+track := func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ println("request to /users")
+ return next(c)
+ }
+}
+e.GET("/users", func(c echo.Context) error {
+ return c.String(http.StatusOK, "/users")
+}, track)
+```
+
+#### Built-in Middleware
+
+Middleware | Description
+:--- | :---
+[BodyLimit]({{< ref "middleware/body-limit.md">}}) | Limit request body
+[Logger]({{< ref "middleware/logger.md">}}) | Log HTTP requests
+[Recover]({{< ref "middleware/recover.md">}}) | Recover from panics
+[Gzip]({{< ref "middleware/gzip.md">}}) | Send gzip HTTP response
+[BasicAuth]({{< ref "middleware/basic-auth.md">}}) | HTTP basic authentication
+[JWTAuth]({{< ref "middleware/jwt.md">}}) | JWT authentication
+[Secure]({{< ref "middleware/secure.md">}}) | Protection against attacks
+[CORS]({{< ref "middleware/cors.md">}}) | Cross-Origin Resource Sharing
+[CSRF]({{< ref "middleware/csrf.md">}}) | Cross-Site Request Forgery
+[Static]({{< ref "middleware/static.md">}}) | Serve static files
+[HTTPSRedirect]({{< ref "middleware/redirect.md#httpsredirect-middleware">}}) | Redirect HTTP requests to HTTPS
+[HTTPSWWWRedirect]({{< ref "middleware/redirect.md#httpswwwredirect-middleware">}}) | Redirect HTTP requests to WWW HTTPS
+[WWWRedirect]({{< ref "middleware/redirect.md#wwwredirect-middleware">}}) | Redirect non WWW requests to WWW
+[NonWWWRedirect]({{< ref "middleware/redirect.md#nonwwwredirect-middleware">}}) | Redirect WWW requests to non WWW
+[AddTrailingSlash]({{< ref "middleware/trailing-slash.md#addtrailingslash-middleware">}}) | Add trailing slash to the request URI
+[RemoveTrailingSlash]({{< ref "middleware/trailing-slash.md#removetrailingslash-middleware">}}) | Remove trailing slash from the request URI
+[MethodOverride]({{< ref "middleware/method-override.md">}}) | Override request method
+
+#### Third-party Middleware
+
+Middleware | Description
+:--- | :---
+[echoperm](https://github.com/xyproto/echoperm) | Keeping track of users, login states and permissions.
+[echopprof](https://github.com/mtojek/echopprof) | Adapt net/http/pprof to labstack/echo.
+
+##### [Learn More](https://echo.labstack.com/middleware/overview)
+
+### Next
+
+- Head over to [guide](https://echo.labstack.com/guide/installation)
+- Browse [recipes](https://echo.labstack.com/recipes/hello-world)
+
+### Need help?
+
+- [Hop on to chat](https://gitter.im/labstack/echo)
+- [Open an issue](https://github.com/labstack/echo/issues/new)
+
+## Support Echo
+
+- ☆ the project
+- [Donate](https://echo.labstack.com/support-echo)
+- 🌐 spread the word
+- [Contribute](#contribute:d680e8a854a7cbad6d490c445cba2eba) to the project
+
+## Contribute
+
+**Use issues for everything**
+
+- Report issues
+- Discuss on chat before sending a pull request
+- Suggest new features or enhancements
+- Improve/fix documentation
+
+## Credits
+
+- [Vishal Rana](https://github.com/vishr) - Author
+- [Nitin Rana](https://github.com/nr17) - Consultant
+- [Contributors](https://github.com/labstack/echo/graphs/contributors)
+
+## License
+
+[MIT](https://github.com/labstack/echo/blob/master/LICENSE)
diff --git a/website/content/middleware/basic-auth.md b/website/content/middleware/basic-auth.md
new file mode 100644
index 00000000..6bf24c01
--- /dev/null
+++ b/website/content/middleware/basic-auth.md
@@ -0,0 +1,59 @@
++++
+title = "BasicAuth Middleware"
+description = "Basic auth middleware for Echo"
+[menu.side]
+ name = "BasicAuth"
+ parent = "middleware"
+ weight = 5
++++
+
+## BasicAuth Middleware
+
+BasicAuth middleware provides an HTTP basic authentication.
+
+- For valid credentials it calls the next handler.
+- For invalid credentials, it sends "401 - Unauthorized" response.
+- For empty or invalid `Authorization` header, it sends "400 - Bad Request" response.
+
+*Usage*
+
+```go
+e := echo.New()
+e.Use(middleware.BasicAuth(func(username, password string) bool {
+ if username == "joe" && password == "secret" {
+ return true
+ }
+ return false
+}))
+```
+
+### Custom Configuration
+
+*Usage*
+
+```go
+e := echo.New()
+e.Use(middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{},
+}))
+```
+
+### Configuration
+
+```go
+BasicAuthConfig struct {
+ // Skipper defines a function to skip middleware.
+ Skipper Skipper
+
+ // Validator is a function to validate BasicAuth credentials.
+ // Required.
+ Validator BasicAuthValidator
+}
+```
+
+*Default Configuration*
+
+```go
+DefaultBasicAuthConfig = BasicAuthConfig{
+ Skipper: defaultSkipper,
+}
+```
diff --git a/website/content/middleware/body-limit.md b/website/content/middleware/body-limit.md
new file mode 100644
index 00000000..4d342b0f
--- /dev/null
+++ b/website/content/middleware/body-limit.md
@@ -0,0 +1,55 @@
++++
+title = "BodyLimit Middleware"
+description = "Body limit middleware for Echo"
+[menu.side]
+ name = "BodyLimit"
+ parent = "middleware"
+ weight = 5
++++
+
+## 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 body limit 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.
+
+*Usage*
+
+```go
+e := echo.New()
+e.Use(middleware.BodyLimit("2M"))
+```
+### Custom Configuration
+
+*Usage*
+
+```go
+e := echo.New()
+e.Use(middleware.BodyLimitWithConfig(middleware.BodyLimitConfig{},
+}))
+```
+
+### Configuration
+
+```go
+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"`
+}
+```
+
+*Default Configuration*
+
+```go
+DefaultBodyLimitConfig = BodyLimitConfig{
+ Skipper: defaultSkipper,
+}
+```
diff --git a/website/content/middleware/cors.md b/website/content/middleware/cors.md
new file mode 100644
index 00000000..d2a1c0fe
--- /dev/null
+++ b/website/content/middleware/cors.md
@@ -0,0 +1,80 @@
++++
+title = "CORS Middleware"
+description = "CORS middleware for Echo"
+[menu.side]
+ name = "CORS"
+ parent = "middleware"
+ weight = 5
++++
+
+## CORS Middleware
+
+CORS middleware implements [CORS](http://www.w3.org/TR/cors) specification.
+CORS gives web servers cross-domain access controls, which enable secure cross-domain
+data transfers.
+
+*Usage*
+
+`e.Use(middleware.CORS())`
+
+### Custom Configuration
+
+*Usage*
+
+```go
+e := echo.New()
+e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
+ AllowOrigins: []string{"https://labstack.com", "https://labstack.net"},
+ AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
+}))
+```
+
+### Configuration
+
+```go
+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"`
+}
+```
+
+*Default Configuration*
+
+```go
+DefaultCORSConfig = CORSConfig{
+ Skipper: defaultSkipper,
+ AllowOrigins: []string{"*"},
+ AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
+}
+```
diff --git a/website/content/middleware/csrf.md b/website/content/middleware/csrf.md
new file mode 100644
index 00000000..2a9d4f87
--- /dev/null
+++ b/website/content/middleware/csrf.md
@@ -0,0 +1,107 @@
++++
+title = "CSRF Middleware"
+description = "CSRF middleware for Echo"
+[menu.side]
+ name = "CSRF"
+ parent = "middleware"
+ weight = 5
++++
+
+## CSRF Middleware
+
+Cross-site request forgery, also known as one-click attack or session riding and
+abbreviated as CSRF (sometimes pronounced sea-surf) or XSRF, is a type of malicious
+exploit of a website where unauthorized commands are transmitted from a user that
+the website trusts.
+
+*Usage*
+
+`e.Use(middleware.CSRF())`
+
+### Custom Configuration
+
+*Usage*
+
+```go
+e := echo.New()
+e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
+ TokenLookup: "header:X-XSRF-TOKEN",
+}))
+```
+
+Example above uses `X-XSRF-TOKEN` request header to extract CSRF token.
+
+### Accessing CSRF Token
+
+#### Server-side
+
+CSRF token can be accessed from `Echo#Context` using `ContextKey` and passed to
+the client via template.
+
+#### Client-side
+
+CSRF token can be accessed from CSRF cookie.
+
+### Configuration
+
+```go
+// 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 "