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 @@ + + + + + go.rice Example + + + +

go.rice Example

+ + diff --git a/recipe/embed-resources/app/main.js b/recipe/embed-resources/app/main.js new file mode 100644 index 00000000..f888dc5c --- /dev/null +++ b/recipe/embed-resources/app/main.js @@ -0,0 +1 @@ +alert("main.js"); diff --git a/recipe/embed-resources/server.go b/recipe/embed-resources/server.go new file mode 100644 index 00000000..e923194f --- /dev/null +++ b/recipe/embed-resources/server.go @@ -0,0 +1,21 @@ +package main + +import ( + "net/http" + + "github.com/GeertJohan/go.rice" + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" +) + +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("/", standard.WrapHandler(assetHandler)) + + // servers other static files + e.GET("/static/*", standard.WrapHandler(http.StripPrefix("/static/", assetHandler))) + e.Run(standard.New(":3000")) +} diff --git a/recipe/file-upload/multiple/public/index.html b/recipe/file-upload/multiple/public/index.html new file mode 100644 index 00000000..b8463601 --- /dev/null +++ b/recipe/file-upload/multiple/public/index.html @@ -0,0 +1,17 @@ + + + + + Multiple file upload + + +

Upload multiple files with fields

+ +
+ Name:
+ Email:
+ Files:

+ +
+ + diff --git a/recipe/file-upload/multiple/server.go b/recipe/file-upload/multiple/server.go new file mode 100644 index 00000000..f9d31ff0 --- /dev/null +++ b/recipe/file-upload/multiple/server.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "io" + "os" + + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "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("

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 @@ + + + + + Single file upload + + +

Upload single file with fields

+ +
+ Name:
+ Email:
+ Files:

+ +
+ + diff --git a/recipe/file-upload/single/server.go b/recipe/file-upload/single/server.go new file mode 100644 index 00000000..cc59ea5f --- /dev/null +++ b/recipe/file-upload/single/server.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "io" + "os" + + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "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("

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 @@ + + + + + + Echo + + + + + +

Echo!

+ + + diff --git a/recipe/google-app-engine/public/scripts/main.js b/recipe/google-app-engine/public/scripts/main.js new file mode 100644 index 00000000..62a4c8f1 --- /dev/null +++ b/recipe/google-app-engine/public/scripts/main.js @@ -0,0 +1 @@ +console.log("Echo!"); diff --git a/recipe/google-app-engine/templates/welcome.html b/recipe/google-app-engine/templates/welcome.html new file mode 100644 index 00000000..5dc667c3 --- /dev/null +++ b/recipe/google-app-engine/templates/welcome.html @@ -0,0 +1 @@ +{{define "welcome"}}Hello, {{.}}!{{end}} diff --git a/recipe/google-app-engine/users.go b/recipe/google-app-engine/users.go new file mode 100644 index 00000000..b7687181 --- /dev/null +++ b/recipe/google-app-engine/users.go @@ -0,0 +1,54 @@ +package main + +import ( + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +type ( + user struct { + ID string `json:"id"` + Name string `json:"name"` + } +) + +var ( + users map[string]user +) + +func init() { + users = map[string]user{ + "1": user{ + ID: "1", + Name: "Wreck-It Ralph", + }, + } + + // hook into the echo instance to create an endpoint group + // and add specific middleware to it plus handlers + g := e.Group("/users") + g.Use(middleware.CORS()) + + g.POST("", createUser) + g.GET("", getUsers) + g.GET("/:id", getUser) +} + +func createUser(c echo.Context) error { + u := new(user) + if err := c.Bind(u); err != nil { + return err + } + users[u.ID] = *u + return c.JSON(http.StatusCreated, u) +} + +func getUsers(c echo.Context) error { + return c.JSON(http.StatusOK, users) +} + +func getUser(c echo.Context) error { + return c.JSON(http.StatusOK, users[c.P(0)]) +} diff --git a/recipe/google-app-engine/welcome.go b/recipe/google-app-engine/welcome.go new file mode 100644 index 00000000..dc23f28b --- /dev/null +++ b/recipe/google-app-engine/welcome.go @@ -0,0 +1,31 @@ +package main + +import ( + "html/template" + "io" + "net/http" + + "github.com/labstack/echo" +) + +type ( + Template struct { + templates *template.Template + } +) + +func init() { + t := &Template{ + templates: template.Must(template.ParseFiles("templates/welcome.html")), + } + e.SetRenderer(t) + e.GET("/welcome", welcome) +} + +func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + return t.templates.ExecuteTemplate(w, name, data) +} + +func welcome(c echo.Context) error { + return c.Render(http.StatusOK, "welcome", "Joe") +} diff --git a/recipe/graceful-shutdown/grace/server.go b/recipe/graceful-shutdown/grace/server.go new file mode 100644 index 00000000..1eeab531 --- /dev/null +++ b/recipe/graceful-shutdown/grace/server.go @@ -0,0 +1,19 @@ +package main + +import ( + "net/http" + + "github.com/facebookgo/grace/gracehttp" + "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, "Six sick bricks tick") + }) + std := standard.New(":1323") + std.SetHandler(e) + gracehttp.Serve(std.Server) +} diff --git a/recipe/graceful-shutdown/graceful/server.go b/recipe/graceful-shutdown/graceful/server.go new file mode 100644 index 00000000..c41ff8b0 --- /dev/null +++ b/recipe/graceful-shutdown/graceful/server.go @@ -0,0 +1,21 @@ +package main + +import ( + "net/http" + "time" + + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "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") + }) + std := standard.New(":1323") + std.SetHandler(e) + graceful.ListenAndServe(std.Server, 5*time.Second) +} diff --git a/recipe/hello-world/server.go b/recipe/hello-world/server.go new file mode 100644 index 00000000..0ba8287e --- /dev/null +++ b/recipe/hello-world/server.go @@ -0,0 +1,26 @@ +package main + +import ( + "net/http" + + "github.com/labstack/echo" + "github.com/labstack/echo/engine/standard" + "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.Run(standard.New(":1323")) +} diff --git a/recipe/http2/cert.pem b/recipe/http2/cert.pem new file mode 100644 index 00000000..34d6746d --- /dev/null +++ b/recipe/http2/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+DCCAeCgAwIBAgIPFB3cowaziqTVN3I2utsEMA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTYwNTA5MTUwNDQ2WhcNMTcwNTA5MTUwNDQ2 +WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw+jr8IJ7W +aUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2iqxyrrRNy +n3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q0SMGF+Yg +RnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3Xc/d96OY +35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phKmB22qnlg +PY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABo0swSTAOBgNVHQ8BAf8EBAMCBaAw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggls +b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAL2SqeH2Z6zKje9XC6O/TC0DKRvE +L/d/1NVz5nr52Gqk+7DzU1QCWHD9+FLEX+wvzcploNoqcR+Xrh7N/2kNGymDdxP+ +UlpmpzLQ6BwcjWgwMa4GMn3L/aczFFuhG9dZdcy9UV4fXtreLO0FMt4BuBQyKMK2 +ZkWgw2rx3fMBgR4B99JpRr0wgqPHbFndvPYbMAEiYB4baRiIpkyBC6/JyZTNTBBH ++WyvuQKbip5WvVST+PjIy2eywJ2yGXKek3TOS8GTulVqUE5u7utzdOLJaLXIvF2K +BAbRa6CZ8+bZWRYpBujlu1mKjedzi/pS5BK5q/gPUWe8YcqEJVZWEHMakZ0= +-----END CERTIFICATE----- diff --git a/recipe/http2/key.pem b/recipe/http2/key.pem new file mode 100644 index 00000000..f6c5a0b7 --- /dev/null +++ b/recipe/http2/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw ++jr8IJ7WaUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2i +qxyrrRNyn3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q +0SMGF+YgRnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3 +Xc/d96OY35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phK +mB22qnlgPY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABAoIBABvDpTMeu/5gwJDW +HXvJJGMATEsBrcX9lsWqzMYDCkXdElPkMKkhaGTHzIdUpkgNUgYG3bYu1dYvXZKi +84X3+2u1KrF0i2hNAzZWAwfhOwkRQuSSUL9wgGk4Jp5Mgehdvcm0FVNFfOG4a0CS +tLVZsVe/h+PfMs4EuPDicvLBH0LI/dE+GeXGIhuF8/g7EN3S+f/WtCejrzah7Ghf +OsaBaBL0O5s82wU9PX0ZFxo4FADfwKLL0Zmbno8NC0cMsNOM2/B2dRF0eTlCDeAy +rxIXL7HEz+fWK96jCbDiMPyP9XstQxO2atZa2gC5vOJCwPpVVDbqW7FNop4VwUOW +bTtDrcUCgYEA6+hyuN0DcaAOM64rVyBbcMOD4L6Q00A1+9I1wBJtx1yNNfTS4iJc +EXxkLS8ae9xXYEGhiU0PyOufs+iTwLwvlbWcZUJailEoBG5EzC1BDZPE20hNcXav +zxxyQxRrzdItmMj8wa4TETVlcPGcKxNUw/UXqFIyNShtALse2mnYQQcCgYEA64sA +3WC+/e0jVoaPiIuCg2vt5QUiyfkblSUjsdBTlMWYIjlJNcGv81vepk3NIAPkTs71 +HZuRwEDENm/7HDSG2SpyB9de8/b061Uo/o+ZhWqPOHx1m7mD9x96CZk4wtxwL14u +SJuwyujqmSYoUYW8FKjHS5n74hP9YUT7hXKWs4sCgYAx2eAMUp/8rd7yata7xZmt +HZPLtVlzWrlNqqEzInHSVCt/AGpj4PDlvQyKQ87r56cLLzNMiV1RjwEjin1WmC3S +DButhjUNz5KORSMCpnl9vgE2eXPsCzGhqZg3tqQFTWnXRHmtD/T1iPwTvurKa35Z +HnzOU/hKJW3LXr9pVj6dlwKBgQCXVaEBm1Y7GbB5uEziIxiAzch0O9+FOxsgsVME +vN/mlynO21WRR1eAGUetPBGN/1Ih3FCabEix6Cro+vuwvILjZqULKrIkN0hXJ0kG +fUba9IL+fOCnZANItJ2ZKyvP7wfZNz6vgfCN/iY0rdJ7xlv4PhSGG3I9aDCE3Who +7h2rTwKBgQCQ5s0JdS/EDX5h7LPPqpIV1kqMmcqvBonZN4TySfqxKSlhasO3oGgC +rYkygiYGU8WdooavGNSmY4+sCBY002PZQ8EcSp+KySpYLeiVrJZTc+ehzk93t2to +b1p1mmLFCM6SVJAIQ+y9jt5GCSEka0BlVoNfLM06OLcC9j8iIGsjrw== +-----END RSA PRIVATE KEY----- diff --git a/recipe/http2/server.go b/recipe/http2/server.go new file mode 100644 index 00000000..9f66e46d --- /dev/null +++ b/recipe/http2/server.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "net/http" + "time" + + "github.com/labstack/echo" + "github.com/labstack/echo/engine" + "github.com/labstack/echo/engine/standard" +) + +func request(c echo.Context) error { + req := c.Request().(*standard.Request).Request + format := "
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 + +Performance + +## 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 ":" that is used + // to extract token from the request. + // Optional. Default value "header:X-CSRF-Token". + // Possible values: + // - "header:" + // - "form:" + // - "query:" + TokenLookup string `json:"token_lookup"` + + // Context key to store generated CSRF token into context. + // Optional. Default value "csrf". + ContextKey string `json:"context_key"` + + // Name of the CSRF cookie. This cookie will store CSRF token. + // Optional. Default value "csrf". + CookieName string `json:"cookie_name"` + + // Domain of the CSRF cookie. + // Optional. Default value none. + CookieDomain string `json:"cookie_domain"` + + // Path of the CSRF cookie. + // Optional. Default value none. + CookiePath string `json:"cookie_path"` + + // Max age (in seconds) of the CSRF cookie. + // Optional. Default value 86400 (24hr). + CookieMaxAge int `json:"cookie_max_age"` + + // Indicates if CSRF cookie is secure. + // Optional. Default value false. + CookieSecure bool `json:"cookie_secure"` + + // Indicates if CSRF cookie is HTTP only. + // Optional. Default value false. + CookieHTTPOnly bool `json:"cookie_http_only"` +} +``` + +*Default Configuration* + +```go +DefaultCSRFConfig = CSRFConfig{ + Skipper: defaultSkipper, + TokenLength: 32, + TokenLookup: "header:" + echo.HeaderXCSRFToken, + ContextKey: "csrf", + CookieName: "_csrf", + CookieMaxAge: 86400, +} +``` diff --git a/website/content/middleware/gzip.md b/website/content/middleware/gzip.md new file mode 100644 index 00000000..97ab46b3 --- /dev/null +++ b/website/content/middleware/gzip.md @@ -0,0 +1,49 @@ ++++ +title = "Gzip Middleware" +description = "Gzip middleware for Echo" +[menu.side] + name = "Gzip" + parent = "middleware" + weight = 5 ++++ + +## Gzip Middleware + +Gzip middleware compresses HTTP response using gzip compression scheme. + +*Usage* + +`e.Use(middleware.Gzip())` + +### Custom Configuration + +*Usage* + +```go +e := echo.New() +e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ + Level: 5, +})) +``` + +### Configuration + +```go +GzipConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Gzip compression level. + // Optional. Default value -1. + Level int `json:"level"` +} +``` + +*Default Configuration* + +```go +DefaultGzipConfig = GzipConfig{ + Skipper: defaultSkipper, + Level: -1, +} +``` diff --git a/website/content/middleware/jwt.md b/website/content/middleware/jwt.md new file mode 100644 index 00000000..393227d0 --- /dev/null +++ b/website/content/middleware/jwt.md @@ -0,0 +1,81 @@ ++++ +title = "JWT Middleware" +description = "JWT middleware for Echo" +[menu.side] + name = "JWT" + parent = "middleware" + weight = 5 ++++ + +## JWT Middleware + +JWT provides a JSON Web Token (JWT) authentication middleware. + +- For valid token, it sets the user in context and calls next handler. +- For invalid token, it sends "401 - Unauthorized" response. +- For empty or invalid `Authorization` header, it sends "400 - Bad Request". + +*Usage* + +`e.Use(middleware.JWT([]byte("secret"))` + +### Custom Configuration + +*Usage* + +```go +e := echo.New() +e.Use(middleware.JWTWithConfig(middleware.JWTConfig{ + SigningKey: []byte("secret"), + TokenLookup: "query:token", +})) +``` + +### Configuration + +```go +// JWTConfig defines the config for JWT middleware. +JWTConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Signing key to validate token. + // Required. + SigningKey interface{} `json:"signing_key"` + + // Signing method, used to check token signing method. + // Optional. Default value HS256. + SigningMethod string `json:"signing_method"` + + // Context key to store user information from the token into context. + // Optional. Default value "user". + ContextKey string `json:"context_key"` + + // Claims are extendable claims data defining token content. + // Optional. Default value jwt.MapClaims + Claims jwt.Claims + + // TokenLookup is a string in the form of ":" that is used + // to extract token from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:" + // - "query:" + // - "cookie:" + TokenLookup string `json:"token_lookup"` +} +``` + +*Default Configuration* + +```go +DefaultJWTConfig = JWTConfig{ + Skipper: defaultSkipper, + SigningMethod: AlgorithmHS256, + ContextKey: "user", + TokenLookup: "header:" + echo.HeaderAuthorization, + Claims: jwt.MapClaims{}, +} +``` + +### [Recipe]({{< ref "recipes/jwt.md">}}) diff --git a/website/content/middleware/logger.md b/website/content/middleware/logger.md new file mode 100644 index 00000000..33754455 --- /dev/null +++ b/website/content/middleware/logger.md @@ -0,0 +1,89 @@ ++++ +title = "Logger Middleware" +description = "Logger middleware for Echo" +[menu.side] + name = "Logger" + parent = "middleware" + weight = 5 ++++ + +## Logger Middleware + +Logger middleware logs the information about each HTTP request. + +*Usage* + +`e.Use(middleware.Logger())` + +*Sample Output* + +```js +{"time":"2016-05-10T07:02:25-07:00","remote_ip":"::1","method":"GET","uri":"/","status":200, "latency":55653,"latency_human":"55.653µs","rx_bytes":0,"tx_bytes":13} +``` + +### Custom Configuration + +*Usage* + +```go +e := echo.New() +e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + Format: "method=${method}, uri=${uri}, status=${status}\n", +})) +``` + +Example above uses a `Format` which logs request method and request URI. + +*Sample Output* + +```sh +method=GET, uri=/hello, status=200 +``` + +### Configuration + +```go +LoggerConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Log format which can be constructed using the following tags: + // + // - time_rfc3339 + // - id (Request ID - Not implemented) + // - remote_ip + // - uri + // - host + // - method + // - path + // - referer + // - user_agent + // - status + // - latency (In microseconds) + // - latency_human (Human readable) + // - bytes_in (Bytes received) + // - bytes_out (Bytes sent) + // + // Example "${remote_ip} ${status}" + // + // Optional. Default value DefaultLoggerConfig.Format. + Format string `json:"format"` + + // Output is a writer where logs are written. + // Optional. Default value os.Stdout. + Output io.Writer +} +``` + +*Default Configuration* + +```go +DefaultLoggerConfig = LoggerConfig{ + Skipper: defaultSkipper, + Format: `{"time":"${time_rfc3339}","remote_ip":"${remote_ip}",` + + `"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` + + `"latency_human":"${latency_human}","bytes_in":${bytes_in},` + + `"bytes_out":${bytes_out}}` + "\n", + Output: os.Stdout, +} +``` diff --git a/website/content/middleware/method-override.md b/website/content/middleware/method-override.md new file mode 100644 index 00000000..13f01681 --- /dev/null +++ b/website/content/middleware/method-override.md @@ -0,0 +1,52 @@ ++++ +title = "MethodOverride Middleware" +description = "Method override middleware for Echo" +[menu.side] + name = "MethodOverride" + parent = "middleware" + weight = 5 ++++ + +## MethodOverride Middleware + +MethodOverride middleware checks for the overridden method from the request and +uses it instead of the original method. + +For security reasons, only `POST` method can be overridden. + +*Usage* + +`e.Pre(middleware.MethodOverride())` + +### Custom Configuration + +*Usage* + +```go +e := echo.New() +e.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{ + Getter: middleware.MethodFromForm("_method"), +})) +``` + +### Configuration + +```go +MethodOverrideConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Getter is a function that gets overridden method from the request. + // Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride). + Getter MethodOverrideGetter +} +``` + +*Default Configuration* + +```go +DefaultMethodOverrideConfig = MethodOverrideConfig{ + Skipper: defaultSkipper, + Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), +} +``` diff --git a/website/content/middleware/overview.md b/website/content/middleware/overview.md new file mode 100644 index 00000000..58e3b9dd --- /dev/null +++ b/website/content/middleware/overview.md @@ -0,0 +1,105 @@ ++++ +title = "Overview" +description = "Overview of Echo middleware" +[menu.side] + name = "Overview" + parent = "middleware" + weight = 1 ++++ + +## Middleware Overview + +Middleware is a function chained in the HTTP request-response cycle with access +to `Echo#Context` which it uses to perform a specific action, for example, logging +every request or limiting the number of requests. + +Handler is processed in the end after all middleware are finished executing. + +### Middleware Levels + +#### Root Level (Before router) + +`Echo#Pre()` can be used to register a middleware which is executed before router +processes the request. It is helpful to make any changes to the request properties, +for example, adding or removing a trailing slash from the path so it matches the +route. + +The following built-in middleware should be registered at this level: + +- HTTPSRedirect +- HTTPSWWWRedirect +- WWWRedirect +- NonWWWRedirect +- AddTrailingSlash +- RemoveTrailingSlash +- MethodOverride + +> As router has not processed the request, middleware at this level won't +have access to any path related API from `echo.Context`. + +#### Root Level (After router) + +Most of the time you will register a middleware at this level using `Echo#Use()`. +This middleware is executed after router processes the request and has full access +to `echo.Context` API. + +The following built-in middleware should be registered at this level: + +- BodyLimit +- Logger +- Gzip +- Recover +- BasicAuth +- JWTAuth +- Secure +- CORS +- Static + +#### Group Level + +When creating a new group, you can register middleware just for that group. For +example, you can have an admin group which is secured by registering a BasicAuth +middleware for it. + +*Usage* + +```go +e := echo.New() +admin := e.Group("/admin", middleware.BasicAuth()) +``` + +You can also add a middleware after creating a group via `admin.Use()`. + +#### Route Level + +When defining a new route, you can optionally register middleware just for it. + +*Usage* + +```go +e := echo.New() +e.GET("/", , ) +``` + +### Skipping Middleware + +There are cases when you would like to skip a middleware based on some condition, +for that each middleware has an option to define a function `Skipper func(c echo.Context) bool`. + +*Usage* + +```go +e := echo.New() +e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + Skipper: func(c echo.Context) bool { + if strings.HasPrefix(c.Request().Host(), "localhost") { + return true + } + return false + }, +})) +``` + +Example above skips Logger middleware when request host starts with localhost. + +### [Writing Custom Middleware]({{< ref "recipes/middleware.md">}}) diff --git a/website/content/middleware/recover.md b/website/content/middleware/recover.md new file mode 100644 index 00000000..912e04d0 --- /dev/null +++ b/website/content/middleware/recover.md @@ -0,0 +1,65 @@ ++++ +title = "Recover Middleware" +description = "Recover middleware for Echo" +[menu.side] + name = "Recover" + parent = "middleware" + weight = 5 ++++ + +## Recover Middleware + +Recover middleware recovers from panics anywhere in the chain, prints stack trace +and handles the control to the centralized +[HTTPErrorHandler]({{< ref "guide/customization.md#http-error-handler">}}). + +*Usage* + +`e.Use(middleware.Recover())` + +### Custom Configuration + +*Usage* + +```go +e := echo.New() +e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{ + StackSize: 1 << 10, // 1 KB +})) +``` + +Example above uses a `StackSize` of 1 KB and default values for `DisableStackAll` +and `DisablePrintStack`. + +### Configuration + +```go +RecoverConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Size of the stack to be printed. + // Optional. Default value 4KB. + StackSize int `json:"stack_size"` + + // DisableStackAll disables formatting stack traces of all other goroutines + // into buffer after the trace for the current goroutine. + // Optional. Default value false. + DisableStackAll bool `json:"disable_stack_all"` + + // DisablePrintStack disables printing stack trace. + // Optional. Default value as false. + DisablePrintStack bool `json:"disable_print_stack"` +} +``` + +*Default Configuration* + +```go +DefaultRecoverConfig = RecoverConfig{ + Skipper: defaultSkipper, + StackSize: 4 << 10, // 4 KB + DisableStackAll: false, + DisablePrintStack: false, +} +``` diff --git a/website/content/middleware/redirect.md b/website/content/middleware/redirect.md new file mode 100644 index 00000000..4735f637 --- /dev/null +++ b/website/content/middleware/redirect.md @@ -0,0 +1,104 @@ ++++ +title = "Redirect Middleware" +description = "Redirect middleware for Echo" +[menu.side] + name = "Redirect" + parent = "middleware" + weight = 5 ++++ + +## HTTPSRedirect Middleware + +HTTPSRedirect middleware redirects http requests to https. +For example, http://labstack.com will be redirected to https://labstack.com. + +*Usage* + +```go +e := echo.New() +e.Pre(middleware.HTTPSRedirect()) +``` + +## HTTPSWWWRedirect Middleware + +HTTPSWWWRedirect redirects http requests to www https. +For example, http://labstack.com will be redirected to https://www.labstack.com. + +*Usage* + +```go +e := echo.New() +e.Pre(middleware.HTTPSWWWRedirect()) +``` + +## HTTPSNonWWWRedirect Middleware + +HTTPSNonWWWRedirect redirects http requests to https non www. +For example, http://www.labstack.com will be redirect to https://labstack.com. + +*Usage* + +```go +e := echo.New() +e.Pre(HTTPSNonWWWRedirect()) +``` + +## WWWRedirect Middleware + +WWWRedirect redirects non www requests to www. + +For example, http://labstack.com will be redirected to http://www.labstack.com. + +*Usage* + +```go +e := echo.New() +e.Pre(middleware.WWWRedirect()) +``` + +## NonWWWRedirect Middleware + +NonWWWRedirect redirects www requests to non www. +For example, http://www.labstack.com will be redirected to http://labstack.com. + +*Usage* + +```go +e := echo.New() +e.Pre(middleware.NonWWWRedirect()) +``` + +### Custom Configuration + +*Usage* + +```go +e := echo.New() +e.Use(middleware.HTTPSRedirectWithConfig(middleware.RedirectConfig{ + Code: http.StatusTemporaryRedirect, +})) +``` + +Example above will redirect the request HTTP to HTTPS with status code `307 - StatusTemporaryRedirect`. + +### Configuration + +```go +RedirectConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // Status code to be used when redirecting the request. + // Optional. Default value http.StatusMovedPermanently. + Code int `json:"code"` +} +``` + +*Default Configuration* + +```go +DefaultRedirectConfig = RedirectConfig{ + Skipper: defaultSkipper, + Code: http.StatusMovedPermanently, +} +``` diff --git a/website/content/middleware/secure.md b/website/content/middleware/secure.md new file mode 100644 index 00000000..7f54dca3 --- /dev/null +++ b/website/content/middleware/secure.md @@ -0,0 +1,98 @@ ++++ +title = "Secure Middleware" +description = "Secure middleware for Echo" +[menu.side] + name = "Secure" + parent = "middleware" + weight = 5 ++++ + +## Secure Middleware + +Secure middleware provides protection against cross-site scripting (XSS) attack, +content type sniffing, clickjacking, insecure connection and other code injection +attacks. + +*Usage* + +`e.Use(middleware.Secure())` + +### Custom Configuration + +*Usage* + +```go +e := echo.New() +e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ + XSSProtection: "", + ContentTypeNosniff: "", + XFrameOptions: "", + HSTSMaxAge: 3600, + ContentSecurityPolicy: "default-src 'self'", +})) +``` + +Passing empty `XSSProtection`, `ContentTypeNosniff`, `XFrameOptions` or `ContentSecurityPolicy` +disables that protection. + +### Configuration + +```go +SecureConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + + // XSSProtection provides protection against cross-site scripting attack (XSS) + // by setting the `X-XSS-Protection` header. + // Optional. Default value "1; mode=block". + XSSProtection string `json:"xss_protection"` + + // ContentTypeNosniff provides protection against overriding Content-Type + // header by setting the `X-Content-Type-Options` header. + // Optional. Default value "nosniff". + ContentTypeNosniff string `json:"content_type_nosniff"` + + // XFrameOptions can be used to indicate whether or not a browser should + // be allowed to render a page in a ,