1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-22 20:06:21 +02:00

Merge pull request #347 from syntaqx/quibbles

Quibbles and Linting
This commit is contained in:
Vishal Rana 2016-01-30 06:42:17 -08:00
commit 28b1b9d57b
13 changed files with 237 additions and 61 deletions

14
.gitattributes vendored
View File

@ -2,19 +2,21 @@
# http://git-scm.com/docs/gitattributes#_end_of_line_conversion # http://git-scm.com/docs/gitattributes#_end_of_line_conversion
* text=auto * text=auto
# For the following file types, normalize line endings to LF on checking and # For the following file types, normalize line endings to LF on checkin and
# prevent conversion to CRLF when they are checked out (this is required in # prevent conversion to CRLF when they are checked out (this is required in
# order to prevent newline related issues) # order to prevent newline related issues)
.* text eol=lf .* text eol=lf
*.go text eol=lf
*.yml text eol=lf
*.html text eol=lf
*.css text eol=lf *.css text eol=lf
*.go text eol=lf
*.html text eol=lf
*.js text eol=lf *.js text eol=lf
*.json text eol=lf *.json text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
LICENSE text eol=lf LICENSE text eol=lf
# Exclude `website` and `recipes` from Github's language statistics # Exclude documentation-related directories from Github's language statistics
# https://github.com/github/linguist#using-gitattributes # https://github.com/github/linguist#using-gitattributes
recipes/* linguist-documentation _fixture/* linguist-documentation
website/* linguist-documentation website/* linguist-documentation

41
.gitignore vendored
View File

@ -1,13 +1,42 @@
# Website # gitignore - Specifies intentionally untracked files to ignore
# http://git-scm.com/docs/gitignore
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# submodule properties
.gitmodules
# compiled web files
website/public website/public
website/make website/make
website/Makefile website/Makefile
website/marathon* website/marathon*
.gitmodules
# Node.js # node.js web dependencies
node_modules node_modules
# IntelliJ # code coverage output files
.idea *.coverprofile
*.iml

1
.godir Normal file
View File

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

View File

@ -1,13 +1,39 @@
language: go language: go
sudo: false
go: go:
- 1.4 - 1.4
- 1.5
- 1.6rc1
- tip - tip
env:
global:
- GO15VENDOREXPERIMENT=1
before_install: before_install:
- export PATH=$PATH:$GOPATH/bin
- go get golang.org/x/tools/cmd/vet
- go get golang.org/x/tools/cmd/cover
- go get github.com/modocache/gover - go get github.com/modocache/gover
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
install:
- go get -t -v ./...
script: script:
- go test -coverprofile=echo.coverprofile - go vet ./...
- go test -coverprofile=middleware.coverprofile ./middleware - go test -v -race ./...
- $HOME/gopath/bin/gover - diff -u <(echo -n) <(gofmt -d -s .)
- $HOME/gopath/bin/goveralls -coverprofile=gover.coverprofile -service=travis-ci - go test -v -coverprofile=echo.coverprofile
- go test -v -coverprofile=middleware.coverprofile ./middleware
- gover
- goveralls -coverprofile=gover.coverprofile -service=travis-ci
notifications:
email:
on_success: change
on_failure: always
matrix:
allow_failures:
- go: tip

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2015 LabStack Copyright (c) 2016 LabStack
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

View File

@ -120,7 +120,7 @@ func (c *Context) Bind(i interface{}) error {
// code. Templates can be registered using `Echo.SetRenderer()`. // code. Templates can be registered using `Echo.SetRenderer()`.
func (c *Context) Render(code int, name string, data interface{}) (err error) { func (c *Context) Render(code int, name string, data interface{}) (err error) {
if c.echo.renderer == nil { if c.echo.renderer == nil {
return RendererNotRegistered return ErrRendererNotRegistered
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err = c.echo.renderer.Render(buf, name, data); err != nil { if err = c.echo.renderer.Render(buf, name, data); err != nil {
@ -244,7 +244,7 @@ func (c *Context) NoContent(code int) error {
// Redirect redirects the request using http.Redirect with status code. // Redirect redirects the request using http.Redirect with status code.
func (c *Context) Redirect(code int, url string) error { func (c *Context) Redirect(code int, url string) error {
if code < http.StatusMultipleChoices || code > http.StatusTemporaryRedirect { if code < http.StatusMultipleChoices || code > http.StatusTemporaryRedirect {
return InvalidRedirectCode return ErrInvalidRedirectCode
} }
http.Redirect(c.response, c.request, url, code) http.Redirect(c.response, c.request, url, code)
return nil return nil

View File

@ -234,6 +234,10 @@ func TestContext(t *testing.T) {
// reset // reset
c.reset(req, NewResponse(httptest.NewRecorder(), e), e) c.reset(req, NewResponse(httptest.NewRecorder(), e), e)
// after reset (nil store) set test
c.Set("user", "Joe")
assert.Equal(t, "Joe", c.Get("user"))
} }
func TestContextPath(t *testing.T) { func TestContextPath(t *testing.T) {
@ -285,6 +289,13 @@ func TestContextNetContext(t *testing.T) {
assert.Equal(t, "val", c.Value("key")) assert.Equal(t, "val", c.Value("key"))
} }
func TestContextEcho(t *testing.T) {
c := new(Context)
// Should be null when initialized without one
assert.Nil(t, c.Echo())
}
func testBindOk(t *testing.T, c *Context, ct string) { func testBindOk(t *testing.T, c *Context, ct string) {
c.request.Header.Set(ContentType, ct) c.request.Header.Set(ContentType, ct)
u := new(user) u := new(user)
@ -307,7 +318,7 @@ func testBindError(t *testing.T, c *Context, ct string) {
} }
default: default:
if assert.IsType(t, new(HTTPError), err) { if assert.IsType(t, new(HTTPError), err) {
assert.Equal(t, UnsupportedMediaType, err) assert.Equal(t, ErrUnsupportedMediaType, err)
} }
} }

53
echo.go
View File

@ -1,8 +1,40 @@
/*
Package echo implements a fast and unfancy micro web framework for Go.
Example:
package main
import (
"net/http"
"github.com/labstack/echo"
mw "github.com/labstack/echo/middleware"
)
func hello(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
}
func main() {
e := echo.New()
e.Use(mw.Logger())
e.Use(mw.Recover())
e.Get("/", hello)
e.Run(":1323")
}
Learn more at https://labstack.com/echo
*/
package echo package echo
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -14,14 +46,13 @@ import (
"strings" "strings"
"sync" "sync"
"encoding/xml"
"github.com/labstack/gommon/log" "github.com/labstack/gommon/log"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
) )
type ( type (
// Echo is the top-level framework instance.
Echo struct { Echo struct {
prefix string prefix string
middleware []MiddlewareFunc middleware []MiddlewareFunc
@ -39,20 +70,29 @@ type (
router *Router router *Router
} }
// Route contains a handler and information for matching against requests.
Route struct { Route struct {
Method string Method string
Path string Path string
Handler Handler Handler Handler
} }
// HTTPError represents an error that occured while handling a request.
HTTPError struct { HTTPError struct {
code int code int
message string message string
} }
// Middleware ...
Middleware interface{} Middleware interface{}
// MiddlewareFunc ...
MiddlewareFunc func(HandlerFunc) HandlerFunc MiddlewareFunc func(HandlerFunc) HandlerFunc
// Handler ...
Handler interface{} Handler interface{}
// HandlerFunc ...
HandlerFunc func(*Context) error HandlerFunc func(*Context) error
// HTTPErrorHandler is a centralized HTTP error handler. // HTTPErrorHandler is a centralized HTTP error handler.
@ -164,9 +204,9 @@ var (
// Errors // Errors
//-------- //--------
UnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType) ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
RendererNotRegistered = errors.New("renderer not registered") ErrRendererNotRegistered = errors.New("renderer not registered")
InvalidRedirectCode = errors.New("invalid redirect status code") ErrInvalidRedirectCode = errors.New("invalid redirect status code")
//---------------- //----------------
// Error handlers // Error handlers
@ -588,6 +628,7 @@ func (e *Echo) run(s *http.Server, files ...string) {
} }
} }
// NewHTTPError creates a new HTTPError instance.
func NewHTTPError(code int, msg ...string) *HTTPError { func NewHTTPError(code int, msg ...string) *HTTPError {
he := &HTTPError{code: code, message: http.StatusText(code)} he := &HTTPError{code: code, message: http.StatusText(code)}
if len(msg) > 0 { if len(msg) > 0 {
@ -691,7 +732,7 @@ func wrapHandler(h Handler) HandlerFunc {
func (binder) Bind(r *http.Request, i interface{}) (err error) { func (binder) Bind(r *http.Request, i interface{}) (err error) {
ct := r.Header.Get(ContentType) ct := r.Header.Get(ContentType)
err = UnsupportedMediaType err = ErrUnsupportedMediaType
if strings.HasPrefix(ct, ApplicationJSON) { if strings.HasPrefix(ct, ApplicationJSON) {
if err = json.NewDecoder(r.Body).Decode(i); err != nil { if err = json.NewDecoder(r.Body).Decode(i); err != nil {
err = NewHTTPError(http.StatusBadRequest, err.Error()) err = NewHTTPError(http.StatusBadRequest, err.Error())

View File

@ -1,81 +1,102 @@
package echo package echo
type ( // Group is a set of subroutes for a specified route. It can be used for inner
Group struct { // routes that share a common middlware or functionality that should be separate
// from the parent echo instance while still inheriting from it.
type Group struct {
echo Echo echo Echo
} }
)
// Use implements the echo.Use interface for subroutes within the Group.
func (g *Group) Use(m ...Middleware) { func (g *Group) Use(m ...Middleware) {
for _, h := range m { for _, h := range m {
g.echo.middleware = append(g.echo.middleware, wrapMiddleware(h)) g.echo.middleware = append(g.echo.middleware, wrapMiddleware(h))
} }
} }
// Connect implements the echo.Connect interface for subroutes within the Group.
func (g *Group) Connect(path string, h Handler) { func (g *Group) Connect(path string, h Handler) {
g.echo.Connect(path, h) g.echo.Connect(path, h)
} }
// Delete implements the echo.Delete interface for subroutes within the Group.
func (g *Group) Delete(path string, h Handler) { func (g *Group) Delete(path string, h Handler) {
g.echo.Delete(path, h) g.echo.Delete(path, h)
} }
// Get implements the echo.Get interface for subroutes within the Group.
func (g *Group) Get(path string, h Handler) { func (g *Group) Get(path string, h Handler) {
g.echo.Get(path, h) g.echo.Get(path, h)
} }
// Head implements the echo.Head interface for subroutes within the Group.
func (g *Group) Head(path string, h Handler) { func (g *Group) Head(path string, h Handler) {
g.echo.Head(path, h) g.echo.Head(path, h)
} }
// Options implements the echo.Options interface for subroutes within the Group.
func (g *Group) Options(path string, h Handler) { func (g *Group) Options(path string, h Handler) {
g.echo.Options(path, h) g.echo.Options(path, h)
} }
// Patch implements the echo.Patch interface for subroutes within the Group.
func (g *Group) Patch(path string, h Handler) { func (g *Group) Patch(path string, h Handler) {
g.echo.Patch(path, h) g.echo.Patch(path, h)
} }
// Post implements the echo.Post interface for subroutes within the Group.
func (g *Group) Post(path string, h Handler) { func (g *Group) Post(path string, h Handler) {
g.echo.Post(path, h) g.echo.Post(path, h)
} }
// Put implements the echo.Put interface for subroutes within the Group.
func (g *Group) Put(path string, h Handler) { func (g *Group) Put(path string, h Handler) {
g.echo.Put(path, h) g.echo.Put(path, h)
} }
// Trace implements the echo.Trace interface for subroutes within the Group.
func (g *Group) Trace(path string, h Handler) { func (g *Group) Trace(path string, h Handler) {
g.echo.Trace(path, h) g.echo.Trace(path, h)
} }
// Any implements the echo.Any interface for subroutes within the Group.
func (g *Group) Any(path string, h Handler) { func (g *Group) Any(path string, h Handler) {
for _, m := range methods { for _, m := range methods {
g.echo.add(m, path, h) g.echo.add(m, path, h)
} }
} }
// Match implements the echo.Match interface for subroutes within the Group.
func (g *Group) Match(methods []string, path string, h Handler) { func (g *Group) Match(methods []string, path string, h Handler) {
for _, m := range methods { for _, m := range methods {
g.echo.add(m, path, h) g.echo.add(m, path, h)
} }
} }
// WebSocket implements the echo.WebSocket interface for subroutes within the
// Group.
func (g *Group) WebSocket(path string, h HandlerFunc) { func (g *Group) WebSocket(path string, h HandlerFunc) {
g.echo.WebSocket(path, h) g.echo.WebSocket(path, h)
} }
// Static implements the echo.Static interface for subroutes within the Group.
func (g *Group) Static(path, root string) { func (g *Group) Static(path, root string) {
g.echo.Static(path, root) g.echo.Static(path, root)
} }
// ServeDir implements the echo.ServeDir interface for subroutes within the
// Group.
func (g *Group) ServeDir(path, root string) { func (g *Group) ServeDir(path, root string) {
g.echo.ServeDir(path, root) g.echo.ServeDir(path, root)
} }
// ServeFile implements the echo.ServeFile interface for subroutes within the
// Group.
func (g *Group) ServeFile(path, file string) { func (g *Group) ServeFile(path, file string) {
g.echo.ServeFile(path, file) g.echo.ServeFile(path, file)
} }
// Group implements the echo.Group interface for subroutes within the Group.
func (g *Group) Group(prefix string, m ...Middleware) *Group { func (g *Group) Group(prefix string, m ...Middleware) *Group {
return g.echo.Group(prefix, m...) return g.echo.Group(prefix, m...)
} }

View File

@ -8,17 +8,19 @@ import (
) )
type ( type (
// BasicValidateFunc is the expected format a BasicAuth fn argument is
// expected to implement.
BasicValidateFunc func(string, string) bool BasicValidateFunc func(string, string) bool
) )
const ( const (
// Basic is the authentication scheme implemented by the middleware.
Basic = "Basic" Basic = "Basic"
) )
// BasicAuth returns an HTTP basic authentication middleware. // BasicAuth returns a HTTP basic authentication middleware.
// // For valid credentials, it calls the next handler.
// For valid credentials it calls the next handler. // For invalid credentials, it returns a "401 Unauthorized" HTTP error.
// For invalid credentials, it sends "401 - Unauthorized" response.
func BasicAuth(fn BasicValidateFunc) echo.HandlerFunc { func BasicAuth(fn BasicValidateFunc) echo.HandlerFunc {
return func(c *echo.Context) error { return func(c *echo.Context) error {
// Skip WebSocket // Skip WebSocket

View File

@ -8,6 +8,9 @@ import (
"github.com/labstack/gommon/color" "github.com/labstack/gommon/color"
) )
const loggerFormat = "%s %s %s %s %s %d"
// Logger returns a Middleware that logs requests.
func Logger() echo.MiddlewareFunc { func Logger() echo.MiddlewareFunc {
return func(h echo.HandlerFunc) echo.HandlerFunc { return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error { return func(c *echo.Context) error {
@ -47,7 +50,7 @@ func Logger() echo.MiddlewareFunc {
code = color.Cyan(n) code = color.Cyan(n)
} }
logger.Info("%s %s %s %s %s %d", remoteAddr, method, path, code, stop.Sub(start), size) logger.Info(loggerFormat, remoteAddr, method, path, code, stop.Sub(start), size)
return nil return nil
} }
} }

View File

@ -6,32 +6,46 @@ import (
"net/http" "net/http"
) )
type ( // Response wraps an http.ResponseWriter and implements its interface to be used
Response struct { // by an HTTP handler to construct an HTTP response.
// See [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter)
type Response struct {
writer http.ResponseWriter writer http.ResponseWriter
status int status int
size int64 size int64
committed bool committed bool
echo *Echo echo *Echo
} }
)
// NewResponse creates a new instance of Response.
func NewResponse(w http.ResponseWriter, e *Echo) *Response { func NewResponse(w http.ResponseWriter, e *Echo) *Response {
return &Response{writer: w, echo: e} return &Response{writer: w, echo: e}
} }
// SetWriter sets the http.ResponseWriter instance for this Response.
func (r *Response) SetWriter(w http.ResponseWriter) { func (r *Response) SetWriter(w http.ResponseWriter) {
r.writer = w r.writer = w
} }
func (r *Response) Header() http.Header { // Writer returns the http.ResponseWriter instance for this Response.
return r.writer.Header()
}
func (r *Response) Writer() http.ResponseWriter { func (r *Response) Writer() http.ResponseWriter {
return r.writer return r.writer
} }
// Header returns the header map for the writer that will be sent by
// WriteHeader. Changing the header after a call to WriteHeader (or Write) has
// no effect unless the modified headers were declared as trailers by setting
// the "Trailer" header before the call to WriteHeader (see example)
// To suppress implicit response headers, set their value to nil.
// Example [ResponseWriter.Trailers](https://golang.org/pkg/net/http/#example_ResponseWriter_trailers)
func (r *Response) Header() http.Header {
return r.writer.Header()
}
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
// not called explicitly, the first call to Write will trigger an implicit
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
// used to send error codes.
func (r *Response) WriteHeader(code int) { func (r *Response) WriteHeader(code int) {
if r.committed { if r.committed {
r.echo.Logger().Warn("response already committed") r.echo.Logger().Warn("response already committed")
@ -42,35 +56,49 @@ func (r *Response) WriteHeader(code int) {
r.committed = true r.committed = true
} }
// Write wraps and implements the http.Response.Write specification.
// Additionally, Write will increment the size of the current response.
// See [http.Response.Write](https://golang.org/pkg/net/http/#Response.Write)
func (r *Response) Write(b []byte) (n int, err error) { func (r *Response) Write(b []byte) (n int, err error) {
n, err = r.writer.Write(b) n, err = r.writer.Write(b)
r.size += int64(n) r.size += int64(n)
return n, err return n, err
} }
// Flush wraps response writer's Flush function. // Flush implements the http.Flusher interface to allow an HTTP handler to flush
// buffered data to the client.
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
func (r *Response) Flush() { func (r *Response) Flush() {
r.writer.(http.Flusher).Flush() r.writer.(http.Flusher).Flush()
} }
// Hijack wraps response writer's Hijack function. // Hijack implements the http.Hijacker interface to allow an HTTP handler to
// take over the connection.
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return r.writer.(http.Hijacker).Hijack() return r.writer.(http.Hijacker).Hijack()
} }
// CloseNotify wraps response writer's CloseNotify function. // CloseNotify implements the http.CloseNotifier interface to allow detecting
// when the underlying connection has gone away.
// This mechanism can be used to cancel long operations on the server if the
// client has disconnected before the response is ready.
// See [http.CloseNotifier](https://golang.org/pkg/net/http/#CloseNotifier)
func (r *Response) CloseNotify() <-chan bool { func (r *Response) CloseNotify() <-chan bool {
return r.writer.(http.CloseNotifier).CloseNotify() return r.writer.(http.CloseNotifier).CloseNotify()
} }
// Status returns the HTTP status code of the response.
func (r *Response) Status() int { func (r *Response) Status() int {
return r.status return r.status
} }
// Size returns the current size, in bytes, of the response.
func (r *Response) Size() int64 { func (r *Response) Size() int64 {
return r.size return r.size
} }
// Committed asserts whether or not the response has been committed to.
func (r *Response) Committed() bool { func (r *Response) Committed() bool {
return r.committed return r.committed
} }

View File

@ -3,6 +3,12 @@ package echo
import "net/http" import "net/http"
type ( type (
// Router is the registry of all registered routes for an Echo instance for
// request matching and handler dispatching.
//
// Router implements the http.Handler specification and can be registered
// to serve requests.
Router struct { Router struct {
tree *node tree *node
routes []Route routes []Route
@ -40,6 +46,7 @@ const (
mkind mkind
) )
// NewRouter returns a new Router instance.
func NewRouter(e *Echo) *Router { func NewRouter(e *Echo) *Router {
return &Router{ return &Router{
tree: &node{ tree: &node{
@ -50,6 +57,7 @@ func NewRouter(e *Echo) *Router {
} }
} }
// Add registers a new route with a matcher for the URL path.
func (r *Router) Add(method, path string, h HandlerFunc, e *Echo) { func (r *Router) Add(method, path string, h HandlerFunc, e *Echo) {
ppath := path // Pristine path ppath := path // Pristine path
pnames := []string{} // Param names pnames := []string{} // Param names
@ -275,6 +283,8 @@ func (n *node) check405() HandlerFunc {
return notFoundHandler return notFoundHandler
} }
// Find dispatches the request to the handler whos route is matched with the
// specified request path.
func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo) { func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo) {
h = notFoundHandler h = notFoundHandler
e = r.echo e = r.echo
@ -400,6 +410,9 @@ End:
return return
} }
// ServeHTTP implements the Handler interface and can be registered to serve a
// particular path or subtree in an HTTP server.
// See Router.Find()
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := r.echo.pool.Get().(*Context) c := r.echo.pool.Get().(*Context)
h, _ := r.Find(req.Method, req.URL.Path, c) h, _ := r.Find(req.Method, req.URL.Path, c)