1
0
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:
Vishal Rana 2016-10-20 09:11:07 -07:00
parent 1dcb7ba9ab
commit f4b0004d2b
106 changed files with 5105 additions and 0 deletions

38
recipe/cors/server.go Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
rice
app.rice-box.go

View 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>

View File

@ -0,0 +1 @@
alert("main.js");

View 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"))
}

View 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>

View 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"))
}

View 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>

View 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"))
}

View 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

View 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
}

View 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

View 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()
}

View 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

View 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"))
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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>

View File

@ -0,0 +1 @@
console.log("Echo!");

View File

@ -0,0 +1 @@
{{define "welcome"}}Hello, {{.}}!{{end}}

View 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)])
}

View 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")
}

View 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)
}

View 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)
}

View 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
View 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
View 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
View 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",
}))
}

View 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
View 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"))
}

View 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"))
}

View 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"))
}

View 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"))
}

View 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"))
}

View 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"))
}

View 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"))
}

View 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"))
}

View 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
View 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."
}
}

View File

@ -0,0 +1,8 @@
+++
title = "echo"
[menu.side]
name = "echo"
parent = "godoc"
weight = 1
url = "https://godoc.org/github.com/labstack/echo"
+++

View File

@ -0,0 +1,8 @@
+++
title = "engine"
[menu.side]
name = "engine"
parent = "godoc"
weight = 3
url = "https://godoc.org/github.com/labstack/echo/engine"
+++

View 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"
+++

View 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"
+++

View 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"
+++

View 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)
})
```

View 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")
}
```

View 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)
```

View 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.

View 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
``` -->

View 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).

View 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.

View 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)
})
```

View 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)
```

View 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")
```

View 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")
}
```

View 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
View 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)

View 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,
}
```

View 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,
}
```

View 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},
}
```

View 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,
}
```

View 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,
}
```

View 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">}})

View 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,
}
```

View 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),
}
```

View 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">}})

View 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,
}
```

View 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,
}
```

View 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",
}
```

View 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",
}
```

View 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,
}
```

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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" >}})

View 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.

View 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>

View 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>

View 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> -->

View File

View 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>

View 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">

View 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>

View 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()">&#9776;</span>
</nav>

View 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>

View File

@ -0,0 +1 @@
<div id="search-menu"></div>

View 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 }}&amp;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 }}&amp;title={{ .Title }}" target="_blank">
<i class="fa fa-reddit-square fa-2x"></i>
</a>
</span>

View 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()">
&times;
</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>

View 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