Skip to content

Commit

Permalink
Changes from master (from 70acd57 to 70acd57)
Browse files Browse the repository at this point in the history
  • Loading branch information
aldas committed Jul 17, 2022
1 parent 2b4c5a4 commit aad765a
Show file tree
Hide file tree
Showing 10 changed files with 493 additions and 41 deletions.
12 changes: 12 additions & 0 deletions echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ const (
PROPFIND = "PROPFIND"
// REPORT Method can be used to get information about a resource, see rfc 3253
REPORT = "REPORT"
// RouteNotFound is special method type for routes handling "route not found" (404) cases
RouteNotFound = "echo_route_not_found"
)

// Headers
Expand Down Expand Up @@ -408,6 +410,16 @@ func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo
return e.Add(http.MethodTrace, path, h, m...)
}

// RouteNotFound registers a special-case route which is executed when no other route is found (i.e. HTTP 404 cases)
// for current request URL.
// Path supports static and named/any parameters just like other http method is defined. Generally path is ended with
// wildcard/match-any character (`/*`, `/download/*` etc).
//
// Example: `e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
func (e *Echo) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
return e.Add(RouteNotFound, path, h, m...)
}

// Any registers a new route for all supported HTTP methods and path with matching handler
// in the router with optional route-level middleware. Panics on error.
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) Routes {
Expand Down
64 changes: 64 additions & 0 deletions echo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,70 @@ func TestEchoGroup(t *testing.T) {
assert.Equal(t, "023", buf.String())
}

func TestEcho_RouteNotFound(t *testing.T) {
var testCases = []struct {
name string
whenURL string
expectRoute interface{}
expectCode int
}{
{
name: "404, route to static not found handler /a/c/xx",
whenURL: "/a/c/xx",
expectRoute: "GET /a/c/xx",
expectCode: http.StatusNotFound,
},
{
name: "404, route to path param not found handler /a/:file",
whenURL: "/a/echo.exe",
expectRoute: "GET /a/:file",
expectCode: http.StatusNotFound,
},
{
name: "404, route to any not found handler /*",
whenURL: "/b/echo.exe",
expectRoute: "GET /*",
expectCode: http.StatusNotFound,
},
{
name: "200, route /a/c/df to /a/c/df",
whenURL: "/a/c/df",
expectRoute: "GET /a/c/df",
expectCode: http.StatusOK,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
e := New()

okHandler := func(c Context) error {
return c.String(http.StatusOK, c.Request().Method+" "+c.Path())
}
notFoundHandler := func(c Context) error {
return c.String(http.StatusNotFound, c.Request().Method+" "+c.Path())
}

e.GET("/", okHandler)
e.GET("/a/c/df", okHandler)
e.GET("/a/b*", okHandler)
e.PUT("/*", okHandler)

e.RouteNotFound("/a/c/xx", notFoundHandler) // static
e.RouteNotFound("/a/:file", notFoundHandler) // param
e.RouteNotFound("/*", notFoundHandler) // any

req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
rec := httptest.NewRecorder()

e.ServeHTTP(rec, req)

assert.Equal(t, tc.expectCode, rec.Code)
assert.Equal(t, tc.expectRoute, rec.Body.String())
})
}
}

func TestEchoNotFound(t *testing.T) {
e := New()
req := httptest.NewRequest(http.MethodGet, "/files", nil)
Expand Down
7 changes: 7 additions & 0 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ func (g *Group) File(path, file string, middleware ...MiddlewareFunc) RouteInfo
return g.Add(http.MethodGet, path, handler, middleware...)
}

// RouteNotFound implements `Echo#RouteNotFound()` for sub-routes within the Group.
//
// Example: `g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
func (g *Group) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
return g.Add(RouteNotFound, path, h, m...)
}

// Add implements `Echo#Add()` for sub-routes within the Group. Panics on error.
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) RouteInfo {
ri, err := g.AddRoute(Route{
Expand Down
65 changes: 65 additions & 0 deletions group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,71 @@ func TestGroup_TRACE(t *testing.T) {
assert.Equal(t, `OK`, body)
}

func TestGroup_RouteNotFound(t *testing.T) {
var testCases = []struct {
name string
whenURL string
expectRoute interface{}
expectCode int
}{
{
name: "404, route to static not found handler /group/a/c/xx",
whenURL: "/group/a/c/xx",
expectRoute: "GET /group/a/c/xx",
expectCode: http.StatusNotFound,
},
{
name: "404, route to path param not found handler /group/a/:file",
whenURL: "/group/a/echo.exe",
expectRoute: "GET /group/a/:file",
expectCode: http.StatusNotFound,
},
{
name: "404, route to any not found handler /group/*",
whenURL: "/group/b/echo.exe",
expectRoute: "GET /group/*",
expectCode: http.StatusNotFound,
},
{
name: "200, route /group/a/c/df to /group/a/c/df",
whenURL: "/group/a/c/df",
expectRoute: "GET /group/a/c/df",
expectCode: http.StatusOK,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
e := New()
g := e.Group("/group")

okHandler := func(c Context) error {
return c.String(http.StatusOK, c.Request().Method+" "+c.Path())
}
notFoundHandler := func(c Context) error {
return c.String(http.StatusNotFound, c.Request().Method+" "+c.Path())
}

g.GET("/", okHandler)
g.GET("/a/c/df", okHandler)
g.GET("/a/b*", okHandler)
g.PUT("/*", okHandler)

g.RouteNotFound("/a/c/xx", notFoundHandler) // static
g.RouteNotFound("/a/:file", notFoundHandler) // param
g.RouteNotFound("/*", notFoundHandler) // any

req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
rec := httptest.NewRecorder()

e.ServeHTTP(rec, req)

assert.Equal(t, tc.expectCode, rec.Code)
assert.Equal(t, tc.expectRoute, rec.Body.String())
})
}
}

func TestGroup_Any(t *testing.T) {
e := New()

Expand Down
6 changes: 4 additions & 2 deletions middleware/basic_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"net/http"
"strconv"
"strings"

Expand Down Expand Up @@ -72,9 +72,11 @@ func (config BasicAuthConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
continue
}

// Invalid base64 shouldn't be treated as error
// instead should be treated as invalid client input
b, errDecode := base64.StdEncoding.DecodeString(auth[l+1:])
if errDecode != nil {
lastError = echo.ErrUnauthorized.WithInternal(fmt.Errorf("invalid basic auth value: %w", errDecode))
lastError = echo.NewHTTPError(http.StatusBadRequest).WithInternal(errDecode)
continue
}
idx := bytes.IndexByte(b, ':')
Expand Down
2 changes: 1 addition & 1 deletion middleware/basic_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestBasicAuth(t *testing.T) {
name: "nok, not base64 Authorization header",
givenConfig: defaultConfig,
whenAuth: []string{strings.ToUpper(basic) + " NOT_BASE64"},
expectErr: "code=401, message=Unauthorized, internal=invalid basic auth value: illegal base64 data at input byte 3",
expectErr: "code=400, message=Bad Request, internal=illegal base64 data at input byte 3",
},
{
name: "nok, missing Authorization header",
Expand Down
6 changes: 6 additions & 0 deletions middleware/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type LoggerConfig struct {
// Tags to construct the logger format.
//
// - time_unix
// - time_unix_milli
// - time_unix_micro
// - time_unix_nano
// - time_rfc3339
// - time_rfc3339_nano
Expand Down Expand Up @@ -119,6 +121,10 @@ func (config LoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
switch tag {
case "time_unix":
return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
case "time_unix_milli":
return buf.WriteString(strconv.FormatInt(time.Now().UnixMilli(), 10))
case "time_unix_micro":
return buf.WriteString(strconv.FormatInt(time.Now().UnixMicro(), 10))
case "time_unix_nano":
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
case "time_rfc3339":
Expand Down
47 changes: 47 additions & 0 deletions middleware/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -172,6 +173,52 @@ func TestLoggerCustomTimestamp(t *testing.T) {
assert.Error(t, err)
}

func TestLoggerTemplateWithTimeUnixMilli(t *testing.T) {
buf := new(bytes.Buffer)

e := echo.New()
e.Use(LoggerWithConfig(LoggerConfig{
Format: `${time_unix_milli}`,
Output: buf,
}))

e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "OK")
})

req := httptest.NewRequest(http.MethodGet, "/", nil)

rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)

unixMillis, err := strconv.ParseInt(buf.String(), 10, 64)
assert.NoError(t, err)
assert.WithinDuration(t, time.Unix(unixMillis/1000, 0), time.Now(), 3*time.Second)
}

func TestLoggerTemplateWithTimeUnixMicro(t *testing.T) {
buf := new(bytes.Buffer)

e := echo.New()
e.Use(LoggerWithConfig(LoggerConfig{
Format: `${time_unix_micro}`,
Output: buf,
}))

e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "OK")
})

req := httptest.NewRequest(http.MethodGet, "/", nil)

rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)

unixMicros, err := strconv.ParseInt(buf.String(), 10, 64)
assert.NoError(t, err)
assert.WithinDuration(t, time.Unix(unixMicros/1000000, 0), time.Now(), 3*time.Second)
}

func BenchmarkLoggerWithConfig_withoutMapFields(b *testing.B) {
e := echo.New()

Expand Down
Loading

0 comments on commit aad765a

Please sign in to comment.