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:
parent
c0571e37c3
commit
c020919cb4
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
# Website
|
||||
site
|
||||
public
|
||||
.publish
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
- 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
|
||||
|
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)
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -1,4 +1,9 @@
|
||||
## Subdomains
|
||||
---
|
||||
title: Subdomains
|
||||
menu:
|
||||
main:
|
||||
parent: recipes
|
||||
---
|
||||
|
||||
`server.go`
|
||||
|
@ -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)
|
||||
|
@ -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