├── .github └── workflows │ ├── deploy.yaml │ ├── test-deploy.yaml │ └── test.yaml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cookbook ├── auto-tls │ └── server.go ├── cors │ ├── origin-func │ │ └── server.go │ └── origin-list │ │ └── server.go ├── crud │ └── server.go ├── embed-resources │ ├── .gitignore │ ├── app │ │ ├── index.html │ │ └── main.js │ └── server.go ├── embed │ ├── app │ │ ├── index.html │ │ └── main.js │ └── server.go ├── file-download │ ├── attachment │ │ ├── attachment.txt │ │ ├── index.html │ │ └── server.go │ ├── echo.svg │ ├── index.html │ ├── inline │ │ ├── index.html │ │ ├── inline.txt │ │ └── server.go │ └── server.go ├── file-upload │ ├── multiple │ │ ├── public │ │ │ └── index.html │ │ └── server.go │ └── single │ │ ├── public │ │ └── index.html │ │ └── server.go ├── google-app-engine │ ├── Dockerfile │ ├── app-engine.go │ ├── app-engine.yaml │ ├── app-managed.go │ ├── app-managed.yaml │ ├── app-standalone.go │ ├── app.go │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── scripts │ │ │ └── main.js │ ├── templates │ │ └── welcome.html │ ├── users.go │ └── welcome.go ├── graceful-shutdown │ └── server.go ├── hello-world │ └── server.go ├── http2-server-push │ ├── cert.pem │ ├── index.html │ ├── key.pem │ ├── server.go │ └── static │ │ ├── app.css │ │ ├── app.js │ │ └── echo.png ├── http2 │ ├── cert.pem │ ├── key.pem │ └── server.go ├── jsonp │ ├── public │ │ └── index.html │ └── server.go ├── jwt │ ├── custom-claims │ │ └── server.go │ └── user-defined-keyfunc │ │ └── server.go ├── load-balancing │ ├── armor.yaml │ ├── nginx.conf │ └── upstream │ │ └── server.go ├── middleware │ └── server.go ├── reverse-proxy │ ├── server.go │ └── upstream │ │ └── server.go ├── sse │ ├── broadcast │ │ ├── index.html │ │ └── server.go │ └── simple │ │ ├── index.html │ │ ├── server.go │ │ └── serversentevent.go ├── streaming-response │ └── server.go ├── subdomain │ └── server.go ├── timeout │ └── server.go ├── twitter │ ├── handler │ │ ├── handler.go │ │ ├── post.go │ │ └── user.go │ ├── model │ │ ├── post.go │ │ └── user.go │ └── server.go └── websocket │ ├── gorilla │ └── server.go │ ├── net │ └── server.go │ └── public │ └── index.html ├── go.mod ├── go.sum └── website ├── .gitignore ├── .nvmrc ├── babel.config.js ├── docs ├── cookbook │ ├── _category_.json │ ├── auto-tls.md │ ├── cors.md │ ├── crud.md │ ├── embed-resources.md │ ├── file-download.md │ ├── file-upload.md │ ├── google-app-engine.md │ ├── graceful-shutdown.md │ ├── hello-world.md │ ├── http2-server-push.md │ ├── http2.md │ ├── jsonp.md │ ├── jwt.md │ ├── load-balancing.md │ ├── middleware.md │ ├── reverse-proxy.md │ ├── sse.md │ ├── streaming-response.md │ ├── subdomain.md │ ├── timeout.md │ ├── twitter.md │ └── websocket.md ├── guide │ ├── _category_.json │ ├── binding.md │ ├── context.md │ ├── cookies.md │ ├── customization.md │ ├── error-handling.md │ ├── ip-address.md │ ├── quick-start.md │ ├── request.md │ ├── response.md │ ├── routing.md │ ├── start-server.md │ ├── static-files.md │ ├── templates.md │ └── testing.md ├── introduction.md └── middleware │ ├── _category_.json │ ├── basic-auth.md │ ├── body-dump.md │ ├── body-limit.md │ ├── casbin-auth.md │ ├── context-timeout.md │ ├── cors.md │ ├── csrf.md │ ├── decompress.md │ ├── gzip.md │ ├── img │ ├── docsVersionDropdown.png │ └── localeDropdown.png │ ├── jaeger.md │ ├── jwt.md │ ├── key-auth.md │ ├── logger.md │ ├── method-override.md │ ├── prometheus.md │ ├── proxy.md │ ├── rate-limiter.md │ ├── recover.md │ ├── redirect.md │ ├── request-id.md │ ├── rewrite.md │ ├── secure.md │ ├── session.md │ ├── static.md │ └── trailing-slash.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src ├── components │ ├── HomepageFeatures │ │ ├── index.js │ │ └── styles.module.css │ └── HomepageSponsors │ │ ├── index.js │ │ └── styles.module.css ├── css │ └── custom.css └── pages │ ├── index.js │ └── index.module.css └── static ├── .nojekyll ├── CNAME └── img ├── 0075-cloud.svg ├── 0101-database-upload.svg ├── 0102-database-download.svg ├── 0221-license2.svg ├── 0243-equalizer.svg ├── 0567-speed-fast.svg ├── 0568-rocket.svg ├── 0780-code.svg ├── 0893-funnel.svg ├── doit-logo.png ├── donation.png ├── favicon.ico ├── labstack-icon.png ├── logo-dark.svg ├── logo-light.svg ├── microcms-logo.png ├── shiguredo-logo.png └── terminal.png /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | # Review gh actions docs if you want to further define triggers, paths, etc 8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 9 | 10 | jobs: 11 | deploy: 12 | name: Deploy to GitHub Pages 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: 20 19 | cache: npm 20 | cache-dependency-path: website/package-lock.json 21 | 22 | - name: Install dependencies 23 | run: npm ci 24 | working-directory: website 25 | - name: Build website 26 | run: npm run build 27 | working-directory: website 28 | 29 | # Popular action to deploy to GitHub Pages: 30 | # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus 31 | - name: Deploy to GitHub Pages 32 | uses: peaceiris/actions-gh-pages@v3 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | # Build output to publish to the `gh-pages` branch: 36 | publish_dir: ./website/build 37 | # The following lines assign commit authorship to the official 38 | # GH-Actions bot for deploys to `gh-pages` branch: 39 | # https://github.com/actions/checkout/issues/13#issuecomment-724415212 40 | # The GH actions bot is used by default if you didn't specify the two fields. 41 | # You can swap them out with your own user credentials. 42 | user_name: github-actions[bot] 43 | user_email: 41898282+github-actions[bot]@users.noreply.github.com -------------------------------------------------------------------------------- /.github/workflows/test-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Test deployment 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | # Review gh actions docs if you want to further define triggers, paths, etc 8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 9 | 10 | jobs: 11 | test-deploy: 12 | name: Test deployment 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: 20 19 | cache: yarn 20 | 21 | - name: Install dependencies 22 | run: yarn install --frozen-lockfile 23 | working-directory: website 24 | - name: Test build website 25 | run: yarn build 26 | working-directory: website -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | # Each major Go release is supported until there are two newer major releases. https://golang.org/doc/devel/release.html#policy 17 | go: [1.23] 18 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - name: Set up Go ${{ matrix.go }} 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version: ${{ matrix.go }} 25 | 26 | - name: Checkout Code 27 | uses: actions/checkout@v4 28 | 29 | - name: Run Tests 30 | run: | 31 | make test 32 | 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | website/public 3 | vendor 4 | .idea 5 | .hugo_build.lock 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 LabStack LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test -race ./... 3 | 4 | serve: 5 | docker run --rm -it --name echo-docs -v ${CURDIR}/website:/home/app -w /home/app -p 3000:3000 -u node node:lts /bin/bash -c "npm install && npm start -- --host=0.0.0.0" 6 | 7 | .PHONY: test serve 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Echo Extra 2 | 3 | ### Running Recipes 4 | 5 | - Go into any cookbook folder and run `go run server.go` 6 | 7 | ### Running/Developing Website Locally 8 | 9 | - Ensure that you have Node.js installed on your computer. If you don't have it, download and install it from the 10 | official Node.js website (https://nodejs.org). Or you could use NVM (https://github.com/nvm-sh/nvm) to use appropriate 11 | version of Node. 12 | - Inside the `website` folder, install the required dependencies by running the following command `npm install`. 13 | - Run the website locally by running the following command `npm start`. 14 | 15 | ```bash 16 | cd website 17 | npm install 18 | npm start 19 | ``` 20 | 21 | ### Running/Developing Website in docker 22 | 23 | This will serve website on http://localhost:3000/ 24 | 25 | ```bash 26 | docker run --rm -it --name echo-docs -v ${PWD}/website:/home/app -w /home/app -p 3000:3000 -u node node:lts /bin/bash -c "npm install && npm start -- --host=0.0.0.0" 27 | ``` 28 | -------------------------------------------------------------------------------- /cookbook/auto-tls/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "golang.org/x/crypto/acme" 6 | "net/http" 7 | 8 | "github.com/labstack/echo/v4" 9 | "github.com/labstack/echo/v4/middleware" 10 | "golang.org/x/crypto/acme/autocert" 11 | ) 12 | 13 | func main() { 14 | e := echo.New() 15 | // e.AutoTLSManager.HostPolicy = autocert.HostWhitelist("") 16 | // Cache certificates to avoid issues with rate limits (https://letsencrypt.org/docs/rate-limits) 17 | e.AutoTLSManager.Cache = autocert.DirCache("/var/www/.cache") 18 | e.Use(middleware.Recover()) 19 | e.Use(middleware.Logger()) 20 | e.GET("/", func(c echo.Context) error { 21 | return c.HTML(http.StatusOK, ` 22 |

Welcome to Echo!

23 |

TLS certificates automatically installed from Let's Encrypt :)

24 | `) 25 | }) 26 | 27 | e.Logger.Fatal(e.StartAutoTLS(":443")) 28 | } 29 | 30 | func customHTTPServer() { 31 | e := echo.New() 32 | e.Use(middleware.Recover()) 33 | e.Use(middleware.Logger()) 34 | e.GET("/", func(c echo.Context) error { 35 | return c.HTML(http.StatusOK, ` 36 |

Welcome to Echo!

37 |

TLS certificates automatically installed from Let's Encrypt :)

38 | `) 39 | }) 40 | 41 | autoTLSManager := autocert.Manager{ 42 | Prompt: autocert.AcceptTOS, 43 | // Cache certificates to avoid issues with rate limits (https://letsencrypt.org/docs/rate-limits) 44 | Cache: autocert.DirCache("/var/www/.cache"), 45 | //HostPolicy: autocert.HostWhitelist(""), 46 | } 47 | s := http.Server{ 48 | Addr: ":443", 49 | Handler: e, // set Echo as handler 50 | TLSConfig: &tls.Config{ 51 | //Certificates: nil, // <-- s.ListenAndServeTLS will populate this field 52 | GetCertificate: autoTLSManager.GetCertificate, 53 | NextProtos: []string{acme.ALPNProto}, 54 | }, 55 | //ReadTimeout: 30 * time.Second, // use custom timeouts 56 | } 57 | if err := s.ListenAndServeTLS("", ""); err != http.ErrServerClosed { 58 | e.Logger.Fatal(err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cookbook/cors/origin-func/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "regexp" 6 | 7 | "github.com/labstack/echo/v4" 8 | "github.com/labstack/echo/v4/middleware" 9 | ) 10 | 11 | var ( 12 | users = []string{"Joe", "Veer", "Zion"} 13 | ) 14 | 15 | func getUsers(c echo.Context) error { 16 | return c.JSON(http.StatusOK, users) 17 | } 18 | 19 | // allowOrigin takes the origin as an argument and returns true if the origin 20 | // is allowed or false otherwise. 21 | func allowOrigin(origin string) (bool, error) { 22 | // In this example we use a regular expression but we can imagine various 23 | // kind of custom logic. For example, an external datasource could be used 24 | // to maintain the list of allowed origins. 25 | return regexp.MatchString(`^https:\/\/labstack\.(net|com)$`, origin) 26 | } 27 | 28 | func main() { 29 | e := echo.New() 30 | e.Use(middleware.Logger()) 31 | e.Use(middleware.Recover()) 32 | 33 | // CORS restricted with a custom function to allow origins 34 | // and with the GET, PUT, POST or DELETE methods allowed. 35 | e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 36 | AllowOriginFunc: allowOrigin, 37 | AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete}, 38 | })) 39 | 40 | e.GET("/api/users", getUsers) 41 | 42 | e.Logger.Fatal(e.Start(":1323")) 43 | } 44 | -------------------------------------------------------------------------------- /cookbook/cors/origin-list/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | ) 9 | 10 | var ( 11 | users = []string{"Joe", "Veer", "Zion"} 12 | ) 13 | 14 | func getUsers(c echo.Context) error { 15 | return c.JSON(http.StatusOK, users) 16 | } 17 | 18 | func main() { 19 | e := echo.New() 20 | e.Use(middleware.Logger()) 21 | e.Use(middleware.Recover()) 22 | 23 | // CORS default 24 | // Allows requests from any origin wth GET, HEAD, PUT, POST or DELETE method. 25 | // e.Use(middleware.CORS()) 26 | 27 | // CORS restricted 28 | // Allows requests from any `https://labstack.com` or `https://labstack.net` origin 29 | // wth GET, PUT, POST or DELETE method. 30 | e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 31 | AllowOrigins: []string{"https://labstack.com", "https://labstack.net"}, 32 | AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete}, 33 | })) 34 | 35 | e.GET("/api/users", getUsers) 36 | 37 | e.Logger.Fatal(e.Start(":1323")) 38 | } 39 | -------------------------------------------------------------------------------- /cookbook/crud/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "sync" 7 | 8 | "github.com/labstack/echo/v4" 9 | "github.com/labstack/echo/v4/middleware" 10 | ) 11 | 12 | type ( 13 | user struct { 14 | ID int `json:"id"` 15 | Name string `json:"name"` 16 | } 17 | ) 18 | 19 | var ( 20 | users = map[int]*user{} 21 | seq = 1 22 | lock = sync.Mutex{} 23 | ) 24 | 25 | //---------- 26 | // Handlers 27 | //---------- 28 | 29 | func createUser(c echo.Context) error { 30 | lock.Lock() 31 | defer lock.Unlock() 32 | u := &user{ 33 | ID: seq, 34 | } 35 | if err := c.Bind(u); err != nil { 36 | return err 37 | } 38 | users[u.ID] = u 39 | seq++ 40 | return c.JSON(http.StatusCreated, u) 41 | } 42 | 43 | func getUser(c echo.Context) error { 44 | lock.Lock() 45 | defer lock.Unlock() 46 | id, _ := strconv.Atoi(c.Param("id")) 47 | return c.JSON(http.StatusOK, users[id]) 48 | } 49 | 50 | func updateUser(c echo.Context) error { 51 | lock.Lock() 52 | defer lock.Unlock() 53 | u := new(user) 54 | if err := c.Bind(u); err != nil { 55 | return err 56 | } 57 | id, _ := strconv.Atoi(c.Param("id")) 58 | users[id].Name = u.Name 59 | return c.JSON(http.StatusOK, users[id]) 60 | } 61 | 62 | func deleteUser(c echo.Context) error { 63 | lock.Lock() 64 | defer lock.Unlock() 65 | id, _ := strconv.Atoi(c.Param("id")) 66 | delete(users, id) 67 | return c.NoContent(http.StatusNoContent) 68 | } 69 | 70 | func getAllUsers(c echo.Context) error { 71 | lock.Lock() 72 | defer lock.Unlock() 73 | return c.JSON(http.StatusOK, users) 74 | } 75 | 76 | func main() { 77 | e := echo.New() 78 | 79 | // Middleware 80 | e.Use(middleware.Logger()) 81 | e.Use(middleware.Recover()) 82 | 83 | // Routes 84 | e.GET("/users", getAllUsers) 85 | e.POST("/users", createUser) 86 | e.GET("/users/:id", getUser) 87 | e.PUT("/users/:id", updateUser) 88 | e.DELETE("/users/:id", deleteUser) 89 | 90 | // Start server 91 | e.Logger.Fatal(e.Start(":1323")) 92 | } 93 | -------------------------------------------------------------------------------- /cookbook/embed-resources/.gitignore: -------------------------------------------------------------------------------- 1 | rice 2 | app.rice-box.go 3 | -------------------------------------------------------------------------------- /cookbook/embed-resources/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | go.rice Example 6 | 7 | 8 | 9 |

go.rice Example

10 | 11 | 12 | -------------------------------------------------------------------------------- /cookbook/embed-resources/app/main.js: -------------------------------------------------------------------------------- 1 | alert("main.js"); 2 | -------------------------------------------------------------------------------- /cookbook/embed-resources/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/GeertJohan/go.rice" 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | func main() { 11 | e := echo.New() 12 | // the file server for rice. "app" is the folder where the files come from. 13 | assetHandler := http.FileServer(rice.MustFindBox("app").HTTPBox()) 14 | // serves the index.html from rice 15 | e.GET("/", echo.WrapHandler(assetHandler)) 16 | 17 | // servers other static files 18 | e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler))) 19 | 20 | e.Logger.Fatal(e.Start(":1323")) 21 | } 22 | -------------------------------------------------------------------------------- /cookbook/embed/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | go embed Example 6 | 7 | 8 | 9 |

go embed Example

10 | 11 | 12 | -------------------------------------------------------------------------------- /cookbook/embed/app/main.js: -------------------------------------------------------------------------------- 1 | alert("main.js"); 2 | -------------------------------------------------------------------------------- /cookbook/embed/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "io/fs" 6 | "log" 7 | "net/http" 8 | "os" 9 | 10 | "github.com/labstack/echo/v4" 11 | ) 12 | 13 | //go:embed app 14 | var embededFiles embed.FS 15 | 16 | func getFileSystem(useOS bool) http.FileSystem { 17 | if useOS { 18 | log.Print("using live mode") 19 | return http.FS(os.DirFS("app")) 20 | } 21 | 22 | log.Print("using embed mode") 23 | fsys, err := fs.Sub(embededFiles, "app") 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | return http.FS(fsys) 29 | } 30 | 31 | func main() { 32 | e := echo.New() 33 | useOS := len(os.Args) > 1 && os.Args[1] == "live" 34 | assetHandler := http.FileServer(getFileSystem(useOS)) 35 | e.GET("/", echo.WrapHandler(assetHandler)) 36 | e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler))) 37 | e.Logger.Fatal(e.Start(":1323")) 38 | } 39 | -------------------------------------------------------------------------------- /cookbook/file-download/attachment/attachment.txt: -------------------------------------------------------------------------------- 1 | Attachment -------------------------------------------------------------------------------- /cookbook/file-download/attachment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | File download 6 | 7 | 8 | 9 |

10 | Attachment file download 11 |

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /cookbook/file-download/attachment/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/labstack/echo/v4/middleware" 6 | ) 7 | 8 | func main() { 9 | e := echo.New() 10 | 11 | e.Use(middleware.Logger()) 12 | e.Use(middleware.Recover()) 13 | 14 | e.GET("/", func(c echo.Context) error { 15 | return c.File("index.html") 16 | }) 17 | e.GET("/attachment", func(c echo.Context) error { 18 | return c.Attachment("attachment.txt", "attachment.txt") 19 | }) 20 | 21 | e.Logger.Fatal(e.Start(":1323")) 22 | } 23 | -------------------------------------------------------------------------------- /cookbook/file-download/echo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | 15 | 16 | 19 | 22 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /cookbook/file-download/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | File download 6 | 7 | 8 | 9 |

10 | File download 11 |

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /cookbook/file-download/inline/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | File download 6 | 7 | 8 | 9 |

10 | Inline file download 11 |

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /cookbook/file-download/inline/inline.txt: -------------------------------------------------------------------------------- 1 | inline -------------------------------------------------------------------------------- /cookbook/file-download/inline/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/labstack/echo/v4/middleware" 6 | ) 7 | 8 | func main() { 9 | e := echo.New() 10 | 11 | e.Use(middleware.Logger()) 12 | e.Use(middleware.Recover()) 13 | 14 | e.GET("/", func(c echo.Context) error { 15 | return c.File("index.html") 16 | }) 17 | e.GET("/inline", func(c echo.Context) error { 18 | return c.Inline("inline.txt", "inline.txt") 19 | }) 20 | 21 | e.Logger.Fatal(e.Start(":1323")) 22 | } 23 | -------------------------------------------------------------------------------- /cookbook/file-download/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | "github.com/labstack/echo/v4/middleware" 6 | ) 7 | 8 | func main() { 9 | e := echo.New() 10 | 11 | e.Use(middleware.Logger()) 12 | e.Use(middleware.Recover()) 13 | 14 | e.GET("/", func(c echo.Context) error { 15 | return c.File("index.html") 16 | }) 17 | e.GET("/file", func(c echo.Context) error { 18 | return c.File("echo.svg") 19 | }) 20 | 21 | e.Logger.Fatal(e.Start(":1323")) 22 | } 23 | -------------------------------------------------------------------------------- /cookbook/file-upload/multiple/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Multiple file upload 6 | 7 | 8 |

Upload multiple files with fields

9 | 10 |
11 | Name:
12 | Email:
13 | Files:

14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /cookbook/file-upload/multiple/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/labstack/echo/v4" 10 | "github.com/labstack/echo/v4/middleware" 11 | ) 12 | 13 | func upload(c echo.Context) error { 14 | // Read form fields 15 | name := c.FormValue("name") 16 | email := c.FormValue("email") 17 | 18 | //------------ 19 | // Read files 20 | //------------ 21 | 22 | // Multipart form 23 | form, err := c.MultipartForm() 24 | if err != nil { 25 | return err 26 | } 27 | files := form.File["files"] 28 | 29 | for _, file := range files { 30 | // Source 31 | src, err := file.Open() 32 | if err != nil { 33 | return err 34 | } 35 | defer src.Close() 36 | 37 | // Destination 38 | dst, err := os.Create(file.Filename) 39 | if err != nil { 40 | return err 41 | } 42 | defer dst.Close() 43 | 44 | // Copy 45 | if _, err = io.Copy(dst, src); err != nil { 46 | return err 47 | } 48 | 49 | } 50 | 51 | return c.HTML(http.StatusOK, fmt.Sprintf("

Uploaded successfully %d files with fields name=%s and email=%s.

", len(files), name, email)) 52 | } 53 | 54 | func main() { 55 | e := echo.New() 56 | 57 | e.Use(middleware.Logger()) 58 | e.Use(middleware.Recover()) 59 | 60 | e.Static("/", "public") 61 | e.POST("/upload", upload) 62 | 63 | e.Logger.Fatal(e.Start(":1323")) 64 | } 65 | -------------------------------------------------------------------------------- /cookbook/file-upload/single/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Single file upload 6 | 7 | 8 |

Upload single file with fields

9 | 10 |
11 | Name:
12 | Email:
13 | Files:

14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /cookbook/file-upload/single/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/labstack/echo/v4" 10 | "github.com/labstack/echo/v4/middleware" 11 | ) 12 | 13 | func upload(c echo.Context) error { 14 | // Read form fields 15 | name := c.FormValue("name") 16 | email := c.FormValue("email") 17 | 18 | //----------- 19 | // Read file 20 | //----------- 21 | 22 | // Source 23 | file, err := c.FormFile("file") 24 | if err != nil { 25 | return err 26 | } 27 | src, err := file.Open() 28 | if err != nil { 29 | return err 30 | } 31 | defer src.Close() 32 | 33 | // Destination 34 | dst, err := os.Create(file.Filename) 35 | if err != nil { 36 | return err 37 | } 38 | defer dst.Close() 39 | 40 | // Copy 41 | if _, err = io.Copy(dst, src); err != nil { 42 | return err 43 | } 44 | 45 | return c.HTML(http.StatusOK, fmt.Sprintf("

File %s uploaded successfully with fields name=%s and email=%s.

", file.Filename, name, email)) 46 | } 47 | 48 | func main() { 49 | e := echo.New() 50 | 51 | e.Use(middleware.Logger()) 52 | e.Use(middleware.Recover()) 53 | 54 | e.Static("/", "public") 55 | e.POST("/upload", upload) 56 | 57 | e.Logger.Fatal(e.Start(":1323")) 58 | } 59 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile extending the generic Go image with application files for a 2 | # single application. 3 | FROM gcr.io/google_appengine/golang 4 | 5 | COPY . /go/src/app 6 | RUN go-wrapper download 7 | RUN go-wrapper install -tags appenginevm -------------------------------------------------------------------------------- /cookbook/google-app-engine/app-engine.go: -------------------------------------------------------------------------------- 1 | // +build appengine 2 | 3 | package main 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/labstack/echo/v4" 9 | ) 10 | 11 | func createMux() *echo.Echo { 12 | e := echo.New() 13 | // note: we don't need to provide the middleware or static handlers, that's taken care of by the platform 14 | // app engine has it's own "main" wrapper - we just need to hook echo into the default handler 15 | http.Handle("/", e) 16 | return e 17 | } 18 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/app-engine.yaml: -------------------------------------------------------------------------------- 1 | application: my-application-id # defined when you create your app using google dev console 2 | module: default # see https://cloud.google.com/appengine/docs/go/ 3 | version: alpha # you can run multiple versions of an app and A/B test 4 | runtime: go # see https://cloud.google.com/appengine/docs/go/ 5 | api_version: go1 # used when appengine supports different go versions 6 | 7 | default_expiration: "1d" # for CDN serving of static files (use url versioning if long!) 8 | 9 | handlers: 10 | # all the static files that we normally serve ourselves are defined here and Google will handle 11 | # serving them for us from it's own CDN / edge locations. For all the configuration options see: 12 | # https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers 13 | - url: / 14 | mime_type: text/html 15 | static_files: public/index.html 16 | upload: public/index.html 17 | 18 | - url: /favicon.ico 19 | mime_type: image/x-icon 20 | static_files: public/favicon.ico 21 | upload: public/favicon.ico 22 | 23 | - url: /scripts 24 | mime_type: text/javascript 25 | static_dir: public/scripts 26 | 27 | # static files normally don't touch the server that the app runs on but server-side template files 28 | # needs to be readable by the app. The application_readable option makes sure they are available as 29 | # part of the app deployment onto the instance. 30 | - url: /templates 31 | static_dir: /templates 32 | application_readable: true 33 | 34 | # finally, we route all other requests to our application. The script name just means "the go app" 35 | - url: /.* 36 | script: _go_app -------------------------------------------------------------------------------- /cookbook/google-app-engine/app-managed.go: -------------------------------------------------------------------------------- 1 | // +build appenginevm 2 | 3 | package main 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/labstack/echo/v4" 9 | "google.golang.org/appengine" 10 | ) 11 | 12 | func createMux() *echo.Echo { 13 | e := echo.New() 14 | // note: we don't need to provide the middleware or static handlers 15 | // for the appengine vm version - that's taken care of by the platform 16 | return e 17 | } 18 | 19 | func main() { 20 | // the appengine package provides a convenient method to handle the health-check requests 21 | // and also run the app on the correct port. We just need to add Echo to the default handler 22 | e := echo.New(":8080") 23 | http.Handle("/", e) 24 | appengine.Main() 25 | } 26 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/app-managed.yaml: -------------------------------------------------------------------------------- 1 | application: my-application-id # defined when you create your app using google dev console 2 | module: default # see https://cloud.google.com/appengine/docs/go/ 3 | version: alpha # you can run multiple versions of an app and A/B test 4 | runtime: go # see https://cloud.google.com/appengine/docs/go/ 5 | api_version: go1 # used when appengine supports different go versions 6 | vm: true # for managed VMs only, remove for appengine classic 7 | 8 | default_expiration: "1d" # for CDN serving of static files (use url versioning if long!) 9 | 10 | handlers: 11 | # all the static files that we normally serve ourselves are defined here and Google will handle 12 | # serving them for us from it's own CDN / edge locations. For all the configuration options see: 13 | # https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers 14 | - url: / 15 | mime_type: text/html 16 | static_files: public/index.html 17 | upload: public/index.html 18 | 19 | - url: /favicon.ico 20 | mime_type: image/x-icon 21 | static_files: public/favicon.ico 22 | upload: public/favicon.ico 23 | 24 | - url: /scripts 25 | mime_type: text/javascript 26 | static_dir: public/scripts 27 | 28 | # static files normally don't touch the server that the app runs on but server-side template files 29 | # needs to be readable by the app. The application_readable option makes sure they are available as 30 | # part of the app deployment onto the instance. 31 | - url: /templates 32 | static_dir: /templates 33 | application_readable: true 34 | 35 | # finally, we route all other requests to our application. The script name just means "the go app" 36 | - url: /.* 37 | script: _go_app -------------------------------------------------------------------------------- /cookbook/google-app-engine/app-standalone.go: -------------------------------------------------------------------------------- 1 | // +build !appengine,!appenginevm 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | ) 9 | 10 | func createMux() *echo.Echo { 11 | e := echo.New() 12 | 13 | e.Use(middleware.Recover()) 14 | e.Use(middleware.Logger()) 15 | e.Use(middleware.Gzip()) 16 | 17 | e.Static("/", "public") 18 | 19 | return e 20 | } 21 | 22 | func main() { 23 | e.Logger.Fatal(e.Start(":8080")) 24 | } 25 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // reference our echo instance and create it early 4 | var e = createMux() 5 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labstack/echox/c495449db4db6fdead7fd00c58fc121b20f87da9/cookbook/google-app-engine/public/favicon.ico -------------------------------------------------------------------------------- /cookbook/google-app-engine/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Echo 7 | 8 | 9 | 10 | 11 | 12 |

Echo!

13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/public/scripts/main.js: -------------------------------------------------------------------------------- 1 | console.log("Echo!"); 2 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/templates/welcome.html: -------------------------------------------------------------------------------- 1 | {{define "welcome"}}Hello, {{.}}!{{end}} 2 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/users.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | ) 9 | 10 | type ( 11 | user struct { 12 | ID string `json:"id"` 13 | Name string `json:"name"` 14 | } 15 | ) 16 | 17 | var ( 18 | users map[string]user 19 | ) 20 | 21 | func init() { 22 | users = map[string]user{ 23 | "1": user{ 24 | ID: "1", 25 | Name: "Wreck-It Ralph", 26 | }, 27 | } 28 | 29 | // hook into the echo instance to create an endpoint group 30 | // and add specific middleware to it plus handlers 31 | g := e.Group("/users") 32 | g.Use(middleware.CORS()) 33 | 34 | g.POST("", createUser) 35 | g.GET("", getUsers) 36 | g.GET("/:id", getUser) 37 | } 38 | 39 | func createUser(c echo.Context) error { 40 | u := new(user) 41 | if err := c.Bind(u); err != nil { 42 | return err 43 | } 44 | users[u.ID] = *u 45 | return c.JSON(http.StatusCreated, u) 46 | } 47 | 48 | func getUsers(c echo.Context) error { 49 | return c.JSON(http.StatusOK, users) 50 | } 51 | 52 | func getUser(c echo.Context) error { 53 | return c.JSON(http.StatusOK, users[c.Param("id")]) 54 | } 55 | -------------------------------------------------------------------------------- /cookbook/google-app-engine/welcome.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "html/template" 5 | "io" 6 | "net/http" 7 | 8 | "github.com/labstack/echo/v4" 9 | ) 10 | 11 | type ( 12 | Template struct { 13 | templates *template.Template 14 | } 15 | ) 16 | 17 | func init() { 18 | t := &Template{ 19 | templates: template.Must(template.ParseFiles("templates/welcome.html")), 20 | } 21 | e.Renderer = t 22 | e.GET("/welcome", welcome) 23 | } 24 | 25 | func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { 26 | return t.templates.ExecuteTemplate(w, name, data) 27 | } 28 | 29 | func welcome(c echo.Context) error { 30 | return c.Render(http.StatusOK, "welcome", "Joe") 31 | } 32 | -------------------------------------------------------------------------------- /cookbook/graceful-shutdown/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "os" 7 | "os/signal" 8 | "time" 9 | 10 | "github.com/labstack/echo/v4" 11 | "github.com/labstack/gommon/log" 12 | ) 13 | 14 | func main() { 15 | // Setup 16 | e := echo.New() 17 | e.Logger.SetLevel(log.INFO) 18 | e.GET("/", func(c echo.Context) error { 19 | time.Sleep(5 * time.Second) 20 | return c.JSON(http.StatusOK, "OK") 21 | }) 22 | 23 | ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) 24 | defer stop() 25 | // Start server 26 | go func() { 27 | if err := e.Start(":1323"); err != nil && err != http.ErrServerClosed { 28 | e.Logger.Fatal("shutting down the server") 29 | } 30 | }() 31 | 32 | // Wait for interrupt signal to gracefully shut down the server with a timeout of 10 seconds. 33 | <-ctx.Done() 34 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 35 | defer cancel() 36 | if err := e.Shutdown(ctx); err != nil { 37 | e.Logger.Fatal(err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cookbook/hello-world/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | ) 9 | 10 | func main() { 11 | // Echo instance 12 | e := echo.New() 13 | 14 | // Middleware 15 | e.Use(middleware.Logger()) 16 | e.Use(middleware.Recover()) 17 | 18 | // Route => handler 19 | e.GET("/", func(c echo.Context) error { 20 | return c.String(http.StatusOK, "Hello, World!\n") 21 | }) 22 | 23 | // Start server 24 | e.Logger.Fatal(e.Start(":1323")) 25 | } 26 | -------------------------------------------------------------------------------- /cookbook/http2-server-push/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+DCCAeCgAwIBAgIPFB3cowaziqTVN3I2utsEMA0GCSqGSIb3DQEBCwUAMBIx 3 | EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTYwNTA5MTUwNDQ2WhcNMTcwNTA5MTUwNDQ2 4 | WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw+jr8IJ7W 6 | aUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2iqxyrrRNy 7 | n3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q0SMGF+Yg 8 | RnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3Xc/d96OY 9 | 35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phKmB22qnlg 10 | PY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABo0swSTAOBgNVHQ8BAf8EBAMCBaAw 11 | EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggls 12 | b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAL2SqeH2Z6zKje9XC6O/TC0DKRvE 13 | L/d/1NVz5nr52Gqk+7DzU1QCWHD9+FLEX+wvzcploNoqcR+Xrh7N/2kNGymDdxP+ 14 | UlpmpzLQ6BwcjWgwMa4GMn3L/aczFFuhG9dZdcy9UV4fXtreLO0FMt4BuBQyKMK2 15 | ZkWgw2rx3fMBgR4B99JpRr0wgqPHbFndvPYbMAEiYB4baRiIpkyBC6/JyZTNTBBH 16 | +WyvuQKbip5WvVST+PjIy2eywJ2yGXKek3TOS8GTulVqUE5u7utzdOLJaLXIvF2K 17 | BAbRa6CZ8+bZWRYpBujlu1mKjedzi/pS5BK5q/gPUWe8YcqEJVZWEHMakZ0= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /cookbook/http2-server-push/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | HTTP/2 Server Push 8 | 9 | 10 | 11 | 12 | 13 |

The following static files are served via HTTP/2 server push

14 | 19 | 20 | -------------------------------------------------------------------------------- /cookbook/http2-server-push/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw 3 | +jr8IJ7WaUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2i 4 | qxyrrRNyn3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q 5 | 0SMGF+YgRnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3 6 | Xc/d96OY35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phK 7 | mB22qnlgPY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABAoIBABvDpTMeu/5gwJDW 8 | HXvJJGMATEsBrcX9lsWqzMYDCkXdElPkMKkhaGTHzIdUpkgNUgYG3bYu1dYvXZKi 9 | 84X3+2u1KrF0i2hNAzZWAwfhOwkRQuSSUL9wgGk4Jp5Mgehdvcm0FVNFfOG4a0CS 10 | tLVZsVe/h+PfMs4EuPDicvLBH0LI/dE+GeXGIhuF8/g7EN3S+f/WtCejrzah7Ghf 11 | OsaBaBL0O5s82wU9PX0ZFxo4FADfwKLL0Zmbno8NC0cMsNOM2/B2dRF0eTlCDeAy 12 | rxIXL7HEz+fWK96jCbDiMPyP9XstQxO2atZa2gC5vOJCwPpVVDbqW7FNop4VwUOW 13 | bTtDrcUCgYEA6+hyuN0DcaAOM64rVyBbcMOD4L6Q00A1+9I1wBJtx1yNNfTS4iJc 14 | EXxkLS8ae9xXYEGhiU0PyOufs+iTwLwvlbWcZUJailEoBG5EzC1BDZPE20hNcXav 15 | zxxyQxRrzdItmMj8wa4TETVlcPGcKxNUw/UXqFIyNShtALse2mnYQQcCgYEA64sA 16 | 3WC+/e0jVoaPiIuCg2vt5QUiyfkblSUjsdBTlMWYIjlJNcGv81vepk3NIAPkTs71 17 | HZuRwEDENm/7HDSG2SpyB9de8/b061Uo/o+ZhWqPOHx1m7mD9x96CZk4wtxwL14u 18 | SJuwyujqmSYoUYW8FKjHS5n74hP9YUT7hXKWs4sCgYAx2eAMUp/8rd7yata7xZmt 19 | HZPLtVlzWrlNqqEzInHSVCt/AGpj4PDlvQyKQ87r56cLLzNMiV1RjwEjin1WmC3S 20 | DButhjUNz5KORSMCpnl9vgE2eXPsCzGhqZg3tqQFTWnXRHmtD/T1iPwTvurKa35Z 21 | HnzOU/hKJW3LXr9pVj6dlwKBgQCXVaEBm1Y7GbB5uEziIxiAzch0O9+FOxsgsVME 22 | vN/mlynO21WRR1eAGUetPBGN/1Ih3FCabEix6Cro+vuwvILjZqULKrIkN0hXJ0kG 23 | fUba9IL+fOCnZANItJ2ZKyvP7wfZNz6vgfCN/iY0rdJ7xlv4PhSGG3I9aDCE3Who 24 | 7h2rTwKBgQCQ5s0JdS/EDX5h7LPPqpIV1kqMmcqvBonZN4TySfqxKSlhasO3oGgC 25 | rYkygiYGU8WdooavGNSmY4+sCBY002PZQ8EcSp+KySpYLeiVrJZTc+ehzk93t2to 26 | b1p1mmLFCM6SVJAIQ+y9jt5GCSEka0BlVoNfLM06OLcC9j8iIGsjrw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /cookbook/http2-server-push/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/labstack/echo/v4" 7 | ) 8 | 9 | func main() { 10 | e := echo.New() 11 | e.Static("/", "static") 12 | e.GET("/", func(c echo.Context) (err error) { 13 | pusher, ok := c.Response().Writer.(http.Pusher) 14 | if ok { 15 | if err = pusher.Push("/app.css", nil); err != nil { 16 | return 17 | } 18 | if err = pusher.Push("/app.js", nil); err != nil { 19 | return 20 | } 21 | if err = pusher.Push("/echo.png", nil); err != nil { 22 | return 23 | } 24 | } 25 | return c.File("index.html") 26 | }) 27 | e.Logger.Fatal(e.StartTLS(":1323", "cert.pem", "key.pem")) 28 | } 29 | -------------------------------------------------------------------------------- /cookbook/http2-server-push/static/app.css: -------------------------------------------------------------------------------- 1 | h2 { 2 | font-weight: 300; 3 | } 4 | 5 | ul { 6 | list-style: none; 7 | } 8 | 9 | .echo { 10 | height: 30px; 11 | } -------------------------------------------------------------------------------- /cookbook/http2-server-push/static/app.js: -------------------------------------------------------------------------------- 1 | console.log("http/2 push") -------------------------------------------------------------------------------- /cookbook/http2-server-push/static/echo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labstack/echox/c495449db4db6fdead7fd00c58fc121b20f87da9/cookbook/http2-server-push/static/echo.png -------------------------------------------------------------------------------- /cookbook/http2/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+DCCAeCgAwIBAgIPFB3cowaziqTVN3I2utsEMA0GCSqGSIb3DQEBCwUAMBIx 3 | EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTYwNTA5MTUwNDQ2WhcNMTcwNTA5MTUwNDQ2 4 | WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw+jr8IJ7W 6 | aUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2iqxyrrRNy 7 | n3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q0SMGF+Yg 8 | RnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3Xc/d96OY 9 | 35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phKmB22qnlg 10 | PY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABo0swSTAOBgNVHQ8BAf8EBAMCBaAw 11 | EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggls 12 | b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAL2SqeH2Z6zKje9XC6O/TC0DKRvE 13 | L/d/1NVz5nr52Gqk+7DzU1QCWHD9+FLEX+wvzcploNoqcR+Xrh7N/2kNGymDdxP+ 14 | UlpmpzLQ6BwcjWgwMa4GMn3L/aczFFuhG9dZdcy9UV4fXtreLO0FMt4BuBQyKMK2 15 | ZkWgw2rx3fMBgR4B99JpRr0wgqPHbFndvPYbMAEiYB4baRiIpkyBC6/JyZTNTBBH 16 | +WyvuQKbip5WvVST+PjIy2eywJ2yGXKek3TOS8GTulVqUE5u7utzdOLJaLXIvF2K 17 | BAbRa6CZ8+bZWRYpBujlu1mKjedzi/pS5BK5q/gPUWe8YcqEJVZWEHMakZ0= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /cookbook/http2/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw 3 | +jr8IJ7WaUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2i 4 | qxyrrRNyn3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q 5 | 0SMGF+YgRnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3 6 | Xc/d96OY35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phK 7 | mB22qnlgPY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABAoIBABvDpTMeu/5gwJDW 8 | HXvJJGMATEsBrcX9lsWqzMYDCkXdElPkMKkhaGTHzIdUpkgNUgYG3bYu1dYvXZKi 9 | 84X3+2u1KrF0i2hNAzZWAwfhOwkRQuSSUL9wgGk4Jp5Mgehdvcm0FVNFfOG4a0CS 10 | tLVZsVe/h+PfMs4EuPDicvLBH0LI/dE+GeXGIhuF8/g7EN3S+f/WtCejrzah7Ghf 11 | OsaBaBL0O5s82wU9PX0ZFxo4FADfwKLL0Zmbno8NC0cMsNOM2/B2dRF0eTlCDeAy 12 | rxIXL7HEz+fWK96jCbDiMPyP9XstQxO2atZa2gC5vOJCwPpVVDbqW7FNop4VwUOW 13 | bTtDrcUCgYEA6+hyuN0DcaAOM64rVyBbcMOD4L6Q00A1+9I1wBJtx1yNNfTS4iJc 14 | EXxkLS8ae9xXYEGhiU0PyOufs+iTwLwvlbWcZUJailEoBG5EzC1BDZPE20hNcXav 15 | zxxyQxRrzdItmMj8wa4TETVlcPGcKxNUw/UXqFIyNShtALse2mnYQQcCgYEA64sA 16 | 3WC+/e0jVoaPiIuCg2vt5QUiyfkblSUjsdBTlMWYIjlJNcGv81vepk3NIAPkTs71 17 | HZuRwEDENm/7HDSG2SpyB9de8/b061Uo/o+ZhWqPOHx1m7mD9x96CZk4wtxwL14u 18 | SJuwyujqmSYoUYW8FKjHS5n74hP9YUT7hXKWs4sCgYAx2eAMUp/8rd7yata7xZmt 19 | HZPLtVlzWrlNqqEzInHSVCt/AGpj4PDlvQyKQ87r56cLLzNMiV1RjwEjin1WmC3S 20 | DButhjUNz5KORSMCpnl9vgE2eXPsCzGhqZg3tqQFTWnXRHmtD/T1iPwTvurKa35Z 21 | HnzOU/hKJW3LXr9pVj6dlwKBgQCXVaEBm1Y7GbB5uEziIxiAzch0O9+FOxsgsVME 22 | vN/mlynO21WRR1eAGUetPBGN/1Ih3FCabEix6Cro+vuwvILjZqULKrIkN0hXJ0kG 23 | fUba9IL+fOCnZANItJ2ZKyvP7wfZNz6vgfCN/iY0rdJ7xlv4PhSGG3I9aDCE3Who 24 | 7h2rTwKBgQCQ5s0JdS/EDX5h7LPPqpIV1kqMmcqvBonZN4TySfqxKSlhasO3oGgC 25 | rYkygiYGU8WdooavGNSmY4+sCBY002PZQ8EcSp+KySpYLeiVrJZTc+ehzk93t2to 26 | b1p1mmLFCM6SVJAIQ+y9jt5GCSEka0BlVoNfLM06OLcC9j8iIGsjrw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /cookbook/http2/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | func main() { 11 | e := echo.New() 12 | e.GET("/request", func(c echo.Context) error { 13 | req := c.Request() 14 | format := ` 15 | 16 | Protocol: %s
17 | Host: %s
18 | Remote Address: %s
19 | Method: %s
20 | Path: %s
21 |
22 | ` 23 | return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path)) 24 | }) 25 | e.Logger.Fatal(e.StartTLS(":1323", "cert.pem", "key.pem")) 26 | } 27 | -------------------------------------------------------------------------------- /cookbook/jsonp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JSONP 8 | 9 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |

31 |


32 |         

33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /cookbook/jsonp/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/labstack/echo/v4" 9 | "github.com/labstack/echo/v4/middleware" 10 | ) 11 | 12 | func main() { 13 | e := echo.New() 14 | e.Use(middleware.Logger()) 15 | e.Use(middleware.Recover()) 16 | 17 | e.Static("/", "public") 18 | 19 | // JSONP 20 | e.GET("/jsonp", func(c echo.Context) error { 21 | callback := c.QueryParam("callback") 22 | var content struct { 23 | Response string `json:"response"` 24 | Timestamp time.Time `json:"timestamp"` 25 | Random int `json:"random"` 26 | } 27 | content.Response = "Sent via JSONP" 28 | content.Timestamp = time.Now().UTC() 29 | content.Random = rand.Intn(1000) 30 | return c.JSONP(http.StatusOK, callback, &content) 31 | }) 32 | 33 | // Start server 34 | e.Logger.Fatal(e.Start(":1323")) 35 | } 36 | -------------------------------------------------------------------------------- /cookbook/jwt/custom-claims/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/golang-jwt/jwt/v5" 5 | echojwt "github.com/labstack/echo-jwt/v4" 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | // jwtCustomClaims are custom claims extending default ones. 13 | // See https://github.com/golang-jwt/jwt for more examples 14 | type jwtCustomClaims struct { 15 | Name string `json:"name"` 16 | Admin bool `json:"admin"` 17 | jwt.RegisteredClaims 18 | } 19 | 20 | func login(c echo.Context) error { 21 | username := c.FormValue("username") 22 | password := c.FormValue("password") 23 | 24 | // Throws unauthorized error 25 | if username != "jon" || password != "shhh!" { 26 | return echo.ErrUnauthorized 27 | } 28 | 29 | // Set custom claims 30 | claims := &jwtCustomClaims{ 31 | "Jon Snow", 32 | true, 33 | jwt.RegisteredClaims{ 34 | ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 72)), 35 | }, 36 | } 37 | 38 | // Create token with claims 39 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 40 | 41 | // Generate encoded token and send it as response. 42 | t, err := token.SignedString([]byte("secret")) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | return c.JSON(http.StatusOK, echo.Map{ 48 | "token": t, 49 | }) 50 | } 51 | 52 | func accessible(c echo.Context) error { 53 | return c.String(http.StatusOK, "Accessible") 54 | } 55 | 56 | func restricted(c echo.Context) error { 57 | user := c.Get("user").(*jwt.Token) 58 | claims := user.Claims.(*jwtCustomClaims) 59 | name := claims.Name 60 | return c.String(http.StatusOK, "Welcome "+name+"!") 61 | } 62 | 63 | func main() { 64 | e := echo.New() 65 | 66 | // Middleware 67 | e.Use(middleware.Logger()) 68 | e.Use(middleware.Recover()) 69 | 70 | // Login route 71 | e.POST("/login", login) 72 | 73 | // Unauthenticated route 74 | e.GET("/", accessible) 75 | 76 | // Restricted group 77 | r := e.Group("/restricted") 78 | 79 | // Configure middleware with the custom claims type 80 | config := echojwt.Config{ 81 | NewClaimsFunc: func(c echo.Context) jwt.Claims { 82 | return new(jwtCustomClaims) 83 | }, 84 | SigningKey: []byte("secret"), 85 | } 86 | r.Use(echojwt.WithConfig(config)) 87 | r.GET("", restricted) 88 | 89 | e.Logger.Fatal(e.Start(":1323")) 90 | } 91 | -------------------------------------------------------------------------------- /cookbook/jwt/user-defined-keyfunc/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | echojwt "github.com/labstack/echo-jwt/v4" 8 | "net/http" 9 | 10 | "github.com/golang-jwt/jwt/v5" 11 | "github.com/labstack/echo/v4" 12 | "github.com/labstack/echo/v4/middleware" 13 | "github.com/lestrrat-go/jwx/jwk" 14 | ) 15 | 16 | func getKey(token *jwt.Token) (interface{}, error) { 17 | 18 | // For a demonstration purpose, Google Sign-in is used. 19 | // https://developers.google.com/identity/sign-in/web/backend-auth 20 | // 21 | // This user-defined KeyFunc verifies tokens issued by Google Sign-In. 22 | // 23 | // Note: In this example, it downloads the keyset every time the restricted route is accessed. 24 | keySet, err := jwk.Fetch(context.Background(), "https://www.googleapis.com/oauth2/v3/certs") 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | keyID, ok := token.Header["kid"].(string) 30 | if !ok { 31 | return nil, errors.New("expecting JWT header to have a key ID in the kid field") 32 | } 33 | 34 | key, found := keySet.LookupKeyID(keyID) 35 | 36 | if !found { 37 | return nil, fmt.Errorf("unable to find key %q", keyID) 38 | } 39 | 40 | var pubkey interface{} 41 | if err := key.Raw(&pubkey); err != nil { 42 | return nil, fmt.Errorf("Unable to get the public key. Error: %s", err.Error()) 43 | } 44 | 45 | return pubkey, nil 46 | } 47 | 48 | func accessible(c echo.Context) error { 49 | return c.String(http.StatusOK, "Accessible") 50 | } 51 | 52 | func restricted(c echo.Context) error { 53 | user := c.Get("user").(*jwt.Token) 54 | claims := user.Claims.(jwt.MapClaims) 55 | name := claims["name"].(string) 56 | return c.String(http.StatusOK, "Welcome "+name+"!") 57 | } 58 | 59 | func main() { 60 | e := echo.New() 61 | 62 | // Middleware 63 | e.Use(middleware.Logger()) 64 | e.Use(middleware.Recover()) 65 | 66 | // Unauthenticated route 67 | e.GET("/", accessible) 68 | 69 | // Restricted group 70 | r := e.Group("/restricted") 71 | { 72 | config := echojwt.Config{ 73 | KeyFunc: getKey, 74 | } 75 | r.Use(echojwt.WithConfig(config)) 76 | r.GET("", restricted) 77 | } 78 | 79 | e.Logger.Fatal(e.Start(":1323")) 80 | } 81 | -------------------------------------------------------------------------------- /cookbook/load-balancing/armor.yaml: -------------------------------------------------------------------------------- 1 | address: ":8080" 2 | plugins: 3 | - name: logger 4 | hosts: 5 | localhost:1323: 6 | paths: 7 | "/": 8 | plugins: 9 | - name: proxy 10 | targets: 11 | - url: http://localhost:8081 12 | - url: http://localhost:8082 -------------------------------------------------------------------------------- /cookbook/load-balancing/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream localhost { 2 | server localhost:8081; 3 | server localhost:8082; 4 | } 5 | 6 | server { 7 | listen 8080; 8 | server_name localhost; 9 | access_log /var/log/nginx/localhost.access.log combined; 10 | 11 | location / { 12 | proxy_pass http://localhost; 13 | } 14 | } -------------------------------------------------------------------------------- /cookbook/load-balancing/upstream/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/labstack/echo/v4" 9 | "github.com/labstack/echo/v4/middleware" 10 | ) 11 | 12 | var index = ` 13 | 14 | 15 | 16 | 17 | 18 | 19 | Upstream Server 20 | 25 | 26 | 27 |

28 | Hello from upstream server %s 29 |

30 | 31 | 32 | ` 33 | 34 | func main() { 35 | name := os.Args[1] 36 | port := os.Args[2] 37 | e := echo.New() 38 | e.Use(middleware.Recover()) 39 | e.Use(middleware.Logger()) 40 | e.GET("/", func(c echo.Context) error { 41 | return c.HTML(http.StatusOK, fmt.Sprintf(index, name)) 42 | }) 43 | e.Logger.Fatal(e.Start(port)) 44 | } 45 | -------------------------------------------------------------------------------- /cookbook/middleware/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "sync" 7 | "time" 8 | 9 | "github.com/labstack/echo/v4" 10 | ) 11 | 12 | type ( 13 | Stats struct { 14 | Uptime time.Time `json:"uptime"` 15 | RequestCount uint64 `json:"requestCount"` 16 | Statuses map[string]int `json:"statuses"` 17 | mutex sync.RWMutex 18 | } 19 | ) 20 | 21 | func NewStats() *Stats { 22 | return &Stats{ 23 | Uptime: time.Now(), 24 | Statuses: map[string]int{}, 25 | } 26 | } 27 | 28 | // Process is the middleware function. 29 | func (s *Stats) Process(next echo.HandlerFunc) echo.HandlerFunc { 30 | return func(c echo.Context) error { 31 | if err := next(c); err != nil { 32 | c.Error(err) 33 | } 34 | s.mutex.Lock() 35 | defer s.mutex.Unlock() 36 | s.RequestCount++ 37 | status := strconv.Itoa(c.Response().Status) 38 | s.Statuses[status]++ 39 | return nil 40 | } 41 | } 42 | 43 | // Handle is the endpoint to get stats. 44 | func (s *Stats) Handle(c echo.Context) error { 45 | s.mutex.RLock() 46 | defer s.mutex.RUnlock() 47 | return c.JSON(http.StatusOK, s) 48 | } 49 | 50 | // ServerHeader middleware adds a `Server` header to the response. 51 | func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc { 52 | return func(c echo.Context) error { 53 | c.Response().Header().Set(echo.HeaderServer, "Echo/3.0") 54 | return next(c) 55 | } 56 | } 57 | 58 | func main() { 59 | e := echo.New() 60 | 61 | // Debug mode 62 | e.Debug = true 63 | 64 | //------------------- 65 | // Custom middleware 66 | //------------------- 67 | // Stats 68 | s := NewStats() 69 | e.Use(s.Process) 70 | e.GET("/stats", s.Handle) // Endpoint to get stats 71 | 72 | // Server header 73 | e.Use(ServerHeader) 74 | 75 | // Handler 76 | e.GET("/", func(c echo.Context) error { 77 | return c.String(http.StatusOK, "Hello, World!") 78 | }) 79 | 80 | // Start server 81 | e.Logger.Fatal(e.Start(":1323")) 82 | } 83 | -------------------------------------------------------------------------------- /cookbook/reverse-proxy/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | ) 9 | 10 | func main() { 11 | e := echo.New() 12 | e.Use(middleware.Recover()) 13 | e.Use(middleware.Logger()) 14 | 15 | // Setup proxy 16 | url1, err := url.Parse("http://localhost:8081") 17 | if err != nil { 18 | e.Logger.Fatal(err) 19 | } 20 | url2, err := url.Parse("http://localhost:8082") 21 | if err != nil { 22 | e.Logger.Fatal(err) 23 | } 24 | targets := []*middleware.ProxyTarget{ 25 | { 26 | URL: url1, 27 | }, 28 | { 29 | URL: url2, 30 | }, 31 | } 32 | e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer(targets))) 33 | 34 | e.Logger.Fatal(e.Start(":1323")) 35 | } 36 | -------------------------------------------------------------------------------- /cookbook/reverse-proxy/upstream/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "time" 8 | 9 | "github.com/labstack/echo/v4" 10 | "github.com/labstack/echo/v4/middleware" 11 | "golang.org/x/net/websocket" 12 | ) 13 | 14 | var index = ` 15 | 16 | 17 | 18 | 19 | 20 | 21 | Upstream Server 22 | 27 | 28 | 29 |

HTTP

30 |

31 | Hello from upstream server %s 32 |

33 |

WebSocket

34 |

35 | 43 | 44 | 45 | ` 46 | 47 | func main() { 48 | name := os.Args[1] 49 | port := os.Args[2] 50 | e := echo.New() 51 | e.Use(middleware.Recover()) 52 | e.Use(middleware.Logger()) 53 | e.GET("/", func(c echo.Context) error { 54 | return c.HTML(http.StatusOK, fmt.Sprintf(index, name)) 55 | }) 56 | 57 | // WebSocket handler 58 | e.GET("/ws", func(c echo.Context) error { 59 | websocket.Handler(func(ws *websocket.Conn) { 60 | defer ws.Close() 61 | for { 62 | // Write 63 | err := websocket.Message.Send(ws, fmt.Sprintf("Hello from upstream server %s!", name)) 64 | if err != nil { 65 | e.Logger.Error(err) 66 | } 67 | time.Sleep(1 * time.Second) 68 | } 69 | }).ServeHTTP(c.Response(), c.Request()) 70 | return nil 71 | }) 72 | 73 | e.Logger.Fatal(e.Start(port)) 74 | } 75 | -------------------------------------------------------------------------------- /cookbook/sse/broadcast/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Getting server-sent updates

6 |
7 | 8 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /cookbook/sse/broadcast/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "github.com/labstack/echo/v4" 6 | "github.com/labstack/echo/v4/middleware" 7 | "github.com/r3labs/sse/v2" 8 | "log" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | e := echo.New() 15 | 16 | server := sse.New() // create SSE broadcaster server 17 | server.AutoReplay = false // do not replay messages for each new subscriber that connects 18 | _ = server.CreateStream("time") // EventSource in "index.html" connecting to stream named "time" 19 | 20 | go func(s *sse.Server) { 21 | ticker := time.NewTicker(1 * time.Second) 22 | defer ticker.Stop() 23 | 24 | for { 25 | select { 26 | case <-ticker.C: 27 | s.Publish("time", &sse.Event{ 28 | Data: []byte("time: " + time.Now().Format(time.RFC3339Nano)), 29 | }) 30 | } 31 | } 32 | }(server) 33 | 34 | e.Use(middleware.Logger()) 35 | e.Use(middleware.Recover()) 36 | e.File("/", "./index.html") 37 | 38 | //e.GET("/sse", echo.WrapHandler(server)) 39 | 40 | e.GET("/sse", func(c echo.Context) error { // longer variant with disconnect logic 41 | log.Printf("The client is connected: %v\n", c.RealIP()) 42 | go func() { 43 | <-c.Request().Context().Done() // Received Browser Disconnection 44 | log.Printf("The client is disconnected: %v\n", c.RealIP()) 45 | return 46 | }() 47 | 48 | server.ServeHTTP(c.Response(), c.Request()) 49 | return nil 50 | }) 51 | 52 | if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) { 53 | log.Fatal(err) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cookbook/sse/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Getting server-sent updates

6 |
7 | 8 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /cookbook/sse/simple/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "github.com/labstack/echo/v4" 6 | "github.com/labstack/echo/v4/middleware" 7 | "log" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | e := echo.New() 14 | 15 | e.Use(middleware.Logger()) 16 | e.Use(middleware.Recover()) 17 | e.File("/", "./index.html") 18 | 19 | e.GET("/sse", func(c echo.Context) error { 20 | log.Printf("SSE client connected, ip: %v", c.RealIP()) 21 | 22 | w := c.Response() 23 | w.Header().Set("Content-Type", "text/event-stream") 24 | w.Header().Set("Cache-Control", "no-cache") 25 | w.Header().Set("Connection", "keep-alive") 26 | 27 | ticker := time.NewTicker(1 * time.Second) 28 | defer ticker.Stop() 29 | for { 30 | select { 31 | case <-c.Request().Context().Done(): 32 | log.Printf("SSE client disconnected, ip: %v", c.RealIP()) 33 | return nil 34 | case <-ticker.C: 35 | event := Event{ 36 | Data: []byte("time: " + time.Now().Format(time.RFC3339Nano)), 37 | } 38 | if err := event.MarshalTo(w); err != nil { 39 | return err 40 | } 41 | w.Flush() 42 | } 43 | } 44 | }) 45 | 46 | if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) { 47 | log.Fatal(err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cookbook/sse/simple/serversentevent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | // Event represents Server-Sent Event. 10 | // SSE explanation: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format 11 | type Event struct { 12 | // ID is used to set the EventSource object's last event ID value. 13 | ID []byte 14 | // Data field is for the message. When the EventSource receives multiple consecutive lines 15 | // that begin with data:, it concatenates them, inserting a newline character between each one. 16 | // Trailing newlines are removed. 17 | Data []byte 18 | // Event is a string identifying the type of event described. If this is specified, an event 19 | // will be dispatched on the browser to the listener for the specified event name; the website 20 | // source code should use addEventListener() to listen for named events. The onmessage handler 21 | // is called if no event name is specified for a message. 22 | Event []byte 23 | // Retry is the reconnection time. If the connection to the server is lost, the browser will 24 | // wait for the specified time before attempting to reconnect. This must be an integer, specifying 25 | // the reconnection time in milliseconds. If a non-integer value is specified, the field is ignored. 26 | Retry []byte 27 | // Comment line can be used to prevent connections from timing out; a server can send a comment 28 | // periodically to keep the connection alive. 29 | Comment []byte 30 | } 31 | 32 | // MarshalTo marshals Event to given Writer 33 | func (ev *Event) MarshalTo(w io.Writer) error { 34 | // Marshalling part is taken from: https://github.com/r3labs/sse/blob/c6d5381ee3ca63828b321c16baa008fd6c0b4564/http.go#L16 35 | if len(ev.Data) == 0 && len(ev.Comment) == 0 { 36 | return nil 37 | } 38 | 39 | if len(ev.Data) > 0 { 40 | if _, err := fmt.Fprintf(w, "id: %s\n", ev.ID); err != nil { 41 | return err 42 | } 43 | 44 | sd := bytes.Split(ev.Data, []byte("\n")) 45 | for i := range sd { 46 | if _, err := fmt.Fprintf(w, "data: %s\n", sd[i]); err != nil { 47 | return err 48 | } 49 | } 50 | 51 | if len(ev.Event) > 0 { 52 | if _, err := fmt.Fprintf(w, "event: %s\n", ev.Event); err != nil { 53 | return err 54 | } 55 | } 56 | 57 | if len(ev.Retry) > 0 { 58 | if _, err := fmt.Fprintf(w, "retry: %s\n", ev.Retry); err != nil { 59 | return err 60 | } 61 | } 62 | } 63 | 64 | if len(ev.Comment) > 0 { 65 | if _, err := fmt.Fprintf(w, ": %s\n", ev.Comment); err != nil { 66 | return err 67 | } 68 | } 69 | 70 | if _, err := fmt.Fprint(w, "\n"); err != nil { 71 | return err 72 | } 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /cookbook/streaming-response/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/labstack/echo/v4" 9 | ) 10 | 11 | type ( 12 | Geolocation struct { 13 | Altitude float64 14 | Latitude float64 15 | Longitude float64 16 | } 17 | ) 18 | 19 | var ( 20 | locations = []Geolocation{ 21 | {-97, 37.819929, -122.478255}, 22 | {1899, 39.096849, -120.032351}, 23 | {2619, 37.865101, -119.538329}, 24 | {42, 33.812092, -117.918974}, 25 | {15, 37.77493, -122.419416}, 26 | } 27 | ) 28 | 29 | func main() { 30 | e := echo.New() 31 | e.GET("/", func(c echo.Context) error { 32 | c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 33 | c.Response().WriteHeader(http.StatusOK) 34 | 35 | enc := json.NewEncoder(c.Response()) 36 | for _, l := range locations { 37 | if err := enc.Encode(l); err != nil { 38 | return err 39 | } 40 | c.Response().Flush() 41 | time.Sleep(1 * time.Second) 42 | } 43 | return nil 44 | }) 45 | e.Logger.Fatal(e.Start(":1323")) 46 | } 47 | -------------------------------------------------------------------------------- /cookbook/subdomain/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | ) 9 | 10 | type ( 11 | Host struct { 12 | Echo *echo.Echo 13 | } 14 | ) 15 | 16 | func main() { 17 | // Hosts 18 | hosts := map[string]*Host{} 19 | 20 | //----- 21 | // API 22 | //----- 23 | 24 | api := echo.New() 25 | api.Use(middleware.Logger()) 26 | api.Use(middleware.Recover()) 27 | 28 | hosts["api.localhost:1323"] = &Host{api} 29 | 30 | api.GET("/", func(c echo.Context) error { 31 | return c.String(http.StatusOK, "API") 32 | }) 33 | 34 | //------ 35 | // Blog 36 | //------ 37 | 38 | blog := echo.New() 39 | blog.Use(middleware.Logger()) 40 | blog.Use(middleware.Recover()) 41 | 42 | hosts["blog.localhost:1323"] = &Host{blog} 43 | 44 | blog.GET("/", func(c echo.Context) error { 45 | return c.String(http.StatusOK, "Blog") 46 | }) 47 | 48 | //--------- 49 | // Website 50 | //--------- 51 | 52 | site := echo.New() 53 | site.Use(middleware.Logger()) 54 | site.Use(middleware.Recover()) 55 | 56 | hosts["localhost:1323"] = &Host{site} 57 | 58 | site.GET("/", func(c echo.Context) error { 59 | return c.String(http.StatusOK, "Website") 60 | }) 61 | 62 | // Server 63 | e := echo.New() 64 | e.Any("/*", func(c echo.Context) (err error) { 65 | req := c.Request() 66 | res := c.Response() 67 | host := hosts[req.Host] 68 | 69 | if host == nil { 70 | err = echo.ErrNotFound 71 | } else { 72 | host.Echo.ServeHTTP(res, req) 73 | } 74 | 75 | return 76 | }) 77 | e.Logger.Fatal(e.Start(":1323")) 78 | } 79 | -------------------------------------------------------------------------------- /cookbook/timeout/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/labstack/echo/v4" 8 | "github.com/labstack/echo/v4/middleware" 9 | ) 10 | 11 | func main() { 12 | // Echo instance 13 | e := echo.New() 14 | 15 | // Middleware 16 | e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{ 17 | Timeout: 5 * time.Second, 18 | })) 19 | 20 | // Route => handler 21 | e.GET("/", func(c echo.Context) error { 22 | time.Sleep(10 * time.Second) 23 | return c.String(http.StatusOK, "Hello, World!\n") 24 | }) 25 | 26 | // Start server 27 | e.Logger.Fatal(e.Start(":1323")) 28 | } 29 | -------------------------------------------------------------------------------- /cookbook/twitter/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "gopkg.in/mgo.v2" 5 | ) 6 | 7 | type ( 8 | Handler struct { 9 | DB *mgo.Session 10 | } 11 | ) 12 | 13 | const ( 14 | // Key (Should come from somewhere else). 15 | Key = "secret" 16 | ) 17 | -------------------------------------------------------------------------------- /cookbook/twitter/handler/post.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/labstack/echo/v4" 8 | "github.com/labstack/echox/cookbook/twitter/model" 9 | "gopkg.in/mgo.v2" 10 | "gopkg.in/mgo.v2/bson" 11 | ) 12 | 13 | func (h *Handler) CreatePost(c echo.Context) (err error) { 14 | u := &model.User{ 15 | ID: bson.ObjectIdHex(userIDFromToken(c)), 16 | } 17 | p := &model.Post{ 18 | ID: bson.NewObjectId(), 19 | From: u.ID.Hex(), 20 | } 21 | if err = c.Bind(p); err != nil { 22 | return 23 | } 24 | 25 | // Validation 26 | if p.To == "" || p.Message == "" { 27 | return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid to or message fields"} 28 | } 29 | 30 | // Find user from database 31 | db := h.DB.Clone() 32 | defer db.Close() 33 | if err = db.DB("twitter").C("users").FindId(u.ID).One(u); err != nil { 34 | if err == mgo.ErrNotFound { 35 | return echo.ErrNotFound 36 | } 37 | return 38 | } 39 | 40 | // Save post in database 41 | if err = db.DB("twitter").C("posts").Insert(p); err != nil { 42 | return 43 | } 44 | return c.JSON(http.StatusCreated, p) 45 | } 46 | 47 | func (h *Handler) FetchPost(c echo.Context) (err error) { 48 | userID := userIDFromToken(c) 49 | page, _ := strconv.Atoi(c.QueryParam("page")) 50 | limit, _ := strconv.Atoi(c.QueryParam("limit")) 51 | 52 | // Defaults 53 | if page == 0 { 54 | page = 1 55 | } 56 | if limit == 0 { 57 | limit = 100 58 | } 59 | 60 | // Retrieve posts from database 61 | posts := []*model.Post{} 62 | db := h.DB.Clone() 63 | if err = db.DB("twitter").C("posts"). 64 | Find(bson.M{"to": userID}). 65 | Skip((page - 1) * limit). 66 | Limit(limit). 67 | All(&posts); err != nil { 68 | return 69 | } 70 | defer db.Close() 71 | 72 | return c.JSON(http.StatusOK, posts) 73 | } 74 | -------------------------------------------------------------------------------- /cookbook/twitter/handler/user.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/golang-jwt/jwt/v5" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/labstack/echo/v4" 9 | "github.com/labstack/echox/cookbook/twitter/model" 10 | "gopkg.in/mgo.v2" 11 | "gopkg.in/mgo.v2/bson" 12 | ) 13 | 14 | func (h *Handler) Signup(c echo.Context) (err error) { 15 | // Bind 16 | u := &model.User{ID: bson.NewObjectId()} 17 | if err = c.Bind(u); err != nil { 18 | return 19 | } 20 | 21 | // Validate 22 | if u.Email == "" || u.Password == "" { 23 | return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid email or password"} 24 | } 25 | 26 | // Save user 27 | db := h.DB.Clone() 28 | defer db.Close() 29 | if err = db.DB("twitter").C("users").Insert(u); err != nil { 30 | return 31 | } 32 | 33 | return c.JSON(http.StatusCreated, u) 34 | } 35 | 36 | func (h *Handler) Login(c echo.Context) (err error) { 37 | // Bind 38 | u := new(model.User) 39 | if err = c.Bind(u); err != nil { 40 | return 41 | } 42 | 43 | // Find user 44 | db := h.DB.Clone() 45 | defer db.Close() 46 | if err = db.DB("twitter").C("users"). 47 | Find(bson.M{"email": u.Email, "password": u.Password}).One(u); err != nil { 48 | if err == mgo.ErrNotFound { 49 | return &echo.HTTPError{Code: http.StatusUnauthorized, Message: "invalid email or password"} 50 | } 51 | return 52 | } 53 | 54 | //----- 55 | // JWT 56 | //----- 57 | 58 | // Create token 59 | token := jwt.New(jwt.SigningMethodHS256) 60 | 61 | // Set claims 62 | claims := token.Claims.(jwt.MapClaims) 63 | claims["id"] = u.ID 64 | claims["exp"] = time.Now().Add(time.Hour * 72).Unix() 65 | 66 | // Generate encoded token and send it as response 67 | u.Token, err = token.SignedString([]byte(Key)) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | u.Password = "" // Don't send password 73 | return c.JSON(http.StatusOK, u) 74 | } 75 | 76 | func (h *Handler) Follow(c echo.Context) (err error) { 77 | userID := userIDFromToken(c) 78 | id := c.Param("id") 79 | 80 | // Add a follower to user 81 | db := h.DB.Clone() 82 | defer db.Close() 83 | if err = db.DB("twitter").C("users"). 84 | UpdateId(bson.ObjectIdHex(id), bson.M{"$addToSet": bson.M{"followers": userID}}); err != nil { 85 | if err == mgo.ErrNotFound { 86 | return echo.ErrNotFound 87 | } 88 | } 89 | 90 | return 91 | } 92 | 93 | func userIDFromToken(c echo.Context) string { 94 | user := c.Get("user").(*jwt.Token) 95 | claims := user.Claims.(jwt.MapClaims) 96 | return claims["id"].(string) 97 | } 98 | -------------------------------------------------------------------------------- /cookbook/twitter/model/post.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | ) 6 | 7 | type ( 8 | Post struct { 9 | ID bson.ObjectId `json:"id" bson:"_id,omitempty"` 10 | To string `json:"to" bson:"to"` 11 | From string `json:"from" bson:"from"` 12 | Message string `json:"message" bson:"message"` 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /cookbook/twitter/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | ) 6 | 7 | type ( 8 | User struct { 9 | ID bson.ObjectId `json:"id" bson:"_id,omitempty"` 10 | Email string `json:"email" bson:"email"` 11 | Password string `json:"password,omitempty" bson:"password"` 12 | Token string `json:"token,omitempty" bson:"-"` 13 | Followers []string `json:"followers,omitempty" bson:"followers,omitempty"` 14 | } 15 | ) 16 | -------------------------------------------------------------------------------- /cookbook/twitter/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | echojwt "github.com/labstack/echo-jwt/v4" 5 | "github.com/labstack/echo/v4" 6 | "github.com/labstack/echo/v4/middleware" 7 | "github.com/labstack/echox/cookbook/twitter/handler" 8 | "github.com/labstack/gommon/log" 9 | "gopkg.in/mgo.v2" 10 | ) 11 | 12 | func main() { 13 | e := echo.New() 14 | e.Logger.SetLevel(log.ERROR) 15 | e.Use(middleware.Logger()) 16 | e.Use(echojwt.WithConfig(echojwt.Config{ 17 | SigningKey: []byte(handler.Key), 18 | Skipper: func(c echo.Context) bool { 19 | // Skip authentication for signup and login requests 20 | if c.Path() == "/login" || c.Path() == "/signup" { 21 | return true 22 | } 23 | return false 24 | }, 25 | })) 26 | 27 | // Database connection 28 | db, err := mgo.Dial("localhost") 29 | if err != nil { 30 | e.Logger.Fatal(err) 31 | } 32 | 33 | // Create indices 34 | if err = db.Copy().DB("twitter").C("users").EnsureIndex(mgo.Index{ 35 | Key: []string{"email"}, 36 | Unique: true, 37 | }); err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | // Initialize handler 42 | h := &handler.Handler{DB: db} 43 | 44 | // Routes 45 | e.POST("/signup", h.Signup) 46 | e.POST("/login", h.Login) 47 | e.POST("/follow/:id", h.Follow) 48 | e.POST("/posts", h.CreatePost) 49 | e.GET("/feed", h.FetchPost) 50 | 51 | // Start server 52 | e.Logger.Fatal(e.Start(":1323")) 53 | } 54 | -------------------------------------------------------------------------------- /cookbook/websocket/gorilla/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gorilla/websocket" 7 | "github.com/labstack/echo/v4" 8 | "github.com/labstack/echo/v4/middleware" 9 | ) 10 | 11 | var ( 12 | upgrader = websocket.Upgrader{} 13 | ) 14 | 15 | func hello(c echo.Context) error { 16 | ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) 17 | if err != nil { 18 | return err 19 | } 20 | defer ws.Close() 21 | 22 | for { 23 | // Write 24 | err := ws.WriteMessage(websocket.TextMessage, []byte("Hello, Client!")) 25 | if err != nil { 26 | c.Logger().Error(err) 27 | } 28 | 29 | // Read 30 | _, msg, err := ws.ReadMessage() 31 | if err != nil { 32 | c.Logger().Error(err) 33 | } 34 | fmt.Printf("%s\n", msg) 35 | } 36 | } 37 | 38 | func main() { 39 | e := echo.New() 40 | e.Use(middleware.Logger()) 41 | e.Use(middleware.Recover()) 42 | e.Static("/", "../public") 43 | e.GET("/ws", hello) 44 | e.Logger.Fatal(e.Start(":1323")) 45 | } 46 | -------------------------------------------------------------------------------- /cookbook/websocket/net/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | "golang.org/x/net/websocket" 9 | ) 10 | 11 | func hello(c echo.Context) error { 12 | websocket.Handler(func(ws *websocket.Conn) { 13 | defer ws.Close() 14 | for { 15 | // Write 16 | err := websocket.Message.Send(ws, "Hello, Client!") 17 | if err != nil { 18 | c.Logger().Error(err) 19 | } 20 | 21 | // Read 22 | msg := "" 23 | err = websocket.Message.Receive(ws, &msg) 24 | if err != nil { 25 | c.Logger().Error(err) 26 | } 27 | fmt.Printf("%s\n", msg) 28 | } 29 | }).ServeHTTP(c.Response(), c.Request()) 30 | return nil 31 | } 32 | 33 | func main() { 34 | e := echo.New() 35 | e.Use(middleware.Logger()) 36 | e.Use(middleware.Recover()) 37 | e.Static("/", "../public") 38 | e.GET("/ws", hello) 39 | e.Logger.Fatal(e.Start(":1323")) 40 | } 41 | -------------------------------------------------------------------------------- /cookbook/websocket/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebSocket 7 | 8 | 9 | 10 |

11 | 12 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/labstack/echox 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/GeertJohan/go.rice v1.0.3 9 | github.com/golang-jwt/jwt/v5 v5.2.2 10 | github.com/gorilla/websocket v1.5.3 11 | github.com/labstack/echo-jwt/v4 v4.3.1 12 | github.com/labstack/echo/v4 v4.13.3 13 | github.com/labstack/gommon v0.4.2 14 | github.com/lestrrat-go/jwx v1.2.30 15 | github.com/r3labs/sse/v2 v2.10.0 16 | golang.org/x/crypto v0.36.0 17 | golang.org/x/net v0.37.0 18 | google.golang.org/appengine v1.6.8 19 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 20 | ) 21 | 22 | require ( 23 | github.com/daaku/go.zipexe v1.0.2 // indirect 24 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect 25 | github.com/goccy/go-json v0.10.5 // indirect 26 | github.com/golang/protobuf v1.5.4 // indirect 27 | github.com/google/go-cmp v0.5.9 // indirect 28 | github.com/kr/pretty v0.3.1 // indirect 29 | github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect 30 | github.com/lestrrat-go/blackmagic v1.0.2 // indirect 31 | github.com/lestrrat-go/httpcc v1.0.1 // indirect 32 | github.com/lestrrat-go/iter v1.0.2 // indirect 33 | github.com/lestrrat-go/option v1.0.1 // indirect 34 | github.com/mattn/go-colorable v0.1.14 // indirect 35 | github.com/mattn/go-isatty v0.0.20 // indirect 36 | github.com/pkg/errors v0.9.1 // indirect 37 | github.com/rogpeppe/go-internal v1.10.0 // indirect 38 | github.com/valyala/bytebufferpool v1.0.0 // indirect 39 | github.com/valyala/fasttemplate v1.2.2 // indirect 40 | golang.org/x/sys v0.31.0 // indirect 41 | golang.org/x/text v0.23.0 // indirect 42 | golang.org/x/time v0.11.0 // indirect 43 | google.golang.org/protobuf v1.36.5 // indirect 44 | gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect 45 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 46 | gopkg.in/yaml.v2 v2.4.0 // indirect 47 | ) 48 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /website/.nvmrc: -------------------------------------------------------------------------------- 1 | lts/iron 2 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/docs/cookbook/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Cookbook", 3 | "position": 4, 4 | "link": { 5 | "type": "generated-index" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/docs/cookbook/auto-tls.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Automatic TLS certificates from Let's Encrypt recipe 3 | --- 4 | 5 | # Auto TLS 6 | 7 | This recipe demonstrates how to obtain TLS certificates for a domain automatically from 8 | Let's Encrypt. `Echo#StartAutoTLS` accepts an address which should listen on port `443`. 9 | 10 | Browse to `https://`. If everything goes fine, you should see a welcome 11 | message with TLS enabled on the website. 12 | 13 | :::tip 14 | 15 | - For added security you should specify host policy in auto TLS manager 16 | - Cache certificates to avoid issues with rate limits (https://letsencrypt.org/docs/rate-limits) 17 | - To redirect HTTP traffic to HTTPS, you can use [redirect middleware](../middleware/redirect#https-redirect) 18 | 19 | ::: 20 | 21 | ## Server 22 | 23 | ```go reference 24 | https://github.com/labstack/echox/blob/master/cookbook/auto-tls/server.go 25 | ``` 26 | -------------------------------------------------------------------------------- /website/docs/cookbook/cors.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: CORS recipe 3 | --- 4 | 5 | # CORS 6 | 7 | ## Server using a list of allowed origins 8 | 9 | ```go reference 10 | https://github.com/labstack/echox/blob/master/cookbook/cors/origin-list/server.go 11 | ``` 12 | 13 | ## Server using a custom function to allow origins 14 | 15 | ```go reference 16 | https://github.com/labstack/echox/blob/master/cookbook/cors/origin-func/server.go 17 | ``` 18 | -------------------------------------------------------------------------------- /website/docs/cookbook/crud.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: CRUD (Create, read, update and delete) recipe 3 | --- 4 | 5 | # CRUD 6 | 7 | ## Server 8 | 9 | ```go reference 10 | https://github.com/labstack/echox/blob/master/cookbook/crud/server.go 11 | ``` 12 | 13 | ## Client 14 | 15 | ### Create user 16 | 17 | #### Request 18 | 19 | ```sh 20 | curl -X POST \ 21 | -H 'Content-Type: application/json' \ 22 | -d '{"name":"Joe Smith"}' \ 23 | localhost:1323/users 24 | ``` 25 | 26 | #### Response 27 | 28 | ```js 29 | { 30 | "id": 1, 31 | "name": "Joe Smith" 32 | } 33 | ``` 34 | 35 | ### Get user 36 | 37 | #### Request 38 | 39 | ```sh 40 | curl localhost:1323/users/1 41 | ``` 42 | 43 | #### Response 44 | 45 | ```js 46 | { 47 | "id": 1, 48 | "name": "Joe Smith" 49 | } 50 | ``` 51 | 52 | ### Update user 53 | 54 | #### Request 55 | 56 | ```sh 57 | curl -X PUT \ 58 | -H 'Content-Type: application/json' \ 59 | -d '{"name":"Joe"}' \ 60 | localhost:1323/users/1 61 | ``` 62 | 63 | #### Response 64 | 65 | ```js 66 | { 67 | "id": 1, 68 | "name": "Joe" 69 | } 70 | ``` 71 | 72 | ### Delete user 73 | 74 | #### Request 75 | 76 | ```sh 77 | curl -X DELETE localhost:1323/users/1 78 | ``` 79 | 80 | #### Response 81 | 82 | `NoContent - 204` 83 | -------------------------------------------------------------------------------- /website/docs/cookbook/embed-resources.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Embed resources recipe 3 | --- 4 | 5 | # Embed Resources 6 | 7 | ## With go 1.16 embed feature 8 | 9 | ```go reference 10 | https://github.com/labstack/echox/blob/master/cookbook/embed/server.go 11 | ``` 12 | 13 | ## With go.rice 14 | 15 | ```go reference 16 | https://github.com/labstack/echox/blob/master/cookbook/embed-resources/server.go 17 | ``` 18 | -------------------------------------------------------------------------------- /website/docs/cookbook/file-download.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: File download recipe 3 | --- 4 | 5 | # File Download 6 | 7 | ## Download file 8 | 9 | ### Server 10 | 11 | ```go reference 12 | https://github.com/labstack/echox/blob/master/cookbook/file-download/server.go 13 | ``` 14 | 15 | ### Client 16 | 17 | ```html reference 18 | https://github.com/labstack/echox/blob/master/cookbook/file-download/index.html 19 | ``` 20 | 21 | ## Download file as inline 22 | 23 | ### Server 24 | 25 | ```go reference 26 | https://github.com/labstack/echox/blob/master/cookbook/file-download/inline/server.go 27 | ``` 28 | 29 | ### Client 30 | 31 | ```html reference 32 | https://github.com/labstack/echox/blob/master/cookbook/file-download/inline/index.html 33 | ``` 34 | 35 | ## Download file as attachment 36 | 37 | ### Server 38 | 39 | ```go reference 40 | https://github.com/labstack/echox/blob/master/cookbook/file-download/attachment/server.go 41 | ``` 42 | 43 | ### Client 44 | 45 | ```html reference 46 | https://github.com/labstack/echox/blob/master/cookbook/file-download/attachment/index.html 47 | ``` 48 | -------------------------------------------------------------------------------- /website/docs/cookbook/file-upload.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: File upload recipe 3 | --- 4 | 5 | # File Upload 6 | 7 | 8 | ## Upload single file with parameters 9 | 10 | ### Server 11 | 12 | ```go reference 13 | https://github.com/labstack/echox/blob/master/cookbook/file-upload/single/server.go 14 | ``` 15 | 16 | ### Client 17 | 18 | ```html reference 19 | https://github.com/labstack/echox/blob/master/cookbook/file-upload/single/public/index.html 20 | ``` 21 | 22 | ## Upload multiple files with parameters 23 | 24 | ### Server 25 | 26 | ```go reference 27 | https://github.com/labstack/echox/blob/master/cookbook/file-upload/multiple/server.go 28 | ``` 29 | 30 | ### Client 31 | 32 | ```html reference 33 | https://github.com/labstack/echox/blob/master/cookbook/file-upload/multiple/public/index.html 34 | ``` 35 | -------------------------------------------------------------------------------- /website/docs/cookbook/graceful-shutdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Graceful shutdown recipe 3 | --- 4 | 5 | # Graceful Shutdown 6 | 7 | ## Using [http.Server#Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) 8 | 9 | ```go reference 10 | https://github.com/labstack/echox/blob/master/cookbook/graceful-shutdown/server.go 11 | ``` 12 | 13 | :::note 14 | 15 | Requires go1.16+ 16 | 17 | ::: 18 | -------------------------------------------------------------------------------- /website/docs/cookbook/hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Hello world recipe 3 | --- 4 | 5 | # Hello World 6 | 7 | ## Server 8 | 9 | ```go reference 10 | https://github.com/labstack/echox/blob/master/cookbook/hello-world/server.go 11 | ``` 12 | -------------------------------------------------------------------------------- /website/docs/cookbook/http2-server-push.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: HTTP/2 server push recipe 3 | --- 4 | 5 | # HTTP/2 Server Push 6 | 7 | :::note 8 | 9 | Requires go1.8+ 10 | 11 | ::: 12 | 13 | ## Send web assets using HTTP/2 server push 14 | 15 | ### [Generate a self-signed X.509 TLS certificate](http2#step-1-generate-a-self-signed-x-509-tls-certificate) 16 | 17 | ### 1) Register a route to serve web assets 18 | 19 | ```go 20 | e.Static("/", "static") 21 | ``` 22 | 23 | ### 2) Create a handler to serve index.html and push it's dependencies 24 | 25 | ```go 26 | e.GET("/", func(c echo.Context) (err error) { 27 | pusher, ok := c.Response().Writer.(http.Pusher) 28 | if ok { 29 | if err = pusher.Push("/app.css", nil); err != nil { 30 | return 31 | } 32 | if err = pusher.Push("/app.js", nil); err != nil { 33 | return 34 | } 35 | if err = pusher.Push("/echo.png", nil); err != nil { 36 | return 37 | } 38 | } 39 | return c.File("index.html") 40 | }) 41 | ``` 42 | 43 | :::info 44 | 45 | If `http.Pusher` is supported, web assets are pushed; otherwise, client makes separate requests to get them. 46 | 47 | ::: 48 | 49 | ### 3) Start TLS server using cert.pem and key.pem 50 | 51 | ```go 52 | if err := e.StartTLS(":1323", "cert.pem", "key.pem"); err != http.ErrServerClosed { 53 | log.Fatal(err) 54 | } 55 | ``` 56 | or use customized HTTP server with your own TLSConfig 57 | ```go 58 | s := http.Server{ 59 | Addr: ":8443", 60 | Handler: e, // set Echo as handler 61 | TLSConfig: &tls.Config{ 62 | //Certificates: nil, // <-- s.ListenAndServeTLS will populate this field 63 | }, 64 | //ReadTimeout: 30 * time.Second, // use custom timeouts 65 | } 66 | if err := s.ListenAndServeTLS("cert.pem", "key.pem"); err != http.ErrServerClosed { 67 | log.Fatal(err) 68 | } 69 | ``` 70 | 71 | ### 4) Start the server and browse to https://localhost:1323 72 | 73 | ```sh 74 | Protocol: HTTP/2.0 75 | Host: localhost:1323 76 | Remote Address: [::1]:60288 77 | Method: GET 78 | Path: / 79 | ``` 80 | 81 | ## Source Code 82 | 83 | ```html reference 84 | https://github.com/labstack/echox/blob/master/cookbook/http2-server-push/index.html 85 | ``` 86 | 87 | ```go reference 88 | https://github.com/labstack/echox/blob/master/cookbook/http2-server-push/server.go 89 | ``` 90 | -------------------------------------------------------------------------------- /website/docs/cookbook/http2.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: HTTP/2 server recipe 3 | --- 4 | 5 | # HTTP/2 Server 6 | 7 | ## 1) Generate a self-signed X.509 TLS certificate 8 | 9 | Run the following command to generate `cert.pem` and `key.pem` files: 10 | 11 | ```sh 12 | go run $GOROOT/src/crypto/tls/generate_cert.go --host localhost 13 | ``` 14 | 15 | :::note 16 | 17 | For demo purpose, we are using a self-signed certificate. Ideally, you should obtain 18 | a certificate from [CA](https://en.wikipedia.org/wiki/Certificate_authority). 19 | 20 | ::: 21 | 22 | ## 2) Create a handler which simply outputs the request information to the client 23 | 24 | ```go 25 | e.GET("/request", func(c echo.Context) error { 26 | req := c.Request() 27 | format := ` 28 | 29 | Protocol: %s
30 | Host: %s
31 | Remote Address: %s
32 | Method: %s
33 | Path: %s
34 |
35 | ` 36 | return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path)) 37 | }) 38 | ``` 39 | 40 | ## 3) Start TLS server using cert.pem and key.pem 41 | 42 | ```go 43 | if err := e.StartTLS(":1323", "cert.pem", "key.pem"); err != http.ErrServerClosed { 44 | log.Fatal(err) 45 | } 46 | ``` 47 | or use customized HTTP server with your own TLSConfig 48 | ```go 49 | s := http.Server{ 50 | Addr: ":8443", 51 | Handler: e, // set Echo as handler 52 | TLSConfig: &tls.Config{ 53 | //Certificates: nil, // <-- s.ListenAndServeTLS will populate this field 54 | }, 55 | //ReadTimeout: 30 * time.Second, // use custom timeouts 56 | } 57 | if err := s.ListenAndServeTLS("cert.pem", "key.pem"); err != http.ErrServerClosed { 58 | log.Fatal(err) 59 | } 60 | ``` 61 | 62 | ## 4) Start the server and browse to https://localhost:1323/request to see the following output 63 | 64 | ```sh 65 | Protocol: HTTP/2.0 66 | Host: localhost:1323 67 | Remote Address: [::1]:60288 68 | Method: GET 69 | Path: / 70 | ``` 71 | 72 | ## Source Code 73 | 74 | ```go reference 75 | https://github.com/labstack/echox/blob/master/cookbook/http2/server.go 76 | ``` 77 | -------------------------------------------------------------------------------- /website/docs/cookbook/jsonp.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: JSONP recipe 3 | --- 4 | 5 | # JSONP 6 | 7 | JSONP is a method that allows cross-domain server calls. You can read more about it at the JSON versus JSONP Tutorial. 8 | 9 | ## Server 10 | 11 | ```go reference 12 | https://github.com/labstack/echox/blob/master/cookbook/jsonp/server.go 13 | ``` 14 | 15 | ## Client 16 | 17 | ```html reference 18 | https://github.com/labstack/echox/blob/master/cookbook/jsonp/public/index.html 19 | ``` 20 | -------------------------------------------------------------------------------- /website/docs/cookbook/jwt.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: JWT recipe 3 | --- 4 | 5 | # JWT 6 | 7 | [JWT middleware](../middleware/jwt.md) configuration can be found [here](../middleware/jwt.md#configuration). 8 | 9 | This is cookbook for: 10 | - JWT authentication using HS256 algorithm. 11 | - JWT is retrieved from `Authorization` request header. 12 | 13 | ## Server 14 | 15 | ### Using custom claims 16 | 17 | ```go reference 18 | https://github.com/labstack/echox/blob/master/cookbook/jwt/custom-claims/server.go 19 | ``` 20 | 21 | ### Using a user-defined KeyFunc 22 | 23 | ```go reference 24 | https://github.com/labstack/echox/blob/master/cookbook/jwt/user-defined-keyfunc/server.go 25 | ``` 26 | 27 | ## Client 28 | 29 | ### Login 30 | 31 | Login using username and password to retrieve a token. 32 | 33 | ```sh 34 | curl -X POST -d 'username=jon' -d 'password=shhh!' localhost:1323/login 35 | ``` 36 | 37 | ### Response 38 | 39 | ```js 40 | { 41 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY" 42 | } 43 | ``` 44 | 45 | ### Request 46 | 47 | Request a restricted resource using the token in `Authorization` request header. 48 | 49 | ```sh 50 | curl localhost:1323/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY" 51 | ``` 52 | 53 | ### Response 54 | 55 | ```sh 56 | Welcome Jon Snow! 57 | ``` 58 | -------------------------------------------------------------------------------- /website/docs/cookbook/load-balancing.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Load balancing recipe 3 | --- 4 | 5 | # Load Balancing 6 | 7 | This recipe demonstrates how you can use Nginx as a reverse proxy server and load balance between multiple Echo servers. 8 | 9 | ## Echo 10 | 11 | ```go reference 12 | https://github.com/labstack/echox/blob/master/cookbook/load-balancing/upstream/server.go 13 | ``` 14 | 15 | ### Start servers 16 | 17 | - `cd upstream` 18 | - `go run server.go server1 :8081` 19 | - `go run server.go server2 :8082` 20 | 21 | ## Nginx 22 | 23 | ### 1) Install Nginx 24 | 25 | https://www.nginx.com/resources/wiki/start/topics/tutorials/install 26 | 27 | ### 2) Configure Nginx 28 | 29 | Create a file `/etc/nginx/sites-enabled/localhost` with the following content: 30 | 31 | ``` reference 32 | https://github.com/labstack/echox/blob/master/cookbook/load-balancing/nginx.conf 33 | ``` 34 | 35 | :::info 36 | 37 | Change listen, server_name, access_log per your need. 38 | 39 | ::: 40 | 41 | ### 3) Restart Nginx 42 | 43 | ```sh 44 | service nginx restart 45 | ``` 46 | 47 | Browse to https://localhost:8080, and you should see a webpage being served from either "server 1" or "server 2". 48 | 49 | ```sh 50 | Hello from upstream server server1 51 | ``` 52 | -------------------------------------------------------------------------------- /website/docs/cookbook/middleware.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Middleware recipe 3 | --- 4 | 5 | # Middleware 6 | 7 | ## Write a custom middleware 8 | 9 | - Middleware to collect request count, statuses and uptime. 10 | - Middleware to write custom `Server` header to the response. 11 | 12 | ### Server 13 | 14 | ```go reference 15 | https://github.com/labstack/echox/blob/master/cookbook/middleware/server.go 16 | ``` 17 | 18 | ### Response 19 | 20 | #### Headers 21 | 22 | ```sh 23 | Content-Length:122 24 | Content-Type:application/json; charset=utf-8 25 | Date:Thu, 14 Apr 2016 20:31:46 GMT 26 | Server:Echo/3.0 27 | ``` 28 | 29 | #### Body 30 | 31 | ```js 32 | { 33 | "uptime": "2016-04-14T13:28:48.486548936-07:00", 34 | "requestCount": 5, 35 | "statuses": { 36 | "200": 4, 37 | "404": 1 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /website/docs/cookbook/reverse-proxy.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Reverse proxy recipe 3 | --- 4 | 5 | # Reverse Proxy 6 | 7 | This recipe demonstrates how you can use Echo as a reverse proxy server and load balancer in front of your favorite applications like WordPress, Node.js, Java, Python, Ruby or even Go. For simplicity, I will use Go upstream servers with WebSocket. 8 | 9 | ## 1) Identify upstream target URL(s) 10 | 11 | ```go 12 | url1, err := url.Parse("http://localhost:8081") 13 | if err != nil { 14 | e.Logger.Fatal(err) 15 | } 16 | url2, err := url.Parse("http://localhost:8082") 17 | if err != nil { 18 | e.Logger.Fatal(err) 19 | } 20 | targets := []*middleware.ProxyTarget{ 21 | { 22 | URL: url1, 23 | }, 24 | { 25 | URL: url2, 26 | }, 27 | } 28 | ``` 29 | 30 | ## 2) Setup proxy middleware with upstream targets 31 | 32 | In the following code snippet we are using round-robin load balancing technique. You may also use `middleware.NewRandomBalancer()`. 33 | 34 | ```go 35 | e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer(targets))) 36 | ``` 37 | 38 | To setup proxy for a sub-route use `Echo#Group()`. 39 | 40 | ```go 41 | g := e.Group("/blog") 42 | g.Use(middleware.Proxy(...)) 43 | ``` 44 | 45 | ## 3) Start upstream servers 46 | 47 | - `cd upstream` 48 | - `go run server.go server1 :8081` 49 | - `go run server.go server2 :8082` 50 | 51 | ## 4) Start the proxy server 52 | 53 | ```sh 54 | go run server.go 55 | ``` 56 | 57 | Browse to http://localhost:1323, and you should see a webpage with an HTTP request being served from "server 1" and a WebSocket request being served from "server 2." 58 | 59 | ```sh 60 | HTTP 61 | 62 | Hello from upstream server server1 63 | 64 | WebSocket 65 | 66 | Hello from upstream server server2! 67 | Hello from upstream server server2! 68 | Hello from upstream server server2! 69 | ``` 70 | 71 | ## Source Code 72 | 73 | ```go reference 74 | https://github.com/labstack/echox/blob/master/cookbook/reverse-proxy/upstream/server.go 75 | ``` 76 | 77 | ```go reference 78 | https://github.com/labstack/echox/blob/master/cookbook/reverse-proxy/server.go 79 | ``` 80 | -------------------------------------------------------------------------------- /website/docs/cookbook/sse.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: SSE recipe 3 | --- 4 | 5 | # Server-Sent-Events (SSE) 6 | 7 | [Server-sent events]( 8 | https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format) can be 9 | used in different ways. This example here is per connection - per handler SSE. If your requirements need more complex 10 | broadcasting logic see https://github.com/r3labs/sse library. 11 | 12 | ## Using SSE 13 | 14 | ### Server 15 | 16 | ```go reference 17 | https://github.com/labstack/echox/blob/master/cookbook/sse/simple/server.go 18 | ``` 19 | 20 | ### Event structure and Marshal method 21 | 22 | ```go reference 23 | https://github.com/labstack/echox/blob/master/cookbook/sse/simple/serversentevent.go 24 | ``` 25 | 26 | ### HTML serving SSE 27 | 28 | ```html reference 29 | https://github.com/labstack/echox/blob/master/cookbook/sse/simple/index.html 30 | ``` 31 | 32 | ## Using 3rd party library [r3labs/sse](https://github.com/r3labs/sse) to broadcast events 33 | 34 | ### Server 35 | 36 | ```go reference 37 | https://github.com/labstack/echox/blob/master/cookbook/sse/broadcast/server.go 38 | ``` 39 | 40 | ### HTML serving SSE 41 | 42 | ```html reference 43 | https://github.com/labstack/echox/blob/master/cookbook/sse/broadcast/index.html 44 | ``` -------------------------------------------------------------------------------- /website/docs/cookbook/streaming-response.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Streaming response recipe 3 | --- 4 | 5 | # Streaming Response 6 | 7 | - Send data as it is produced 8 | - Streaming JSON response with chunked transfer encoding 9 | 10 | ## Server 11 | 12 | ```go reference 13 | https://github.com/labstack/echox/blob/master/cookbook/streaming-response/server.go 14 | ``` 15 | 16 | ## Client 17 | 18 | ```sh 19 | $ curl localhost:1323 20 | ``` 21 | 22 | ### Output 23 | 24 | ```js 25 | {"Altitude":-97,"Latitude":37.819929,"Longitude":-122.478255} 26 | {"Altitude":1899,"Latitude":39.096849,"Longitude":-120.032351} 27 | {"Altitude":2619,"Latitude":37.865101,"Longitude":-119.538329} 28 | {"Altitude":42,"Latitude":33.812092,"Longitude":-117.918974} 29 | {"Altitude":15,"Latitude":37.77493,"Longitude":-122.419416} 30 | ``` 31 | -------------------------------------------------------------------------------- /website/docs/cookbook/subdomain.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Subdomain recipe 3 | --- 4 | 5 | # Subdomain 6 | 7 | ## Server 8 | 9 | ```go reference 10 | https://github.com/labstack/echox/blob/master/cookbook/subdomain/server.go 11 | ``` 12 | -------------------------------------------------------------------------------- /website/docs/cookbook/timeout.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Timeout recipe 3 | --- 4 | 5 | # Timeout 6 | 7 | ## Server 8 | 9 | ```go reference 10 | https://github.com/labstack/echox/blob/master/cookbook/timeout/server.go 11 | ``` 12 | -------------------------------------------------------------------------------- /website/docs/cookbook/websocket.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: WebSocket recipe 3 | --- 4 | 5 | # WebSocket 6 | 7 | ## Using net WebSocket 8 | 9 | ### Server 10 | 11 | ```go reference 12 | https://github.com/labstack/echox/blob/master/cookbook/websocket/net/server.go 13 | ``` 14 | 15 | ## Using gorilla WebSocket 16 | 17 | ### Server 18 | 19 | ```go reference 20 | https://github.com/labstack/echox/blob/master/cookbook/websocket/gorilla/server.go 21 | ``` 22 | 23 | ## Client 24 | 25 | ```html reference 26 | https://github.com/labstack/echox/blob/master/cookbook/websocket/public/index.html 27 | ``` 28 | 29 | ## Output 30 | 31 | ```sh 32 | Hello, Client! 33 | Hello, Client! 34 | Hello, Client! 35 | Hello, Client! 36 | Hello, Client! 37 | ``` 38 | 39 | ```sh 40 | Hello, Server! 41 | Hello, Server! 42 | Hello, Server! 43 | Hello, Server! 44 | Hello, Server! 45 | ``` 46 | -------------------------------------------------------------------------------- /website/docs/guide/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Guide", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "5 minutes to learn the most important Echo concepts." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /website/docs/guide/context.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Context in Echo 3 | slug: /context 4 | sidebar_position: 4 5 | --- 6 | 7 | # Context 8 | 9 | `echo.Context` represents the context of the current HTTP request. It holds request and 10 | response reference, path, path parameters, data, registered handler and APIs to read 11 | request and write response. As Context is an interface, it is easy to extend it with 12 | custom APIs. 13 | 14 | ## Extending 15 | 16 | **Define a custom context** 17 | 18 | ```go 19 | type CustomContext struct { 20 | echo.Context 21 | } 22 | 23 | func (c *CustomContext) Foo() { 24 | println("foo") 25 | } 26 | 27 | func (c *CustomContext) Bar() { 28 | println("bar") 29 | } 30 | ``` 31 | 32 | **Create a middleware to extend default context** 33 | 34 | ```go 35 | e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { 36 | return func(c echo.Context) error { 37 | cc := &CustomContext{c} 38 | return next(cc) 39 | } 40 | }) 41 | ``` 42 | 43 | :::caution 44 | 45 | This middleware should be registered before any other middleware. 46 | 47 | ::: 48 | 49 | :::caution 50 | 51 | Custom context cannot be defined in a middleware before the router ran (Pre) 52 | 53 | ::: 54 | 55 | **Use in handler** 56 | 57 | ```go 58 | e.GET("/", func(c echo.Context) error { 59 | cc := c.(*CustomContext) 60 | cc.Foo() 61 | cc.Bar() 62 | return cc.String(200, "OK") 63 | }) 64 | ``` 65 | 66 | ## Concurrency 67 | 68 | :::caution 69 | 70 | `Context` must not be accessed out of the goroutine handling the request. There are two reasons: 71 | 72 | 1. `Context` has functions that are dangerous to execute from multiple goroutines. Therefore, only one goroutine should access it. 73 | 2. Echo uses a pool to create `Context`'s. When the request handling finishes, Echo returns the `Context` to the pool. 74 | 75 | See issue [1908](https://github.com/labstack/echo/issues/1908) for a "cautionary tale" caused by this reason. Concurrency is complicated. Beware of this pitfall when working with goroutines. 76 | 77 | ::: 78 | 79 | ### Solution 80 | 81 | Use a channel 82 | 83 | ```go 84 | func(c echo.Context) error { 85 | ca := make(chan string, 1) // To prevent this channel from blocking, size is set to 1. 86 | r := c.Request() 87 | method := r.Method 88 | 89 | go func() { 90 | // This function must not touch the Context. 91 | 92 | fmt.Printf("Method: %s\n", method) 93 | 94 | // Do some long running operations... 95 | 96 | ca <- "Hey!" 97 | }() 98 | 99 | select { 100 | case result := <-ca: 101 | return c.String(http.StatusOK, "Result: "+result) 102 | case <-c.Request().Context().Done(): // Check context. 103 | // If it reaches here, this means that context was canceled (a timeout was reached, etc.). 104 | return nil 105 | } 106 | } 107 | ``` -------------------------------------------------------------------------------- /website/docs/guide/cookies.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Handling cookie 3 | slug: /cookies 4 | sidebar_position: 5 5 | --- 6 | 7 | # Cookies 8 | 9 | Cookie is a small piece of data sent from a website server and stored in the user's web 10 | browser while browsing. Every time the user loads the website, the browser 11 | sends the cookies back to the server to notify the server of user's latest activity. 12 | Cookies were designed to be a reliable mechanism for websites to remember stateful 13 | information (e.g. items added to the shopping cart in an online store) or to 14 | record the user's browsing activity (such as clicking particular buttons, logging 15 | in, or user previously visited pages of the website). Cookies can also store form content a user has previously entered, such as username, gender, age, address, etc. 16 | 17 | ## Cookie Attributes 18 | 19 | Attribute | Optional 20 | :--- | :--- 21 | `Name` | No 22 | `Value` | No 23 | `Path` | Yes 24 | `Domain` | Yes 25 | `Expires` | Yes 26 | `Secure` | Yes 27 | `HttpOnly` | Yes 28 | 29 | Echo uses go standard `http.Cookie` object to add/retrieve cookies from the context received in the handler function. 30 | 31 | ## Create a Cookie 32 | 33 | ```go 34 | func writeCookie(c echo.Context) error { 35 | cookie := new(http.Cookie) 36 | cookie.Name = "username" 37 | cookie.Value = "jon" 38 | cookie.Expires = time.Now().Add(24 * time.Hour) 39 | c.SetCookie(cookie) 40 | return c.String(http.StatusOK, "write a cookie") 41 | } 42 | ``` 43 | 44 | - Cookie is created using `new(http.Cookie)`. 45 | - Attributes for the cookie are set assigning to the `http.Cookie` instance public attributes. 46 | - Finally `c.SetCookie(cookie)` adds a `Set-Cookie` header in HTTP response. 47 | 48 | ## Read a Cookie 49 | 50 | ```go 51 | func readCookie(c echo.Context) error { 52 | cookie, err := c.Cookie("username") 53 | if err != nil { 54 | return err 55 | } 56 | fmt.Println(cookie.Name) 57 | fmt.Println(cookie.Value) 58 | return c.String(http.StatusOK, "read a cookie") 59 | } 60 | ``` 61 | 62 | - Cookie is read by name using `c.Cookie("username")` from the HTTP request. 63 | - Cookie attributes are accessed using `Getter` function. 64 | 65 | ## Read all the Cookies 66 | 67 | ```go 68 | func readAllCookies(c echo.Context) error { 69 | for _, cookie := range c.Cookies() { 70 | fmt.Println(cookie.Name) 71 | fmt.Println(cookie.Value) 72 | } 73 | return c.String(http.StatusOK, "read all the cookies") 74 | } 75 | ``` -------------------------------------------------------------------------------- /website/docs/guide/customization.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Customization 3 | slug: /customization 4 | sidebar_position: 2 5 | --- 6 | 7 | # Customization 8 | 9 | ## Debug 10 | 11 | `Echo#Debug` can be used to enable / disable debug mode. Debug mode sets the log level 12 | to `DEBUG`. 13 | 14 | ## Logging 15 | 16 | The default format for logging is JSON, which can be changed by modifying the header. 17 | 18 | ### Log Header 19 | 20 | `Echo#Logger.SetHeader(string)` can be used to set the header for 21 | the logger. Default value: 22 | 23 | ```js 24 | {"time":"${time_rfc3339_nano}","level":"${level}","prefix":"${prefix}","file":"${short_file}","line":"${line}"} 25 | ``` 26 | 27 | *Example* 28 | ```go 29 | import "github.com/labstack/gommon/log" 30 | 31 | /* ... */ 32 | 33 | if l, ok := e.Logger.(*log.Logger); ok { 34 | l.SetHeader("${time_rfc3339} ${level}") 35 | } 36 | ``` 37 | 38 | ```sh 39 | 2018-05-08T20:30:06-07:00 INFO info 40 | ``` 41 | 42 | #### Available Tags 43 | 44 | - `time_rfc3339` 45 | - `time_rfc3339_nano` 46 | - `level` 47 | - `prefix` 48 | - `long_file` 49 | - `short_file` 50 | - `line` 51 | 52 | ### Log Output 53 | 54 | `Echo#Logger.SetOutput(io.Writer)` can be used to set the output destination for 55 | the logger. Default value is `os.Stdout` 56 | 57 | To completely disable logs use `Echo#Logger.SetOutput(io.Discard)` or `Echo#Logger.SetLevel(log.OFF)` 58 | 59 | ### Log Level 60 | 61 | `Echo#Logger.SetLevel(log.Lvl)` can be used to set the log level for the logger. 62 | Default value is `ERROR`. Possible values: 63 | 64 | - `DEBUG` 65 | - `INFO` 66 | - `WARN` 67 | - `ERROR` 68 | - `OFF` 69 | 70 | ### Custom Logger 71 | 72 | Logging is implemented using `echo.Logger` interface which allows you to register 73 | a custom logger using `Echo#Logger`. 74 | 75 | ## Startup Banner 76 | 77 | `Echo#HideBanner` can be used to hide the startup banner. 78 | 79 | ## Listener Port 80 | 81 | `Echo#HidePort` can be used to hide the listener port message. 82 | 83 | ## Custom Listener 84 | 85 | `Echo#*Listener` can be used to run a custom listener. 86 | 87 | *Example* 88 | 89 | ```go 90 | l, err := net.Listen("tcp", ":1323") 91 | if err != nil { 92 | e.Logger.Fatal(err) 93 | } 94 | e.Listener = l 95 | e.Logger.Fatal(e.Start("")) 96 | ``` 97 | 98 | ## Disable HTTP/2 99 | 100 | `Echo#DisableHTTP2` can be used to disable HTTP/2 protocol. 101 | 102 | ## Read Timeout 103 | 104 | `Echo#*Server#ReadTimeout` can be used to set the maximum duration before timing out read 105 | of the request. 106 | 107 | ## Write Timeout 108 | 109 | `Echo#*Server#WriteTimeout` can be used to set the maximum duration before timing out write 110 | of the response. 111 | 112 | ## Validator 113 | 114 | `Echo#Validator` can be used to register a validator for performing data validation 115 | on request payload. 116 | 117 | [Learn more](./request.md#validate-data) 118 | 119 | ## Custom Binder 120 | 121 | `Echo#Binder` can be used to register a custom binder for binding request payload. 122 | 123 | [Learn more](./binding#custom-binding) 124 | 125 | ## Custom JSON Serializer 126 | 127 | `Echo#JSONSerializer` can be used to register a custom JSON serializer. 128 | 129 | Have a look at `DefaultJSONSerializer` on [json.go](https://github.com/labstack/echo/blob/master/json.go). 130 | 131 | ## Renderer 132 | 133 | `Echo#Renderer` can be used to register a renderer for template rendering. 134 | 135 | [Learn more](./templates.md) 136 | 137 | ## HTTP Error Handler 138 | 139 | `Echo#HTTPErrorHandler` can be used to register a custom http error handler. 140 | 141 | [Learn more](./error-handling.md) 142 | -------------------------------------------------------------------------------- /website/docs/guide/error-handling.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Error handling 3 | slug: /error-handling 4 | sidebar_position: 6 5 | --- 6 | 7 | # Error Handling 8 | 9 | Echo advocates for centralized HTTP error handling by returning error from middleware 10 | and handlers. Centralized error handler allows us to log errors to external services 11 | from a unified location and send a customized HTTP response to the client. 12 | 13 | You can return a standard `error` or `echo.*HTTPError`. 14 | 15 | For example, when basic auth middleware finds invalid credentials it returns 16 | 401 - Unauthorized error, aborting the current HTTP request. 17 | 18 | ```go 19 | e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { 20 | return func(c echo.Context) error { 21 | // Extract the credentials from HTTP request header and perform a security 22 | // check 23 | 24 | // For invalid credentials 25 | return echo.NewHTTPError(http.StatusUnauthorized, "Please provide valid credentials") 26 | 27 | // For valid credentials call next 28 | // return next(c) 29 | } 30 | }) 31 | ``` 32 | 33 | You can also use `echo.NewHTTPError()` without a message, in that case status text is used 34 | as an error message. For example, "Unauthorized". 35 | 36 | ## Default HTTP Error Handler 37 | 38 | Echo provides a default HTTP error handler which sends error in a JSON format. 39 | 40 | ```js 41 | { 42 | "message": "error connecting to redis" 43 | } 44 | ``` 45 | 46 | For a standard `error`, response is sent as `500 - Internal Server Error`; however, 47 | if you are running in a debug mode, the original error message is sent. If error 48 | is `*HTTPError`, response is sent with the provided status code and message. 49 | If logging is on, the error message is also logged. 50 | 51 | ## Custom HTTP Error Handler 52 | 53 | Custom HTTP error handler can be set via `e.HTTPErrorHandler` 54 | 55 | For most cases default error HTTP handler should be sufficient; however, a custom HTTP 56 | error handler can come handy if you want to capture different type of errors and 57 | take action accordingly e.g. send notification email or log error to a centralized 58 | system. You can also send customized response to the client e.g. error page or 59 | just a JSON response. 60 | 61 | ### Error Pages 62 | 63 | The following custom HTTP error handler shows how to display error pages for different 64 | type of errors and logs the error. The name of the error page should be like `.html` e.g. `500.html`. You can look into this project 65 | https://github.com/AndiDittrich/HttpErrorPages for pre-built error pages. 66 | 67 | ```go 68 | func customHTTPErrorHandler(err error, c echo.Context) { 69 | if c.Response().Committed { 70 | return 71 | } 72 | 73 | code := http.StatusInternalServerError 74 | if he, ok := err.(*echo.HTTPError); ok { 75 | code = he.Code 76 | } 77 | c.Logger().Error(err) 78 | errorPage := fmt.Sprintf("%d.html", code) 79 | if err := c.File(errorPage); err != nil { 80 | c.Logger().Error(err) 81 | } 82 | } 83 | 84 | e.HTTPErrorHandler = customHTTPErrorHandler 85 | ``` 86 | 87 | :::tip 88 | 89 | Instead of writing logs to the logger, you can also write them to an external service like Elasticsearch or Splunk. 90 | 91 | ::: 92 | -------------------------------------------------------------------------------- /website/docs/guide/request.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Handling request 3 | slug: /request 4 | sidebar_position: 9 5 | --- 6 | 7 | # Request 8 | 9 | ## Retrieve Data 10 | 11 | ### Form Data 12 | 13 | Form data can be retrieved by name using `Context#FormValue(name string)`. 14 | 15 | ```go 16 | // Handler 17 | func(c echo.Context) error { 18 | name := c.FormValue("name") 19 | return c.String(http.StatusOK, name) 20 | } 21 | ``` 22 | 23 | ```sh 24 | curl -X POST http://localhost:1323 -d 'name=Joe' 25 | ``` 26 | 27 | To bind a custom data type, you can implement `Echo#BindUnmarshaler` interface. 28 | 29 | ```go 30 | type Timestamp time.Time 31 | 32 | func (t *Timestamp) UnmarshalParam(src string) error { 33 | ts, err := time.Parse(time.RFC3339, src) 34 | *t = Timestamp(ts) 35 | return err 36 | } 37 | ``` 38 | 39 | ### Query Parameters 40 | 41 | Query parameters can be retrieved by name using `Context#QueryParam(name string)`. 42 | 43 | ```go 44 | // Handler 45 | func(c echo.Context) error { 46 | name := c.QueryParam("name") 47 | return c.String(http.StatusOK, name) 48 | }) 49 | ``` 50 | 51 | ```sh 52 | curl \ 53 | -X GET \ 54 | http://localhost:1323\?name\=Joe 55 | ``` 56 | 57 | Similar to form data, custom data type can be bind using `Context#QueryParam(name string)`. 58 | 59 | ### Path Parameters 60 | 61 | Registered path parameters can be retrieved by name using `Context#Param(name string) string`. 62 | 63 | ```go 64 | e.GET("/users/:name", func(c echo.Context) error { 65 | name := c.Param("name") 66 | return c.String(http.StatusOK, name) 67 | }) 68 | ``` 69 | 70 | ```sh 71 | curl http://localhost:1323/users/Joe 72 | ``` 73 | 74 | ### Binding Data 75 | 76 | Also binding of request data to native Go structs and variables is supported. 77 | See [Binding Data](./binding.md) 78 | 79 | ## Validate Data 80 | 81 | Echo doesn't have built-in data validation capabilities, however, you can register 82 | a custom validator using `Echo#Validator` and leverage third-party [libraries](https://github.com/avelino/awesome-go#validation). 83 | 84 | Example below uses https://github.com/go-playground/validator framework for validation: 85 | 86 | ```go 87 | package main 88 | 89 | import ( 90 | "net/http" 91 | 92 | "github.com/go-playground/validator" 93 | "github.com/labstack/echo/v4" 94 | "github.com/labstack/echo/v4/middleware" 95 | ) 96 | 97 | type ( 98 | User struct { 99 | Name string `json:"name" validate:"required"` 100 | Email string `json:"email" validate:"required,email"` 101 | } 102 | 103 | CustomValidator struct { 104 | validator *validator.Validate 105 | } 106 | ) 107 | 108 | func (cv *CustomValidator) Validate(i interface{}) error { 109 | if err := cv.validator.Struct(i); err != nil { 110 | // Optionally, you could return the error to give each route more control over the status code 111 | return echo.NewHTTPError(http.StatusBadRequest, err.Error()) 112 | } 113 | return nil 114 | } 115 | 116 | func main() { 117 | e := echo.New() 118 | e.Validator = &CustomValidator{validator: validator.New()} 119 | e.POST("/users", func(c echo.Context) (err error) { 120 | u := new(User) 121 | if err = c.Bind(u); err != nil { 122 | return echo.NewHTTPError(http.StatusBadRequest, err.Error()) 123 | } 124 | if err = c.Validate(u); err != nil { 125 | return err 126 | } 127 | return c.JSON(http.StatusOK, u) 128 | }) 129 | e.Logger.Fatal(e.Start(":1323")) 130 | } 131 | ``` 132 | 133 | ```sh 134 | curl -X POST http://localhost:1323/users \ 135 | -H 'Content-Type: application/json' \ 136 | -d '{"name":"Joe","email":"joe@invalid-domain"}' 137 | {"message":"Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag"} 138 | ``` -------------------------------------------------------------------------------- /website/docs/guide/start-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Starting server 3 | slug: /start-server 4 | sidebar_position: 7 5 | --- 6 | 7 | # Start Server 8 | 9 | Echo provides following convenience methods to start the server: 10 | 11 | - `Echo.Start(address string)` 12 | - `Echo.StartTLS(address string, certFile, keyFile interface{})` 13 | - `Echo.StartAutoTLS(address string)` 14 | - `Echo.StartH2CServer(address string, h2s *http2.Server)` 15 | - `Echo.StartServer(s *http.Server)` 16 | 17 | ## HTTP Server 18 | 19 | `Echo.Start` is convenience method that starts http server with Echo serving requests. 20 | ```go 21 | func main() { 22 | e := echo.New() 23 | // add middleware and routes 24 | // ... 25 | if err := e.Start(":8080"); err != http.ErrServerClosed { 26 | log.Fatal(err) 27 | } 28 | } 29 | ``` 30 | 31 | Following is equivalent to `Echo.Start` previous example 32 | ```go 33 | func main() { 34 | e := echo.New() 35 | // add middleware and routes 36 | // ... 37 | s := http.Server{ 38 | Addr: ":8080", 39 | Handler: e, 40 | //ReadTimeout: 30 * time.Second, // customize http.Server timeouts 41 | } 42 | if err := s.ListenAndServe(); err != http.ErrServerClosed { 43 | log.Fatal(err) 44 | } 45 | } 46 | ``` 47 | 48 | ## HTTPS Server 49 | 50 | `Echo.StartTLS` is convenience method that starts HTTPS server with Echo serving requests on given address and uses 51 | `server.crt` and `server.key` as TLS certificate pair. 52 | ```go 53 | func main() { 54 | e := echo.New() 55 | // add middleware and routes 56 | // ... 57 | if err := e.StartTLS(":8443", "server.crt", "server.key"); err != http.ErrServerClosed { 58 | log.Fatal(err) 59 | } 60 | } 61 | ``` 62 | 63 | Following is equivalent to `Echo.StartTLS` previous example 64 | ```go 65 | func main() { 66 | e := echo.New() 67 | // add middleware and routes 68 | // ... 69 | s := http.Server{ 70 | Addr: ":8443", 71 | Handler: e, // set Echo as handler 72 | TLSConfig: &tls.Config{ 73 | //MinVersion: 1, // customize TLS configuration 74 | }, 75 | //ReadTimeout: 30 * time.Second, // use custom timeouts 76 | } 77 | if err := s.ListenAndServeTLS("server.crt", "server.key"); err != http.ErrServerClosed { 78 | log.Fatal(err) 79 | } 80 | } 81 | ``` 82 | 83 | ## Auto TLS Server with Let’s Encrypt 84 | 85 | See [Auto TLS Recipe](../cookbook/auto-tls.md#server) 86 | 87 | ## HTTP/2 Cleartext Server (HTTP2 over HTTP) 88 | 89 | `Echo.StartH2CServer` is convenience method that starts a custom HTTP/2 cleartext server on given address 90 | ```go 91 | func main() { 92 | e := echo.New() 93 | // add middleware and routes 94 | // ... 95 | s := &http2.Server{ 96 | MaxConcurrentStreams: 250, 97 | MaxReadFrameSize: 1048576, 98 | IdleTimeout: 10 * time.Second, 99 | } 100 | if err := e.StartH2CServer(":8080", s); err != http.ErrServerClosed { 101 | log.Fatal(err) 102 | } 103 | } 104 | ``` 105 | 106 | Following is equivalent to `Echo.StartH2CServer` previous example 107 | ```go 108 | func main() { 109 | e := echo.New() 110 | // add middleware and routes 111 | // ... 112 | h2s := &http2.Server{ 113 | MaxConcurrentStreams: 250, 114 | MaxReadFrameSize: 1048576, 115 | IdleTimeout: 10 * time.Second, 116 | } 117 | s := http.Server{ 118 | Addr: ":8080", 119 | Handler: h2c.NewHandler(e, h2s), 120 | } 121 | if err := s.ListenAndServe(); err != http.ErrServerClosed { 122 | log.Fatal(err) 123 | } 124 | } 125 | ``` -------------------------------------------------------------------------------- /website/docs/guide/static-files.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Serving static files 3 | slug: /static-files 4 | sidebar_position: 11 5 | --- 6 | 7 | # Static Files 8 | 9 | Images, JavaScript, CSS, PDF, Fonts and so on... 10 | 11 | ## Using Static Middleware 12 | 13 | [See ](/middleware/static.md) 14 | 15 | ## Using Echo#Static() 16 | 17 | `Echo#Static(prefix, root string)` registers a new route with path prefix to serve 18 | static files from the provided root directory. 19 | 20 | *Usage 1* 21 | 22 | ```go 23 | e := echo.New() 24 | e.Static("/static", "assets") 25 | ``` 26 | 27 | Example above will serve any file from the assets directory for path `/static/*`. For example, 28 | a request to `/static/js/main.js` will fetch and serve `assets/js/main.js` file. 29 | 30 | *Usage 2* 31 | 32 | ```go 33 | e := echo.New() 34 | e.Static("/", "assets") 35 | ``` 36 | 37 | Example above will serve any file from the assets directory for path `/*`. For example, 38 | a request to `/js/main.js` will fetch and serve `assets/js/main.js` file. 39 | 40 | ## Using Echo#File() 41 | 42 | `Echo#File(path, file string)` registers a new route with path to serve a static 43 | file. 44 | 45 | *Usage 1* 46 | 47 | Serving an index page from `public/index.html` 48 | 49 | ```go 50 | e.File("/", "public/index.html") 51 | ``` 52 | 53 | *Usage 2* 54 | 55 | Serving a favicon from `images/favicon.ico` 56 | 57 | ```go 58 | e.File("/favicon.ico", "images/favicon.ico") 59 | ``` 60 | -------------------------------------------------------------------------------- /website/docs/guide/templates.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Using templates 3 | slug: /templates 4 | sidebar_position: 12 5 | --- 6 | 7 | # Templates 8 | 9 | ## Rendering 10 | 11 | `Context#Render(code int, name string, data interface{}) error` renders a template 12 | with data and sends a text/html response with status code. Templates can be registered by setting `Echo.Renderer`, allowing us to use any template engine. 13 | 14 | Example below shows how to use Go `html/template`: 15 | 16 | 1. Implement `echo.Renderer` interface 17 | 18 | ```go 19 | type Template struct { 20 | templates *template.Template 21 | } 22 | 23 | func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { 24 | return t.templates.ExecuteTemplate(w, name, data) 25 | } 26 | ``` 27 | 28 | 2. Pre-compile templates 29 | 30 | `public/views/hello.html` 31 | 32 | ```html 33 | {{define "hello"}}Hello, {{.}}!{{end}} 34 | ``` 35 | 36 | ```go 37 | t := &Template{ 38 | templates: template.Must(template.ParseGlob("public/views/*.html")), 39 | } 40 | ``` 41 | 42 | 3. Register templates 43 | 44 | ```go 45 | e := echo.New() 46 | e.Renderer = t 47 | e.GET("/hello", Hello) 48 | ``` 49 | 50 | 4. Render a template inside your handler 51 | 52 | ```go 53 | func Hello(c echo.Context) error { 54 | return c.Render(http.StatusOK, "hello", "World") 55 | } 56 | ``` 57 | 58 | ## Advanced - Calling Echo from templates 59 | 60 | In certain situations it might be useful to generate URIs from the templates. In order to do so, you need to call `Echo#Reverse` from the templates itself. Golang's `html/template` package is not the best suited for this job, but this can be done in two ways: by providing a common method on all objects passed to templates or by passing `map[string]interface{}` and augmenting this object in the custom renderer. Given the flexibility of the latter approach, here is a sample program: 61 | 62 | `template.html` 63 | 64 | ```html 65 | 66 | 67 |

Hello {{index . "name"}}

68 | 69 |

{{ with $x := index . "reverse" }} 70 | {{ call $x "foobar" }} <-- this will call the $x with parameter "foobar" 71 | {{ end }} 72 |

73 | 74 | 75 | ``` 76 | 77 | `server.go` 78 | 79 | ```go 80 | package main 81 | 82 | import ( 83 | "html/template" 84 | "io" 85 | "net/http" 86 | 87 | "github.com/labstack/echo/v4" 88 | ) 89 | 90 | // TemplateRenderer is a custom html/template renderer for Echo framework 91 | type TemplateRenderer struct { 92 | templates *template.Template 93 | } 94 | 95 | // Render renders a template document 96 | func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { 97 | 98 | // Add global methods if data is a map 99 | if viewContext, isMap := data.(map[string]interface{}); isMap { 100 | viewContext["reverse"] = c.Echo().Reverse 101 | } 102 | 103 | return t.templates.ExecuteTemplate(w, name, data) 104 | } 105 | 106 | func main() { 107 | e := echo.New() 108 | renderer := &TemplateRenderer{ 109 | templates: template.Must(template.ParseGlob("*.html")), 110 | } 111 | e.Renderer = renderer 112 | 113 | // Named route "foobar" 114 | e.GET("/something", func(c echo.Context) error { 115 | return c.Render(http.StatusOK, "template.html", map[string]interface{}{ 116 | "name": "Dolly!", 117 | }) 118 | }).Name = "foobar" 119 | 120 | e.Logger.Fatal(e.Start(":8000")) 121 | } 122 | ``` 123 | -------------------------------------------------------------------------------- /website/docs/guide/testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Testing handler and middleware 3 | slug: /testing 4 | sidebar_position: 13 5 | --- 6 | 7 | # Testing 8 | 9 | ## Testing Handler 10 | 11 | `GET` `/users/:id` 12 | 13 | Handler below retrieves user by id from the database. If user is not found it returns 14 | `404` error with a message. 15 | 16 | ### CreateUser 17 | 18 | `POST` `/users` 19 | 20 | - Accepts JSON payload 21 | - On success `201 - Created` 22 | - On error `500 - Internal Server Error` 23 | 24 | ### GetUser 25 | 26 | `GET` `/users/:email` 27 | 28 | - On success `200 - OK` 29 | - On error `404 - Not Found` if user is not found otherwise `500 - Internal Server Error` 30 | 31 | `handler.go` 32 | 33 | ```go 34 | package handler 35 | 36 | import ( 37 | "net/http" 38 | 39 | "github.com/labstack/echo/v4" 40 | ) 41 | 42 | type ( 43 | User struct { 44 | Name string `json:"name" form:"name"` 45 | Email string `json:"email" form:"email"` 46 | } 47 | handler struct { 48 | db map[string]*User 49 | } 50 | ) 51 | 52 | func (h *handler) createUser(c echo.Context) error { 53 | u := new(User) 54 | if err := c.Bind(u); err != nil { 55 | return err 56 | } 57 | return c.JSON(http.StatusCreated, u) 58 | } 59 | 60 | func (h *handler) getUser(c echo.Context) error { 61 | email := c.Param("email") 62 | user := h.db[email] 63 | if user == nil { 64 | return echo.NewHTTPError(http.StatusNotFound, "user not found") 65 | } 66 | return c.JSON(http.StatusOK, user) 67 | } 68 | ``` 69 | 70 | `handler_test.go` 71 | 72 | ```go 73 | package handler 74 | 75 | import ( 76 | "net/http" 77 | "net/http/httptest" 78 | "strings" 79 | "testing" 80 | 81 | "github.com/labstack/echo/v4" 82 | "github.com/stretchr/testify/assert" 83 | ) 84 | 85 | var ( 86 | mockDB = map[string]*User{ 87 | "jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"}, 88 | } 89 | userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}` 90 | ) 91 | 92 | func TestCreateUser(t *testing.T) { 93 | // Setup 94 | e := echo.New() 95 | req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON)) 96 | req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) 97 | rec := httptest.NewRecorder() 98 | c := e.NewContext(req, rec) 99 | h := &handler{mockDB} 100 | 101 | // Assertions 102 | if assert.NoError(t, h.createUser(c)) { 103 | assert.Equal(t, http.StatusCreated, rec.Code) 104 | assert.Equal(t, userJSON, rec.Body.String()) 105 | } 106 | } 107 | 108 | func TestGetUser(t *testing.T) { 109 | // Setup 110 | e := echo.New() 111 | req := httptest.NewRequest(http.MethodGet, "/", nil) 112 | rec := httptest.NewRecorder() 113 | c := e.NewContext(req, rec) 114 | c.SetPath("/users/:email") 115 | c.SetParamNames("email") 116 | c.SetParamValues("jon@labstack.com") 117 | h := &handler{mockDB} 118 | 119 | // Assertions 120 | if assert.NoError(t, h.getUser(c)) { 121 | assert.Equal(t, http.StatusOK, rec.Code) 122 | assert.Equal(t, userJSON, rec.Body.String()) 123 | } 124 | } 125 | ``` 126 | 127 | ### Using Form Payload 128 | 129 | ```go 130 | // import "net/url" 131 | f := make(url.Values) 132 | f.Set("name", "Jon Snow") 133 | f.Set("email", "jon@labstack.com") 134 | req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode())) 135 | req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm) 136 | ``` 137 | 138 | ### Setting Path Params 139 | 140 | ```go 141 | c.SetParamNames("id", "email") 142 | c.SetParamValues("1", "jon@labstack.com") 143 | ``` 144 | 145 | ### Setting Query Params 146 | 147 | ```go 148 | // import "net/url" 149 | q := make(url.Values) 150 | q.Set("email", "jon@labstack.com") 151 | req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil) 152 | ``` 153 | 154 | ## Testing Middleware 155 | 156 | *TBD* 157 | 158 | For now you can look into built-in middleware [test cases](https://github.com/labstack/echo/tree/master/middleware). 159 | -------------------------------------------------------------------------------- /website/docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | slug: / 4 | --- 5 | 6 | # Introduction 7 | 8 | ## ![LabStack](../static/img/labstack-icon.png) Echo Project 9 | 10 | The Echo project is a powerful and versatile web framework for building scalable and high-performance web applications in the Go programming language. It follows the principles of simplicity, flexibility, and performance to provide developers with an efficient toolkit for building robust web applications. 11 | 12 | ## Key Features 13 | 14 | - **Fast and Lightweight**: Echo is designed for speed and efficiency, ensuring minimal overhead and high performance for handling HTTP requests and responses. 15 | - **Routing**: The framework offers a flexible and intuitive routing system that allows developers to define routes with parameters, query strings, and custom handlers. 16 | - **Middleware Support**: Echo provides extensive middleware support, enabling developers to easily implement cross-cutting concerns such as logging, authentication, error handling, and more. 17 | - **Context-based Request Handling**: With its context-based request handling, Echo offers easy access to request-specific data and parameters, simplifying the development of web applications. 18 | - **Powerful Template Rendering**: Echo includes a powerful template rendering engine that supports various template languages, allowing developers to generate dynamic HTML content effortlessly. 19 | - **Validation and Binding**: The framework provides robust validation and data binding capabilities, making it straightforward to validate incoming request data and bind it to Go structs. 20 | - **Extensibility**: Echo is highly extensible, with support for custom middleware, template engines, and other components, enabling developers to tailor the framework to their specific needs. 21 | - **Community and Ecosystem**: The Echo project benefits from a vibrant and active community that contributes libraries, plugins, and extensions, fostering an ecosystem of reusable components. 22 | 23 | ## Resources and Documentation 24 | 25 | To learn more about the Echo project, you can refer to the following resources: 26 | 27 | - Official Website: [https://echo.labstack.com](https://echo.labstack.com) 28 | - GitHub Repository: [https://github.com/labstack/echo](https://github.com/labstack/echo) 29 | - Documentation: [https://echo.labstack.com/docs](https://echo.labstack.com/guide) 30 | - Community Forum: [https://github.com/labstack/echo/discussions](https://github.com/labstack/echo/discussions) 31 | 32 | The Echo project offers an array of features that empower developers to build robust web applications. Its fast and lightweight nature ensures optimal performance, while the flexible routing system and middleware support streamline development processes. Developers can leverage the context-based request handling, powerful template rendering, and validation capabilities to create dynamic and secure web applications. Additionally, the extensibility of Echo allows developers to customize and enhance the framework to suit their specific needs. 33 | 34 | Join the vibrant community of Echo developers, explore the vast ecosystem of plugins and extensions, and unleash the power of Echo for your web development needs. 35 | -------------------------------------------------------------------------------- /website/docs/middleware/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Middleware", 3 | "position": 3, 4 | "link": { 5 | "type": "generated-index" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/docs/middleware/basic-auth.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Basic auth middleware 3 | --- 4 | 5 | # Basic Auth 6 | 7 | Basic auth middleware provides an HTTP basic authentication. 8 | 9 | - For valid credentials it calls the next handler. 10 | - For missing or invalid credentials, it sends "401 - Unauthorized" response. 11 | 12 | ## Usage 13 | 14 | ```go 15 | e.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) { 16 | // Be careful to use constant time comparison to prevent timing attacks 17 | if subtle.ConstantTimeCompare([]byte(username), []byte("joe")) == 1 && 18 | subtle.ConstantTimeCompare([]byte(password), []byte("secret")) == 1 { 19 | return true, nil 20 | } 21 | return false, nil 22 | })) 23 | ``` 24 | 25 | ## Custom Configuration 26 | 27 | ### Usage 28 | 29 | ```go 30 | e.Use(middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{})) 31 | ``` 32 | 33 | ## Configuration 34 | 35 | ```go 36 | BasicAuthConfig struct { 37 | // Skipper defines a function to skip middleware. 38 | Skipper Skipper 39 | 40 | // Validator is a function to validate BasicAuth credentials. 41 | // Required. 42 | Validator BasicAuthValidator 43 | 44 | // Realm is a string to define realm attribute of BasicAuth. 45 | // Default value "Restricted". 46 | Realm string 47 | } 48 | ``` 49 | 50 | ### Default Configuration 51 | 52 | ```go 53 | DefaultBasicAuthConfig = BasicAuthConfig{ 54 | Skipper: DefaultSkipper, 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /website/docs/middleware/body-dump.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Body dump middleware 3 | --- 4 | 5 | # Body Dump 6 | 7 | Body dump middleware captures the request and response payload and calls the registered handler. Generally used for debugging/logging purpose. Avoid using it if your request/response payload is huge e.g. file upload/download, but if you still need to, add an exception for your endpoints in the skipper function. 8 | 9 | ## Usage 10 | 11 | ```go 12 | e := echo.New() 13 | e.Use(middleware.BodyDump(func(c echo.Context, reqBody, resBody []byte) { 14 | })) 15 | ``` 16 | 17 | ## Custom Configuration 18 | 19 | ### Usage 20 | 21 | ```go 22 | e := echo.New() 23 | e.Use(middleware.BodyDumpWithConfig(middleware.BodyDumpConfig{})) 24 | ``` 25 | 26 | ## Configuration 27 | 28 | ```go 29 | BodyDumpConfig struct { 30 | // Skipper defines a function to skip middleware. 31 | Skipper Skipper 32 | 33 | // Handler receives request and response payload. 34 | // Required. 35 | Handler BodyDumpHandler 36 | } 37 | ``` 38 | 39 | ### Default Configuration* 40 | 41 | ```go 42 | DefaultBodyDumpConfig = BodyDumpConfig{ 43 | Skipper: DefaultSkipper, 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /website/docs/middleware/body-limit.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Body limit middleware 3 | --- 4 | 5 | # Body Limit 6 | 7 | Body limit middleware sets the maximum allowed size for a request body, if the 8 | size exceeds the configured limit, it sends "413 - Request Entity Too Large" 9 | response. The body limit is determined based on both `Content-Length` request 10 | header and actual content read, which makes it super secure. 11 | 12 | Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M, 13 | G, T or P. 14 | 15 | ## Usage 16 | 17 | ```go 18 | e := echo.New() 19 | e.Use(middleware.BodyLimit("2M")) 20 | ``` 21 | 22 | ## Custom Configuration 23 | 24 | ### Usage 25 | 26 | ```go 27 | e := echo.New() 28 | e.Use(middleware.BodyLimitWithConfig(middleware.BodyLimitConfig{})) 29 | ``` 30 | 31 | ## Configuration 32 | 33 | ```go 34 | BodyLimitConfig struct { 35 | // Skipper defines a function to skip middleware. 36 | Skipper Skipper 37 | 38 | // Maximum allowed size for a request body, it can be specified 39 | // as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P. 40 | Limit string `json:"limit"` 41 | } 42 | ``` 43 | 44 | ### Default Configuration 45 | 46 | ```go 47 | DefaultBodyLimitConfig = BodyLimitConfig{ 48 | Skipper: DefaultSkipper, 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /website/docs/middleware/casbin-auth.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Casbin auth middleware 3 | --- 4 | 5 | # Casbin Auth 6 | 7 | :::note 8 | 9 | Echo community contribution 10 | 11 | ::: 12 | 13 | [Casbin](https://github.com/casbin/casbin) is a powerful and efficient open-source access control library for Go. It provides support for enforcing authorization based on various models. So far, the access control models supported by Casbin are: 14 | 15 | - ACL (Access Control List) 16 | - ACL with superuser 17 | - ACL without users: especially useful for systems that don't have authentication or user log-ins. 18 | - ACL without resources: some scenarios may target for a type of resources instead of an individual resource by using permissions like write-article, read-log. It doesn't control the access to a specific article or log. 19 | - RBAC (Role-Based Access Control) 20 | - RBAC with resource roles: both users and resources can have roles (or groups) at the same time. 21 | - RBAC with domains/tenants: users can have different role sets for different domains/tenants. 22 | - ABAC (Attribute-Based Access Control) 23 | - RESTful 24 | - Deny-override: both allow and deny authorizations are supported, deny overrides the allow. 25 | 26 | :::info 27 | 28 | Currently, only HTTP basic authentication is supported. 29 | 30 | ::: 31 | 32 | ## Dependencies 33 | 34 | ```go 35 | import ( 36 | "github.com/casbin/casbin" 37 | casbin_mw "github.com/labstack/echo-contrib/casbin" 38 | ) 39 | ``` 40 | 41 | ## Usage 42 | 43 | ```go 44 | e := echo.New() 45 | enforcer, err := casbin.NewEnforcer("casbin_auth_model.conf", "casbin_auth_policy.csv") 46 | e.Use(casbin_mw.Middleware(enforcer)) 47 | ``` 48 | 49 | For syntax, see: [Syntax for Models](https://casbin.org/docs/syntax-for-models). 50 | 51 | 52 | ## Custom Configuration 53 | 54 | ### Usage 55 | 56 | ```go 57 | e := echo.New() 58 | ce := casbin.NewEnforcer("casbin_auth_model.conf", "") 59 | ce.AddRoleForUser("alice", "admin") 60 | ce.AddPolicy(...) 61 | e.Use(casbin_mw.MiddlewareWithConfig(casbin_mw.Config{ 62 | Enforcer: ce, 63 | })) 64 | ``` 65 | 66 | ## Configuration 67 | 68 | ```go 69 | // Config defines the config for CasbinAuth middleware. 70 | Config struct { 71 | // Skipper defines a function to skip middleware. 72 | Skipper middleware.Skipper 73 | 74 | // Enforcer CasbinAuth main rule. 75 | // Required. 76 | Enforcer *casbin.Enforcer 77 | } 78 | ``` 79 | 80 | ### Default Configuration 81 | 82 | ```go 83 | // DefaultConfig is the default CasbinAuth middleware config. 84 | DefaultConfig = Config{ 85 | Skipper: middleware.DefaultSkipper, 86 | } 87 | ``` 88 | -------------------------------------------------------------------------------- /website/docs/middleware/context-timeout.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Context Timeout middleware 3 | --- 4 | 5 | # Context Timeout 6 | 7 | Timeout middleware is used to timeout request context within a predefined period so context aware methods could return 8 | early. 9 | 10 | ## Usage 11 | 12 | ```go 13 | e.Use(middleware.ContextTimeout(60 * time.Second)) 14 | ``` 15 | 16 | ## Custom Configuration 17 | 18 | ```go 19 | e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{ 20 | // Skipper defines a function to skip middleware. 21 | Skipper: nil, 22 | // ErrorHandler is a function when error aries in middleware execution. 23 | ErrorHandler: nil, 24 | // Timeout configures a timeout for the middleware, defaults to 0 for no timeout 25 | Timeout: 60 * time.Second, 26 | })) 27 | ``` 28 | 29 | 30 | -------------------------------------------------------------------------------- /website/docs/middleware/cors.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: CORS middleware 3 | --- 4 | 5 | # CORS 6 | 7 | CORS middleware implements [CORS](http://www.w3.org/TR/cors) specification. 8 | CORS gives web servers cross-domain access controls, which enable secure cross-domain 9 | data transfers. 10 | 11 | ## Usage 12 | 13 | ```go 14 | e.Use(middleware.CORS()) 15 | ``` 16 | 17 | ## Custom Configuration 18 | 19 | ### Usage 20 | 21 | ```go 22 | e := echo.New() 23 | e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ 24 | AllowOrigins: []string{"https://labstack.com", "https://labstack.net"}, 25 | AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept}, 26 | })) 27 | ``` 28 | 29 | ## Configuration 30 | 31 | ```go 32 | CORSConfig struct { 33 | // Skipper defines a function to skip middleware. 34 | Skipper Skipper 35 | 36 | // AllowOrigin defines a list of origins that may access the resource. 37 | // Optional. Default value []string{"*"}. 38 | AllowOrigins []string `yaml:"allow_origins"` 39 | 40 | // AllowOriginFunc is a custom function to validate the origin. It takes the 41 | // origin as an argument and returns true if allowed or false otherwise. If 42 | // an error is returned, it is returned by the handler. If this option is 43 | // set, AllowOrigins is ignored. 44 | // Optional. 45 | AllowOriginFunc func(origin string) (bool, error) `yaml:"allow_origin_func"` 46 | 47 | // AllowMethods defines a list methods allowed when accessing the resource. 48 | // This is used in response to a preflight request. 49 | // Optional. Default value DefaultCORSConfig.AllowMethods. 50 | AllowMethods []string `yaml:"allow_methods"` 51 | 52 | // AllowHeaders defines a list of request headers that can be used when 53 | // making the actual request. This is in response to a preflight request. 54 | // Optional. Default value []string{}. 55 | AllowHeaders []string `yaml:"allow_headers"` 56 | 57 | // AllowCredentials indicates whether or not the response to the request 58 | // can be exposed when the credentials flag is true. When used as part of 59 | // a response to a preflight request, this indicates whether or not the 60 | // actual request can be made using credentials. 61 | // Optional. Default value false. 62 | AllowCredentials bool `yaml:"allow_credentials"` 63 | 64 | // ExposeHeaders defines a whitelist headers that clients are allowed to 65 | // access. 66 | // Optional. Default value []string{}. 67 | ExposeHeaders []string `yaml:"expose_headers"` 68 | 69 | // MaxAge indicates how long (in seconds) the results of a preflight request 70 | // can be cached. 71 | // Optional. Default value 0. 72 | MaxAge int `yaml:"max_age"` 73 | } 74 | ``` 75 | 76 | ### Default Configuration 77 | 78 | ```go 79 | DefaultCORSConfig = CORSConfig{ 80 | Skipper: DefaultSkipper, 81 | AllowOrigins: []string{"*"}, 82 | AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /website/docs/middleware/csrf.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: CSRF middleware 3 | --- 4 | 5 | # CSRF 6 | 7 | Cross-site request forgery, also known as one-click attack or session riding and 8 | abbreviated as CSRF (sometimes pronounced sea-surf) or XSRF, is a type of malicious 9 | exploit of a website where unauthorized commands are transmitted from a user that 10 | the website trusts. 11 | 12 | ## Usage 13 | 14 | ```go 15 | e.Use(middleware.CSRF()) 16 | ``` 17 | 18 | ## Custom Configuration 19 | 20 | ### Usage 21 | 22 | ```go 23 | e := echo.New() 24 | e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{ 25 | TokenLookup: "header:X-XSRF-TOKEN", 26 | })) 27 | ``` 28 | 29 | Example above uses `X-XSRF-TOKEN` request header to extract CSRF token. 30 | 31 | *Example Configuration that reads token from Cookie* 32 | 33 | ```go 34 | middleware.CSRFWithConfig(middleware.CSRFConfig{ 35 | TokenLookup: "cookie:_csrf", 36 | CookiePath: "/", 37 | CookieDomain: "example.com", 38 | CookieSecure: true, 39 | CookieHTTPOnly: true, 40 | CookieSameSite: http.SameSiteStrictMode, 41 | }) 42 | ``` 43 | 44 | ## Accessing CSRF Token 45 | 46 | ### Server-side 47 | 48 | CSRF token can be accessed from `Echo#Context` using `ContextKey` and passed to 49 | the client via template. 50 | 51 | ### Client-side 52 | 53 | CSRF token can be accessed from CSRF cookie. 54 | 55 | ## Configuration 56 | 57 | ```go 58 | CSRFConfig struct { 59 | // Skipper defines a function to skip middleware. 60 | Skipper Skipper 61 | 62 | // TokenLength is the length of the generated token. 63 | TokenLength uint8 `json:"token_length"` 64 | // Optional. Default value 32. 65 | 66 | // TokenLookup is a string in the form of ":" that is used 67 | // to extract token from the request. 68 | // Optional. Default value "header:X-CSRF-Token". 69 | // Possible values: 70 | // - "header:" 71 | // - "form:" 72 | // - "query:" 73 | // - "cookie:" 74 | TokenLookup string `json:"token_lookup"` 75 | 76 | // Context key to store generated CSRF token into context. 77 | // Optional. Default value "csrf". 78 | ContextKey string `json:"context_key"` 79 | 80 | // Name of the CSRF cookie. This cookie will store CSRF token. 81 | // Optional. Default value "_csrf". 82 | CookieName string `json:"cookie_name"` 83 | 84 | // Domain of the CSRF cookie. 85 | // Optional. Default value none. 86 | CookieDomain string `json:"cookie_domain"` 87 | 88 | // Path of the CSRF cookie. 89 | // Optional. Default value none. 90 | CookiePath string `json:"cookie_path"` 91 | 92 | // Max age (in seconds) of the CSRF cookie. 93 | // Optional. Default value 86400 (24hr). 94 | CookieMaxAge int `json:"cookie_max_age"` 95 | 96 | // Indicates if CSRF cookie is secure. 97 | // Optional. Default value false. 98 | CookieSecure bool `json:"cookie_secure"` 99 | 100 | // Indicates if CSRF cookie is HTTP only. 101 | // Optional. Default value false. 102 | CookieHTTPOnly bool `json:"cookie_http_only"` 103 | } 104 | ``` 105 | 106 | ### Default Configuration 107 | 108 | ```go 109 | DefaultCSRFConfig = CSRFConfig{ 110 | Skipper: DefaultSkipper, 111 | TokenLength: 32, 112 | TokenLookup: "header:" + echo.HeaderXCSRFToken, 113 | ContextKey: "csrf", 114 | CookieName: "_csrf", 115 | CookieMaxAge: 86400, 116 | } 117 | ``` 118 | -------------------------------------------------------------------------------- /website/docs/middleware/decompress.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Decompress middleware 3 | --- 4 | 5 | # Decompress 6 | 7 | Decompress middleware decompresses HTTP request if Content-Encoding header is set to gzip. 8 | 9 | :::note 10 | 11 | The body will be decompressed in memory and consume it for the lifetime of the request (and garbage collection). 12 | 13 | ::: 14 | 15 | ## Usage 16 | 17 | ```go 18 | e.Use(middleware.Decompress()) 19 | ``` 20 | 21 | ## Custom Configuration 22 | 23 | ### Usage 24 | 25 | ```go 26 | e := echo.New() 27 | e.Use(middleware.DecompressWithConfig(middleware.DecompressConfig{ 28 | Skipper: Skipper 29 | })) 30 | ``` 31 | 32 | ## Configuration 33 | 34 | ```go 35 | DecompressConfig struct { 36 | // Skipper defines a function to skip middleware. 37 | Skipper Skipper 38 | } 39 | ``` 40 | 41 | ### Default Configuration 42 | 43 | ```go 44 | DefaultDecompressConfig = DecompressConfig{ 45 | Skipper: DefaultSkipper, 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /website/docs/middleware/gzip.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Gzip middleware 3 | --- 4 | 5 | # Gzip 6 | 7 | Gzip middleware compresses HTTP response using gzip compression scheme. 8 | 9 | ## Usage 10 | 11 | `e.Use(middleware.Gzip())` 12 | 13 | ## Custom Configuration 14 | 15 | ### Usage 16 | 17 | ```go 18 | e := echo.New() 19 | e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ 20 | Level: 5, 21 | })) 22 | ``` 23 | 24 | :::tip 25 | 26 | A middleware skipper can be passed to avoid gzip to certain URL(s). 27 | 28 | ::: 29 | 30 | #### Example 31 | 32 | ```go 33 | e := echo.New() 34 | e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ 35 | Skipper: func(c echo.Context) bool { 36 | return strings.Contains(c.Path(), "metrics") // Change "metrics" for your own path 37 | }, 38 | })) 39 | ``` 40 | 41 | ## Configuration 42 | 43 | ```go 44 | GzipConfig struct { 45 | // Skipper defines a function to skip middleware. 46 | Skipper Skipper 47 | 48 | // Gzip compression level. 49 | // Optional. Default value -1. 50 | Level int `json:"level"` 51 | } 52 | ``` 53 | 54 | ### Default Configuration 55 | 56 | ```go 57 | DefaultGzipConfig = GzipConfig{ 58 | Skipper: DefaultSkipper, 59 | Level: -1, 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /website/docs/middleware/img/docsVersionDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labstack/echox/c495449db4db6fdead7fd00c58fc121b20f87da9/website/docs/middleware/img/docsVersionDropdown.png -------------------------------------------------------------------------------- /website/docs/middleware/img/localeDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labstack/echox/c495449db4db6fdead7fd00c58fc121b20f87da9/website/docs/middleware/img/localeDropdown.png -------------------------------------------------------------------------------- /website/docs/middleware/key-auth.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Key auth middleware 3 | --- 4 | 5 | # Key Auth 6 | 7 | Key auth middleware provides a key based authentication. 8 | 9 | - For valid key it calls the next handler. 10 | - For invalid key, it sends "401 - Unauthorized" response. 11 | - For missing key, it sends "400 - Bad Request" response. 12 | 13 | ## Usage 14 | 15 | ```go 16 | e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { 17 | return key == "valid-key", nil 18 | })) 19 | ``` 20 | 21 | ## Custom Configuration 22 | 23 | ### Usage 24 | 25 | ```go 26 | e := echo.New() 27 | e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{ 28 | KeyLookup: "query:api-key", 29 | Validator: func(key string, c echo.Context) (bool, error) { 30 | return key == "valid-key", nil 31 | }, 32 | })) 33 | ``` 34 | 35 | ## Configuration 36 | 37 | ```go 38 | KeyAuthConfig struct { 39 | // Skipper defines a function to skip middleware. 40 | Skipper Skipper 41 | 42 | // KeyLookup is a string in the form of ":" that is used 43 | // to extract key from the request. 44 | // Optional. Default value "header:Authorization". 45 | // Possible values: 46 | // - "header:" 47 | // - "query:" 48 | // - "cookie:" 49 | // - "form:" 50 | KeyLookup string `yaml:"key_lookup"` 51 | 52 | // AuthScheme to be used in the Authorization header. 53 | // Optional. Default value "Bearer". 54 | AuthScheme string 55 | 56 | // Validator is a function to validate key. 57 | // Required. 58 | Validator KeyAuthValidator 59 | 60 | // ErrorHandler defines a function which is executed for an invalid key. 61 | // It may be used to define a custom error. 62 | ErrorHandler KeyAuthErrorHandler 63 | } 64 | ``` 65 | 66 | ### Default Configuration 67 | 68 | ```go 69 | DefaultKeyAuthConfig = KeyAuthConfig{ 70 | Skipper: DefaultSkipper, 71 | KeyLookup: "header:" + echo.HeaderAuthorization, 72 | AuthScheme: "Bearer", 73 | } 74 | ``` 75 | -------------------------------------------------------------------------------- /website/docs/middleware/method-override.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Method override middleware 3 | --- 4 | 5 | # Method Override 6 | 7 | Method override middleware checks for the overridden method from the request and 8 | uses it instead of the original method. 9 | 10 | :::info 11 | 12 | For security reasons, only `POST` method can be overridden. 13 | 14 | ::: 15 | 16 | ## Usage 17 | 18 | ```go 19 | e.Pre(middleware.MethodOverride()) 20 | ``` 21 | 22 | ## Custom Configuration 23 | 24 | ### Usage 25 | 26 | ```go 27 | e := echo.New() 28 | e.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{ 29 | Getter: middleware.MethodFromForm("_method"), 30 | })) 31 | ``` 32 | 33 | ## Configuration 34 | 35 | ```go 36 | MethodOverrideConfig struct { 37 | // Skipper defines a function to skip middleware. 38 | Skipper Skipper 39 | 40 | // Getter is a function that gets overridden method from the request. 41 | // Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride). 42 | Getter MethodOverrideGetter 43 | } 44 | ``` 45 | 46 | ### Default Configuration 47 | 48 | ```go 49 | DefaultMethodOverrideConfig = MethodOverrideConfig{ 50 | Skipper: DefaultSkipper, 51 | Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /website/docs/middleware/proxy.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Reverse proxy middleware 3 | --- 4 | 5 | # Proxy 6 | 7 | Proxy provides an HTTP/WebSocket reverse proxy middleware. It forwards a request 8 | to upstream server using a configured load balancing technique. 9 | 10 | ### Usage 11 | 12 | ```go 13 | url1, err := url.Parse("http://localhost:8081") 14 | if err != nil { 15 | e.Logger.Fatal(err) 16 | } 17 | url2, err := url.Parse("http://localhost:8082") 18 | if err != nil { 19 | e.Logger.Fatal(err) 20 | } 21 | e.Use(middleware.Proxy(middleware.NewRoundRobinBalancer([]*middleware.ProxyTarget{ 22 | { 23 | URL: url1, 24 | }, 25 | { 26 | URL: url2, 27 | }, 28 | }))) 29 | ``` 30 | 31 | ## Custom Configuration 32 | 33 | ### Usage 34 | 35 | ```go 36 | e := echo.New() 37 | e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{})) 38 | ``` 39 | 40 | ### Configuration 41 | 42 | ```go 43 | // ProxyConfig defines the config for Proxy middleware. 44 | ProxyConfig struct { 45 | // Skipper defines a function to skip middleware. 46 | Skipper Skipper 47 | 48 | // Balancer defines a load balancing technique. 49 | // Required. 50 | Balancer ProxyBalancer 51 | 52 | // Rewrite defines URL path rewrite rules. The values captured in asterisk can be 53 | // retrieved by index e.g. $1, $2 and so on. 54 | Rewrite map[string]string 55 | 56 | // RegexRewrite defines rewrite rules using regexp.Rexexp with captures 57 | // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. 58 | RegexRewrite map[*regexp.Regexp]string 59 | 60 | // Context key to store selected ProxyTarget into context. 61 | // Optional. Default value "target". 62 | ContextKey string 63 | 64 | // To customize the transport to remote. 65 | // Examples: If custom TLS certificates are required. 66 | Transport http.RoundTripper 67 | 68 | // ModifyResponse defines function to modify response from ProxyTarget. 69 | ModifyResponse func(*http.Response) error 70 | ``` 71 | 72 | ### Default Configuration 73 | 74 | | Name | Value | 75 | | ---------- | -------------- | 76 | | Skipper | DefaultSkipper | 77 | | ContextKey | `target` | 78 | 79 | ### Regex-based Rules 80 | 81 | For advanced rewriting of proxy requests rules may also be defined using 82 | regular expression. Normal capture groups can be defined using `()` and referenced by index (`$1`, `$2`, ...) for the rewritten path. 83 | 84 | `RegexRules` and normal `Rules` can be combined. 85 | 86 | ```go 87 | e.Use(ProxyWithConfig(ProxyConfig{ 88 | Balancer: rrb, 89 | Rewrite: map[string]string{ 90 | "^/v1/*": "/v2/$1", 91 | }, 92 | RegexRewrite: map[*regexp.Regexp]string{ 93 | regexp.MustCompile("^/foo/([0-9].*)"): "/num/$1", 94 | regexp.MustCompile("^/bar/(.+?)/(.*)"): "/baz/$2/$1", 95 | }, 96 | })) 97 | ``` 98 | 99 | ## [Example](../cookbook/reverse-proxy.md) 100 | -------------------------------------------------------------------------------- /website/docs/middleware/rate-limiter.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Rate limiter middleware 3 | --- 4 | 5 | # Rate Limiter 6 | 7 | `RateLimiter` provides a Rate Limiter middleware for limiting the amount of requests to the server from a particular IP or id within a time period. 8 | 9 | By default an in-memory store is used for keeping track of requests. The default in-memory implementation is focused on correctness and 10 | may not be the best option for a high number of concurrent requests or a large number of different identifiers (>16k). 11 | 12 | ## Usage 13 | 14 | To add a rate limit to your application simply add the `RateLimiter` middleware. 15 | The example below will limit the application to 20 requests/sec using the default in-memory store: 16 | 17 | ```go 18 | e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(rate.Limit(20)))) 19 | ``` 20 | 21 | :::info 22 | 23 | If the provided rate is a float number, Burst will be treated as the rounded down value of the rate. 24 | 25 | ::: 26 | 27 | ## Custom Configuration 28 | 29 | ```go 30 | config := middleware.RateLimiterConfig{ 31 | Skipper: middleware.DefaultSkipper, 32 | Store: middleware.NewRateLimiterMemoryStoreWithConfig( 33 | middleware.RateLimiterMemoryStoreConfig{Rate: rate.Limit(10), Burst: 30, ExpiresIn: 3 * time.Minute}, 34 | ), 35 | IdentifierExtractor: func(ctx echo.Context) (string, error) { 36 | id := ctx.RealIP() 37 | return id, nil 38 | }, 39 | ErrorHandler: func(context echo.Context, err error) error { 40 | return context.JSON(http.StatusForbidden, nil) 41 | }, 42 | DenyHandler: func(context echo.Context, identifier string,err error) error { 43 | return context.JSON(http.StatusTooManyRequests, nil) 44 | }, 45 | } 46 | 47 | e.Use(middleware.RateLimiterWithConfig(config)) 48 | ``` 49 | 50 | ### Errors 51 | 52 | ```go 53 | var ( 54 | // ErrRateLimitExceeded denotes an error raised when rate limit is exceeded 55 | ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded") 56 | // ErrExtractorError denotes an error raised when extractor function is unsuccessful 57 | ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier") 58 | ) 59 | ``` 60 | 61 | :::tip 62 | 63 | If you need to implement your own store, be sure to implement the RateLimiterStore interface and pass it to RateLimiterConfig and you're good to go! 64 | 65 | ::: 66 | 67 | ## Configuration 68 | 69 | ```go 70 | type RateLimiterConfig struct { 71 | Skipper Skipper 72 | BeforeFunc BeforeFunc 73 | // IdentifierExtractor uses echo.Context to extract the identifier for a visitor 74 | IdentifierExtractor Extractor 75 | // Store defines a store for the rate limiter 76 | Store RateLimiterStore 77 | // ErrorHandler provides a handler to be called when IdentifierExtractor returns a non-nil error 78 | ErrorHandler func(context echo.Context, err error) error 79 | // DenyHandler provides a handler to be called when RateLimiter denies access 80 | DenyHandler func(context echo.Context, identifier string, err error) error 81 | } 82 | ``` 83 | 84 | ### Default Configuration 85 | 86 | ```go 87 | // DefaultRateLimiterConfig defines default values for RateLimiterConfig 88 | var DefaultRateLimiterConfig = RateLimiterConfig{ 89 | Skipper: DefaultSkipper, 90 | IdentifierExtractor: func(ctx echo.Context) (string, error) { 91 | id := ctx.RealIP() 92 | return id, nil 93 | }, 94 | ErrorHandler: func(context echo.Context, err error) error { 95 | return &echo.HTTPError{ 96 | Code: ErrExtractorError.Code, 97 | Message: ErrExtractorError.Message, 98 | Internal: err, 99 | } 100 | }, 101 | DenyHandler: func(context echo.Context, identifier string, err error) error { 102 | return &echo.HTTPError{ 103 | Code: ErrRateLimitExceeded.Code, 104 | Message: ErrRateLimitExceeded.Message, 105 | Internal: err, 106 | } 107 | }, 108 | } 109 | ``` 110 | 111 | -------------------------------------------------------------------------------- /website/docs/middleware/recover.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Recover middleware 3 | --- 4 | 5 | # Recover 6 | 7 | Recover middleware recovers from panics anywhere in the chain, prints stack trace 8 | and handles the control to the centralized 9 | [HTTPErrorHandler](../guide/customization.md#http-error-handler). 10 | 11 | ## Usage 12 | 13 | ```go 14 | e.Use(middleware.Recover()) 15 | ``` 16 | 17 | ## Custom Configuration 18 | 19 | ### Usage 20 | 21 | ```go 22 | e := echo.New() 23 | e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{ 24 | StackSize: 1 << 10, // 1 KB 25 | LogLevel: log.ERROR, 26 | })) 27 | ``` 28 | 29 | Example above uses a `StackSize` of 1 KB, `LogLevel` of error and 30 | default values for `DisableStackAll` and `DisablePrintStack`. 31 | 32 | ## Configuration 33 | 34 | ```go 35 | // LogErrorFunc defines a function for custom logging in the middleware. 36 | LogErrorFunc func(c echo.Context, err error, stack []byte) error 37 | 38 | RecoverConfig struct { 39 | // Skipper defines a function to skip middleware. 40 | Skipper Skipper 41 | 42 | // Size of the stack to be printed. 43 | // Optional. Default value 4KB. 44 | StackSize int `yaml:"stack_size"` 45 | 46 | // DisableStackAll disables formatting stack traces of all other goroutines 47 | // into buffer after the trace for the current goroutine. 48 | // Optional. Default value false. 49 | DisableStackAll bool `yaml:"disable_stack_all"` 50 | 51 | // DisablePrintStack disables printing stack trace. 52 | // Optional. Default value as false. 53 | DisablePrintStack bool `yaml:"disable_print_stack"` 54 | 55 | // LogLevel is log level to printing stack trace. 56 | // Optional. Default value 0 (Print). 57 | LogLevel log.Lvl 58 | 59 | // LogErrorFunc defines a function for custom logging in the middleware. 60 | // If it's set you don't need to provide LogLevel for config. 61 | LogErrorFunc LogErrorFunc 62 | 63 | // DisableErrorHandler disables the call to centralized HTTPErrorHandler. 64 | // The recovered error is then passed back to upstream middleware, instead of swallowing the error. 65 | // Optional. Default value false. 66 | DisableErrorHandler bool `yaml:"disable_error_handler"` 67 | 68 | } 69 | ``` 70 | 71 | ### Default Configuration 72 | 73 | ```go 74 | DefaultRecoverConfig = RecoverConfig{ 75 | Skipper: DefaultSkipper, 76 | StackSize: 4 << 10, // 4 KB 77 | DisableStackAll: false, 78 | DisablePrintStack: false, 79 | LogLevel: 0, 80 | LogErrorFunc: nil, 81 | DisableErrorHandler: false, 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /website/docs/middleware/redirect.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Redirect middleware 3 | --- 4 | 5 | # Redirect 6 | 7 | ## HTTPS Redirect 8 | 9 | HTTPS redirect middleware redirects http requests to https. 10 | For example, http://labstack.com will be redirected to https://labstack.com. 11 | 12 | ### Usage 13 | 14 | ```go 15 | e := echo.New() 16 | e.Pre(middleware.HTTPSRedirect()) 17 | ``` 18 | 19 | ## HTTPS WWW Redirect 20 | 21 | HTTPS WWW redirect redirects http requests to www https. 22 | For example, http://labstack.com will be redirected to https://www.labstack.com. 23 | 24 | ## Usage 25 | 26 | ```go 27 | e := echo.New() 28 | e.Pre(middleware.HTTPSWWWRedirect()) 29 | ``` 30 | 31 | ## HTTPS NonWWW Redirect 32 | 33 | HTTPS NonWWW redirect redirects http requests to https non www. 34 | For example, http://www.labstack.com will be redirect to https://labstack.com. 35 | 36 | ### Usage 37 | 38 | ```go 39 | e := echo.New() 40 | e.Pre(middleware.HTTPSNonWWWRedirect()) 41 | ``` 42 | 43 | ## WWW Redirect 44 | 45 | WWW redirect redirects non www requests to www. 46 | 47 | For example, http://labstack.com will be redirected to http://www.labstack.com. 48 | 49 | ### Usage 50 | 51 | ```go 52 | e := echo.New() 53 | e.Pre(middleware.WWWRedirect()) 54 | ``` 55 | 56 | ## NonWWW Redirect 57 | 58 | NonWWW redirect redirects www requests to non www. 59 | For example, http://www.labstack.com will be redirected to http://labstack.com. 60 | 61 | ### Usage 62 | 63 | ```go 64 | e := echo.New() 65 | e.Pre(middleware.NonWWWRedirect()) 66 | ``` 67 | 68 | ## Custom Configuration 69 | 70 | ### Usage 71 | 72 | ```go 73 | e := echo.New() 74 | e.Use(middleware.HTTPSRedirectWithConfig(middleware.RedirectConfig{ 75 | Code: http.StatusTemporaryRedirect, 76 | })) 77 | ``` 78 | 79 | Example above will redirect the request HTTP to HTTPS with status code `307 - StatusTemporaryRedirect`. 80 | 81 | ## Configuration 82 | 83 | ```go 84 | RedirectConfig struct { 85 | // Skipper defines a function to skip middleware. 86 | Skipper Skipper 87 | 88 | // Status code to be used when redirecting the request. 89 | // Optional. Default value http.StatusMovedPermanently. 90 | Code int `json:"code"` 91 | } 92 | ``` 93 | 94 | ### Default Configuration* 95 | 96 | ```go 97 | DefaultRedirectConfig = RedirectConfig{ 98 | Skipper: DefaultSkipper, 99 | Code: http.StatusMovedPermanently, 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /website/docs/middleware/request-id.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Request ID middleware 3 | --- 4 | 5 | # Request ID 6 | 7 | Request ID middleware generates a unique id for a request. 8 | 9 | ## Usage 10 | 11 | ```go 12 | e.Use(middleware.RequestID()) 13 | ``` 14 | 15 | *Example* 16 | 17 | ```go 18 | e := echo.New() 19 | 20 | e.Use(middleware.RequestID()) 21 | 22 | e.GET("/", func(c echo.Context) error { 23 | return c.String(http.StatusOK, c.Response().Header().Get(echo.HeaderXRequestID)) 24 | }) 25 | e.Logger.Fatal(e.Start(":1323")) 26 | ``` 27 | 28 | ## Custom Configuration 29 | 30 | ### Usage 31 | 32 | ```go 33 | e.Use(middleware.RequestIDWithConfig(middleware.RequestIDConfig{ 34 | Generator: func() string { 35 | return customGenerator() 36 | }, 37 | })) 38 | ``` 39 | 40 | ## Configuration 41 | 42 | ```go 43 | RequestIDConfig struct { 44 | // Skipper defines a function to skip middleware. 45 | Skipper Skipper 46 | 47 | // Generator defines a function to generate an ID. 48 | // Optional. Default value random.String(32). 49 | Generator func() string 50 | 51 | // RequestIDHandler defines a function which is executed for a request id. 52 | RequestIDHandler func(echo.Context, string) 53 | 54 | // TargetHeader defines what header to look for to populate the id 55 | TargetHeader string 56 | } 57 | ``` 58 | 59 | ### Default Configuration 60 | 61 | ```go 62 | DefaultRequestIDConfig = RequestIDConfig{ 63 | Skipper: DefaultSkipper, 64 | Generator: generator, 65 | TargetHeader: echo.HeaderXRequestID, 66 | } 67 | ``` 68 | 69 | ## Set ID 70 | 71 | You can set the id from the requester with the `X-Request-ID`-Header 72 | 73 | ### Request 74 | 75 | ```sh 76 | curl -H "X-Request-ID: 3" --compressed -v "http://localhost:1323/?my=param" 77 | ``` 78 | 79 | ### Log 80 | 81 | ```js 82 | {"time":"2017-11-13T20:26:28.6438003+01:00","id":"3","remote_ip":"::1","host":"localhost:1323","method":"GET","uri":"/?my=param","my":"param","status":200, "latency":0,"latency_human":"0s","bytes_in":0,"bytes_out":13} 83 | ``` 84 | -------------------------------------------------------------------------------- /website/docs/middleware/rewrite.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Rewrite middleware 3 | --- 4 | 5 | # Rewrite 6 | 7 | Rewrite middleware allows to rewrite an URL path based on provided rules. It can be helpful for backward compatibility or just creating cleaner and more descriptive links. 8 | 9 | ## Usage 10 | 11 | ```go 12 | e.Pre(middleware.Rewrite(map[string]string{ 13 | "/old": "/new", 14 | "/api/*": "/$1", 15 | "/js/*": "/public/javascripts/$1", 16 | "/users/*/orders/*": "/user/$1/order/$2", 17 | })) 18 | ``` 19 | 20 | The values captured in asterisk can be retrieved by index e.g. $1, $2 and so on. 21 | Each asterisk will be non-greedy (translated to a capture group `(.*?)`) and if using 22 | multiple asterisk a trailing `*` will match the "rest" of the path. 23 | 24 | :::caution 25 | 26 | Rewrite middleware should be registered via `Echo#Pre()` to get triggered before the router. 27 | 28 | ::: 29 | 30 | ## Custom Configuration 31 | 32 | ### Usage 33 | 34 | ```go 35 | e := echo.New() 36 | e.Pre(middleware.RewriteWithConfig(middleware.RewriteConfig{})) 37 | ``` 38 | 39 | ### Configuration 40 | 41 | ```go 42 | // RewriteConfig defines the config for Rewrite middleware. 43 | RewriteConfig struct { 44 | // Skipper defines a function to skip middleware. 45 | Skipper Skipper 46 | 47 | // Rules defines the URL path rewrite rules. The values captured in asterisk can be 48 | // retrieved by index e.g. $1, $2 and so on. 49 | Rules map[string]string `yaml:"rules"` 50 | 51 | // RegexRules defines the URL path rewrite rules using regexp.Rexexp with captures 52 | // Every capture group in the values can be retrieved by index e.g. $1, $2 and so on. 53 | RegexRules map[*regexp.Regexp]string 54 | } 55 | ``` 56 | 57 | Default Configuration: 58 | 59 | | Name | Value | 60 | | ------- | -------------- | 61 | | Skipper | DefaultSkipper | 62 | 63 | ### Regex-based Rules 64 | 65 | For advanced rewriting of paths rules may also be defined using regular expression. 66 | Normal capture groups can be defined using `()` and referenced by index (`$1`, `$2`, ...) for the rewritten path. 67 | 68 | `RegexRules` and normal `Rules` can be combined. 69 | 70 | ```go 71 | e.Pre(RewriteWithConfig(RewriteConfig{ 72 | Rules: map[string]string{ 73 | "^/v1/*": "/v2/$1", 74 | }, 75 | RegexRules: map[*regexp.Regexp]string{ 76 | regexp.MustCompile("^/foo/([0-9].*)"): "/num/$1", 77 | regexp.MustCompile("^/bar/(.+?)/(.*)"): "/baz/$2/$1", 78 | }, 79 | })) 80 | ``` 81 | -------------------------------------------------------------------------------- /website/docs/middleware/secure.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Secure middleware 3 | --- 4 | 5 | # Secure 6 | 7 | Secure middleware provides protection against cross-site scripting (XSS) attack, 8 | content type sniffing, clickjacking, insecure connection and other code injection 9 | attacks. 10 | 11 | ## Usage 12 | 13 | ```go 14 | e.Use(middleware.Secure()) 15 | ``` 16 | 17 | ## Custom Configuration 18 | 19 | ### Usage 20 | 21 | ```go 22 | e := echo.New() 23 | e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ 24 | XSSProtection: "", 25 | ContentTypeNosniff: "", 26 | XFrameOptions: "", 27 | HSTSMaxAge: 3600, 28 | ContentSecurityPolicy: "default-src 'self'", 29 | })) 30 | ``` 31 | 32 | :::info 33 | 34 | Passing empty `XSSProtection`, `ContentTypeNosniff`, `XFrameOptions` or `ContentSecurityPolicy` 35 | disables that protection. 36 | 37 | ::: 38 | 39 | ## Configuration 40 | 41 | ```go 42 | SecureConfig struct { 43 | // Skipper defines a function to skip middleware. 44 | Skipper Skipper 45 | 46 | // XSSProtection provides protection against cross-site scripting attack (XSS) 47 | // by setting the `X-XSS-Protection` header. 48 | // Optional. Default value "1; mode=block". 49 | XSSProtection string `json:"xss_protection"` 50 | 51 | // ContentTypeNosniff provides protection against overriding Content-Type 52 | // header by setting the `X-Content-Type-Options` header. 53 | // Optional. Default value "nosniff". 54 | ContentTypeNosniff string `json:"content_type_nosniff"` 55 | 56 | // XFrameOptions can be used to indicate whether or not a browser should 57 | // be allowed to render a page in a ,