From e9808b69b4ef3e3188eb6085fa578efb7f152354 Mon Sep 17 00:00:00 2001 From: Chase Hutchins Date: Fri, 29 Jan 2016 04:06:20 -0800 Subject: [PATCH] license update, some .gitattributes and .gitignore expansion, .travis.yml expansion, golint fixes and .godir for heroku --- .gitattributes | 18 +++++++------ .gitignore | 41 +++++++++++++++++++++++++----- .godir | 1 + .travis.yml | 44 +++++++++++++++++++++++++------- LICENSE | 2 +- echo.go | 28 ++++++++++++++------- group.go | 31 +++++++++++++++++++---- middleware/auth.go | 10 +++++--- middleware/logger.go | 5 +++- response.go | 60 ++++++++++++++++++++++++++++++++------------ router.go | 13 ++++++++++ 11 files changed, 194 insertions(+), 59 deletions(-) create mode 100644 .godir diff --git a/.gitattributes b/.gitattributes index 178af863..591a6897 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,19 +2,21 @@ # http://git-scm.com/docs/gitattributes#_end_of_line_conversion * 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 -# order to prevent newline related issues) -.* text eol=lf -*.go text eol=lf -*.yml text eol=lf -*.html text eol=lf +# order) to prevent newline related issues) +.* text eolf=lf *.css text eol=lf +*.go text eol=lf +*.html text eol=lf *.js text eol=lf *.json text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml 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 -recipes/* linguist-documentation +_fixture/* linguist-documentation website/* linguist-documentation diff --git a/.gitignore b/.gitignore index a66a5e3b..d9fd17d0 100644 --- a/.gitignore +++ b/.gitignore @@ -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/make website/Makefile website/marathon* -.gitmodules -# Node.js +# node.js web dependencies node_modules -# IntelliJ -.idea -*.iml +# code coverage output files +*.coverprofile diff --git a/.godir b/.godir new file mode 100644 index 00000000..37aac7c1 --- /dev/null +++ b/.godir @@ -0,0 +1 @@ +github.com/labstack/echo diff --git a/.travis.yml b/.travis.yml index 084ec791..7b70c96b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,39 @@ language: go +sudo: false + go: - - 1.4 - - tip + - 1.4 + - 1.5 + - 1.6rc1 + - tip +env: + global: + - GO15VENDOREXPERIMENT=1 + before_install: - - go get github.com/modocache/gover - - go get github.com/mattn/goveralls - - go get golang.org/x/tools/cmd/cover + - 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/mattn/goveralls + +install: + - go get -t -v ./... + script: - - go test -coverprofile=echo.coverprofile - - go test -coverprofile=middleware.coverprofile ./middleware - - $HOME/gopath/bin/gover - - $HOME/gopath/bin/goveralls -coverprofile=gover.coverprofile -service=travis-ci + - go vet ./... + - go test -v -race ./... + - diff -u <(echo -n) <(gofmt -d -s .) + - 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 diff --git a/LICENSE b/LICENSE index a14f926e..00d0c181 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ 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 of this software and associated documentation files (the "Software"), to deal diff --git a/echo.go b/echo.go index 51d209f6..b67e5449 100644 --- a/echo.go +++ b/echo.go @@ -3,6 +3,7 @@ package echo import ( "bytes" "encoding/json" + "encoding/xml" "errors" "fmt" "io" @@ -14,14 +15,13 @@ import ( "strings" "sync" - "encoding/xml" - "github.com/labstack/gommon/log" "golang.org/x/net/http2" "golang.org/x/net/websocket" ) type ( + // Echo is the top-level framework instance. Echo struct { prefix string middleware []MiddlewareFunc @@ -39,21 +39,30 @@ type ( router *Router } + // Route contains a handler and information for matching against requests. Route struct { Method string Path string Handler Handler } + // HTTPError represents an error that occured while handling a request. HTTPError struct { code int message string } - Middleware interface{} + // Middleware ... + Middleware interface{} + + // MiddlewareFunc ... MiddlewareFunc func(HandlerFunc) HandlerFunc - Handler interface{} - HandlerFunc func(*Context) error + + // Handler ... + Handler interface{} + + // HandlerFunc ... + HandlerFunc func(*Context) error // HTTPErrorHandler is a centralized HTTP error handler. HTTPErrorHandler func(error, *Context) @@ -164,9 +173,9 @@ var ( // Errors //-------- - UnsupportedMediaType = errors.New("unsupported media type") - RendererNotRegistered = errors.New("renderer not registered") - InvalidRedirectCode = errors.New("invalid redirect status code") + ErrUnsupportedMediaType = errors.New("unsupported media type") + ErrRendererNotRegistered = errors.New("renderer not registered") + ErrInvalidRedirectCode = errors.New("invalid redirect status code") //---------------- // Error handlers @@ -588,6 +597,7 @@ func (e *Echo) run(s *http.Server, files ...string) { } } +// NewHTTPError creates a new HTTPError instance. func NewHTTPError(code int, msg ...string) *HTTPError { he := &HTTPError{code: code, message: http.StatusText(code)} if len(msg) > 0 { @@ -691,7 +701,7 @@ func wrapHandler(h Handler) HandlerFunc { func (binder) Bind(r *http.Request, i interface{}) (err error) { ct := r.Header.Get(ContentType) - err = UnsupportedMediaType + err = ErrUnsupportedMediaType if strings.HasPrefix(ct, ApplicationJSON) { err = json.NewDecoder(r.Body).Decode(i) } else if strings.HasPrefix(ct, ApplicationXML) { diff --git a/group.go b/group.go index 56effb9c..2d5240b8 100644 --- a/group.go +++ b/group.go @@ -1,81 +1,102 @@ package echo -type ( - Group struct { - echo Echo - } -) +// Group is a set of subroutes for a specified route. It can be used for inner +// 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 +} +// Use implements the echo.Use interface for subroutes within the Group. func (g *Group) Use(m ...Middleware) { for _, h := range m { 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) { g.echo.Connect(path, h) } +// Delete implements the echo.Delete interface for subroutes within the Group. func (g *Group) Delete(path string, h Handler) { g.echo.Delete(path, h) } +// Get implements the echo.Get interface for subroutes within the Group. func (g *Group) Get(path string, h Handler) { g.echo.Get(path, h) } +// Head implements the echo.Head interface for subroutes within the Group. func (g *Group) Head(path string, h Handler) { g.echo.Head(path, h) } +// Options implements the echo.Options interface for subroutes within the Group. func (g *Group) Options(path string, h Handler) { g.echo.Options(path, h) } +// Patch implements the echo.Patch interface for subroutes within the Group. func (g *Group) Patch(path string, h Handler) { g.echo.Patch(path, h) } +// Post implements the echo.Post interface for subroutes within the Group. func (g *Group) Post(path string, h Handler) { g.echo.Post(path, h) } +// Put implements the echo.Put interface for subroutes within the Group. func (g *Group) Put(path string, h Handler) { g.echo.Put(path, h) } +// Trace implements the echo.Trace interface for subroutes within the Group. func (g *Group) Trace(path string, h Handler) { g.echo.Trace(path, h) } +// Any implements the echo.Any interface for subroutes within the Group. func (g *Group) Any(path string, h Handler) { for _, m := range methods { 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) { for _, m := range methods { 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) { g.echo.WebSocket(path, h) } +// Static implements the echo.Static interface for subroutes within the Group. func (g *Group) Static(path, root string) { g.echo.Static(path, root) } +// ServeDir implements the echo.ServeDir interface for subroutes within the +// Group. func (g *Group) ServeDir(path, root string) { g.echo.ServeDir(path, root) } +// ServeFile implements the echo.ServeFile interface for subroutes within the +// Group. func (g *Group) ServeFile(path, file string) { 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 { return g.echo.Group(prefix, m...) } diff --git a/middleware/auth.go b/middleware/auth.go index 23c494c5..1b864df3 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -8,17 +8,19 @@ import ( ) type ( + // BasicValidateFunc is the expected format a BasicAuth fn argument is + // expected to implement. BasicValidateFunc func(string, string) bool ) const ( + // Basic is the authentication scheme implemented by the middleware. Basic = "Basic" ) -// BasicAuth returns an HTTP basic authentication middleware. -// -// For valid credentials it calls the next handler. -// For invalid credentials, it sends "401 - Unauthorized" response. +// BasicAuth returns a HTTP basic authentication middleware. +// For valid credentials, it calls the next handler. +// For invalid credentials, it returns a "401 Unauthorized" HTTP error. func BasicAuth(fn BasicValidateFunc) echo.HandlerFunc { return func(c *echo.Context) error { // Skip WebSocket diff --git a/middleware/logger.go b/middleware/logger.go index 9b88980c..88c1d0a7 100644 --- a/middleware/logger.go +++ b/middleware/logger.go @@ -8,6 +8,9 @@ import ( "github.com/labstack/gommon/color" ) +const loggerFormat = "%s %s %s %s %s %d" + +// Logger returns a Middleware that logs requests. func Logger() echo.MiddlewareFunc { return func(h echo.HandlerFunc) echo.HandlerFunc { return func(c *echo.Context) error { @@ -47,7 +50,7 @@ func Logger() echo.MiddlewareFunc { 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 } } diff --git a/response.go b/response.go index 1e43df9e..a40bd48e 100644 --- a/response.go +++ b/response.go @@ -6,32 +6,46 @@ import ( "net/http" ) -type ( - Response struct { - writer http.ResponseWriter - status int - size int64 - committed bool - echo *Echo - } -) +// Response wraps an http.ResponseWriter and implements its interface to be used +// 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 + status int + size int64 + committed bool + echo *Echo +} +// NewResponse creates a new instance of Response. func NewResponse(w http.ResponseWriter, e *Echo) *Response { return &Response{writer: w, echo: e} } +// SetWriter sets the http.ResponseWriter instance for this Response. func (r *Response) SetWriter(w http.ResponseWriter) { r.writer = w } -func (r *Response) Header() http.Header { - return r.writer.Header() -} - +// Writer returns the http.ResponseWriter instance for this Response. func (r *Response) Writer() http.ResponseWriter { 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) { if r.committed { r.echo.Logger().Warn("response already committed") @@ -42,35 +56,49 @@ func (r *Response) WriteHeader(code int) { 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) { n, err = r.writer.Write(b) r.size += int64(n) 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() { 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) { 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 { return r.writer.(http.CloseNotifier).CloseNotify() } +// Status returns the HTTP status code of the response. func (r *Response) Status() int { return r.status } +// Size returns the current size, in bytes, of the response. func (r *Response) Size() int64 { return r.size } +// Committed asserts whether or not the response has been committed to. func (r *Response) Committed() bool { return r.committed } diff --git a/router.go b/router.go index a3bba24b..42a05663 100644 --- a/router.go +++ b/router.go @@ -3,6 +3,12 @@ package echo import "net/http" 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 { tree *node routes []Route @@ -40,6 +46,7 @@ const ( mkind ) +// NewRouter returns a new Router instance. func NewRouter(e *Echo) *Router { return &Router{ 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) { ppath := path // Pristine path pnames := []string{} // Param names @@ -275,6 +283,8 @@ func (n *node) check405() HandlerFunc { 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) { h = notFoundHandler e = r.echo @@ -400,6 +410,9 @@ End: 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) { c := r.echo.pool.Get().(*Context) h, _ := r.Find(req.Method, req.URL.Path, c)