1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-24 20:14:31 +02:00

Updated website

Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
Vishal Rana 2015-10-01 18:24:38 -07:00
parent c0571e37c3
commit c020919cb4
37 changed files with 1177 additions and 601 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
# Website
site
public
.publish
# Node.js

7
website/Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM busybox
MAINTAINER Vishal Rana <vr@labstack.com>
COPY server /server
COPY public /public
CMD ["/server"]

25
website/config.json Normal file
View File

@ -0,0 +1,25 @@
{
"baseurl": "http://labstack.com/echo",
"languageCode": "en-us",
"title": "Echo",
"menu": {
"main": [{
"Name": "Guide",
"Pre": "<i class='fa fa-heart'></i>",
"Weight": -110,
"Identifier": "guide",
"URL": "guide"
}, {
"Name": "Recipes",
"Pre": "<i class='fa fa-road'></i>",
"Weight": -100,
"Identifier": "recipes",
"URL": "recipes"
}]
},
"params": {
"googleAnayticsId": "UA-51208124-3"
}
}

View File

@ -0,0 +1,35 @@
---
title: Customization
menu:
main:
parent: guide
---
### HTTP error handler
`Echo.SetHTTPErrorHandler(h HTTPErrorHandler)`
Registers a custom `Echo.HTTPErrorHandler`.
Default 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)`
Enables/disables debug mode.
### Disable colored log
`Echo.DisableColoredLog()`
### StripTrailingSlash
StripTrailingSlash enables removing trailing slash from the request path.
`e.StripTrailingSlash()`

View File

@ -0,0 +1,47 @@
---
title: Error Handling
menu:
main:
parent: guide
---
Echo advocates centralized HTTP error handling by returning `error` from middleware
and handlers.
It allows you to
- Debug by writing stack trace to the HTTP response.
- Customize HTTP responses.
- Recover from panics inside middleware or handlers.
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](#customization) handles it.

View File

@ -0,0 +1,23 @@
---
title: Installation
menu:
main:
parent: guide
---
Echo has been developed and tested using Go `1.4.x`
Install the latest version of Echo via `go get`
```sh
$ go get github.com/labstack/echo
```
To upgrade
```sh
$ go get -u github.com/labstack/echo
```
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,65 @@
---
title: Middleware
menu:
main:
parent: guide
---
Middleware is a function which is chained in the HTTP request-response cycle. Middleware
has access to the request and response objects which it utilizes to perform a specific
action, for example, logging every request.
### Logger
Logs each HTTP request with method, path, status, response time and bytes served.
*Example*
```go
e.Use(Logger())
// Output: `2015/06/07 18:16:16 GET / 200 13.238µs 14`
```
### BasicAuth
BasicAuth middleware provides an HTTP basic authentication.
- For valid credentials it calls the next handler in the chain.
- For invalid Authorization header it sends "404 - Bad Request" response.
- For invalid credentials, it sends "401 - Unauthorized" response.
*Example*
```go
e.Group("/admin")
e.Use(mw.BasicAuth(func(usr, pwd string) bool {
if usr == "joe" && pwd == "secret" {
return true
}
return false
}))
```
### Gzip
Gzip middleware compresses HTTP response using gzip compression scheme.
*Example*
```go
e.Use(mw.Gzip())
```
### Recover
Recover middleware recovers from panics anywhere in the chain and handles the control
to the centralized [HTTPErrorHandler](#error-handling).
*Example*
```go
e.Use(mw.Recover())
```
[Examples](https://github.com/labstack/echo/tree/master/examples/middleware)

View File

@ -0,0 +1,64 @@
---
title: Request
menu:
main:
parent: guide
---
### Path parameter
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
```
### Query parameter
Query parameter can be retrieved by name using `Context.Query(name string)`.
*Example*
```go
e.Get("/users", func(c *echo.Context) error {
name := c.Query("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.Form(name string)`.
*Example*
```go
e.Post("/users", func(c *echo.Context) error {
name := c.Form("name")
return c.String(http.StatusOK, name)
})
```
```sh
$ curl -d "name=joe" http://localhost:1323/users
```

View File

@ -0,0 +1,135 @@
---
title: Response
menu:
main:
parent: guide
---
### Template
```go
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.
Below is an example using Go `html/template`
- Implement `echo.Render` interface
```go
Template struct {
templates *template.Template
}
func (t *Template) Render(w io.Writer, name string, data interface{}) error {
return t.templates.ExecuteTemplate(w, name, data)
}
```
- Pre-compile templates
`public/views/hello.html`
```html
{{define "hello"}}Hello, {{.}}!{{end}}
```
```go
t := &Template{
templates: template.Must(template.ParseGlob("public/views/*.html")),
}
```
- Register templates
```go
e := echo.New()
e.SetRenderer(t)
e.Get("/hello", Hello)
```
- Render template
```go
func Hello(c *echo.Context) error {
return c.Render(http.StatusOK, "hello", "World")
}
```
### JSON
```go
Context.JSON(code int, v interface{}) error
```
Sends a JSON HTTP response with status code.
### XML
```go
Context.XML(code int, v interface{}) error
```
Sends an XML HTTP response with status code.
### HTML
```go
Context.HTML(code int, html string) error
```
Sends an HTML HTTP response with status code.
### String
```go
Context.String(code int, s string) error
```
Sends a text/plain HTTP response with status code.
### File
```go
Context.File(name string, attachment bool) error
```
File sends a response with the content of the file. If attachment is `true`, the client
is prompted to save the file.
### Static files
`Echo.Static(path, root string)` serves static files. For example, code below serves
files from directory `public/scripts` for any request path starting with `/scripts/`.
```go
e.Static("/scripts/", "public/scripts")
```
### Serving a file
`Echo.ServeFile(path, file string)` serves a file. For example, code below serves
file `welcome.html` for request path `/welcome`.
```go
e.ServeFile("/welcome", "welcome.html")
```
### Serving an index file
`Echo.Index(file string)` serves root index page - `GET /`. For example, code below
serves root index page from file `public/index.html`.
```go
e.Index("public/index.html")
```
### Serving favicon
`Echo.Favicon(file string)` serves default favicon - `GET /favicon.ico`. For example,
code below serves favicon from file `public/favicon.ico`.
```go
e.Favicon("public/favicon.ico")
```

View File

@ -0,0 +1,105 @@
---
title: Routing
menu:
main:
parent: guide
---
Echo's router is [fast, optimized](https://github.com/labstack/echo#benchmark) 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 handler. For example,
code below registers a route for method `GET`, path `/hello` and a handler which sends
`Hello!` HTTP response.
```go
e.Get("/hello", func(c *echo.Context) error {
return c.String(http.StatusOK, "Hello!")
})
```
Echo's default handler is `func(*echo.Context) error` where `echo.Context` primarily
holds HTTP request and response objects. Echo also has a support for other types
of handlers.
### 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. If middleware is passed to the function, it overrides parent middleware
- helpful if you want a completely new middleware stack for the group. To add middleware
later 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
echo.Group("/admin")
e.Use(mw.BasicAuth(func(usr, pwd string) bool {
if usr == "joe" && pwd == "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

@ -1,6 +1,10 @@
---
title: Index
---
# Echo
A fast and unfancy micro web framework for Go.
A fast and unfancy micro web framework for Go.
---

View File

@ -1,10 +1,15 @@
## File Upload
---
title: File Upload
menu:
main:
parent: recipes
---
- Multipart/form-data file upload
- Multiple form fields and files
Use `req.ParseMultipartForm(16 << 20)` for manually parsing multipart form. It gives
us an option to specify the maximum memory used while parsing the request body.
us an option to specify the maximum memory used while parsing the request body.
## Server

View File

@ -0,0 +1,261 @@
---
title: Google App Engine
menu:
main:
parent: recipes
---
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`
```go
package main
// referecnce our echo instance and create it early
var e = createMux()
```
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`
```go
// +build !appengine,!appenginevm
package main
import (
"github.com/labstack/echo"
"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.Index("public/index.html")
e.Static("/public", "public")
return e
}
func main() {
e.Run(":8080")
}
```
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).
`some-endpoint.go`
```go
package main
import "github.com/labstack/echo"
func init() {
// our endpoint registers itself with the echo instance
// and could add it's own middleware if it needed
g := e.Group("/some-endpoint")
g.Get("", listHandler)
g.Get("/:id", getHandler)
g.Patch("/:id", updateHandler)
g.Post("/:id", addHandler)
g.Put("/:id", replaceHandler)
g.Delete("/:id", deleteHandler)
}
func listHandler(c *echo.Context) error {
// actual handler implementations ...
```
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.yaml`
```yaml
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
```
#### 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`
```go
// +build appengine
package main
import (
"net/http"
"github.com/labstack/echo"
)
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
http.Handle("/", e)
return e
}
```
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`
```go
// +build appenginevm
package main
import (
"runtime"
"net/http"
"github.com/labstack/echo"
"google.golang.org/appengine"
)
func createMux() *echo.Echo {
// we're in a container on a Google Compute Engine instance so are not sandboxed anymore ...
runtime.GOMAXPROCS(runtime.NumCPU())
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
http.Handle("/", e)
appengine.Main()
}
```
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.
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/google-app-engine)

View File

@ -1,4 +1,9 @@
## Graceful Shutdown
---
title: Graceful Shutdown
menu:
main:
parent: recipes
---
### With [graceful](https://github.com/tylerb/graceful)

View File

@ -1,9 +1,14 @@
## JWT Authentication
---
title: JWT Authentication
menu:
main:
parent: recipes
---
Most applications dealing with client authentication will require a more secure
mechanism than that provided by [basic authentication](https://github.com/labstack/echo/blob/master/middleware/auth.go). [JSON Web Tokens](http://jwt.io/)
are one such mechanism - JWTs are a compact means of transferring cryptographically
signed claims between the client and server.
signed claims between the client and server.
This recipe demonstrates the use of a simple JWT authentication Echo middleware
using Dave Grijalva's [jwt-go](https://github.com/dgrijalva/jwt-go). This middleware
@ -149,8 +154,3 @@ $ curl localhost:1323/restricted -H "Authorization: Bearer <token>" => Access g
```
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/jwt-authentication)

View File

@ -1,4 +1,9 @@
## Streaming File Upload
---
title: Streaming File Upload
menu:
main:
parent: recipes
---
- Streaming multipart/form-data file upload
- Multiple form fields and files
@ -114,4 +119,3 @@ func main() {
```
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-file-upload)

View File

@ -1,4 +1,9 @@
## Streaming Response
---
title: Streaming Response
menu:
main:
parent: recipes
---
- Send data as it is produced
- Streaming JSON response with chunked transfer encoding
@ -72,4 +77,3 @@ $ curl localhost:1323
```
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-response)

View File

@ -1,4 +1,9 @@
## Subdomains
---
title: Subdomains
menu:
main:
parent: recipes
---
`server.go`

View File

@ -1,4 +1,9 @@
## WebSocket
---
title: WebSocket
menu:
main:
parent: recipes
---
## Server
@ -108,4 +113,3 @@ Hello, Server!
```
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/websocket)

View File

@ -1 +0,0 @@
echo.labstack.com

View File

@ -1,453 +0,0 @@
# Guide
<!---
Some info about guide
-->
---
## Installation
Echo has been developed and tested using Go `1.4.x`
Install the latest version of Echo via `go get`
```sh
$ go get github.com/labstack/echo
```
To upgrade
```sh
$ go get -u github.com/labstack/echo
```
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).
## Customization
### HTTP error handler
`Echo.SetHTTPErrorHandler(h HTTPErrorHandler)`
Registers a custom `Echo.HTTPErrorHandler`.
Default 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)`
Enables/disables debug mode.
### Disable colored log
`Echo.DisableColoredLog()`
### StripTrailingSlash
StripTrailingSlash enables removing trailing slash from the request path.
`e.StripTrailingSlash()`
## Routing
Echo's router is [fast, optimized](https://github.com/labstack/echo#benchmark) 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 handler. For example,
code below registers a route for method `GET`, path `/hello` and a handler which sends
`Hello!` HTTP response.
```go
e.Get("/hello", func(c *echo.Context) error {
return c.String(http.StatusOK, "Hello!")
})
```
Echo's default handler is `func(*echo.Context) error` where `echo.Context` primarily
holds HTTP request and response objects. Echo also has a support for other types
of handlers.
### 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. If middleware is passed to the function, it overrides parent middleware
- helpful if you want a completely new middleware stack for the group. To add middleware
later 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
echo.Group("/admin")
e.Use(mw.BasicAuth(func(usr, pwd string) bool {
if usr == "joe" && pwd == "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)
```
## Middleware
Middleware is a function which is chained in the HTTP request-response cycle. Middleware
has access to the request and response objects which it utilizes to perform a specific
action, for example, logging every request.
### Logger
Logs each HTTP request with method, path, status, response time and bytes served.
*Example*
```go
e.Use(Logger())
// Output: `2015/06/07 18:16:16 GET / 200 13.238µs 14`
```
### BasicAuth
BasicAuth middleware provides an HTTP basic authentication.
- For valid credentials it calls the next handler in the chain.
- For invalid Authorization header it sends "404 - Bad Request" response.
- For invalid credentials, it sends "401 - Unauthorized" response.
*Example*
```go
e.Group("/admin")
e.Use(mw.BasicAuth(func(usr, pwd string) bool {
if usr == "joe" && pwd == "secret" {
return true
}
return false
}))
```
### Gzip
Gzip middleware compresses HTTP response using gzip compression scheme.
*Example*
```go
e.Use(mw.Gzip())
```
### Recover
Recover middleware recovers from panics anywhere in the chain and handles the control
to the centralized [HTTPErrorHandler](#error-handling).
*Example*
```go
e.Use(mw.Recover())
```
[Examples](https://github.com/labstack/echo/tree/master/examples/middleware)
## Request
### Path parameter
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
```
### Query parameter
Query parameter can be retrieved by name using `Context.Query(name string)`.
*Example*
```go
e.Get("/users", func(c *echo.Context) error {
name := c.Query("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.Form(name string)`.
*Example*
```go
e.Post("/users", func(c *echo.Context) error {
name := c.Form("name")
return c.String(http.StatusOK, name)
})
```
```sh
$ curl -d "name=joe" http://localhost:1323/users
```
## Response
### Template
```go
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.
Below is an example using Go `html/template`
- Implement `echo.Render` interface
```go
Template struct {
templates *template.Template
}
func (t *Template) Render(w io.Writer, name string, data interface{}) error {
return t.templates.ExecuteTemplate(w, name, data)
}
```
- Pre-compile templates
`public/views/hello.html`
```html
{{define "hello"}}Hello, {{.}}!{{end}}
```
```go
t := &Template{
templates: template.Must(template.ParseGlob("public/views/*.html")),
}
```
- Register templates
```go
e := echo.New()
e.SetRenderer(t)
e.Get("/hello", Hello)
```
- Render template
```go
func Hello(c *echo.Context) error {
return c.Render(http.StatusOK, "hello", "World")
}
```
### JSON
```go
Context.JSON(code int, v interface{}) error
```
Sends a JSON HTTP response with status code.
### XML
```go
Context.XML(code int, v interface{}) error
```
Sends an XML HTTP response with status code.
### HTML
```go
Context.HTML(code int, html string) error
```
Sends an HTML HTTP response with status code.
### String
```go
Context.String(code int, s string) error
```
Sends a text/plain HTTP response with status code.
### File
```go
Context.File(name string, attachment bool) error
```
File sends a response with the content of the file. If attachment is `true`, the client
is prompted to save the file.
### Static files
`Echo.Static(path, root string)` serves static files. For example, code below serves
files from directory `public/scripts` for any request path starting with `/scripts/`.
```go
e.Static("/scripts/", "public/scripts")
```
### Serving a file
`Echo.ServeFile(path, file string)` serves a file. For example, code below serves
file `welcome.html` for request path `/welcome`.
```go
e.ServeFile("/welcome", "welcome.html")
```
### Serving an index file
`Echo.Index(file string)` serves root index page - `GET /`. For example, code below
serves root index page from file `public/index.html`.
```go
e.Index("public/index.html")
```
### Serving favicon
`Echo.Favicon(file string)` serves default favicon - `GET /favicon.ico`. For example,
code below serves favicon from file `public/favicon.ico`.
```go
e.Favicon("public/favicon.ico")
```
## Error Handling
Echo advocates centralized HTTP error handling by returning `error` from middleware
and handlers.
It allows you to
- Debug by writing stack trace to the HTTP response.
- Customize HTTP responses.
- Recover from panics inside middleware or handlers.
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](#customization) handles it.

View File

@ -1,92 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if page_description %}
<meta name="description" content="{{ page_description }}">
{% endif %}
{% if site_author %}
<meta name="author" content="{{ site_author }}">
{% endif %}
{% if canonical_url %}
<link rel="canonical" href="{{ canonical_url }}">
{% endif %}
{% if favicon %}
<link rel="shortcut icon" href="{{ favicon }}">
{% else %}
<link rel="shortcut icon" href="{{ base_url }}/img/favicon.ico">
{% endif %}
<title>{% if page_title %}{{ page_title }} - {% endif %}{{
config.extra.site_title }}</title>
<link href="{{ base_url }}/css/bootstrap-custom.min.css" rel="stylesheet">
<link href="{{ base_url }}/css/font-awesome-4.0.3.css" rel="stylesheet">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/styles/tomorrow-night.min.css">
<link href="{{ base_url }}/css/base.css" rel="stylesheet">
<link href="{{ base_url }}/css/echo.css" rel="stylesheet">
{%- for path in extra_css %}
<link href="{{ path }}" rel="stylesheet">
{%- endfor %}
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script
src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script
src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
{% if google_analytics %}
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '{{ google_analytics[0] }}', '{{ google_analytics[1] }}');
ga('send', 'pageview');
</script>
{% endif %}
</head>
<body>
{% include "nav.html" %}
<div class="container">
<div class="col-md-3">{% include "toc.html" %}</div>
<div class="col-md-9" role="main">{% include "content.html" %}</div>
</div>
<footer class="col-md-12">
<hr>
<center class="social">
<a href="https://github.com/labstack" target="_blank">
<i class="fa fa-github fa-2x"></i>
</a>
<a href="https://twitter.com/labstack" target="_blank">
<i class="fa fa-twitter fa-2x"></i>
</a>
<a href="https://www.facebook.com/labstack" target="_blank">
<i class="fa fa-facebook fa-2x"></i>
</a>
</center>
{% if copyright %}
<center>{{ copyright }}</center>
{% endif %}
</footer>
<script src="{{ base_url }}/js/jquery-1.10.2.min.js"></script>
<script src="{{ base_url }}/js/bootstrap-3.0.3.min.js"></script>
<script src="{{ base_url }}/js/highlight.pack.js"></script>
<script src="{{ base_url }}/js/base.js"></script>
{%- for path in extra_javascript %}
<script src="{{ path }}"></script>
{%- endfor %}
</body>
</html>

View File

@ -1,7 +0,0 @@
.social a {
text-decoration: none;
}
.social a:not(:first-child) {
margin-left: 20px;
}

View File

@ -1,10 +0,0 @@
var gulp = require('gulp');
var shell = require('gulp-shell');
var ghPages = require('gulp-gh-pages');
gulp.task('build', shell.task('mkdocs build --clean'))
gulp.task('deploy',['build'], function() {
return gulp.src('site/**/*')
.pipe(ghPages());
});

View File

@ -0,0 +1,45 @@
{{ partial "head.html" . }}
<body>
{{ . }}
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
{{ partial "header.html" . }}
<main class="mdl-layout__content">
<div class="page-content single">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--3-col">
{{ partial "menu.html" . }}
</div>
<div class="mdl-cell mdl-cell--9-col">
<article>
<header>
<h2>{{ .Title }}</h2>
</header>
<section class="content">
{{ .Content }}
</section>
<footer>
<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>
</main>
{{ partial "footer.html" . }}
</div>
</body>
</html>

View File

@ -0,0 +1,24 @@
{{ partial "head.html" . }}
<body>
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
{{ partial "header.html" . }}
<main class="mdl-layout__content">
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--3-col">
{{ partial "menu.html" . }}
</div>
<div class="mdl-cell mdl-cell--9-col">
{{ range where .Data.Pages "Title" "Index" }}
{{ .Content }}
{{ end }}
</div>
</div>
</div>
</main>
{{ partial "footer.html" . }}
</div>
</body>
</html>

View File

View File

@ -0,0 +1,54 @@
<footer class="mdl-mini-footer">
<div class="mdl-mini-footer__left-section">
<span class="mdl-typography__font-light">© 2015 LabStack</span>
<!-- <ul class="mdl-mini-footer__link-list">
<li><a href="#">Help</a></li>
<li><a href="#">Privacy & Terms</a></li>
</ul> -->
</div>
<div class="mdl-mini-footer__right-section">
<ul class="mdl-mini-footer__link-list">
<li>
<a href="http://facebook.com/chilimushroom">
<i class="fa fa-facebook-square fa-2x"></i>
</a>
</li>
<li>
<a href="http://twitter.com/chilimushroom">
<i class="fa fa-twitter-square fa-2x"></i>
</a>
</li>
<li>
<a href="http://plus.google.com/+chilimushroom">
<i class="fa fa-google-plus-square fa-2x"></i>
</a>
</li>
</ul>
<!-- <a href="https://twitter.com/chilimushroom" class="social-btn social-btn__twitter" role="button" title="Twitter">
<i class="fa fa-facebook-square"></i>
</a>
<button class="mdl-mini-footer__social-btn"><i class="fa fa-facebook-square"></i></button>
&nbsp;
<button class="mdl-mini-footer__social-btn"></button>
&nbsp;
<button class="mdl-mini-footer__social-btn social-btn__twitter"></button> -->
</div>
</footer>
<script src="{{ .Site.BaseURL }}/scripts/highlight.pack.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', '{{ .Site.Params.googleAnayticsId }}', 'auto');
ga('send', 'pageview');
</script>

View File

@ -0,0 +1,20 @@
<!doctype html>
<html lang="{{ .Site.LanguageCode }}">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>
{{ if ne .URL "/" }}{{ .Title }} | {{ end }}{{ .Site.Title }}
</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- <link rel="apple-touch-icon" href="apple-touch-icon.png"> -->
<!-- Place favicon.ico in the root directory -->
<link rel="stylesheet" href="//storage.googleapis.com/code.getmdl.io/1.0.5/material.blue-pink.min.css">
<link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<link rel="stylesheet" href="{{ .Site.BaseURL }}/styles/monokai.css">
<link rel="stylesheet" href="{{ .Site.BaseURL }}/styles/echo.css">
</head>

View File

@ -0,0 +1,13 @@
<header class="mdl-layout__header">
<div class="mdl-layout__header-row">
<a href="{{ .Site.BaseURL }}" class="mdl-navigation__link mdl-layout-title">
{{ .Site.Title }}
</a>
<div class="mdl-layout-spacer"></div>
<nav class="mdl-navigation mdl-layout--large-screen-only">
<a href="https://github.com/labstack/echo" class="mdl-navigation__link">
<i class="fa fa-github fa-lg"></i> GitHub
</a>
</nav>
</div>
</header>

View File

@ -0,0 +1,12 @@
<aside class="menu">
<div>
{{ range .Site.Menus.main }}
{{ if .HasChildren }}
<h4>{{ .Name }}</h4>
{{ range .Children }}
<a href="{{ .URL }}">{{ .Name }}</a>
{{ end }}
{{ end }}
{{ end }}
</div>
</aside>

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

@ -1,19 +0,0 @@
site_name: Echo
theme: flatly
theme_dir: echo
copyright: '&copy; 2015 LabStack'
repo_url: https://github.com/labstack/echo
google_analytics: ['UA-51208124-3', 'auto']
pages:
- Home: index.md
- Guide: guide.md
- Recipes:
- File Upload: recipes/file-upload.md
- Streaming File Upload: recipes/streaming-file-upload.md
- Streaming Response: recipes/streaming-response.md
- WebSocket: recipes/websocket.md
- Subdomains: recipes/subdomains.md
- JWT Authentication: recipes/jwt-authentication.md
- Graceful Shutdown: recipes/graceful-shutdown.md
extra:
site_title: Echo, a fast and unfancy micro web framework for Go.

17
website/server.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"github.com/labstack/echo"
mw "github.com/labstack/echo/middleware"
)
func main() {
e := echo.New()
e.Use(mw.Logger())
e.Use(mw.Recover())
e.Use(mw.Gzip())
e.Static("/", "public")
e.Run(":5091")
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,26 @@
body {
background-color: #efefef;
}
footer {
padding: 40px 80px !important;
}
a:link {
text-decoration: none;
}
.page-content {
padding: 20px 0;
max-width: 960px;
margin: auto;
}
.page-content header {
padding-bottom: 16px;
}
.menu a {
display: block;
padding: 2px 0
}

View File

@ -0,0 +1,126 @@
/*
Monokai style - ported by Luigi Maselli - http://grigio.org
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #272822;
-webkit-text-size-adjust: none;
}
.hljs-tag,
.hljs-tag .hljs-title,
.hljs-keyword,
.hljs-literal,
.hljs-strong,
.hljs-change,
.hljs-winutils,
.hljs-flow,
.nginx .hljs-title,
.tex .hljs-special {
color: #f92672;
}
.hljs {
color: #ddd;
}
.hljs .hljs-constant,
.asciidoc .hljs-code,
.markdown .hljs-code {
color: #66d9ef;
}
.hljs-code,
.hljs-class .hljs-title,
.hljs-header {
color: white;
}
.hljs-link_label,
.hljs-attribute,
.hljs-symbol,
.hljs-symbol .hljs-string,
.hljs-value,
.hljs-regexp {
color: #bf79db;
}
.hljs-link_url,
.hljs-tag .hljs-value,
.hljs-string,
.hljs-bullet,
.hljs-subst,
.hljs-title,
.hljs-emphasis,
.hljs-type,
.hljs-preprocessor,
.hljs-pragma,
.ruby .hljs-class .hljs-parent,
.hljs-built_in,
.django .hljs-template_tag,
.django .hljs-variable,
.smalltalk .hljs-class,
.django .hljs-filter .hljs-argument,
.smalltalk .hljs-localvars,
.smalltalk .hljs-array,
.hljs-attr_selector,
.hljs-pseudo,
.hljs-addition,
.hljs-stream,
.hljs-envvar,
.apache .hljs-tag,
.apache .hljs-cbracket,
.tex .hljs-command,
.hljs-prompt,
.hljs-name {
color: #a6e22e;
}
.hljs-comment,
.hljs-annotation,
.smartquote,
.hljs-blockquote,
.hljs-horizontal_rule,
.hljs-decorator,
.hljs-pi,
.hljs-doctype,
.hljs-deletion,
.hljs-shebang,
.apache .hljs-sqbracket,
.tex .hljs-formula {
color: #75715e;
}
.hljs-keyword,
.hljs-literal,
.css .hljs-id,
.hljs-doctag,
.hljs-title,
.hljs-header,
.hljs-type,
.vbscript .hljs-built_in,
.rsl .hljs-built_in,
.smalltalk .hljs-class,
.diff .hljs-header,
.hljs-chunk,
.hljs-winutils,
.bash .hljs-variable,
.apache .hljs-tag,
.tex .hljs-special,
.hljs-request,
.hljs-status {
font-weight: bold;
}
.coffeescript .javascript,
.javascript .xml,
.tex .hljs-formula,
.xml .javascript,
.xml .vbscript,
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
}