mirror of
https://github.com/labstack/echo.git
synced 2024-12-22 20:06:21 +02:00
website/recipe in the main repo
Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
parent
1dcb7ba9ab
commit
f4b0004d2b
38
recipe/cors/server.go
Normal file
38
recipe/cors/server.go
Normal file
@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
var (
|
||||
users = []string{"Joe", "Veer", "Zion"}
|
||||
)
|
||||
|
||||
func getUsers(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, users)
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// CORS default
|
||||
// Allows requests from any origin wth GET, HEAD, PUT, POST or DELETE method.
|
||||
// e.Use(middleware.CORS())
|
||||
|
||||
// CORS restricted
|
||||
// Allows requests from any `https://labstack.com` or `https://labstack.net` origin
|
||||
// wth GET, PUT, POST or DELETE method.
|
||||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: []string{"https://labstack.com", "https://labstack.net"},
|
||||
AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
|
||||
}))
|
||||
|
||||
e.GET("/api/users", getUsers)
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
76
recipe/crud/server.go
Normal file
76
recipe/crud/server.go
Normal file
@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
type (
|
||||
user struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
users = map[int]*user{}
|
||||
seq = 1
|
||||
)
|
||||
|
||||
//----------
|
||||
// Handlers
|
||||
//----------
|
||||
|
||||
func createUser(c echo.Context) error {
|
||||
u := &user{
|
||||
ID: seq,
|
||||
}
|
||||
if err := c.Bind(u); err != nil {
|
||||
return err
|
||||
}
|
||||
users[u.ID] = u
|
||||
seq++
|
||||
return c.JSON(http.StatusCreated, u)
|
||||
}
|
||||
|
||||
func getUser(c echo.Context) error {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
return c.JSON(http.StatusOK, users[id])
|
||||
}
|
||||
|
||||
func updateUser(c echo.Context) error {
|
||||
u := new(user)
|
||||
if err := c.Bind(u); err != nil {
|
||||
return err
|
||||
}
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
users[id].Name = u.Name
|
||||
return c.JSON(http.StatusOK, users[id])
|
||||
}
|
||||
|
||||
func deleteUser(c echo.Context) error {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
delete(users, id)
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// Routes
|
||||
e.POST("/users", createUser)
|
||||
e.GET("/users/:id", getUser)
|
||||
e.PUT("/users/:id", updateUser)
|
||||
e.DELETE("/users/:id", deleteUser)
|
||||
|
||||
// Start server
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
2
recipe/embed-resources/.gitignore
vendored
Normal file
2
recipe/embed-resources/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
rice
|
||||
app.rice-box.go
|
11
recipe/embed-resources/app/index.html
Normal file
11
recipe/embed-resources/app/index.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>go.rice Example</title>
|
||||
<script src="/static/main.js" charset="utf-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>go.rice Example</h1>
|
||||
</body>
|
||||
</html>
|
1
recipe/embed-resources/app/main.js
Normal file
1
recipe/embed-resources/app/main.js
Normal file
@ -0,0 +1 @@
|
||||
alert("main.js");
|
21
recipe/embed-resources/server.go
Normal file
21
recipe/embed-resources/server.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/GeertJohan/go.rice"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
)
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
// the file server for rice. "app" is the folder where the files come from.
|
||||
assetHandler := http.FileServer(rice.MustFindBox("app").HTTPBox())
|
||||
// serves the index.html from rice
|
||||
e.GET("/", standard.WrapHandler(assetHandler))
|
||||
|
||||
// servers other static files
|
||||
e.GET("/static/*", standard.WrapHandler(http.StripPrefix("/static/", assetHandler)))
|
||||
e.Run(standard.New(":3000"))
|
||||
}
|
17
recipe/file-upload/multiple/public/index.html
Normal file
17
recipe/file-upload/multiple/public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Multiple file upload</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Upload multiple files with fields</h1>
|
||||
|
||||
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||
Name: <input type="text" name="name"><br>
|
||||
Email: <input type="email" name="email"><br>
|
||||
Files: <input type="file" name="files" multiple><br><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
66
recipe/file-upload/multiple/server.go
Normal file
66
recipe/file-upload/multiple/server.go
Normal file
@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
func upload(c echo.Context) error {
|
||||
// Read form fields
|
||||
name := c.FormValue("name")
|
||||
email := c.FormValue("email")
|
||||
|
||||
//------------
|
||||
// Read files
|
||||
//------------
|
||||
|
||||
// Multipart form
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files := form.File["files"]
|
||||
|
||||
for _, file := range files {
|
||||
// Source
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// Destination
|
||||
dst, err := os.Create(file.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
// Copy
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf("<p>Uploaded successfully %d files with fields name=%s and email=%s.</p>", len(files), name, email))
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.Static("public"))
|
||||
|
||||
e.POST("/upload", upload)
|
||||
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
17
recipe/file-upload/single/public/index.html
Normal file
17
recipe/file-upload/single/public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Single file upload</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Upload single file with fields</h1>
|
||||
|
||||
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||
Name: <input type="text" name="name"><br>
|
||||
Email: <input type="email" name="email"><br>
|
||||
Files: <input type="file" name="file"><br><br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
60
recipe/file-upload/single/server.go
Normal file
60
recipe/file-upload/single/server.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
func upload(c echo.Context) error {
|
||||
// Read form fields
|
||||
name := c.FormValue("name")
|
||||
email := c.FormValue("email")
|
||||
|
||||
//-----------
|
||||
// Read file
|
||||
//-----------
|
||||
|
||||
// Source
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// Destination
|
||||
dst, err := os.Create(file.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
// Copy
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf("<p>File %s uploaded successfully with fields name=%s and email=%s.</p>", file.Filename, name, email))
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.Static("public"))
|
||||
|
||||
e.POST("/upload", upload)
|
||||
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
7
recipe/google-app-engine/Dockerfile
Normal file
7
recipe/google-app-engine/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
# Dockerfile extending the generic Go image with application files for a
|
||||
# single application.
|
||||
FROM gcr.io/google_appengine/golang
|
||||
|
||||
COPY . /go/src/app
|
||||
RUN go-wrapper download
|
||||
RUN go-wrapper install -tags appenginevm
|
21
recipe/google-app-engine/app-engine.go
Normal file
21
recipe/google-app-engine/app-engine.go
Normal file
@ -0,0 +1,21 @@
|
||||
// +build appengine
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func createMux() *echo.Echo {
|
||||
e := echo.New()
|
||||
|
||||
// note: we don't need to provide the middleware or static handlers, that's taken care of by the platform
|
||||
// app engine has it's own "main" wrapper - we just need to hook echo into the default handler
|
||||
s := standard.New("")
|
||||
s.SetHandler(e)
|
||||
http.Handle("/", s)
|
||||
|
||||
return e
|
||||
}
|
36
recipe/google-app-engine/app-engine.yaml
Normal file
36
recipe/google-app-engine/app-engine.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
application: my-application-id # defined when you create your app using google dev console
|
||||
module: default # see https://cloud.google.com/appengine/docs/go/
|
||||
version: alpha # you can run multiple versions of an app and A/B test
|
||||
runtime: go # see https://cloud.google.com/appengine/docs/go/
|
||||
api_version: go1 # used when appengine supports different go versions
|
||||
|
||||
default_expiration: "1d" # for CDN serving of static files (use url versioning if long!)
|
||||
|
||||
handlers:
|
||||
# all the static files that we normally serve ourselves are defined here and Google will handle
|
||||
# serving them for us from it's own CDN / edge locations. For all the configuration options see:
|
||||
# https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers
|
||||
- url: /
|
||||
mime_type: text/html
|
||||
static_files: public/index.html
|
||||
upload: public/index.html
|
||||
|
||||
- url: /favicon.ico
|
||||
mime_type: image/x-icon
|
||||
static_files: public/favicon.ico
|
||||
upload: public/favicon.ico
|
||||
|
||||
- url: /scripts
|
||||
mime_type: text/javascript
|
||||
static_dir: public/scripts
|
||||
|
||||
# static files normally don't touch the server that the app runs on but server-side template files
|
||||
# needs to be readable by the app. The application_readable option makes sure they are available as
|
||||
# part of the app deployment onto the instance.
|
||||
- url: /templates
|
||||
static_dir: /templates
|
||||
application_readable: true
|
||||
|
||||
# finally, we route all other requests to our application. The script name just means "the go app"
|
||||
- url: /.*
|
||||
script: _go_app
|
29
recipe/google-app-engine/app-managed.go
Normal file
29
recipe/google-app-engine/app-managed.go
Normal file
@ -0,0 +1,29 @@
|
||||
// +build appenginevm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"google.golang.org/appengine"
|
||||
"net/http"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func createMux() *echo.Echo {
|
||||
e := echo.New()
|
||||
|
||||
// note: we don't need to provide the middleware or static handlers
|
||||
// for the appengine vm version - that's taken care of by the platform
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func main() {
|
||||
// the appengine package provides a convenient method to handle the health-check requests
|
||||
// and also run the app on the correct port. We just need to add Echo to the default handler
|
||||
s := standard.New(":8080")
|
||||
s.SetHandler(e)
|
||||
http.Handle("/", s)
|
||||
appengine.Main()
|
||||
}
|
37
recipe/google-app-engine/app-managed.yaml
Normal file
37
recipe/google-app-engine/app-managed.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
application: my-application-id # defined when you create your app using google dev console
|
||||
module: default # see https://cloud.google.com/appengine/docs/go/
|
||||
version: alpha # you can run multiple versions of an app and A/B test
|
||||
runtime: go # see https://cloud.google.com/appengine/docs/go/
|
||||
api_version: go1 # used when appengine supports different go versions
|
||||
vm: true # for managed VMs only, remove for appengine classic
|
||||
|
||||
default_expiration: "1d" # for CDN serving of static files (use url versioning if long!)
|
||||
|
||||
handlers:
|
||||
# all the static files that we normally serve ourselves are defined here and Google will handle
|
||||
# serving them for us from it's own CDN / edge locations. For all the configuration options see:
|
||||
# https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers
|
||||
- url: /
|
||||
mime_type: text/html
|
||||
static_files: public/index.html
|
||||
upload: public/index.html
|
||||
|
||||
- url: /favicon.ico
|
||||
mime_type: image/x-icon
|
||||
static_files: public/favicon.ico
|
||||
upload: public/favicon.ico
|
||||
|
||||
- url: /scripts
|
||||
mime_type: text/javascript
|
||||
static_dir: public/scripts
|
||||
|
||||
# static files normally don't touch the server that the app runs on but server-side template files
|
||||
# needs to be readable by the app. The application_readable option makes sure they are available as
|
||||
# part of the app deployment onto the instance.
|
||||
- url: /templates
|
||||
static_dir: /templates
|
||||
application_readable: true
|
||||
|
||||
# finally, we route all other requests to our application. The script name just means "the go app"
|
||||
- url: /.*
|
||||
script: _go_app
|
25
recipe/google-app-engine/app-standalone.go
Normal file
25
recipe/google-app-engine/app-standalone.go
Normal file
@ -0,0 +1,25 @@
|
||||
// +build !appengine,!appenginevm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
func createMux() *echo.Echo {
|
||||
e := echo.New()
|
||||
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Gzip())
|
||||
|
||||
e.Use(middleware.Static("public"))
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func main() {
|
||||
e.Run(standard.New(":8080"))
|
||||
}
|
4
recipe/google-app-engine/app.go
Normal file
4
recipe/google-app-engine/app.go
Normal file
@ -0,0 +1,4 @@
|
||||
package main
|
||||
|
||||
// reference our echo instance and create it early
|
||||
var e = createMux()
|
BIN
recipe/google-app-engine/public/favicon.ico
Normal file
BIN
recipe/google-app-engine/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
15
recipe/google-app-engine/public/index.html
Normal file
15
recipe/google-app-engine/public/index.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Echo</title>
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Echo!</h1>
|
||||
<script src="/scripts/main.js"></script>
|
||||
</body>
|
||||
</html>
|
1
recipe/google-app-engine/public/scripts/main.js
Normal file
1
recipe/google-app-engine/public/scripts/main.js
Normal file
@ -0,0 +1 @@
|
||||
console.log("Echo!");
|
1
recipe/google-app-engine/templates/welcome.html
Normal file
1
recipe/google-app-engine/templates/welcome.html
Normal file
@ -0,0 +1 @@
|
||||
{{define "welcome"}}Hello, {{.}}!{{end}}
|
54
recipe/google-app-engine/users.go
Normal file
54
recipe/google-app-engine/users.go
Normal file
@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
type (
|
||||
user struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
users map[string]user
|
||||
)
|
||||
|
||||
func init() {
|
||||
users = map[string]user{
|
||||
"1": user{
|
||||
ID: "1",
|
||||
Name: "Wreck-It Ralph",
|
||||
},
|
||||
}
|
||||
|
||||
// hook into the echo instance to create an endpoint group
|
||||
// and add specific middleware to it plus handlers
|
||||
g := e.Group("/users")
|
||||
g.Use(middleware.CORS())
|
||||
|
||||
g.POST("", createUser)
|
||||
g.GET("", getUsers)
|
||||
g.GET("/:id", getUser)
|
||||
}
|
||||
|
||||
func createUser(c echo.Context) error {
|
||||
u := new(user)
|
||||
if err := c.Bind(u); err != nil {
|
||||
return err
|
||||
}
|
||||
users[u.ID] = *u
|
||||
return c.JSON(http.StatusCreated, u)
|
||||
}
|
||||
|
||||
func getUsers(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, users)
|
||||
}
|
||||
|
||||
func getUser(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, users[c.P(0)])
|
||||
}
|
31
recipe/google-app-engine/welcome.go
Normal file
31
recipe/google-app-engine/welcome.go
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
Template struct {
|
||||
templates *template.Template
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
t := &Template{
|
||||
templates: template.Must(template.ParseFiles("templates/welcome.html")),
|
||||
}
|
||||
e.SetRenderer(t)
|
||||
e.GET("/welcome", welcome)
|
||||
}
|
||||
|
||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
return t.templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
|
||||
func welcome(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "welcome", "Joe")
|
||||
}
|
19
recipe/graceful-shutdown/grace/server.go
Normal file
19
recipe/graceful-shutdown/grace/server.go
Normal file
@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/facebookgo/grace/gracehttp"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
)
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Six sick bricks tick")
|
||||
})
|
||||
std := standard.New(":1323")
|
||||
std.SetHandler(e)
|
||||
gracehttp.Serve(std.Server)
|
||||
}
|
21
recipe/graceful-shutdown/graceful/server.go
Normal file
21
recipe/graceful-shutdown/graceful/server.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/tylerb/graceful"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Setup
|
||||
e := echo.New()
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Sue sews rose on slow joe crows nose")
|
||||
})
|
||||
std := standard.New(":1323")
|
||||
std.SetHandler(e)
|
||||
graceful.ListenAndServe(std.Server, 5*time.Second)
|
||||
}
|
26
recipe/hello-world/server.go
Normal file
26
recipe/hello-world/server.go
Normal file
@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Echo instance
|
||||
e := echo.New()
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// Route => handler
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Hello, World!\n")
|
||||
})
|
||||
|
||||
// Start server
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
18
recipe/http2/cert.pem
Normal file
18
recipe/http2/cert.pem
Normal file
@ -0,0 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC+DCCAeCgAwIBAgIPFB3cowaziqTVN3I2utsEMA0GCSqGSIb3DQEBCwUAMBIx
|
||||
EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTYwNTA5MTUwNDQ2WhcNMTcwNTA5MTUwNDQ2
|
||||
WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw+jr8IJ7W
|
||||
aUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2iqxyrrRNy
|
||||
n3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q0SMGF+Yg
|
||||
RnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3Xc/d96OY
|
||||
35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phKmB22qnlg
|
||||
PY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABo0swSTAOBgNVHQ8BAf8EBAMCBaAw
|
||||
EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggls
|
||||
b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAL2SqeH2Z6zKje9XC6O/TC0DKRvE
|
||||
L/d/1NVz5nr52Gqk+7DzU1QCWHD9+FLEX+wvzcploNoqcR+Xrh7N/2kNGymDdxP+
|
||||
UlpmpzLQ6BwcjWgwMa4GMn3L/aczFFuhG9dZdcy9UV4fXtreLO0FMt4BuBQyKMK2
|
||||
ZkWgw2rx3fMBgR4B99JpRr0wgqPHbFndvPYbMAEiYB4baRiIpkyBC6/JyZTNTBBH
|
||||
+WyvuQKbip5WvVST+PjIy2eywJ2yGXKek3TOS8GTulVqUE5u7utzdOLJaLXIvF2K
|
||||
BAbRa6CZ8+bZWRYpBujlu1mKjedzi/pS5BK5q/gPUWe8YcqEJVZWEHMakZ0=
|
||||
-----END CERTIFICATE-----
|
27
recipe/http2/key.pem
Normal file
27
recipe/http2/key.pem
Normal file
@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw
|
||||
+jr8IJ7WaUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2i
|
||||
qxyrrRNyn3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q
|
||||
0SMGF+YgRnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3
|
||||
Xc/d96OY35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phK
|
||||
mB22qnlgPY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABAoIBABvDpTMeu/5gwJDW
|
||||
HXvJJGMATEsBrcX9lsWqzMYDCkXdElPkMKkhaGTHzIdUpkgNUgYG3bYu1dYvXZKi
|
||||
84X3+2u1KrF0i2hNAzZWAwfhOwkRQuSSUL9wgGk4Jp5Mgehdvcm0FVNFfOG4a0CS
|
||||
tLVZsVe/h+PfMs4EuPDicvLBH0LI/dE+GeXGIhuF8/g7EN3S+f/WtCejrzah7Ghf
|
||||
OsaBaBL0O5s82wU9PX0ZFxo4FADfwKLL0Zmbno8NC0cMsNOM2/B2dRF0eTlCDeAy
|
||||
rxIXL7HEz+fWK96jCbDiMPyP9XstQxO2atZa2gC5vOJCwPpVVDbqW7FNop4VwUOW
|
||||
bTtDrcUCgYEA6+hyuN0DcaAOM64rVyBbcMOD4L6Q00A1+9I1wBJtx1yNNfTS4iJc
|
||||
EXxkLS8ae9xXYEGhiU0PyOufs+iTwLwvlbWcZUJailEoBG5EzC1BDZPE20hNcXav
|
||||
zxxyQxRrzdItmMj8wa4TETVlcPGcKxNUw/UXqFIyNShtALse2mnYQQcCgYEA64sA
|
||||
3WC+/e0jVoaPiIuCg2vt5QUiyfkblSUjsdBTlMWYIjlJNcGv81vepk3NIAPkTs71
|
||||
HZuRwEDENm/7HDSG2SpyB9de8/b061Uo/o+ZhWqPOHx1m7mD9x96CZk4wtxwL14u
|
||||
SJuwyujqmSYoUYW8FKjHS5n74hP9YUT7hXKWs4sCgYAx2eAMUp/8rd7yata7xZmt
|
||||
HZPLtVlzWrlNqqEzInHSVCt/AGpj4PDlvQyKQ87r56cLLzNMiV1RjwEjin1WmC3S
|
||||
DButhjUNz5KORSMCpnl9vgE2eXPsCzGhqZg3tqQFTWnXRHmtD/T1iPwTvurKa35Z
|
||||
HnzOU/hKJW3LXr9pVj6dlwKBgQCXVaEBm1Y7GbB5uEziIxiAzch0O9+FOxsgsVME
|
||||
vN/mlynO21WRR1eAGUetPBGN/1Ih3FCabEix6Cro+vuwvILjZqULKrIkN0hXJ0kG
|
||||
fUba9IL+fOCnZANItJ2ZKyvP7wfZNz6vgfCN/iY0rdJ7xlv4PhSGG3I9aDCE3Who
|
||||
7h2rTwKBgQCQ5s0JdS/EDX5h7LPPqpIV1kqMmcqvBonZN4TySfqxKSlhasO3oGgC
|
||||
rYkygiYGU8WdooavGNSmY4+sCBY002PZQ8EcSp+KySpYLeiVrJZTc+ehzk93t2to
|
||||
b1p1mmLFCM6SVJAIQ+y9jt5GCSEka0BlVoNfLM06OLcC9j8iIGsjrw==
|
||||
-----END RSA PRIVATE KEY-----
|
48
recipe/http2/server.go
Normal file
48
recipe/http2/server.go
Normal file
@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
)
|
||||
|
||||
func request(c echo.Context) error {
|
||||
req := c.Request().(*standard.Request).Request
|
||||
format := "<pre><strong>Request Information</strong>\n\n<code>Protocol: %s\nHost: %s\nRemote Address: %s\nMethod: %s\nPath: %s\n</code></pre>"
|
||||
return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path))
|
||||
}
|
||||
|
||||
func stream(c echo.Context) error {
|
||||
res := c.Response().(*standard.Response).ResponseWriter
|
||||
gone := res.(http.CloseNotifier).CloseNotify()
|
||||
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
||||
res.WriteHeader(http.StatusOK)
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
fmt.Fprint(res, "<pre><strong>Clock Stream</strong>\n\n<code>")
|
||||
for {
|
||||
fmt.Fprintf(res, "%v\n", time.Now())
|
||||
res.(http.Flusher).Flush()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-gone:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.GET("/request", request)
|
||||
e.GET("/stream", stream)
|
||||
e.Run(standard.WithConfig(engine.Config{
|
||||
Address: ":1323",
|
||||
TLSCertFile: "cert.pem",
|
||||
TLSKeyFile: "key.pem",
|
||||
}))
|
||||
}
|
36
recipe/jsonp/public/index.html
Normal file
36
recipe/jsonp/public/index.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||
<title>JSONP</title>
|
||||
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var host_prefix = 'http://localhost:1323';
|
||||
$(document).ready(function() {
|
||||
// JSONP version - add 'callback=?' to the URL - fetch the JSONP response to the request
|
||||
$("#jsonp-button").click(function(e) {
|
||||
e.preventDefault();
|
||||
// The only difference on the client end is the addition of 'callback=?' to the URL
|
||||
var url = host_prefix + '/jsonp?callback=?';
|
||||
$.getJSON(url, function(jsonp) {
|
||||
console.log(jsonp);
|
||||
$("#jsonp-response").html(JSON.stringify(jsonp, null, 2));
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container" style="margin-top: 50px;">
|
||||
<input type="button" class="btn btn-primary btn-lg" id="jsonp-button" value="Get JSONP response">
|
||||
<p>
|
||||
<pre id="jsonp-response"></pre>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
35
recipe/jsonp/server.go
Normal file
35
recipe/jsonp/server.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.Static("public"))
|
||||
|
||||
// JSONP
|
||||
e.GET("/jsonp", func(c echo.Context) error {
|
||||
callback := c.QueryParam("callback")
|
||||
var content struct {
|
||||
Response string `json:"response"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Random int `json:"random"`
|
||||
}
|
||||
content.Response = "Sent via JSONP"
|
||||
content.Timestamp = time.Now().UTC()
|
||||
content.Random = rand.Intn(1000)
|
||||
return c.JSONP(http.StatusOK, callback, &content)
|
||||
})
|
||||
|
||||
// Start server
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
87
recipe/jwt/custom-claims/server.go
Normal file
87
recipe/jwt/custom-claims/server.go
Normal file
@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
// jwtCustomClaims are custom claims extending default ones.
|
||||
type jwtCustomClaims struct {
|
||||
Name string `json:"name"`
|
||||
Admin bool `json:"admin"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func login(c echo.Context) error {
|
||||
username := c.FormValue("username")
|
||||
password := c.FormValue("password")
|
||||
|
||||
if username == "jon" && password == "shhh!" {
|
||||
|
||||
// Set custom claims
|
||||
claims := &jwtCustomClaims{
|
||||
"Jon Snow",
|
||||
true,
|
||||
jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
// Create token with claims
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Generate encoded token and send it as response.
|
||||
t, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]string{
|
||||
"token": t,
|
||||
})
|
||||
}
|
||||
|
||||
return echo.ErrUnauthorized
|
||||
}
|
||||
|
||||
func accessible(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Accessible")
|
||||
}
|
||||
|
||||
func restricted(c echo.Context) error {
|
||||
user := c.Get("user").(*jwt.Token)
|
||||
claims := user.Claims.(*jwtCustomClaims)
|
||||
name := claims.Name
|
||||
return c.String(http.StatusOK, "Welcome "+name+"!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// Login route
|
||||
e.POST("/login", login)
|
||||
|
||||
// Unauthenticated route
|
||||
e.GET("/", accessible)
|
||||
|
||||
// Restricted group
|
||||
r := e.Group("/restricted")
|
||||
|
||||
// Configure middleware with the custom claims type
|
||||
config := middleware.JWTConfig{
|
||||
Claims: &jwtCustomClaims{},
|
||||
SigningKey: []byte("secret"),
|
||||
}
|
||||
r.Use(middleware.JWTWithConfig(config))
|
||||
r.GET("", restricted)
|
||||
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
70
recipe/jwt/map-claims/server.go
Normal file
70
recipe/jwt/map-claims/server.go
Normal file
@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
func login(c echo.Context) error {
|
||||
username := c.FormValue("username")
|
||||
password := c.FormValue("password")
|
||||
|
||||
if username == "jon" && password == "shhh!" {
|
||||
// Create token
|
||||
token := jwt.New(jwt.SigningMethodHS256)
|
||||
|
||||
// Set claims
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
claims["name"] = "Jon Snow"
|
||||
claims["admin"] = true
|
||||
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
|
||||
|
||||
// Generate encoded token and send it as response.
|
||||
t, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]string{
|
||||
"token": t,
|
||||
})
|
||||
}
|
||||
|
||||
return echo.ErrUnauthorized
|
||||
}
|
||||
|
||||
func accessible(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Accessible")
|
||||
}
|
||||
|
||||
func restricted(c echo.Context) error {
|
||||
user := c.Get("user").(*jwt.Token)
|
||||
claims := user.Claims.(jwt.MapClaims)
|
||||
name := claims["name"].(string)
|
||||
return c.String(http.StatusOK, "Welcome "+name+"!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// Login route
|
||||
e.POST("/login", login)
|
||||
|
||||
// Unauthenticated route
|
||||
e.GET("/", accessible)
|
||||
|
||||
// Restricted group
|
||||
r := e.Group("/restricted")
|
||||
r.Use(middleware.JWT([]byte("secret")))
|
||||
r.GET("", restricted)
|
||||
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
83
recipe/middleware/server.go
Normal file
83
recipe/middleware/server.go
Normal file
@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
)
|
||||
|
||||
type (
|
||||
Stats struct {
|
||||
Uptime time.Time `json:"uptime"`
|
||||
RequestCount uint64 `json:"requestCount"`
|
||||
Statuses map[string]int `json:"statuses"`
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
)
|
||||
|
||||
func NewStats() *Stats {
|
||||
return &Stats{
|
||||
Uptime: time.Now(),
|
||||
Statuses: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
// Process is the middleware function.
|
||||
func (s *Stats) Process(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if err := next(c); err != nil {
|
||||
c.Error(err)
|
||||
}
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.RequestCount++
|
||||
status := strconv.Itoa(c.Response().Status())
|
||||
s.Statuses[status]++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Handle is the endpoint to get stats.
|
||||
func (s *Stats) Handle(c echo.Context) error {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
return c.JSON(http.StatusOK, s)
|
||||
}
|
||||
|
||||
// ServerHeader middleware adds a `Server` header to the response.
|
||||
func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
c.Response().Header().Set(echo.HeaderServer, "Echo/2.0")
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
|
||||
// Debug mode
|
||||
e.SetDebug(true)
|
||||
|
||||
//-------------------
|
||||
// Custom middleware
|
||||
//-------------------
|
||||
// Stats
|
||||
s := NewStats()
|
||||
e.Use(s.Process)
|
||||
e.GET("/stats", s.Handle) // Endpoint to get stats
|
||||
|
||||
// Server header
|
||||
e.Use(ServerHeader)
|
||||
|
||||
// Handler
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Hello, World!")
|
||||
})
|
||||
|
||||
// Start server
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
46
recipe/streaming-response/server.go
Normal file
46
recipe/streaming-response/server.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
)
|
||||
|
||||
type (
|
||||
Geolocation struct {
|
||||
Altitude float64
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
locations = []Geolocation{
|
||||
{-97, 37.819929, -122.478255},
|
||||
{1899, 39.096849, -120.032351},
|
||||
{2619, 37.865101, -119.538329},
|
||||
{42, 33.812092, -117.918974},
|
||||
{15, 37.77493, -122.419416},
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
c.Response().WriteHeader(http.StatusOK)
|
||||
for _, l := range locations {
|
||||
if err := json.NewEncoder(c.Response()).Encode(l); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Response().(http.Flusher).Flush()
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
79
recipe/subdomains/server.go
Normal file
79
recipe/subdomains/server.go
Normal file
@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
type (
|
||||
Host struct {
|
||||
Echo *echo.Echo
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Hosts
|
||||
hosts := make(map[string]*Host)
|
||||
|
||||
//-----
|
||||
// API
|
||||
//-----
|
||||
|
||||
api := echo.New()
|
||||
api.Use(middleware.Logger())
|
||||
api.Use(middleware.Recover())
|
||||
|
||||
hosts["api.localhost:1323"] = &Host{api}
|
||||
|
||||
api.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "API")
|
||||
})
|
||||
|
||||
//------
|
||||
// Blog
|
||||
//------
|
||||
|
||||
blog := echo.New()
|
||||
blog.Use(middleware.Logger())
|
||||
blog.Use(middleware.Recover())
|
||||
|
||||
hosts["blog.localhost:1323"] = &Host{blog}
|
||||
|
||||
blog.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Blog")
|
||||
})
|
||||
|
||||
//---------
|
||||
// Website
|
||||
//---------
|
||||
|
||||
site := echo.New()
|
||||
site.Use(middleware.Logger())
|
||||
site.Use(middleware.Recover())
|
||||
|
||||
hosts["localhost:1323"] = &Host{site}
|
||||
|
||||
site.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Website")
|
||||
})
|
||||
|
||||
// Server
|
||||
e := echo.New()
|
||||
e.Any("/*", func(c echo.Context) (err error) {
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
host := hosts[req.Host()]
|
||||
|
||||
if host == nil {
|
||||
err = echo.ErrNotFound
|
||||
} else {
|
||||
host.Echo.ServeHTTP(req, res)
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
52
recipe/websocket/gorilla/server.go
Normal file
52
recipe/websocket/gorilla/server.go
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
var (
|
||||
upgrader = websocket.Upgrader{}
|
||||
)
|
||||
|
||||
func hello() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Print("upgrade:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for {
|
||||
// Write
|
||||
err := c.WriteMessage(websocket.TextMessage, []byte("Hello, Client!"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Read
|
||||
_, msg, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s\n", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.Static("../public"))
|
||||
e.GET("/ws", standard.WrapHandler(http.HandlerFunc(hello())))
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
40
recipe/websocket/net/server.go
Normal file
40
recipe/websocket/net/server.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/labstack/echo/middleware"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
func hello() websocket.Handler {
|
||||
return websocket.Handler(func(ws *websocket.Conn) {
|
||||
for {
|
||||
// Write
|
||||
err := websocket.Message.Send(ws, "Hello, Client!")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Read
|
||||
msg := ""
|
||||
err = websocket.Message.Receive(ws, &msg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s\n", msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.Static("../public"))
|
||||
e.GET("/ws", standard.WrapHandler(hello()))
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
39
recipe/websocket/public/index.html
Normal file
39
recipe/websocket/public/index.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>WebSocket</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p id="output"></p>
|
||||
|
||||
<script>
|
||||
var loc = window.location;
|
||||
var uri = 'ws:';
|
||||
|
||||
if (loc.protocol === 'https:') {
|
||||
uri = 'wss:';
|
||||
}
|
||||
uri += '//' + loc.host;
|
||||
uri += loc.pathname + 'ws';
|
||||
|
||||
ws = new WebSocket(uri)
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('Connected')
|
||||
}
|
||||
|
||||
ws.onmessage = function(evt) {
|
||||
var out = document.getElementById('output');
|
||||
out.innerHTML += evt.data + '<br>';
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
ws.send('Hello, Server!');
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
43
website/config.json
Normal file
43
website/config.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"baseurl": "https://echo.labstack.com",
|
||||
"languageCode": "en-us",
|
||||
"title": "Echo - Fast and unfancy HTTP server framework for Go (Golang)",
|
||||
"canonifyurls": true,
|
||||
"googleAnalytics": "UA-85059636-2",
|
||||
"permalinks": {
|
||||
"guide": "/guide/:filename",
|
||||
"middleware": "/middleware/:filename",
|
||||
"recipes": "/recipes/:filename"
|
||||
},
|
||||
"menu": {
|
||||
"side": [{
|
||||
"name": "Guide",
|
||||
"pre": "<i class='fa fa-book'></i>",
|
||||
"weight": 1,
|
||||
"identifier": "guide",
|
||||
"url": "guide"
|
||||
},{
|
||||
"name": "Middleware",
|
||||
"pre": "<i class='fa fa-filter'></i>",
|
||||
"weight": 1,
|
||||
"identifier": "middleware",
|
||||
"url": "middleware"
|
||||
}, {
|
||||
"name": "Recipes",
|
||||
"pre": "<i class='fa fa-code'></i>",
|
||||
"weight": 2,
|
||||
"identifier": "recipes",
|
||||
"url": "recipes"
|
||||
}, {
|
||||
"name": "Godoc",
|
||||
"pre": "<i class='fa fa-file-text-o'></i>",
|
||||
"weight": 3,
|
||||
"identifier": "godoc",
|
||||
"url": "godoc"
|
||||
}]
|
||||
},
|
||||
"params": {
|
||||
"image": "https://echo.labstack.com/images/logo.png",
|
||||
"description": "Echo is micro web framework for Go (Golang), high performance, minimalistic and fast."
|
||||
}
|
||||
}
|
8
website/content/godoc/echo.md
Normal file
8
website/content/godoc/echo.md
Normal file
@ -0,0 +1,8 @@
|
||||
+++
|
||||
title = "echo"
|
||||
[menu.side]
|
||||
name = "echo"
|
||||
parent = "godoc"
|
||||
weight = 1
|
||||
url = "https://godoc.org/github.com/labstack/echo"
|
||||
+++
|
8
website/content/godoc/engine.md
Normal file
8
website/content/godoc/engine.md
Normal file
@ -0,0 +1,8 @@
|
||||
+++
|
||||
title = "engine"
|
||||
[menu.side]
|
||||
name = "engine"
|
||||
parent = "godoc"
|
||||
weight = 3
|
||||
url = "https://godoc.org/github.com/labstack/echo/engine"
|
||||
+++
|
8
website/content/godoc/fasthttp.md
Normal file
8
website/content/godoc/fasthttp.md
Normal file
@ -0,0 +1,8 @@
|
||||
+++
|
||||
title = "fasthttp"
|
||||
[menu.side]
|
||||
name = "engine/fasthttp"
|
||||
parent = "godoc"
|
||||
weight = 5
|
||||
url = "https://godoc.org/github.com/labstack/echo/engine/fasthttp"
|
||||
+++
|
9
website/content/godoc/middleware.md
Normal file
9
website/content/godoc/middleware.md
Normal file
@ -0,0 +1,9 @@
|
||||
+++
|
||||
title = "middleware"
|
||||
[menu.side]
|
||||
name = "middleware"
|
||||
identifier = "godoc-middleware"
|
||||
parent = "godoc"
|
||||
weight = 1
|
||||
url = "https://godoc.org/github.com/labstack/echo/middleware"
|
||||
+++
|
8
website/content/godoc/standard.md
Normal file
8
website/content/godoc/standard.md
Normal file
@ -0,0 +1,8 @@
|
||||
+++
|
||||
title = "standard"
|
||||
[menu.side]
|
||||
name = "engine/standard"
|
||||
parent = "godoc"
|
||||
weight = 4
|
||||
url = "https://godoc.org/github.com/labstack/echo/engine/standard"
|
||||
+++
|
75
website/content/guide/context.md
Normal file
75
website/content/guide/context.md
Normal file
@ -0,0 +1,75 @@
|
||||
+++
|
||||
title = "Context"
|
||||
description = "Context in Echo"
|
||||
[menu.side]
|
||||
name = "Context"
|
||||
identifier = "context"
|
||||
parent = "guide"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## Context
|
||||
|
||||
`echo.Context` represents the context of the current HTTP request. It holds request and
|
||||
response reference, path, path parameters, data, registered handler and APIs to read
|
||||
request and write response. Context is 100% compatible with standard `context.Context`.
|
||||
As Context is an interface, it is easy to extend it with custom APIs.
|
||||
|
||||
#### Extending Context
|
||||
|
||||
**Define a custom context**
|
||||
|
||||
```go
|
||||
type CustomContext struct {
|
||||
echo.Context
|
||||
}
|
||||
|
||||
func (c *CustomContext) Foo() {
|
||||
println("foo")
|
||||
}
|
||||
|
||||
func (c *CustomContext) Bar() {
|
||||
println("bar")
|
||||
}
|
||||
```
|
||||
|
||||
**Create a middleware to extend default context**
|
||||
|
||||
```go
|
||||
e.Use(func(h echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
cc := &CustomContext{c}
|
||||
return h(cc)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
> This middleware should be registered before any other middleware.
|
||||
|
||||
**Use in handler**
|
||||
|
||||
```go
|
||||
e.Get("/", func(c echo.Context) error {
|
||||
cc := c.(*CustomContext)
|
||||
cc.Foo()
|
||||
cc.Bar()
|
||||
return cc.String(200, "OK")
|
||||
})
|
||||
```
|
||||
|
||||
### Standard Context
|
||||
|
||||
`echo.Context` embeds standard `context.Context` interface, so all it's functions
|
||||
are available right from `echo.Context`.
|
||||
|
||||
*Example*
|
||||
|
||||
```go
|
||||
e.GET("/users/:name", func(c echo.Context) error) {
|
||||
c.SetContext(context.WithValue(nil, "key", "val"))
|
||||
// Pass it down...
|
||||
// Use it...
|
||||
val := c.Value("key").(string)
|
||||
return c.String(http.StatusOK, name)
|
||||
})
|
||||
```
|
78
website/content/guide/cookies.md
Normal file
78
website/content/guide/cookies.md
Normal file
@ -0,0 +1,78 @@
|
||||
+++
|
||||
title = "Cookies"
|
||||
description = "Handling cookie in Echo"
|
||||
[menu.side]
|
||||
name = "Cookies"
|
||||
parent = "guide"
|
||||
weight = 6
|
||||
+++
|
||||
|
||||
## Cookies
|
||||
|
||||
Cookie is a small piece of data sent from a website and stored in the user's web
|
||||
browser while the user is browsing. Every time the user loads the website, the browser
|
||||
sends the cookie back to the server to notify the user's previous activity.
|
||||
Cookies were designed to be a reliable mechanism for websites to remember stateful
|
||||
information (such as items added in the shopping cart in an online store) or to
|
||||
record the user's browsing activity (including clicking particular buttons, logging
|
||||
in, or recording which pages were visited in the past). Cookies can also store
|
||||
passwords and form content a user has previously entered, such as a credit card
|
||||
number or an address.
|
||||
|
||||
### Cookie Attributes
|
||||
|
||||
Attribute | Optional
|
||||
:--- | :---
|
||||
`Name` | No
|
||||
`Value` | No
|
||||
`Path` | Yes
|
||||
`Domain` | Yes
|
||||
`Expires` | Yes
|
||||
`Secure` | Yes
|
||||
`HTTPOnly` | Yes
|
||||
|
||||
### Create a Cookie
|
||||
|
||||
```go
|
||||
func writeCookie(c echo.Context) error {
|
||||
cookie := new(echo.Cookie)
|
||||
cookie.SetName("username")
|
||||
cookie.SetValue("jon")
|
||||
cookie.SetExpires(time.Now().Add(24 * time.Hour))
|
||||
c.SetCookie(cookie)
|
||||
return c.String(http.StatusOK, "write a cookie")
|
||||
}
|
||||
```
|
||||
|
||||
- Cookie is created using `new(echo.Cookie)`.
|
||||
- Attributes for the cookie are set using `Setter` functions.
|
||||
- Finally `c.SetCookie(cookies)` adds a `Set-Cookie` header in HTTP response.
|
||||
|
||||
### Read a Cookie
|
||||
|
||||
```go
|
||||
func readCookie(c echo.Context) error {
|
||||
cookie, err := c.Cookie("username")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(cookie.Name())
|
||||
fmt.Println(cookie.Value())
|
||||
return c.String(http.StatusOK, "read a cookie")
|
||||
}
|
||||
```
|
||||
|
||||
- Cookie is read by name using `c.Cookie("username")` from the HTTP request.
|
||||
- Cookie attributes are accessed using `Getter` function.
|
||||
|
||||
### Read all Cookies
|
||||
|
||||
```go
|
||||
func readAllCookies(c echo.Context) error {
|
||||
for _, cookie := range c.Cookies() {
|
||||
fmt.Println(cookie.Name())
|
||||
fmt.Println(cookie.Value())
|
||||
}
|
||||
return c.String(http.StatusOK, "read all cookie")
|
||||
}
|
||||
```
|
98
website/content/guide/customization.md
Normal file
98
website/content/guide/customization.md
Normal file
@ -0,0 +1,98 @@
|
||||
+++
|
||||
title = "Customization"
|
||||
description = "Customizing Echo"
|
||||
[menu.side]
|
||||
name = "Customization"
|
||||
parent = "guide"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
## Customization
|
||||
|
||||
### HTTP Error Handler
|
||||
|
||||
`Echo#SetHTTPErrorHandler(h HTTPErrorHandler)` registers a custom `Echo#HTTPErrorHandler`.
|
||||
|
||||
Default HTTP error handler rules:
|
||||
|
||||
- If error is of type `Echo#HTTPError` it sends HTTP response with status code `HTTPError.Code`
|
||||
and message `HTTPError.Message`.
|
||||
- Else it sends `500 - Internal Server Error`.
|
||||
- If debug mode is enabled, it uses `error.Error()` as status message.
|
||||
|
||||
### Debug
|
||||
|
||||
`Echo#SetDebug(on bool)` enable/disable debug mode.
|
||||
|
||||
### Logging
|
||||
|
||||
#### Custom Logger
|
||||
|
||||
`Echo#SetLogger(l log.Logger)`
|
||||
|
||||
SetLogger defines a custom logger.
|
||||
|
||||
#### Log Output
|
||||
|
||||
`Echo#SetLogOutput(w io.Writer)` sets the output destination for the logger. Default
|
||||
value `os.Stdout`
|
||||
|
||||
To completely disable logs use `Echo#SetLogOutput(io.Discard)`
|
||||
|
||||
#### Log Level
|
||||
|
||||
`Echo#SetLogLevel(l log.Level)`
|
||||
|
||||
SetLogLevel sets the log level for the logger. Default value `5` (OFF).
|
||||
Possible values:
|
||||
|
||||
- `0` (DEBUG)
|
||||
- `1` (INFO)
|
||||
- `2` (WARN)
|
||||
- `3` (ERROR)
|
||||
- `4` (FATAL)
|
||||
- `5` (OFF)
|
||||
|
||||
### HTTP Engine
|
||||
|
||||
Echo currently supports standard and [fasthttp](https://github.com/valyala/fasthttp)
|
||||
server engines. Echo utilizes interfaces to abstract the internal implementation
|
||||
of these servers so you can seamlessly switch from one engine to another based on
|
||||
your preference.
|
||||
|
||||
#### Running a standard HTTP server
|
||||
|
||||
`e.Run(standard.New(":1323"))`
|
||||
|
||||
#### Running a fasthttp server
|
||||
|
||||
`e.Run(fasthttp.New(":1323"))`
|
||||
|
||||
#### Running a server with TLS configuration
|
||||
|
||||
`e.Run(<engine>.WithTLS(":1323", "<certFile>", "<keyFile>"))`
|
||||
|
||||
#### Running a server with engine configuration
|
||||
|
||||
`e.Run(<engine>.WithConfig(<config>))`
|
||||
|
||||
##### Configuration
|
||||
|
||||
```go
|
||||
Config struct {
|
||||
Address string // TCP address to listen on.
|
||||
Listener net.Listener // Custom `net.Listener`. If set, server accepts connections on it.
|
||||
TLSCertFile string // TLS certificate file path.
|
||||
TLSKeyFile string // TLS key file path.
|
||||
ReadTimeout time.Duration // Maximum duration before timing out read of the request.
|
||||
WriteTimeout time.Duration // Maximum duration before timing out write of the response.
|
||||
}
|
||||
```
|
||||
|
||||
#### Access internal server instance and configure its properties
|
||||
|
||||
```go
|
||||
s := standard.New(":1323")
|
||||
s.MaxHeaderBytes = 1 << 20
|
||||
e.Run(s)
|
||||
```
|
48
website/content/guide/error-handling.md
Normal file
48
website/content/guide/error-handling.md
Normal file
@ -0,0 +1,48 @@
|
||||
+++
|
||||
title = "Error Handling"
|
||||
description = "Error handling in Echo"
|
||||
[menu.side]
|
||||
name = "Error Handling"
|
||||
parent = "guide"
|
||||
weight = 8
|
||||
+++
|
||||
|
||||
## Error Handling
|
||||
|
||||
Echo advocates centralized HTTP error handling by returning error from middleware
|
||||
or handlers.
|
||||
|
||||
- Log errors from a unified location
|
||||
- Send customized HTTP responses
|
||||
|
||||
For example, when basic auth middleware finds invalid credentials it returns
|
||||
`401 - Unauthorized` error, aborting the current HTTP request.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.Use(func(c echo.Context) error {
|
||||
// Extract the credentials from HTTP request header and perform a security
|
||||
// check
|
||||
|
||||
// For invalid credentials
|
||||
return echo.NewHTTPError(http.StatusUnauthorized)
|
||||
})
|
||||
e.GET("/welcome", welcome)
|
||||
e.Run(":1323")
|
||||
}
|
||||
|
||||
func welcome(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Welcome!")
|
||||
}
|
||||
```
|
||||
|
||||
See how [HTTPErrorHandler](/guide/customization#http-error-handler) handles it.
|
90
website/content/guide/faq.md
Normal file
90
website/content/guide/faq.md
Normal file
@ -0,0 +1,90 @@
|
||||
+++
|
||||
title = "FAQ"
|
||||
description = "Frequently asked questions in Echo"
|
||||
[menu.side]
|
||||
name = "FAQ"
|
||||
parent = "guide"
|
||||
weight = 20
|
||||
+++
|
||||
|
||||
## FAQ
|
||||
|
||||
Q: **How to retrieve `*http.Request` and `http.ResponseWriter` from `echo.Context`?**
|
||||
|
||||
- `http.Request` > `c.Request().(*standard.Request).Request`
|
||||
- `http.ResponseWriter` > `c.Response()`
|
||||
|
||||
> Standard engine only
|
||||
|
||||
Q: **How to use standard handler `func(http.ResponseWriter, *http.Request)` with Echo?**
|
||||
|
||||
```go
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "Handler!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.GET("/", standard.WrapHandler(http.HandlerFunc(handler)))
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
||||
```
|
||||
|
||||
Q: **How to use fasthttp handler `func(fasthttp.RequestCtx)` with Echo?**
|
||||
|
||||
```go
|
||||
func handler(c *fh.RequestCtx) {
|
||||
io.WriteString(c, "Handler!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.GET("/", fasthttp.WrapHandler(handler))
|
||||
e.Run(fasthttp.New(":1323"))
|
||||
}
|
||||
```
|
||||
|
||||
Q: **How to use standard middleware `func(http.Handler) http.Handler` with Echo?**
|
||||
|
||||
```go
|
||||
func middleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
println("Middleware!")
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.Use(standard.WrapMiddleware(middleware))
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
||||
```
|
||||
|
||||
Q: **How to use fasthttp middleware `func(http.Handler) http.Handler` with Echo?**
|
||||
|
||||
```go
|
||||
func middleware(h fh.RequestHandler) fh.RequestHandler {
|
||||
return func(ctx *fh.RequestCtx) {
|
||||
println("Middleware!")
|
||||
h(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.Use(fasthttp.WrapMiddleware(middleware))
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "OK")
|
||||
})
|
||||
e.Run(fasthttp.New(":1323"))
|
||||
}
|
||||
```
|
||||
|
||||
<!-- ### Q: How to run Echo on specific IP and port?
|
||||
|
||||
```go
|
||||
``` -->
|
23
website/content/guide/installation.md
Normal file
23
website/content/guide/installation.md
Normal file
@ -0,0 +1,23 @@
|
||||
+++
|
||||
title = "Installation"
|
||||
description = "Installing Echo"
|
||||
[menu.side]
|
||||
name = "Installation"
|
||||
parent = "guide"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
## Installation
|
||||
|
||||
Echo is developed and tested using Go `1.6.x` and `1.7.x`
|
||||
|
||||
```sh
|
||||
$ go get -u github.com/labstack/echo
|
||||
```
|
||||
|
||||
> Ideally, you should rely on a [package manager](https://github.com/avelino/awesome-go#package-management) like glide or govendor to use a specific [version](https://github.com/labstack/echo/releases) of Echo.
|
||||
|
||||
### [Migrating from v1](/guide/migrating)
|
||||
|
||||
Echo follows [semantic versioning](http://semver.org) managed through GitHub releases.
|
||||
Specific version of Echo can be installed using a [package manager](https://github.com/avelino/awesome-go#package-management).
|
93
website/content/guide/migrating.md
Normal file
93
website/content/guide/migrating.md
Normal file
@ -0,0 +1,93 @@
|
||||
+++
|
||||
title = "Migrating"
|
||||
description = "Migrating from Echo v1 to v2"
|
||||
[menu.side]
|
||||
name = "Migrating"
|
||||
parent = "guide"
|
||||
weight = 2
|
||||
+++
|
||||
|
||||
## Migrating from v1
|
||||
|
||||
### Change Log
|
||||
|
||||
- Good news, 85% of the API remains the same.
|
||||
- `Engine` interface to abstract `HTTP` server implementation, allowing
|
||||
us to use HTTP servers beyond Go standard library. It currently supports standard and [fasthttp](https://github.com/valyala/fasthttp) server.
|
||||
- Context, Request and Response are converted to interfaces. [More...](https://github.com/labstack/echo/issues/146)
|
||||
- Handler signature is changed to `func (c echo.Context) error`.
|
||||
- Dropped auto wrapping of handler and middleware to enforce compile time check.
|
||||
- APIs to run middleware before or after the router, which doesn't require `Echo#Hook` API now.
|
||||
- Ability to define middleware at route level.
|
||||
- `Echo#HTTPError` exposed it's fields `Code` and `Message`.
|
||||
- Option to specify log format in logger middleware and default logger.
|
||||
|
||||
#### API
|
||||
|
||||
v1 | v2
|
||||
--- | ---
|
||||
`Context#Query()` | `Context#QueryParam()`
|
||||
`Context#Form()` | `Context#FormValue()`
|
||||
|
||||
### FAQ
|
||||
|
||||
Q. How to access original objects from interfaces?
|
||||
|
||||
A. Only if you need to...
|
||||
|
||||
```go
|
||||
// `*http.Request`
|
||||
c.Request().(*standard.Request).Request
|
||||
|
||||
// `*http.URL`
|
||||
c.Request().URL().(*standard.URL).URL
|
||||
|
||||
// Request `http.Header`
|
||||
c.Request().Header().(*standard.Header).Header
|
||||
|
||||
// `http.ResponseWriter`
|
||||
c.Response().(*standard.Response).ResponseWriter
|
||||
|
||||
// Response `http.Header`
|
||||
c.Response().Header().(*standard.Header).Header
|
||||
```
|
||||
|
||||
Q. How to use standard handler and middleware?
|
||||
|
||||
A.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
)
|
||||
|
||||
// Standard middleware
|
||||
func middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
println("standard middleware")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Standard handler
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
println("standard handler")
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.Use(standard.WrapMiddleware(middleware))
|
||||
e.GET("/", standard.WrapHandler(http.HandlerFunc(handler)))
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
||||
```
|
||||
|
||||
### Next?
|
||||
|
||||
- Browse through [recipes](/recipes/hello-world) freshly converted to v2.
|
||||
- Read documentation and dig into test cases.
|
98
website/content/guide/request.md
Normal file
98
website/content/guide/request.md
Normal file
@ -0,0 +1,98 @@
|
||||
+++
|
||||
title = "HTTP Request"
|
||||
description = "Handling HTTP request in Echo"
|
||||
[menu.side]
|
||||
name = "Request"
|
||||
parent = "guide"
|
||||
weight = 6
|
||||
+++
|
||||
|
||||
## Request
|
||||
|
||||
### Bind Request Body
|
||||
|
||||
To bind request body into a provided Go type use `Context#Bind(interface{})`.
|
||||
The default binder supports decoding application/json, application/xml and
|
||||
application/x-www-form-urlencoded payload based on Context-Type header.
|
||||
|
||||
*Example*
|
||||
|
||||
TODO
|
||||
|
||||
> Custom binder can be registered via `Echo#SetBinder(Binder)`
|
||||
|
||||
### Query Parameter
|
||||
|
||||
Query parameter can be retrieved by name using `Context#QueryParam(name string)`.
|
||||
|
||||
*Example*
|
||||
|
||||
```go
|
||||
e.GET("/users", func(c echo.Context) error {
|
||||
name := c.QueryParam("name")
|
||||
return c.String(http.StatusOK, name)
|
||||
})
|
||||
```
|
||||
|
||||
```sh
|
||||
$ curl -G -d "name=joe" http://localhost:1323/users
|
||||
```
|
||||
|
||||
### Form Parameter
|
||||
|
||||
Form parameter can be retrieved by name using `Context#FormValue(name string)`.
|
||||
|
||||
*Example*
|
||||
|
||||
```go
|
||||
e.POST("/users", func(c echo.Context) error {
|
||||
name := c.FormValue("name")
|
||||
return c.String(http.StatusOK, name)
|
||||
})
|
||||
```
|
||||
|
||||
```sh
|
||||
$ curl -d "name=joe" http://localhost:1323/users
|
||||
```
|
||||
|
||||
### Path Parameter
|
||||
|
||||
Registered path parameter can be retrieved either by name `Context#Param(name string) string`
|
||||
or by index `Context#P(i int) string`. Getting parameter by index gives a slightly
|
||||
better performance.
|
||||
|
||||
*Example*
|
||||
|
||||
```go
|
||||
e.GET("/users/:name", func(c echo.Context) error {
|
||||
// By name
|
||||
name := c.Param("name")
|
||||
|
||||
// By index
|
||||
name := c.P(0)
|
||||
|
||||
return c.String(http.StatusOK, name)
|
||||
})
|
||||
```
|
||||
|
||||
```sh
|
||||
$ curl http://localhost:1323/users/joe
|
||||
```
|
||||
|
||||
|
||||
### Handler Path
|
||||
|
||||
`Context#Path()` returns the registered path for the handler, it can be used in the
|
||||
middleware for logging purpose.
|
||||
|
||||
*Example*
|
||||
|
||||
```go
|
||||
e.Use(func(c echo.Context) error {
|
||||
println(c.Path()) // Prints `/users/:name`
|
||||
return nil
|
||||
})
|
||||
e.GET("/users/:name", func(c echo.Context) error) {
|
||||
return c.String(http.StatusOK, name)
|
||||
})
|
||||
```
|
115
website/content/guide/routing.md
Normal file
115
website/content/guide/routing.md
Normal file
@ -0,0 +1,115 @@
|
||||
+++
|
||||
title = "HTTP Routing"
|
||||
description = "Routing HTTP request in Echo"
|
||||
[menu.side]
|
||||
name = "Routing"
|
||||
parent = "guide"
|
||||
weight = 4
|
||||
+++
|
||||
|
||||
## Routing
|
||||
|
||||
Echo's router is [fast, optimized]({{< ref "index.md#performance">}}) and
|
||||
flexible. It's based on [radix tree](http://en.wikipedia.org/wiki/Radix_tree) data
|
||||
structure which makes route lookup really fast. Router leverages [sync pool](https://golang.org/pkg/sync/#Pool)
|
||||
to reuse memory and achieve zero dynamic memory allocation with no GC overhead.
|
||||
|
||||
Routes can be registered by specifying HTTP method, path and a matching handler.
|
||||
For example, code below registers a route for method `GET`, path `/hello` and a
|
||||
handler which sends `Hello, World!` HTTP response.
|
||||
|
||||
```go
|
||||
// Handler
|
||||
func hello(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Hello, World!")
|
||||
}
|
||||
|
||||
// Route
|
||||
e.GET("/hello", hello)
|
||||
```
|
||||
|
||||
You can use `Echo.Any(path string, h Handler)` to register a handler for all HTTP methods.
|
||||
If you want to register it for some methods use `Echo.Match(methods []string, path string, h Handler)`.
|
||||
|
||||
Echo defines handler function as `func(echo.Context) error` where `echo.Context` primarily
|
||||
holds HTTP request and response interfaces.
|
||||
|
||||
### Match-any
|
||||
|
||||
Matches zero or more characters in the path. For example, pattern `/users/*` will
|
||||
match:
|
||||
|
||||
- `/users/`
|
||||
- `/users/1`
|
||||
- `/users/1/files/1`
|
||||
- `/users/anything...`
|
||||
|
||||
### Path matching order
|
||||
|
||||
- Static
|
||||
- Param
|
||||
- Match any
|
||||
|
||||
#### Example
|
||||
|
||||
```go
|
||||
e.GET("/users/:id", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "/users/:id")
|
||||
})
|
||||
|
||||
e.GET("/users/new", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "/users/new")
|
||||
})
|
||||
|
||||
e.GET("/users/1/files/*", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "/users/1/files/*")
|
||||
})
|
||||
```
|
||||
|
||||
Above routes would resolve in the following order:
|
||||
|
||||
- `/users/new`
|
||||
- `/users/:id`
|
||||
- `/users/1/files/*`
|
||||
|
||||
> Routes can be written in any order.
|
||||
|
||||
### Group
|
||||
|
||||
`Echo#Group(prefix string, m ...Middleware) *Group`
|
||||
|
||||
Routes with common prefix can be grouped to define a new sub-router with optional
|
||||
middleware. In addition to specified middleware group also inherits parent middleware.
|
||||
To add middleware later in the group you can use `Group.Use(m ...Middleware)`.
|
||||
Groups can also be nested.
|
||||
|
||||
In the code below, we create an admin group which requires basic HTTP authentication
|
||||
for routes `/admin/*`.
|
||||
|
||||
```go
|
||||
g := e.Group("/admin")
|
||||
g.Use(middleware.BasicAuth(func(username, password string) bool {
|
||||
if username == "joe" && password == "secret" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}))
|
||||
```
|
||||
|
||||
### URI building
|
||||
|
||||
`Echo.URI` can be used to generate URI for any handler with specified path parameters.
|
||||
It's helpful to centralize all your URI patterns which ease in refactoring your
|
||||
application.
|
||||
|
||||
`e.URI(h, 1)` will generate `/users/1` for the route registered below
|
||||
|
||||
```go
|
||||
// Handler
|
||||
h := func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "OK")
|
||||
}
|
||||
|
||||
// Route
|
||||
e.GET("/users/:id", h)
|
||||
```
|
60
website/content/guide/static-files.md
Normal file
60
website/content/guide/static-files.md
Normal file
@ -0,0 +1,60 @@
|
||||
+++
|
||||
title = "Static Files"
|
||||
description = "Serving static files in Echo"
|
||||
[menu.side]
|
||||
name = "Static Files"
|
||||
parent = "guide"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
Images, JavaScript, CSS, PDF, Fonts and so on...
|
||||
|
||||
## Static Files
|
||||
|
||||
### [Using Static Middleware]({{< ref "middleware/static.md">}})
|
||||
|
||||
### Using `Echo#Static()`
|
||||
|
||||
`Echo#Static(prefix, root string)` registers a new route with path prefix to serve
|
||||
static files from the provided root directory.
|
||||
|
||||
*Usage 1*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Static("/static", "assets")
|
||||
```
|
||||
|
||||
Example above will serve any file from the assets directory for path `/static/*`. For example,
|
||||
a request to `/static/js/main.js` will fetch and serve `assets/js/main.js` file.
|
||||
|
||||
*Usage 2*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Static("/", "assets")
|
||||
```
|
||||
|
||||
Example above will serve any file from the assets directory for path `/*`. For example,
|
||||
a request to `/js/main.js` will fetch and serve `assets/js/main.js` file.
|
||||
|
||||
### Using `Echo#File()`
|
||||
|
||||
`Echo#File(path, file string)` registers a new route with path to serve a static
|
||||
file.
|
||||
|
||||
*Usage 1*
|
||||
|
||||
Serving an index page from `public/index.html`
|
||||
|
||||
```go
|
||||
e.File("/", "public/index.html")
|
||||
```
|
||||
|
||||
*Usage 2*
|
||||
|
||||
Serving a favicon from `images/favicon.ico`
|
||||
|
||||
```go
|
||||
e.File("/favicon.ico", "images/favicon.ico")
|
||||
```
|
60
website/content/guide/templates.md
Normal file
60
website/content/guide/templates.md
Normal file
@ -0,0 +1,60 @@
|
||||
+++
|
||||
title = "Templates"
|
||||
description = "How to use templates in Echo"
|
||||
[menu.side]
|
||||
name = "Templates"
|
||||
parent = "guide"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
## Templates
|
||||
|
||||
### Template Rendering
|
||||
|
||||
`Context#Render(code int, name string, data interface{}) error` renders a template
|
||||
with data and sends a text/html response with status code. Templates can be registered
|
||||
using `Echo.SetRenderer()`, allowing us to use any template engine.
|
||||
|
||||
Example below shows how to use Go `html/template`:
|
||||
|
||||
1. Implement `echo.Renderer` interface
|
||||
|
||||
```go
|
||||
type Template struct {
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
return t.templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
```
|
||||
|
||||
2. Pre-compile templates
|
||||
|
||||
`public/views/hello.html`
|
||||
|
||||
```html
|
||||
{{define "hello"}}Hello, {{.}}!{{end}}
|
||||
```
|
||||
|
||||
```go
|
||||
t := &Template{
|
||||
templates: template.Must(template.ParseGlob("public/views/*.html")),
|
||||
}
|
||||
```
|
||||
|
||||
3. Register templates
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.SetRenderer(t)
|
||||
e.GET("/hello", Hello)
|
||||
```
|
||||
|
||||
4. Render a template inside your handler
|
||||
|
||||
```go
|
||||
func Hello(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "hello", "World")
|
||||
}
|
||||
```
|
161
website/content/guide/testing.md
Normal file
161
website/content/guide/testing.md
Normal file
@ -0,0 +1,161 @@
|
||||
+++
|
||||
title = "Testing"
|
||||
description = "Testing handler and middleware in Echo"
|
||||
[menu.side]
|
||||
name = "Testing"
|
||||
parent = "guide"
|
||||
weight = 9
|
||||
+++
|
||||
|
||||
## Testing
|
||||
|
||||
### Testing Handler
|
||||
|
||||
`GET` `/users/:id`
|
||||
|
||||
Handler below retrieves user by id from the database. If user is not found it returns
|
||||
`404` error with a message.
|
||||
|
||||
#### CreateUser
|
||||
|
||||
`POST` `/users`
|
||||
|
||||
- Accepts JSON payload
|
||||
- On success `201 - Created`
|
||||
- On error `500 - Internal Server Error`
|
||||
|
||||
#### GetUser
|
||||
|
||||
`GET` `/users/:email`
|
||||
|
||||
- On success `200 - OK`
|
||||
- On error `404 - Not Found` if user is not found otherwise `500 - Internal Server Error`
|
||||
|
||||
`handler.go`
|
||||
|
||||
```go
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
User struct {
|
||||
Name string `json:"name" form:"name"`
|
||||
Email string `json:"email" form:"email"`
|
||||
}
|
||||
handler struct {
|
||||
db map[string]*User
|
||||
}
|
||||
)
|
||||
|
||||
func (h *handler) createUser(c echo.Context) error {
|
||||
u := new(User)
|
||||
if err := c.Bind(u); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusCreated, u)
|
||||
}
|
||||
|
||||
func (h *handler) getUser(c echo.Context) error {
|
||||
email := c.Param("email")
|
||||
user := h.db[email]
|
||||
if user == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "user not found")
|
||||
}
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
```
|
||||
|
||||
`handler_test.go`
|
||||
|
||||
```go
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
mockDB = map[string]*User{
|
||||
"jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"},
|
||||
}
|
||||
userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}`
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
// Setup
|
||||
e := echo.New()
|
||||
req, err := http.NewRequest(echo.POST, "/users", strings.NewReader(userJSON))
|
||||
if assert.NoError(t, err) {
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(standard.NewRequest(req, e.Logger()), standard.NewResponse(rec, e.Logger()))
|
||||
h := &handler{mockDB}
|
||||
|
||||
// Assertions
|
||||
if assert.NoError(t, h.createUser(c)) {
|
||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||
assert.Equal(t, userJSON, rec.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
// Setup
|
||||
e := echo.New()
|
||||
req := new(http.Request)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(standard.NewRequest(req, e.Logger()), standard.NewResponse(rec, e.Logger()))
|
||||
c.SetPath("/users/:email")
|
||||
c.SetParamNames("email")
|
||||
c.SetParamValues("jon@labstack.com")
|
||||
h := &handler{mockDB}
|
||||
|
||||
// Assertions
|
||||
if assert.NoError(t, h.getUser(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, userJSON, rec.Body.String())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Using Form Payload
|
||||
|
||||
```go
|
||||
f := make(url.Values)
|
||||
f.Set("name", "Jon Snow")
|
||||
f.Set("email", "jon@labstack.com")
|
||||
req, err := http.NewRequest(echo.POST, "/", strings.NewReader(f.Encode()))
|
||||
```
|
||||
|
||||
#### Setting Path Params
|
||||
|
||||
```go
|
||||
c.SetParamNames("id", "email")
|
||||
c.SetParamValues("1", "jon@labstack.com")
|
||||
```
|
||||
|
||||
#### Setting Query Params
|
||||
|
||||
```go
|
||||
q := make(url.Values)
|
||||
q.Set("email", "jon@labstack.com")
|
||||
req, err := http.NewRequest(echo.POST, "/?"+q.Encode(), nil)
|
||||
```
|
||||
|
||||
### Testing Middleware
|
||||
|
||||
*TBD*
|
||||
|
||||
You can looking to built-in middleware [test cases](https://github.com/labstack/echo/tree/master/middleware).
|
291
website/content/index.md
Normal file
291
website/content/index.md
Normal file
@ -0,0 +1,291 @@
|
||||
+++
|
||||
title = "Index"
|
||||
+++
|
||||
|
||||
# Fast and unfancy HTTP server framework for Go (Golang).
|
||||
|
||||
## Feature Overview
|
||||
|
||||
- Optimized HTTP router which smartly prioritize routes
|
||||
- Build robust and scalable RESTful APIs
|
||||
- Run with standard HTTP server or FastHTTP server
|
||||
- Group APIs
|
||||
- Extensible middleware framework
|
||||
- Define middleware at root, group or route level
|
||||
- Data binding for JSON, XML and form payload
|
||||
- Handy functions to send variety of HTTP responses
|
||||
- Centralized HTTP error handling
|
||||
- Template rendering with any template engine
|
||||
- Define your format for the logger
|
||||
- Highly customizable
|
||||
|
||||
|
||||
## Performance
|
||||
|
||||
- Environment:
|
||||
- Go 1.6
|
||||
- wrk 4.0.0
|
||||
- 2 GB, 2 Core (DigitalOcean)
|
||||
- Test Suite: https://github.com/vishr/web-framework-benchmark
|
||||
- Date: 4/4/2016
|
||||
|
||||
<img style="width: 100%;" src="https://i.imgur.com/fZVnK52.png" alt="Performance">
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
$ go get -u github.com/labstack/echo
|
||||
```
|
||||
|
||||
### Hello, World!
|
||||
|
||||
Create `server.go`
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/engine/standard"
|
||||
)
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Hello, World!")
|
||||
})
|
||||
e.Run(standard.New(":1323"))
|
||||
}
|
||||
```
|
||||
|
||||
Start server
|
||||
|
||||
```sh
|
||||
$ go run server.go
|
||||
```
|
||||
|
||||
Browse to [http://localhost:1323](http://localhost:1323) and you should see
|
||||
Hello, World! on the page.
|
||||
|
||||
### Routing
|
||||
|
||||
```go
|
||||
e.POST("/users", saveUser)
|
||||
e.GET("/users/:id", getUser)
|
||||
e.PUT("/users/:id", updateUser)
|
||||
e.DELETE("/users/:id", deleteUser)
|
||||
```
|
||||
|
||||
### Path Parameters
|
||||
|
||||
```go
|
||||
func getUser(c echo.Context) error {
|
||||
// User ID from path `users/:id`
|
||||
id := c.Param("id")
|
||||
}
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
|
||||
`/show?team=x-men&member=wolverine`
|
||||
|
||||
```go
|
||||
func show(c echo.Context) error {
|
||||
// Get team and member from the query string
|
||||
team := c.QueryParam("team")
|
||||
member := c.QueryParam("member")
|
||||
}
|
||||
```
|
||||
|
||||
### Form `application/x-www-form-urlencoded`
|
||||
|
||||
`POST` `/save`
|
||||
|
||||
name | value
|
||||
:--- | :---
|
||||
name | Joe Smith
|
||||
email | joe@labstack.com
|
||||
|
||||
```go
|
||||
func save(c echo.Context) error {
|
||||
// Get name and email
|
||||
name := c.FormValue("name")
|
||||
email := c.FormValue("email")
|
||||
}
|
||||
```
|
||||
|
||||
### Form `multipart/form-data`
|
||||
|
||||
`POST` `/save`
|
||||
|
||||
name | value
|
||||
:--- | :---
|
||||
name | Joe Smith
|
||||
email | joe@labstack.com
|
||||
avatar | avatar
|
||||
|
||||
```go
|
||||
func save(c echo.Context) error {
|
||||
// Get name and email
|
||||
name := c.FormValue("name")
|
||||
email := c.FormValue("email")
|
||||
// Get avatar
|
||||
avatar, err := c.FormFile("avatar")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Source
|
||||
src, err := avatar.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// Destination
|
||||
dst, err := os.Create(avatar.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
// Copy
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, "<b>Thank you!</b>")
|
||||
}
|
||||
```
|
||||
|
||||
### Handling Request
|
||||
|
||||
- Bind `JSON` or `XML` or `form` payload into Go struct based on `Content-Type` request header.
|
||||
- Render response as `JSON` or `XML` with status code.
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Name string `json:"name" xml:"name" form:"name"`
|
||||
Email string `json:"email" xml:"email" form:"email"`
|
||||
}
|
||||
|
||||
e.POST("/users", func(c echo.Context) error {
|
||||
u := new(User)
|
||||
if err := c.Bind(u); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusCreated, u)
|
||||
// or
|
||||
// return c.XML(http.StatusCreated, u)
|
||||
})
|
||||
```
|
||||
|
||||
### Static Content
|
||||
|
||||
Server any file from static directory for path `/static/*`.
|
||||
|
||||
```go
|
||||
e.Static("/static", "static")
|
||||
```
|
||||
|
||||
##### [Learn More](https://echo.labstack.com/guide/static-files)
|
||||
|
||||
### [Template Rendering](https://echo.labstack.com/guide/templates)
|
||||
|
||||
### Middleware
|
||||
|
||||
```go
|
||||
// Root level middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// Group level middleware
|
||||
g := e.Group("/admin")
|
||||
g.Use(middleware.BasicAuth(func(username, password string) bool {
|
||||
if username == "joe" && password == "secret" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}))
|
||||
|
||||
// Route level middleware
|
||||
track := func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
println("request to /users")
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
e.GET("/users", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "/users")
|
||||
}, track)
|
||||
```
|
||||
|
||||
#### Built-in Middleware
|
||||
|
||||
Middleware | Description
|
||||
:--- | :---
|
||||
[BodyLimit]({{< ref "middleware/body-limit.md">}}) | Limit request body
|
||||
[Logger]({{< ref "middleware/logger.md">}}) | Log HTTP requests
|
||||
[Recover]({{< ref "middleware/recover.md">}}) | Recover from panics
|
||||
[Gzip]({{< ref "middleware/gzip.md">}}) | Send gzip HTTP response
|
||||
[BasicAuth]({{< ref "middleware/basic-auth.md">}}) | HTTP basic authentication
|
||||
[JWTAuth]({{< ref "middleware/jwt.md">}}) | JWT authentication
|
||||
[Secure]({{< ref "middleware/secure.md">}}) | Protection against attacks
|
||||
[CORS]({{< ref "middleware/cors.md">}}) | Cross-Origin Resource Sharing
|
||||
[CSRF]({{< ref "middleware/csrf.md">}}) | Cross-Site Request Forgery
|
||||
[Static]({{< ref "middleware/static.md">}}) | Serve static files
|
||||
[HTTPSRedirect]({{< ref "middleware/redirect.md#httpsredirect-middleware">}}) | Redirect HTTP requests to HTTPS
|
||||
[HTTPSWWWRedirect]({{< ref "middleware/redirect.md#httpswwwredirect-middleware">}}) | Redirect HTTP requests to WWW HTTPS
|
||||
[WWWRedirect]({{< ref "middleware/redirect.md#wwwredirect-middleware">}}) | Redirect non WWW requests to WWW
|
||||
[NonWWWRedirect]({{< ref "middleware/redirect.md#nonwwwredirect-middleware">}}) | Redirect WWW requests to non WWW
|
||||
[AddTrailingSlash]({{< ref "middleware/trailing-slash.md#addtrailingslash-middleware">}}) | Add trailing slash to the request URI
|
||||
[RemoveTrailingSlash]({{< ref "middleware/trailing-slash.md#removetrailingslash-middleware">}}) | Remove trailing slash from the request URI
|
||||
[MethodOverride]({{< ref "middleware/method-override.md">}}) | Override request method
|
||||
|
||||
#### Third-party Middleware
|
||||
|
||||
Middleware | Description
|
||||
:--- | :---
|
||||
[echoperm](https://github.com/xyproto/echoperm) | Keeping track of users, login states and permissions.
|
||||
[echopprof](https://github.com/mtojek/echopprof) | Adapt net/http/pprof to labstack/echo.
|
||||
|
||||
##### [Learn More](https://echo.labstack.com/middleware/overview)
|
||||
|
||||
### Next
|
||||
|
||||
- Head over to [guide](https://echo.labstack.com/guide/installation)
|
||||
- Browse [recipes](https://echo.labstack.com/recipes/hello-world)
|
||||
|
||||
### Need help?
|
||||
|
||||
- [Hop on to chat](https://gitter.im/labstack/echo)
|
||||
- [Open an issue](https://github.com/labstack/echo/issues/new)
|
||||
|
||||
## Support Echo
|
||||
|
||||
- ☆ the project
|
||||
- [Donate](https://echo.labstack.com/support-echo)
|
||||
- 🌐 spread the word
|
||||
- [Contribute](#contribute:d680e8a854a7cbad6d490c445cba2eba) to the project
|
||||
|
||||
## Contribute
|
||||
|
||||
**Use issues for everything**
|
||||
|
||||
- Report issues
|
||||
- Discuss on chat before sending a pull request
|
||||
- Suggest new features or enhancements
|
||||
- Improve/fix documentation
|
||||
|
||||
## Credits
|
||||
|
||||
- [Vishal Rana](https://github.com/vishr) - Author
|
||||
- [Nitin Rana](https://github.com/nr17) - Consultant
|
||||
- [Contributors](https://github.com/labstack/echo/graphs/contributors)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/labstack/echo/blob/master/LICENSE)
|
59
website/content/middleware/basic-auth.md
Normal file
59
website/content/middleware/basic-auth.md
Normal file
@ -0,0 +1,59 @@
|
||||
+++
|
||||
title = "BasicAuth Middleware"
|
||||
description = "Basic auth middleware for Echo"
|
||||
[menu.side]
|
||||
name = "BasicAuth"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## BasicAuth Middleware
|
||||
|
||||
BasicAuth middleware provides an HTTP basic authentication.
|
||||
|
||||
- For valid credentials it calls the next handler.
|
||||
- For invalid credentials, it sends "401 - Unauthorized" response.
|
||||
- For empty or invalid `Authorization` header, it sends "400 - Bad Request" response.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.BasicAuth(func(username, password string) bool {
|
||||
if username == "joe" && password == "secret" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}))
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{},
|
||||
}))
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
BasicAuthConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Validator is a function to validate BasicAuth credentials.
|
||||
// Required.
|
||||
Validator BasicAuthValidator
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultBasicAuthConfig = BasicAuthConfig{
|
||||
Skipper: defaultSkipper,
|
||||
}
|
||||
```
|
55
website/content/middleware/body-limit.md
Normal file
55
website/content/middleware/body-limit.md
Normal file
@ -0,0 +1,55 @@
|
||||
+++
|
||||
title = "BodyLimit Middleware"
|
||||
description = "Body limit middleware for Echo"
|
||||
[menu.side]
|
||||
name = "BodyLimit"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## BodyLimit Middleware
|
||||
|
||||
BodyLimit middleware sets the maximum allowed size for a request body, if the
|
||||
size exceeds the configured limit, it sends "413 - Request Entity Too Large"
|
||||
response. The body limit is determined based on both `Content-Length` request
|
||||
header and actual content read, which makes it super secure.
|
||||
|
||||
Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M,
|
||||
G, T or P.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.BodyLimit("2M"))
|
||||
```
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.BodyLimitWithConfig(middleware.BodyLimitConfig{},
|
||||
}))
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
BodyLimitConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Maximum allowed size for a request body, it can be specified
|
||||
// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
|
||||
Limit string `json:"limit"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultBodyLimitConfig = BodyLimitConfig{
|
||||
Skipper: defaultSkipper,
|
||||
}
|
||||
```
|
80
website/content/middleware/cors.md
Normal file
80
website/content/middleware/cors.md
Normal file
@ -0,0 +1,80 @@
|
||||
+++
|
||||
title = "CORS Middleware"
|
||||
description = "CORS middleware for Echo"
|
||||
[menu.side]
|
||||
name = "CORS"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## CORS Middleware
|
||||
|
||||
CORS middleware implements [CORS](http://www.w3.org/TR/cors) specification.
|
||||
CORS gives web servers cross-domain access controls, which enable secure cross-domain
|
||||
data transfers.
|
||||
|
||||
*Usage*
|
||||
|
||||
`e.Use(middleware.CORS())`
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: []string{"https://labstack.com", "https://labstack.net"},
|
||||
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
|
||||
}))
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
CORSConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// AllowOrigin defines a list of origins that may access the resource.
|
||||
// Optional. Default value []string{"*"}.
|
||||
AllowOrigins []string `json:"allow_origins"`
|
||||
|
||||
// AllowMethods defines a list methods allowed when accessing the resource.
|
||||
// This is used in response to a preflight request.
|
||||
// Optional. Default value DefaultCORSConfig.AllowMethods.
|
||||
AllowMethods []string `json:"allow_methods"`
|
||||
|
||||
// AllowHeaders defines a list of request headers that can be used when
|
||||
// making the actual request. This in response to a preflight request.
|
||||
// Optional. Default value []string{}.
|
||||
AllowHeaders []string `json:"allow_headers"`
|
||||
|
||||
// AllowCredentials indicates whether or not the response to the request
|
||||
// can be exposed when the credentials flag is true. When used as part of
|
||||
// a response to a preflight request, this indicates whether or not the
|
||||
// actual request can be made using credentials.
|
||||
// Optional. Default value false.
|
||||
AllowCredentials bool `json:"allow_credentials"`
|
||||
|
||||
// ExposeHeaders defines a whitelist headers that clients are allowed to
|
||||
// access.
|
||||
// Optional. Default value []string{}.
|
||||
ExposeHeaders []string `json:"expose_headers"`
|
||||
|
||||
// MaxAge indicates how long (in seconds) the results of a preflight request
|
||||
// can be cached.
|
||||
// Optional. Default value 0.
|
||||
MaxAge int `json:"max_age"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultCORSConfig = CORSConfig{
|
||||
Skipper: defaultSkipper,
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
|
||||
}
|
||||
```
|
107
website/content/middleware/csrf.md
Normal file
107
website/content/middleware/csrf.md
Normal file
@ -0,0 +1,107 @@
|
||||
+++
|
||||
title = "CSRF Middleware"
|
||||
description = "CSRF middleware for Echo"
|
||||
[menu.side]
|
||||
name = "CSRF"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## CSRF Middleware
|
||||
|
||||
Cross-site request forgery, also known as one-click attack or session riding and
|
||||
abbreviated as CSRF (sometimes pronounced sea-surf) or XSRF, is a type of malicious
|
||||
exploit of a website where unauthorized commands are transmitted from a user that
|
||||
the website trusts.
|
||||
|
||||
*Usage*
|
||||
|
||||
`e.Use(middleware.CSRF())`
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
|
||||
TokenLookup: "header:X-XSRF-TOKEN",
|
||||
}))
|
||||
```
|
||||
|
||||
Example above uses `X-XSRF-TOKEN` request header to extract CSRF token.
|
||||
|
||||
### Accessing CSRF Token
|
||||
|
||||
#### Server-side
|
||||
|
||||
CSRF token can be accessed from `Echo#Context` using `ContextKey` and passed to
|
||||
the client via template.
|
||||
|
||||
#### Client-side
|
||||
|
||||
CSRF token can be accessed from CSRF cookie.
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
// CSRFConfig defines the config for CSRF middleware.
|
||||
CSRFConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// TokenLength is the length of the generated token.
|
||||
TokenLength uint8 `json:"token_length"`
|
||||
// Optional. Default value 32.
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<key>" that is used
|
||||
// to extract token from the request.
|
||||
// Optional. Default value "header:X-CSRF-Token".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "form:<name>"
|
||||
// - "query:<name>"
|
||||
TokenLookup string `json:"token_lookup"`
|
||||
|
||||
// Context key to store generated CSRF token into context.
|
||||
// Optional. Default value "csrf".
|
||||
ContextKey string `json:"context_key"`
|
||||
|
||||
// Name of the CSRF cookie. This cookie will store CSRF token.
|
||||
// Optional. Default value "csrf".
|
||||
CookieName string `json:"cookie_name"`
|
||||
|
||||
// Domain of the CSRF cookie.
|
||||
// Optional. Default value none.
|
||||
CookieDomain string `json:"cookie_domain"`
|
||||
|
||||
// Path of the CSRF cookie.
|
||||
// Optional. Default value none.
|
||||
CookiePath string `json:"cookie_path"`
|
||||
|
||||
// Max age (in seconds) of the CSRF cookie.
|
||||
// Optional. Default value 86400 (24hr).
|
||||
CookieMaxAge int `json:"cookie_max_age"`
|
||||
|
||||
// Indicates if CSRF cookie is secure.
|
||||
// Optional. Default value false.
|
||||
CookieSecure bool `json:"cookie_secure"`
|
||||
|
||||
// Indicates if CSRF cookie is HTTP only.
|
||||
// Optional. Default value false.
|
||||
CookieHTTPOnly bool `json:"cookie_http_only"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultCSRFConfig = CSRFConfig{
|
||||
Skipper: defaultSkipper,
|
||||
TokenLength: 32,
|
||||
TokenLookup: "header:" + echo.HeaderXCSRFToken,
|
||||
ContextKey: "csrf",
|
||||
CookieName: "_csrf",
|
||||
CookieMaxAge: 86400,
|
||||
}
|
||||
```
|
49
website/content/middleware/gzip.md
Normal file
49
website/content/middleware/gzip.md
Normal file
@ -0,0 +1,49 @@
|
||||
+++
|
||||
title = "Gzip Middleware"
|
||||
description = "Gzip middleware for Echo"
|
||||
[menu.side]
|
||||
name = "Gzip"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## Gzip Middleware
|
||||
|
||||
Gzip middleware compresses HTTP response using gzip compression scheme.
|
||||
|
||||
*Usage*
|
||||
|
||||
`e.Use(middleware.Gzip())`
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
|
||||
Level: 5,
|
||||
}))
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
GzipConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Gzip compression level.
|
||||
// Optional. Default value -1.
|
||||
Level int `json:"level"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultGzipConfig = GzipConfig{
|
||||
Skipper: defaultSkipper,
|
||||
Level: -1,
|
||||
}
|
||||
```
|
81
website/content/middleware/jwt.md
Normal file
81
website/content/middleware/jwt.md
Normal file
@ -0,0 +1,81 @@
|
||||
+++
|
||||
title = "JWT Middleware"
|
||||
description = "JWT middleware for Echo"
|
||||
[menu.side]
|
||||
name = "JWT"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## JWT Middleware
|
||||
|
||||
JWT provides a JSON Web Token (JWT) authentication middleware.
|
||||
|
||||
- For valid token, it sets the user in context and calls next handler.
|
||||
- For invalid token, it sends "401 - Unauthorized" response.
|
||||
- For empty or invalid `Authorization` header, it sends "400 - Bad Request".
|
||||
|
||||
*Usage*
|
||||
|
||||
`e.Use(middleware.JWT([]byte("secret"))`
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.JWTWithConfig(middleware.JWTConfig{
|
||||
SigningKey: []byte("secret"),
|
||||
TokenLookup: "query:token",
|
||||
}))
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
// JWTConfig defines the config for JWT middleware.
|
||||
JWTConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Signing key to validate token.
|
||||
// Required.
|
||||
SigningKey interface{} `json:"signing_key"`
|
||||
|
||||
// Signing method, used to check token signing method.
|
||||
// Optional. Default value HS256.
|
||||
SigningMethod string `json:"signing_method"`
|
||||
|
||||
// Context key to store user information from the token into context.
|
||||
// Optional. Default value "user".
|
||||
ContextKey string `json:"context_key"`
|
||||
|
||||
// Claims are extendable claims data defining token content.
|
||||
// Optional. Default value jwt.MapClaims
|
||||
Claims jwt.Claims
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" that is used
|
||||
// to extract token from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "query:<name>"
|
||||
// - "cookie:<name>"
|
||||
TokenLookup string `json:"token_lookup"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultJWTConfig = JWTConfig{
|
||||
Skipper: defaultSkipper,
|
||||
SigningMethod: AlgorithmHS256,
|
||||
ContextKey: "user",
|
||||
TokenLookup: "header:" + echo.HeaderAuthorization,
|
||||
Claims: jwt.MapClaims{},
|
||||
}
|
||||
```
|
||||
|
||||
### [Recipe]({{< ref "recipes/jwt.md">}})
|
89
website/content/middleware/logger.md
Normal file
89
website/content/middleware/logger.md
Normal file
@ -0,0 +1,89 @@
|
||||
+++
|
||||
title = "Logger Middleware"
|
||||
description = "Logger middleware for Echo"
|
||||
[menu.side]
|
||||
name = "Logger"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## Logger Middleware
|
||||
|
||||
Logger middleware logs the information about each HTTP request.
|
||||
|
||||
*Usage*
|
||||
|
||||
`e.Use(middleware.Logger())`
|
||||
|
||||
*Sample Output*
|
||||
|
||||
```js
|
||||
{"time":"2016-05-10T07:02:25-07:00","remote_ip":"::1","method":"GET","uri":"/","status":200, "latency":55653,"latency_human":"55.653µs","rx_bytes":0,"tx_bytes":13}
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||
Format: "method=${method}, uri=${uri}, status=${status}\n",
|
||||
}))
|
||||
```
|
||||
|
||||
Example above uses a `Format` which logs request method and request URI.
|
||||
|
||||
*Sample Output*
|
||||
|
||||
```sh
|
||||
method=GET, uri=/hello, status=200
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
LoggerConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Log format which can be constructed using the following tags:
|
||||
//
|
||||
// - time_rfc3339
|
||||
// - id (Request ID - Not implemented)
|
||||
// - remote_ip
|
||||
// - uri
|
||||
// - host
|
||||
// - method
|
||||
// - path
|
||||
// - referer
|
||||
// - user_agent
|
||||
// - status
|
||||
// - latency (In microseconds)
|
||||
// - latency_human (Human readable)
|
||||
// - bytes_in (Bytes received)
|
||||
// - bytes_out (Bytes sent)
|
||||
//
|
||||
// Example "${remote_ip} ${status}"
|
||||
//
|
||||
// Optional. Default value DefaultLoggerConfig.Format.
|
||||
Format string `json:"format"`
|
||||
|
||||
// Output is a writer where logs are written.
|
||||
// Optional. Default value os.Stdout.
|
||||
Output io.Writer
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultLoggerConfig = LoggerConfig{
|
||||
Skipper: defaultSkipper,
|
||||
Format: `{"time":"${time_rfc3339}","remote_ip":"${remote_ip}",` +
|
||||
`"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
|
||||
`"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
|
||||
`"bytes_out":${bytes_out}}` + "\n",
|
||||
Output: os.Stdout,
|
||||
}
|
||||
```
|
52
website/content/middleware/method-override.md
Normal file
52
website/content/middleware/method-override.md
Normal file
@ -0,0 +1,52 @@
|
||||
+++
|
||||
title = "MethodOverride Middleware"
|
||||
description = "Method override middleware for Echo"
|
||||
[menu.side]
|
||||
name = "MethodOverride"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## MethodOverride Middleware
|
||||
|
||||
MethodOverride middleware checks for the overridden method from the request and
|
||||
uses it instead of the original method.
|
||||
|
||||
For security reasons, only `POST` method can be overridden.
|
||||
|
||||
*Usage*
|
||||
|
||||
`e.Pre(middleware.MethodOverride())`
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{
|
||||
Getter: middleware.MethodFromForm("_method"),
|
||||
}))
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
MethodOverrideConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Getter is a function that gets overridden method from the request.
|
||||
// Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
|
||||
Getter MethodOverrideGetter
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultMethodOverrideConfig = MethodOverrideConfig{
|
||||
Skipper: defaultSkipper,
|
||||
Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride),
|
||||
}
|
||||
```
|
105
website/content/middleware/overview.md
Normal file
105
website/content/middleware/overview.md
Normal file
@ -0,0 +1,105 @@
|
||||
+++
|
||||
title = "Overview"
|
||||
description = "Overview of Echo middleware"
|
||||
[menu.side]
|
||||
name = "Overview"
|
||||
parent = "middleware"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
## Middleware Overview
|
||||
|
||||
Middleware is a function chained in the HTTP request-response cycle with access
|
||||
to `Echo#Context` which it uses to perform a specific action, for example, logging
|
||||
every request or limiting the number of requests.
|
||||
|
||||
Handler is processed in the end after all middleware are finished executing.
|
||||
|
||||
### Middleware Levels
|
||||
|
||||
#### Root Level (Before router)
|
||||
|
||||
`Echo#Pre()` can be used to register a middleware which is executed before router
|
||||
processes the request. It is helpful to make any changes to the request properties,
|
||||
for example, adding or removing a trailing slash from the path so it matches the
|
||||
route.
|
||||
|
||||
The following built-in middleware should be registered at this level:
|
||||
|
||||
- HTTPSRedirect
|
||||
- HTTPSWWWRedirect
|
||||
- WWWRedirect
|
||||
- NonWWWRedirect
|
||||
- AddTrailingSlash
|
||||
- RemoveTrailingSlash
|
||||
- MethodOverride
|
||||
|
||||
> As router has not processed the request, middleware at this level won't
|
||||
have access to any path related API from `echo.Context`.
|
||||
|
||||
#### Root Level (After router)
|
||||
|
||||
Most of the time you will register a middleware at this level using `Echo#Use()`.
|
||||
This middleware is executed after router processes the request and has full access
|
||||
to `echo.Context` API.
|
||||
|
||||
The following built-in middleware should be registered at this level:
|
||||
|
||||
- BodyLimit
|
||||
- Logger
|
||||
- Gzip
|
||||
- Recover
|
||||
- BasicAuth
|
||||
- JWTAuth
|
||||
- Secure
|
||||
- CORS
|
||||
- Static
|
||||
|
||||
#### Group Level
|
||||
|
||||
When creating a new group, you can register middleware just for that group. For
|
||||
example, you can have an admin group which is secured by registering a BasicAuth
|
||||
middleware for it.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
admin := e.Group("/admin", middleware.BasicAuth())
|
||||
```
|
||||
|
||||
You can also add a middleware after creating a group via `admin.Use()`.
|
||||
|
||||
#### Route Level
|
||||
|
||||
When defining a new route, you can optionally register middleware just for it.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.GET("/", <Handler>, <Middleware...>)
|
||||
```
|
||||
|
||||
### Skipping Middleware
|
||||
|
||||
There are cases when you would like to skip a middleware based on some condition,
|
||||
for that each middleware has an option to define a function `Skipper func(c echo.Context) bool`.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||
Skipper: func(c echo.Context) bool {
|
||||
if strings.HasPrefix(c.Request().Host(), "localhost") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
Example above skips Logger middleware when request host starts with localhost.
|
||||
|
||||
### [Writing Custom Middleware]({{< ref "recipes/middleware.md">}})
|
65
website/content/middleware/recover.md
Normal file
65
website/content/middleware/recover.md
Normal file
@ -0,0 +1,65 @@
|
||||
+++
|
||||
title = "Recover Middleware"
|
||||
description = "Recover middleware for Echo"
|
||||
[menu.side]
|
||||
name = "Recover"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## Recover Middleware
|
||||
|
||||
Recover middleware recovers from panics anywhere in the chain, prints stack trace
|
||||
and handles the control to the centralized
|
||||
[HTTPErrorHandler]({{< ref "guide/customization.md#http-error-handler">}}).
|
||||
|
||||
*Usage*
|
||||
|
||||
`e.Use(middleware.Recover())`
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
|
||||
StackSize: 1 << 10, // 1 KB
|
||||
}))
|
||||
```
|
||||
|
||||
Example above uses a `StackSize` of 1 KB and default values for `DisableStackAll`
|
||||
and `DisablePrintStack`.
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
RecoverConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Size of the stack to be printed.
|
||||
// Optional. Default value 4KB.
|
||||
StackSize int `json:"stack_size"`
|
||||
|
||||
// DisableStackAll disables formatting stack traces of all other goroutines
|
||||
// into buffer after the trace for the current goroutine.
|
||||
// Optional. Default value false.
|
||||
DisableStackAll bool `json:"disable_stack_all"`
|
||||
|
||||
// DisablePrintStack disables printing stack trace.
|
||||
// Optional. Default value as false.
|
||||
DisablePrintStack bool `json:"disable_print_stack"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultRecoverConfig = RecoverConfig{
|
||||
Skipper: defaultSkipper,
|
||||
StackSize: 4 << 10, // 4 KB
|
||||
DisableStackAll: false,
|
||||
DisablePrintStack: false,
|
||||
}
|
||||
```
|
104
website/content/middleware/redirect.md
Normal file
104
website/content/middleware/redirect.md
Normal file
@ -0,0 +1,104 @@
|
||||
+++
|
||||
title = "Redirect Middleware"
|
||||
description = "Redirect middleware for Echo"
|
||||
[menu.side]
|
||||
name = "Redirect"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## HTTPSRedirect Middleware
|
||||
|
||||
HTTPSRedirect middleware redirects http requests to https.
|
||||
For example, http://labstack.com will be redirected to https://labstack.com.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Pre(middleware.HTTPSRedirect())
|
||||
```
|
||||
|
||||
## HTTPSWWWRedirect Middleware
|
||||
|
||||
HTTPSWWWRedirect redirects http requests to www https.
|
||||
For example, http://labstack.com will be redirected to https://www.labstack.com.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Pre(middleware.HTTPSWWWRedirect())
|
||||
```
|
||||
|
||||
## HTTPSNonWWWRedirect Middleware
|
||||
|
||||
HTTPSNonWWWRedirect redirects http requests to https non www.
|
||||
For example, http://www.labstack.com will be redirect to https://labstack.com.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Pre(HTTPSNonWWWRedirect())
|
||||
```
|
||||
|
||||
## WWWRedirect Middleware
|
||||
|
||||
WWWRedirect redirects non www requests to www.
|
||||
|
||||
For example, http://labstack.com will be redirected to http://www.labstack.com.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Pre(middleware.WWWRedirect())
|
||||
```
|
||||
|
||||
## NonWWWRedirect Middleware
|
||||
|
||||
NonWWWRedirect redirects www requests to non www.
|
||||
For example, http://www.labstack.com will be redirected to http://labstack.com.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Pre(middleware.NonWWWRedirect())
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.HTTPSRedirectWithConfig(middleware.RedirectConfig{
|
||||
Code: http.StatusTemporaryRedirect,
|
||||
}))
|
||||
```
|
||||
|
||||
Example above will redirect the request HTTP to HTTPS with status code `307 - StatusTemporaryRedirect`.
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
RedirectConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Status code to be used when redirecting the request.
|
||||
// Optional. Default value http.StatusMovedPermanently.
|
||||
Code int `json:"code"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultRedirectConfig = RedirectConfig{
|
||||
Skipper: defaultSkipper,
|
||||
Code: http.StatusMovedPermanently,
|
||||
}
|
||||
```
|
98
website/content/middleware/secure.md
Normal file
98
website/content/middleware/secure.md
Normal file
@ -0,0 +1,98 @@
|
||||
+++
|
||||
title = "Secure Middleware"
|
||||
description = "Secure middleware for Echo"
|
||||
[menu.side]
|
||||
name = "Secure"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## Secure Middleware
|
||||
|
||||
Secure middleware provides protection against cross-site scripting (XSS) attack,
|
||||
content type sniffing, clickjacking, insecure connection and other code injection
|
||||
attacks.
|
||||
|
||||
*Usage*
|
||||
|
||||
`e.Use(middleware.Secure())`
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
|
||||
XSSProtection: "",
|
||||
ContentTypeNosniff: "",
|
||||
XFrameOptions: "",
|
||||
HSTSMaxAge: 3600,
|
||||
ContentSecurityPolicy: "default-src 'self'",
|
||||
}))
|
||||
```
|
||||
|
||||
Passing empty `XSSProtection`, `ContentTypeNosniff`, `XFrameOptions` or `ContentSecurityPolicy`
|
||||
disables that protection.
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
SecureConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// XSSProtection provides protection against cross-site scripting attack (XSS)
|
||||
// by setting the `X-XSS-Protection` header.
|
||||
// Optional. Default value "1; mode=block".
|
||||
XSSProtection string `json:"xss_protection"`
|
||||
|
||||
// ContentTypeNosniff provides protection against overriding Content-Type
|
||||
// header by setting the `X-Content-Type-Options` header.
|
||||
// Optional. Default value "nosniff".
|
||||
ContentTypeNosniff string `json:"content_type_nosniff"`
|
||||
|
||||
// XFrameOptions can be used to indicate whether or not a browser should
|
||||
// be allowed to render a page in a <frame>, <iframe> or <object> .
|
||||
// Sites can use this to avoid clickjacking attacks, by ensuring that their
|
||||
// content is not embedded into other sites.provides protection against
|
||||
// clickjacking.
|
||||
// Optional. Default value "SAMEORIGIN".
|
||||
// Possible values:
|
||||
// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
|
||||
// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
|
||||
// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
|
||||
XFrameOptions string `json:"x_frame_options"`
|
||||
|
||||
// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
|
||||
// long (in seconds) browsers should remember that this site is only to
|
||||
// be accessed using HTTPS. This reduces your exposure to some SSL-stripping
|
||||
// man-in-the-middle (MITM) attacks.
|
||||
// Optional. Default value 0.
|
||||
HSTSMaxAge int `json:"hsts_max_age"`
|
||||
|
||||
// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
|
||||
// header, excluding all subdomains from security policy. It has no effect
|
||||
// unless HSTSMaxAge is set to a non-zero value.
|
||||
// Optional. Default value false.
|
||||
HSTSExcludeSubdomains bool `json:"hsts_exclude_subdomains"`
|
||||
|
||||
// ContentSecurityPolicy sets the `Content-Security-Policy` header providing
|
||||
// security against cross-site scripting (XSS), clickjacking and other code
|
||||
// injection attacks resulting from execution of malicious content in the
|
||||
// trusted web page context.
|
||||
// Optional. Default value "".
|
||||
ContentSecurityPolicy string `json:"content_security_policy"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultSecureConfig = SecureConfig{
|
||||
Skipper: defaultSkipper,
|
||||
XSSProtection: "1; mode=block",
|
||||
ContentTypeNosniff: "nosniff",
|
||||
XFrameOptions: "SAMEORIGIN",
|
||||
}
|
||||
```
|
71
website/content/middleware/static.md
Normal file
71
website/content/middleware/static.md
Normal file
@ -0,0 +1,71 @@
|
||||
+++
|
||||
title = "Static Middleware"
|
||||
description = "Static middleware for Echo"
|
||||
[menu.side]
|
||||
name = "Static"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## Static Middleware
|
||||
|
||||
Static middleware can be used to serve static files from the provided root directory.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.Static("/static"))
|
||||
```
|
||||
|
||||
This serves static files from `static` directory. For example, a request to `/js/main.js`
|
||||
will fetch and serve `static/js/main.js` file.
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
||||
Root: "static",
|
||||
Browse: true,
|
||||
}))
|
||||
```
|
||||
|
||||
This serves static files from `static` directory and enables directory browsing.
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
StaticConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Root directory from where the static content is served.
|
||||
// Required.
|
||||
Root string `json:"root"`
|
||||
|
||||
// Index file for serving a directory.
|
||||
// Optional. Default value "index.html".
|
||||
Index string `json:"index"`
|
||||
|
||||
// Enable HTML5 mode by forwarding all not-found requests to root so that
|
||||
// SPA (single-page application) can handle the routing.
|
||||
// Optional. Default value false.
|
||||
HTML5 bool `json:"html5"`
|
||||
|
||||
// Enable directory browsing.
|
||||
// Optional. Default value false.
|
||||
Browse bool `json:"browse"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultStaticConfig = StaticConfig{
|
||||
Skipper: defaultSkipper,
|
||||
Index: "index.html",
|
||||
}
|
||||
```
|
64
website/content/middleware/trailing-slash.md
Normal file
64
website/content/middleware/trailing-slash.md
Normal file
@ -0,0 +1,64 @@
|
||||
+++
|
||||
title = "TrailingSlash Middleware"
|
||||
description = "Trailing slash middleware for Echo"
|
||||
[menu.side]
|
||||
name = "TrailingSlash"
|
||||
parent = "middleware"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## AddTrailingSlash Middleware
|
||||
|
||||
AddTrailingSlash middleware adds a trailing slash to the request URI.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Pre(middleware.AddTrailingSlash())
|
||||
```
|
||||
|
||||
## RemoveTrailingSlash Middleware
|
||||
|
||||
RemoveTrailingSlash middleware removes a trailing slash from the request URI.
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Pre(middleware.RemoveTrailingSlash())
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
*Usage*
|
||||
|
||||
```go
|
||||
e := echo.New()
|
||||
e.Use(middleware.AddTrailingSlashWithConfig(middleware.TrailingSlashConfig{
|
||||
RedirectCode: http.StatusMovedPermanently,
|
||||
}))
|
||||
```
|
||||
|
||||
Example above will add a trailing slash to the request URI and redirect with `308 - StatusMovedPermanently`.
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
TrailingSlashConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Status code to be used when redirecting the request.
|
||||
// Optional, but when provided the request is redirected using this code.
|
||||
RedirectCode int `json:"redirect_code"`
|
||||
}
|
||||
```
|
||||
|
||||
*Default Configuration*
|
||||
|
||||
```go
|
||||
DefaultTrailingSlashConfig = TrailingSlashConfig{
|
||||
Skipper: defaultSkipper,
|
||||
}
|
||||
```
|
23
website/content/recipes/cors.md
Normal file
23
website/content/recipes/cors.md
Normal file
@ -0,0 +1,23 @@
|
||||
+++
|
||||
title = "CORS Recipe"
|
||||
description = "CORS recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "CORS"
|
||||
identifier = "cors-middleware"
|
||||
parent = "recipes"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
## CORS Recipe
|
||||
|
||||
### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "cors/server.go" >}}
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### [Source Code]({{< source "cors" >}})
|
87
website/content/recipes/crud.md
Normal file
87
website/content/recipes/crud.md
Normal file
@ -0,0 +1,87 @@
|
||||
+++
|
||||
title = "CRUD Recipe"
|
||||
description = "CRUD (Create, read, update and delete) recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "CRUD"
|
||||
parent = "recipes"
|
||||
weight = 2
|
||||
+++
|
||||
|
||||
## CRUD (Create, read, update and delete) Recipe
|
||||
|
||||
### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "crud/server.go" >}}
|
||||
|
||||
### Client
|
||||
|
||||
`curl`
|
||||
|
||||
#### Create User
|
||||
|
||||
```sh
|
||||
curl -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name":"Joe Smith"}' \
|
||||
localhost:1323/users
|
||||
```
|
||||
|
||||
*Response*
|
||||
|
||||
```js
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Joe Smith"
|
||||
}
|
||||
```
|
||||
|
||||
#### Get User
|
||||
|
||||
```sh
|
||||
curl localhost:1323/users/1
|
||||
```
|
||||
|
||||
*Response*
|
||||
|
||||
```js
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Joe Smith"
|
||||
}
|
||||
```
|
||||
|
||||
#### Update User
|
||||
|
||||
```sh
|
||||
curl -X PUT \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name":"Joe"}' \
|
||||
localhost:1323/users/1
|
||||
```
|
||||
|
||||
*Response*
|
||||
|
||||
```js
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Joe"
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete User
|
||||
|
||||
```sh
|
||||
curl -X DELETE localhost:1323/users/1
|
||||
```
|
||||
|
||||
*Response*
|
||||
|
||||
`NoContent - 204`
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### [Source Code]({{< source "crud" >}})
|
23
website/content/recipes/embed-resources.md
Normal file
23
website/content/recipes/embed-resources.md
Normal file
@ -0,0 +1,23 @@
|
||||
+++
|
||||
title = "Embed Resources Recipe"
|
||||
description = "Embed resources recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "Embed Resources"
|
||||
parent = "recipes"
|
||||
weight = 14
|
||||
+++
|
||||
|
||||
## Embed Resources Recipe
|
||||
|
||||
### With go.rice
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "embed-resources/server.go" >}}
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [caarlos0](https://github.com/caarlos0)
|
||||
- [maddie](https://github.com/maddie)
|
||||
|
||||
### [Source Code]({{< source "embed-resources" >}})
|
47
website/content/recipes/file-upload.md
Normal file
47
website/content/recipes/file-upload.md
Normal file
@ -0,0 +1,47 @@
|
||||
+++
|
||||
title = "File Upload Recipe"
|
||||
description = "File upload recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "File Upload"
|
||||
parent = "recipes"
|
||||
weight = 7
|
||||
+++
|
||||
|
||||
## File Upload Recipe
|
||||
|
||||
### How to upload single file with fields?
|
||||
|
||||
#### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "file-upload/single/server.go" >}}
|
||||
|
||||
#### Client
|
||||
|
||||
`index.html`
|
||||
|
||||
{{< embed "file-upload/single/public/index.html" >}}
|
||||
|
||||
### How to upload multiple files with fields?
|
||||
|
||||
#### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "file-upload/multiple/server.go" >}}
|
||||
|
||||
#### Client
|
||||
|
||||
`index.html`
|
||||
|
||||
{{< embed "file-upload/multiple/public/index.html" >}}
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### Source Code
|
||||
|
||||
- [single]({{< source "file-upload/single" >}})
|
||||
- [multiple]({{< source "file-upload/multiple" >}})
|
138
website/content/recipes/google-app-engine.md
Normal file
138
website/content/recipes/google-app-engine.md
Normal file
@ -0,0 +1,138 @@
|
||||
+++
|
||||
title = "Google App Engine Recipe"
|
||||
description = "Google App Engine recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "Google App Engine"
|
||||
parent = "recipes"
|
||||
weight = 12
|
||||
+++
|
||||
|
||||
## Google App Engine Recipe
|
||||
|
||||
Google App Engine (GAE) provides a range of hosting options from pure PaaS (App Engine Classic)
|
||||
through Managed VMs to fully self-managed or container-driven Compute Engine instances. Echo
|
||||
works great with all of these but requires a few changes to the usual examples to run on the
|
||||
AppEngine Classic and Managed VM options. With a small amount of effort though it's possible
|
||||
to produce a codebase that will run on these and also non-managed platforms automatically.
|
||||
|
||||
We'll walk through the changes needed to support each option.
|
||||
|
||||
### Standalone
|
||||
|
||||
Wait? What? I thought this was about AppEngine! Bear with me - the easiest way to show the changes
|
||||
required is to start with a setup for standalone and work from there plus there's no reason we
|
||||
wouldn't want to retain the ability to run our app anywhere, right?
|
||||
|
||||
We take advantage of the go [build constraints or tags](http://golang.org/pkg/go/build/) to change
|
||||
how we create and run the Echo server for each platform while keeping the rest of the application
|
||||
(e.g. handler wireup) the same across all of them.
|
||||
|
||||
First, we have the normal setup based on the examples but we split it into two files - `app.go` will
|
||||
be common to all variations and holds the Echo instance variable. We initialise it from a function
|
||||
and because it is a `var` this will happen _before_ any `init()` functions run - a feature that we'll
|
||||
use to connect our handlers later.
|
||||
|
||||
`app.go`
|
||||
|
||||
{{< embed "google-app-engine/app.go" >}}
|
||||
|
||||
A separate source file contains the function to create the Echo instance and add the static
|
||||
file handlers and middleware. Note the build tag on the first line which says to use this when _not_
|
||||
bulding with appengine or appenginevm tags (which thoese platforms automatically add for us). We also
|
||||
have the `main()` function to start serving our app as normal. This should all be very familiar.
|
||||
|
||||
`app-standalone.go`
|
||||
|
||||
{{< embed "google-app-engine/app-standalone.go" >}}
|
||||
|
||||
The handler-wireup that would normally also be a part of this Echo setup moves to separate files which
|
||||
take advantage of the ability to have multiple `init()` functions which run _after_ the `e` Echo var is
|
||||
initialised but _before_ the `main()` function is executed. These allow additional handlers to attach
|
||||
themselves to the instance - I've found the `Group` feature naturally fits into this pattern with a file
|
||||
per REST endpoint, often with a higher-level `api` group created that they attach to instead of the root
|
||||
Echo instance directly (so things like CORS middleware can be added at this higher common-level).
|
||||
|
||||
`users.go`
|
||||
|
||||
{{< embed "google-app-engine/users.go" >}}
|
||||
|
||||
If we run our app it should execute as it did before when everything was in one file although we have
|
||||
at least gained the ability to organize our handlers a little more cleanly.
|
||||
|
||||
### AppEngine Classic and Managed VMs
|
||||
|
||||
So far we've seen how to split apart the Echo creation and setup but still have the same app that
|
||||
still only runs standalone. Now we'll see hwo those changes allow us to add support for AppEngine
|
||||
hosting.
|
||||
|
||||
Refer to the [AppEngine site](https://cloud.google.com/appengine/docs/go/) for full configuration
|
||||
and deployment information.
|
||||
|
||||
#### app.yaml configuration file
|
||||
|
||||
Both of these are Platform as as Service options running on either sandboxed micro-containers
|
||||
or managed Compute Engine instances. Both require an `app.yaml` file to describe the app to
|
||||
the service. While the app _could_ still serve all it's static files itself, one of the benefits
|
||||
of the platform is having Google's infrastructure handle that for us so it can be offloaded and
|
||||
the app only has to deal with dynamic requests. The platform also handles logging and http gzip
|
||||
compression so these can be removed from the codebase as well.
|
||||
|
||||
The yaml file also contains other options to control instance size and auto-scaling so for true
|
||||
deployment freedom you would likely have separate `app-classic.yaml` and `app-vm.yaml` files and
|
||||
this can help when making the transition from AppEngine Classic to Managed VMs.
|
||||
|
||||
`app-engine.yaml`
|
||||
|
||||
{{< embed "google-app-engine/app-engine.yaml" >}}
|
||||
|
||||
#### Router configuration
|
||||
|
||||
We'll now use the [build constraints](http://golang.org/pkg/go/build/) again like we did when creating
|
||||
our `app-standalone.go` instance but this time with the opposite tags to use this file _if_ the build has
|
||||
the appengine or appenginevm tags (added automatically when deploying to these platforms).
|
||||
|
||||
This allows us to replace the `createMux()` function to create our Echo server _without_ any of the
|
||||
static file handling and logging + gzip middleware which is no longer required. Also worth nothing is
|
||||
that GAE classic provides a wrapper to handle serving the app so instead of a `main()` function where
|
||||
we run the server, we instead wire up the router to the default `http.Handler` instead.
|
||||
|
||||
`app-engine.go`
|
||||
|
||||
{{< embed "google-app-engine/app-engine.go" >}}
|
||||
|
||||
Managed VMs are slightly different. They are expected to respond to requests on port 8080 as well
|
||||
as special health-check requests used by the service to detect if an instance is still running in
|
||||
order to provide automated failover and instance replacement. The `google.golang.org/appengine`
|
||||
package provides this for us so we have a slightly different version for Managed VMs:
|
||||
|
||||
`app-managed.go`
|
||||
|
||||
{{< embed "google-app-engine/app-managed.go" >}}
|
||||
|
||||
So now we have three different configurations. We can build and run our app as normal so it can
|
||||
be executed locally, on a full Compute Engine instance or any other traditional hosting provider
|
||||
(including EC2, Docker etc...). This build will ignore the code in appengine and appenginevm tagged
|
||||
files and the `app.yaml` file is meaningless to anything other than the AppEngine platform.
|
||||
|
||||
We can also run locally using the [Google AppEngine SDK for Go](https://cloud.google.com/appengine/downloads)
|
||||
either emulating [AppEngine Classic](https://cloud.google.com/appengine/docs/go/tools/devserver):
|
||||
|
||||
goapp serve
|
||||
|
||||
Or [Managed VMs](https://cloud.google.com/appengine/docs/managed-vms/sdk#run-local):
|
||||
|
||||
gcloud config set project [your project id]
|
||||
gcloud preview app run .
|
||||
|
||||
And of course we can deploy our app to both of these platforms for easy and inexpensive auto-scaling joy.
|
||||
|
||||
Depending on what your app actually does it's possible you may need to make other changes to allow
|
||||
switching between AppEngine provided service such as Datastore and alternative storage implementations
|
||||
such as MongoDB. A combination of go interfaces and build constraints can make this fairly straightforward
|
||||
but is outside the scope of this recipe.
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [CaptainCodeman](https://github.com/CaptainCodeman)
|
||||
|
||||
### [Source Code]({{< source "google-app-engine" >}})
|
31
website/content/recipes/graceful-shutdown.md
Normal file
31
website/content/recipes/graceful-shutdown.md
Normal file
@ -0,0 +1,31 @@
|
||||
+++
|
||||
title = "Graceful Shutdown Recipe"
|
||||
description = "Graceful shutdown recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "Graceful Shutdown"
|
||||
parent = "recipes"
|
||||
weight = 13
|
||||
+++
|
||||
|
||||
## Graceful Shutdown Recipe
|
||||
|
||||
### Using [grace](https://github.com/facebookgo/grace)
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "graceful-shutdown/grace/server.go" >}}
|
||||
|
||||
### Using [graceful](https://github.com/tylerb/graceful)
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "graceful-shutdown/graceful/server.go" >}}
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [mertenvg](https://github.com/mertenvg)
|
||||
|
||||
### Source Code
|
||||
|
||||
- [graceful]({{< source "graceful-shutdown/graceful" >}})
|
||||
- [grace]({{< source "graceful-shutdown/grace" >}})
|
22
website/content/recipes/hello-world.md
Normal file
22
website/content/recipes/hello-world.md
Normal file
@ -0,0 +1,22 @@
|
||||
+++
|
||||
title = "Hello World Recipe"
|
||||
description = "Hello world recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "Hello World"
|
||||
parent = "recipes"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
## Hello World Recipe
|
||||
|
||||
### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "hello-world/server.go" >}}
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### [Source Code]({{< source "hello-world" >}})
|
52
website/content/recipes/http2.md
Normal file
52
website/content/recipes/http2.md
Normal file
@ -0,0 +1,52 @@
|
||||
+++
|
||||
title = "HTTP/2 Recipe"
|
||||
description = "HTTP/2 recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "HTTP/2"
|
||||
parent = "recipes"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
## What is HTTP/2?
|
||||
|
||||
HTTP/2 (originally named HTTP/2.0) is the second major version of the HTTP network
|
||||
protocol used by the World Wide Web
|
||||
|
||||
### Features
|
||||
|
||||
- Binary, instead of textual.
|
||||
- Fully multiplexed, instead of ordered and blocking, can therefore use just one TCP connection.
|
||||
- Uses header compression to reduce overhead.
|
||||
- Allows servers to "push" responses proactively into client caches.
|
||||
|
||||
## How to run an HTTP/2 and HTTPS server?
|
||||
|
||||
> Standard engine only
|
||||
|
||||
### Generate a self-signed X.509 TLS certificate (HTTP/2 requires TLS to operate)
|
||||
|
||||
```sh
|
||||
go run $GOROOT/src/crypto/tls/generate_cert.go --host localhost
|
||||
```
|
||||
|
||||
This will generate `cert.pem` and `key.pem` files.
|
||||
|
||||
> For demo purpose, we are using a self-signed certificate. Ideally you should obtain
|
||||
a certificate from [CA](https://en.wikipedia.org/wiki/Certificate_authority).
|
||||
|
||||
### Configure a server with `engine.Config`
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "http2/server.go" >}}
|
||||
|
||||
### Endpoints
|
||||
|
||||
- https://localhost:1323/request (Displays the information about received HTTP request)
|
||||
- https://localhost:1323/stream (Streams the current time every second)
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### [Source Code]({{< source "http2" >}})
|
30
website/content/recipes/jsonp.md
Normal file
30
website/content/recipes/jsonp.md
Normal file
@ -0,0 +1,30 @@
|
||||
+++
|
||||
title = "JSONP Recipe"
|
||||
description = "JSONP recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "JSONP"
|
||||
parent = "recipes"
|
||||
weight = 6
|
||||
+++
|
||||
|
||||
## JSONP Recipe
|
||||
|
||||
JSONP is a method that allows cross-domain server calls. You can read more about it at the JSON versus JSONP Tutorial.
|
||||
|
||||
### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "jsonp/server.go" >}}
|
||||
|
||||
### Client
|
||||
|
||||
`index.html`
|
||||
|
||||
{{< embed "jsonp/public/index.html" >}}
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [willf](https://github.com/willf)
|
||||
|
||||
### [Source Code]({{< source "jsonp" >}})
|
71
website/content/recipes/jwt.md
Normal file
71
website/content/recipes/jwt.md
Normal file
@ -0,0 +1,71 @@
|
||||
+++
|
||||
title = "JWT Recipe"
|
||||
description = "JWT recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "JWT"
|
||||
identifier = "jwt-recipe"
|
||||
parent = "recipes"
|
||||
weight = 11
|
||||
+++
|
||||
|
||||
## JWT Recipe
|
||||
|
||||
- JWT authentication using HS256 algorithm.
|
||||
- JWT is retrieved from `Authorization` request header.
|
||||
|
||||
### Server using Map claims
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "jwt/map-claims/server.go" >}}
|
||||
|
||||
### Server using custom claims
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "jwt/custom-claims/server.go" >}}
|
||||
|
||||
### Client
|
||||
|
||||
`curl`
|
||||
|
||||
#### Login
|
||||
|
||||
Login using username and password to retrieve a token.
|
||||
|
||||
```sh
|
||||
curl -X POST -d 'username=jon' -d 'password=shhh!' localhost:1323/login
|
||||
```
|
||||
|
||||
*Response*
|
||||
|
||||
```js
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"
|
||||
}
|
||||
```
|
||||
|
||||
#### Request
|
||||
|
||||
Request a restricted resource using the token in `Authorization` request header.
|
||||
|
||||
```sh
|
||||
curl localhost:1323/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"
|
||||
```
|
||||
|
||||
*Response*
|
||||
|
||||
```
|
||||
Welcome Jon Snow!
|
||||
```
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
- [axdg](https://github.com/axdg)
|
||||
- [matcornic](https://github.com/matcornic)
|
||||
|
||||
### Source Code
|
||||
|
||||
- [With default Map claims]({{< source "jwt/map-claims" >}})
|
||||
- [With custom claims]({{< source "jwt/custom-claims" >}})
|
50
website/content/recipes/middleware.md
Normal file
50
website/content/recipes/middleware.md
Normal file
@ -0,0 +1,50 @@
|
||||
+++
|
||||
title = "Middleware Recipe"
|
||||
description = "Middleware recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "Middleware"
|
||||
parent = "recipes"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
## Middleware Recipe
|
||||
|
||||
### How to write a custom middleware?
|
||||
|
||||
- Middleware to collect request count, statuses and uptime.
|
||||
- Middleware to write custom `Server` header to the response.
|
||||
|
||||
#### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "middleware/server.go" >}}
|
||||
|
||||
#### Response
|
||||
|
||||
*Headers*
|
||||
```sh
|
||||
Content-Length:122
|
||||
Content-Type:application/json; charset=utf-8
|
||||
Date:Thu, 14 Apr 2016 20:31:46 GMT
|
||||
Server:Echo/2.0
|
||||
```
|
||||
|
||||
*Body*
|
||||
|
||||
```js
|
||||
{
|
||||
"uptime": "2016-04-14T13:28:48.486548936-07:00",
|
||||
"requestCount": 5,
|
||||
"statuses": {
|
||||
"200": 4,
|
||||
"404": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### [Source Code]({{< source "middleware" >}})
|
41
website/content/recipes/streaming-response.md
Normal file
41
website/content/recipes/streaming-response.md
Normal file
@ -0,0 +1,41 @@
|
||||
+++
|
||||
title = "Streaming Response Recipe"
|
||||
description = "Streaming response recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "Streaming Response"
|
||||
parent = "recipes"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
## Streaming Response Recipe
|
||||
|
||||
- Send data as it is produced
|
||||
- Streaming JSON response with chunked transfer encoding
|
||||
|
||||
### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "streaming-response/server.go" >}}
|
||||
|
||||
### Client
|
||||
|
||||
```sh
|
||||
$ curl localhost:1323
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
```sh
|
||||
{"Altitude":-97,"Latitude":37.819929,"Longitude":-122.478255}
|
||||
{"Altitude":1899,"Latitude":39.096849,"Longitude":-120.032351}
|
||||
{"Altitude":2619,"Latitude":37.865101,"Longitude":-119.538329}
|
||||
{"Altitude":42,"Latitude":33.812092,"Longitude":-117.918974}
|
||||
{"Altitude":15,"Latitude":37.77493,"Longitude":-122.419416}
|
||||
```
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### [Source Code]({{< source "streaming-response" >}})
|
21
website/content/recipes/subdomains.md
Normal file
21
website/content/recipes/subdomains.md
Normal file
@ -0,0 +1,21 @@
|
||||
+++
|
||||
title = "Subdomains Recipe"
|
||||
description = "Subdomains recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "Subdomains"
|
||||
parent = "recipes"
|
||||
weight = 10
|
||||
+++
|
||||
|
||||
## Subdomains Recipe
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "subdomains/server.go" >}}
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [axdg](https://github.com/axdg)
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### [Source Code]({{< source "subdomains" >}})
|
62
website/content/recipes/websocket.md
Normal file
62
website/content/recipes/websocket.md
Normal file
@ -0,0 +1,62 @@
|
||||
+++
|
||||
title = "WebSocket Recipe"
|
||||
description = "WebSocket recipe / example for Echo"
|
||||
[menu.side]
|
||||
name = "WebSocket"
|
||||
parent = "recipes"
|
||||
weight = 5
|
||||
+++
|
||||
|
||||
## WebSocket Recipe
|
||||
|
||||
> Only supported in `standard` engine.
|
||||
|
||||
### Using `net` WebSocket
|
||||
|
||||
#### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "websocket/net/server.go" >}}
|
||||
|
||||
### Using `gorilla` WebSocket
|
||||
|
||||
#### Server
|
||||
|
||||
`server.go`
|
||||
|
||||
{{< embed "websocket/gorilla/server.go" >}}
|
||||
|
||||
### Client
|
||||
|
||||
`index.html`
|
||||
|
||||
{{< embed "websocket/public/index.html" >}}
|
||||
|
||||
### Output
|
||||
|
||||
`Client`
|
||||
|
||||
```sh
|
||||
Hello, Client!
|
||||
Hello, Client!
|
||||
Hello, Client!
|
||||
Hello, Client!
|
||||
Hello, Client!
|
||||
```
|
||||
|
||||
`Server`
|
||||
|
||||
```sh
|
||||
Hello, Server!
|
||||
Hello, Server!
|
||||
Hello, Server!
|
||||
Hello, Server!
|
||||
Hello, Server!
|
||||
```
|
||||
|
||||
### Maintainers
|
||||
|
||||
- [vishr](https://github.com/vishr)
|
||||
|
||||
### [Source Code]({{< source "websocket" >}})
|
23
website/content/support-echo.md
Normal file
23
website/content/support-echo.md
Normal file
@ -0,0 +1,23 @@
|
||||
+++
|
||||
title = "Support"
|
||||
+++
|
||||
|
||||
## Support Echo Development
|
||||
|
||||
<p>
|
||||
<a href="https://patreon.com/labstack" target="_blank"><br>
|
||||
<img style="width: 120px;" src="https://s3.amazonaws.com/patreon_public_assets/toolbox/patreon.png"><br>
|
||||
<p>Support via Patreon (recurring pledge)</p>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://paypal.me/labstack" target="_blank"><br>
|
||||
<img style="width: 120px;" src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-200px.png"><br>
|
||||
<p>Donate via PayPal (one time)</p>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Echo is an MIT licensed open source project and completely free to use. If you are
|
||||
using Echo in your products/projects, please consider sponsoring Echo to ensure
|
||||
it is actively developed and maintained.
|
37
website/layouts/_default/single.html
Normal file
37
website/layouts/_default/single.html
Normal file
@ -0,0 +1,37 @@
|
||||
{{ partial "head.html" . }}
|
||||
<body>
|
||||
{{ partial "navbar.html" . }}
|
||||
{{ partial "sidenav.html" . }}
|
||||
{{ partial "search.html" . }}
|
||||
<div class="w3-main w3-padding-128">
|
||||
{{ partial "ad.html" }}
|
||||
<div class="w3-row-padding">
|
||||
<div class="w3-col m9 l9">
|
||||
{{ partial "notice.html" }}
|
||||
<article class="content">
|
||||
<section>
|
||||
{{ .Content }}
|
||||
</section>
|
||||
<footer style="margin-top: 40px;">
|
||||
<div id="disqus_thread"></div>
|
||||
<script type="text/javascript">
|
||||
/* * * CONFIGURATION VARIABLES * * */
|
||||
var disqus_shortname = 'labstack';
|
||||
|
||||
/* * * DON'T EDIT BELOW THIS LINE * * */
|
||||
(function() {
|
||||
var dsq = document.createElement('script');
|
||||
dsq.type = 'text/javascript';
|
||||
dsq.async = true;
|
||||
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
|
||||
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
|
||||
})();
|
||||
</script>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ partial "footer.html" . }}
|
||||
</body>
|
||||
</html>
|
19
website/layouts/index.html
Normal file
19
website/layouts/index.html
Normal file
@ -0,0 +1,19 @@
|
||||
{{ partial "head.html" . }}
|
||||
<body>
|
||||
{{ partial "navbar.html" . }}
|
||||
{{ partial "sidenav.html" . }}
|
||||
{{ partial "search.html" . }}
|
||||
<div class="w3-main w3-padding-128">
|
||||
{{ partial "ad.html" }}
|
||||
<div class="w3-row-padding">
|
||||
<div class="w3-col m9 l9">
|
||||
{{ partial "notice.html" }}
|
||||
{{ range where .Data.Pages "Title" "Index" }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ partial "footer.html" . }}
|
||||
</body>
|
||||
</html>
|
10
website/layouts/partials/ad.html
Normal file
10
website/layouts/partials/ad.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="ad">
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=echolabstackcom" id="_carbonads_js"></script>
|
||||
</div>
|
||||
<!-- <div class="w3-container w3-card-2 w3-theme promo">
|
||||
<a href="https://up.labstack.com">
|
||||
<h3><i class="fa fa-rocket"></i> Up</h3>
|
||||
<p>Real-time File Transfer</p>
|
||||
<small><span class="w3-tag w3-deep-orange">promo</span></small>
|
||||
</a>
|
||||
</div> -->
|
0
website/layouts/partials/connect.html
Normal file
0
website/layouts/partials/connect.html
Normal file
43
website/layouts/partials/footer.html
Normal file
43
website/layouts/partials/footer.html
Normal file
@ -0,0 +1,43 @@
|
||||
<footer class="w3-container w3-padding-48 w3-center footer">
|
||||
<p>
|
||||
<strong>Echo</strong> by <a href="https://labstack.com">LabStack</a> © 2016 LabStack. All Rights Reserved.
|
||||
</p>
|
||||
<p>
|
||||
<a class="icon" href="https://blog.labstack.com">
|
||||
<i class="fa fa-rss" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a class="icon" href="https://github.com/labstack">
|
||||
<i class="fa fa-github" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a class="icon" href="https://facebook.com/labstack">
|
||||
<i class="fa fa-facebook" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a class="icon" href="https://twitter.com/labstack">
|
||||
<i class="fa fa-twitter" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a class="icon" href="https://plus.google.com/+labstack">
|
||||
<i class="fa fa-google-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
</p>
|
||||
</footer>
|
||||
<script type="text/javascript" src="//cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
docsearch({
|
||||
apiKey: '69dfc65b57ccba29ec34b68aa5b274ed',
|
||||
indexName: 'labstack',
|
||||
inputSelector: '#search-box',
|
||||
autocompleteOptions: {
|
||||
dropdownMenuContainer: '#search-menu'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!-- <script src="//sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
|
||||
<script>
|
||||
((window.gitter = {}).chat = {}).options = {
|
||||
room: 'labstack/echo'
|
||||
};
|
||||
</script> -->
|
||||
<script async defer id="github-bjs" src="//buttons.github.io/buttons.js"></script>
|
||||
{{ template "_internal/google_analytics_async.html" . }}
|
||||
<script src="/scripts/prism.js"></script>
|
||||
<script src="/scripts/echo.js"></script>
|
22
website/layouts/partials/head.html
Normal file
22
website/layouts/partials/head.html
Normal file
@ -0,0 +1,22 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="description" content="{{ .Description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:site" content="@echo">
|
||||
<meta name="twitter:title" content="{{ if ne .URL "/" }}{{ .Title }} | {{ end }}{{ .Site.Title }}">
|
||||
<meta name="twitter:description" content="{{ .Description }}">
|
||||
<meta name="twitter:image" content="{{ .Site.Params.image }}">
|
||||
<meta property="og:title" content="{{ if ne .URL "/" }}{{ .Title }} | {{ end }}{{ .Site.Title }}">
|
||||
<meta property="og:site_name" content="echo">
|
||||
<meta property="og:og:description" content="{{ .Description }}">
|
||||
<meta property="og:url" content="{{ .Site.BaseURL }}">
|
||||
<meta property="og:image" content="{{ .Site.Params.image }}">
|
||||
<title>
|
||||
{{ if ne .URL "/" }}{{ .Title }} | {{ end }}{{ .Site.Title }}
|
||||
</title>
|
||||
<link rel="stylesheet" href="//cdn.labstack.com/css/w3.css">
|
||||
<link rel="stylesheet" href="//unpkg.com/font-awesome@4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css">
|
||||
<link rel="stylesheet" href="/styles/prism.css">
|
||||
<link rel="stylesheet" href="/styles/main.css">
|
18
website/layouts/partials/menu.html
Normal file
18
website/layouts/partials/menu.html
Normal file
@ -0,0 +1,18 @@
|
||||
<nav class="w3-sidenav">
|
||||
{{ $currentNode := . }}
|
||||
{{ range .Site.Menus.side }}
|
||||
<h4>{{ .Pre }} {{ .Name }}</h4>
|
||||
{{ range .Children }}
|
||||
<a{{ if $currentNode.IsMenuCurrent "side" . }} class="w3-theme"{{ end }} href="{{ .URL }}">
|
||||
{{ .Name }}
|
||||
</a>
|
||||
{{ if .HasChildren }}
|
||||
{{ range .Children }}
|
||||
<a {{ if $currentNode.IsMenuCurrent "side" . }} class="w3-theme"{{ end }} href="{{ .URL }}">
|
||||
{{ .Name }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</nav>
|
11
website/layouts/partials/navbar.html
Normal file
11
website/layouts/partials/navbar.html
Normal file
@ -0,0 +1,11 @@
|
||||
<nav class="navbar">
|
||||
<a href="/"><img class="logo" src="/images/logo.png" alt="Echo"></a>
|
||||
<span class="w3-hide-small w3-hide-medium">
|
||||
<input id="search-box" type="text" class="w3-input" placeholder="Search...">
|
||||
<i class="fa fa-search"></i>
|
||||
</span>
|
||||
<a class="github-button" href="https://github.com/labstack/echo" data-icon="octicon-star" data-style="mega" data-count-href="/labstack/echo/stargazers" data-count-api="/repos/labstack/echo#stargazers_count" data-count-aria-label="# stargazers on GitHub" aria-label="Star labstack/echo on GitHub">
|
||||
Star
|
||||
</a>
|
||||
<span class="w3-xxlarge w3-hide-large" onclick="openSidenav()">☰</span>
|
||||
</nav>
|
8
website/layouts/partials/notice.html
Normal file
8
website/layouts/partials/notice.html
Normal file
@ -0,0 +1,8 @@
|
||||
<div class="w3-panel w3-red notice">
|
||||
<h3>
|
||||
Check out our new project <a href="https://github.com/labstack/armor">Armor</a>
|
||||
</h3>
|
||||
<p>
|
||||
Simple HTTP server, supports HTTP/2 and auto TLS, based on Echo v3.
|
||||
</p>
|
||||
</div>
|
1
website/layouts/partials/search.html
Normal file
1
website/layouts/partials/search.html
Normal file
@ -0,0 +1 @@
|
||||
<div id="search-menu"></div>
|
14
website/layouts/partials/share.html
Normal file
14
website/layouts/partials/share.html
Normal file
@ -0,0 +1,14 @@
|
||||
<span class="share">
|
||||
<a href="https://www.facebook.com/sharer/sharer.php?u={{ .Permalink }}" target="_blank">
|
||||
<i class="fa fa-facebook-square fa-2x"></i>
|
||||
</a>
|
||||
<a href="http://twitter.com/share?text={{ .Title }}&url={{ .Permalink }}" target="_blank">
|
||||
<i class="fa fa-twitter-square fa-2x"></i>
|
||||
</a>
|
||||
<a href="https://plus.google.com/share?url={{ .Permalink }}" target="_blank">
|
||||
<i class="fa fa-google-plus-square fa-2x"></i>
|
||||
</a>
|
||||
<a class="ui reddit icon button" href="http://www.reddit.com/submit?url={{ .Permalink }}&title={{ .Title }}" target="_blank">
|
||||
<i class="fa fa-reddit-square fa-2x"></i>
|
||||
</a>
|
||||
</span>
|
24
website/layouts/partials/sidenav.html
Normal file
24
website/layouts/partials/sidenav.html
Normal file
@ -0,0 +1,24 @@
|
||||
<nav id="sidenav" class="w3-sidenav w3-collapse w3-animate-left">
|
||||
<span class="w3-closenav w3-xxlarge w3-hide-large" onclick="closeSidenav()">
|
||||
×
|
||||
</span>
|
||||
<a class="support w3-btn w3-white w3-border w3-border-theme w3-round-xlarge" href="/support-echo">
|
||||
<i class="fa fa-heart" aria-hidden="true"></i> Support Echo
|
||||
</a>
|
||||
{{ $currentNode := . }}
|
||||
{{ range .Site.Menus.side }}
|
||||
<h4>{{ .Pre }} {{ .Name }}</h4>
|
||||
{{ range .Children }}
|
||||
<a{{ if $currentNode.IsMenuCurrent "side" . }} class="active"{{ end }} href="{{ .URL }}">
|
||||
{{ .Name }}
|
||||
</a>
|
||||
{{ if .HasChildren }}
|
||||
{{ range .Children }}
|
||||
<a {{ if $currentNode.IsMenuCurrent "side" . }} class="active"{{ end }} href="{{ .URL }}">
|
||||
{{ .Name }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</nav>
|
2
website/layouts/shortcodes/embed.html
Normal file
2
website/layouts/shortcodes/embed.html
Normal file
@ -0,0 +1,2 @@
|
||||
<pre data-src="https://raw.githubusercontent.com/labstack/echox/master/recipe/{{ .Get 0 }}">
|
||||
</pre>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user