mirror of
https://github.com/labstack/echo.git
synced 2025-01-12 01:22:21 +02:00
Updated website
Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
parent
c0571e37c3
commit
c020919cb4
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,5 @@
|
|||||||
# Website
|
# Website
|
||||||
site
|
public
|
||||||
.publish
|
.publish
|
||||||
|
|
||||||
# Node.js
|
# Node.js
|
||||||
|
7
website/Dockerfile
Normal file
7
website/Dockerfile
Normal 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
25
website/config.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
35
website/content/guide/customization.md
Normal file
35
website/content/guide/customization.md
Normal 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()`
|
47
website/content/guide/error-handling.md
Normal file
47
website/content/guide/error-handling.md
Normal 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.
|
23
website/content/guide/installation.md
Normal file
23
website/content/guide/installation.md
Normal 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).
|
65
website/content/guide/middleware.md
Normal file
65
website/content/guide/middleware.md
Normal 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)
|
64
website/content/guide/request.md
Normal file
64
website/content/guide/request.md
Normal 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
|
||||||
|
```
|
135
website/content/guide/response.md
Normal file
135
website/content/guide/response.md
Normal 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")
|
||||||
|
```
|
105
website/content/guide/routing.md
Normal file
105
website/content/guide/routing.md
Normal 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)
|
||||||
|
```
|
@ -1,6 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: Index
|
||||||
|
---
|
||||||
|
|
||||||
# Echo
|
# Echo
|
||||||
|
|
||||||
A fast and unfancy micro web framework for Go.
|
A fast and unfancy micro web framework for Go.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -1,10 +1,15 @@
|
|||||||
## File Upload
|
---
|
||||||
|
title: File Upload
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
parent: recipes
|
||||||
|
---
|
||||||
|
|
||||||
- Multipart/form-data file upload
|
- Multipart/form-data file upload
|
||||||
- Multiple form fields and files
|
- Multiple form fields and files
|
||||||
|
|
||||||
Use `req.ParseMultipartForm(16 << 20)` for manually parsing multipart form. It gives
|
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
|
## Server
|
||||||
|
|
261
website/content/recipes/google-app-engine.md
Normal file
261
website/content/recipes/google-app-engine.md
Normal 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)
|
@ -1,4 +1,9 @@
|
|||||||
## Graceful Shutdown
|
---
|
||||||
|
title: Graceful Shutdown
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
parent: recipes
|
||||||
|
---
|
||||||
|
|
||||||
### With [graceful](https://github.com/tylerb/graceful)
|
### With [graceful](https://github.com/tylerb/graceful)
|
||||||
|
|
@ -1,9 +1,14 @@
|
|||||||
## JWT Authentication
|
---
|
||||||
|
title: JWT Authentication
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
parent: recipes
|
||||||
|
---
|
||||||
|
|
||||||
Most applications dealing with client authentication will require a more secure
|
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/)
|
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
|
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
|
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
|
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)
|
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/jwt-authentication)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
|||||||
## Streaming File Upload
|
---
|
||||||
|
title: Streaming File Upload
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
parent: recipes
|
||||||
|
---
|
||||||
|
|
||||||
- Streaming multipart/form-data file upload
|
- Streaming multipart/form-data file upload
|
||||||
- Multiple form fields and files
|
- Multiple form fields and files
|
||||||
@ -114,4 +119,3 @@ func main() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-file-upload)
|
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-file-upload)
|
||||||
|
|
@ -1,4 +1,9 @@
|
|||||||
## Streaming Response
|
---
|
||||||
|
title: Streaming Response
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
parent: recipes
|
||||||
|
---
|
||||||
|
|
||||||
- Send data as it is produced
|
- Send data as it is produced
|
||||||
- Streaming JSON response with chunked transfer encoding
|
- 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)
|
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-response)
|
||||||
|
|
@ -1,4 +1,9 @@
|
|||||||
## Subdomains
|
---
|
||||||
|
title: Subdomains
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
parent: recipes
|
||||||
|
---
|
||||||
|
|
||||||
`server.go`
|
`server.go`
|
||||||
|
|
@ -1,4 +1,9 @@
|
|||||||
## WebSocket
|
---
|
||||||
|
title: WebSocket
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
parent: recipes
|
||||||
|
---
|
||||||
|
|
||||||
## Server
|
## Server
|
||||||
|
|
||||||
@ -108,4 +113,3 @@ Hello, Server!
|
|||||||
```
|
```
|
||||||
|
|
||||||
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/websocket)
|
## [Source Code](https://github.com/labstack/echo/blob/master/recipes/websocket)
|
||||||
|
|
@ -1 +0,0 @@
|
|||||||
echo.labstack.com
|
|
@ -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.
|
|
@ -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>
|
|
@ -1,7 +0,0 @@
|
|||||||
.social a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social a:not(:first-child) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
@ -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());
|
|
||||||
});
|
|
45
website/layouts/_default/single.html
Normal file
45
website/layouts/_default/single.html
Normal 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>
|
24
website/layouts/index.html
Normal file
24
website/layouts/index.html
Normal 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>
|
0
website/layouts/partials/connect.html
Normal file
0
website/layouts/partials/connect.html
Normal file
54
website/layouts/partials/footer.html
Normal file
54
website/layouts/partials/footer.html
Normal 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>
|
||||||
|
|
||||||
|
<button class="mdl-mini-footer__social-btn"></button>
|
||||||
|
|
||||||
|
<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>
|
20
website/layouts/partials/head.html
Normal file
20
website/layouts/partials/head.html
Normal 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>
|
13
website/layouts/partials/header.html
Normal file
13
website/layouts/partials/header.html
Normal 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>
|
12
website/layouts/partials/menu.html
Normal file
12
website/layouts/partials/menu.html
Normal 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>
|
14
website/layouts/partials/share.html
Normal file
14
website/layouts/partials/share.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<span class="share">
|
||||||
|
<a href="https://www.facebook.com/sharer/sharer.php?u={{ .Permalink }}" target="_blank">
|
||||||
|
<i class="fa fa-facebook-square fa-2x"></i>
|
||||||
|
</a>
|
||||||
|
<a href="http://twitter.com/share?text={{ .Title }}&url={{ .Permalink }}" target="_blank">
|
||||||
|
<i class="fa fa-twitter-square fa-2x"></i>
|
||||||
|
</a>
|
||||||
|
<a href="https://plus.google.com/share?url={{ .Permalink }}" target="_blank">
|
||||||
|
<i class="fa fa-google-plus-square fa-2x"></i>
|
||||||
|
</a>
|
||||||
|
<a class="ui reddit icon button" href="http://www.reddit.com/submit?url={{ .Permalink }}&title={{ .Title }}" target="_blank">
|
||||||
|
<i class="fa fa-reddit-square fa-2x"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
@ -1,19 +0,0 @@
|
|||||||
site_name: Echo
|
|
||||||
theme: flatly
|
|
||||||
theme_dir: echo
|
|
||||||
copyright: '© 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
17
website/server.go
Normal 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")
|
||||||
|
}
|
9
website/static/scripts/highlight.pack.min.js
vendored
Normal file
9
website/static/scripts/highlight.pack.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
26
website/static/styles/echo.css
Normal file
26
website/static/styles/echo.css
Normal 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
|
||||||
|
}
|
126
website/static/styles/monokai.css
Normal file
126
website/static/styles/monokai.css
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user