Skip to content

Commit aad765a

Browse files
committed
Changes from master (from 70acd57 to 70acd57)
1 parent 2b4c5a4 commit aad765a

10 files changed

+493
-41
lines changed

echo.go

+12
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ const (
153153
PROPFIND = "PROPFIND"
154154
// REPORT Method can be used to get information about a resource, see rfc 3253
155155
REPORT = "REPORT"
156+
// RouteNotFound is special method type for routes handling "route not found" (404) cases
157+
RouteNotFound = "echo_route_not_found"
156158
)
157159

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

413+
// RouteNotFound registers a special-case route which is executed when no other route is found (i.e. HTTP 404 cases)
414+
// for current request URL.
415+
// Path supports static and named/any parameters just like other http method is defined. Generally path is ended with
416+
// wildcard/match-any character (`/*`, `/download/*` etc).
417+
//
418+
// Example: `e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
419+
func (e *Echo) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
420+
return e.Add(RouteNotFound, path, h, m...)
421+
}
422+
411423
// Any registers a new route for all supported HTTP methods and path with matching handler
412424
// in the router with optional route-level middleware. Panics on error.
413425
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) Routes {

echo_test.go

+64
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,70 @@ func TestEchoGroup(t *testing.T) {
918918
assert.Equal(t, "023", buf.String())
919919
}
920920

921+
func TestEcho_RouteNotFound(t *testing.T) {
922+
var testCases = []struct {
923+
name string
924+
whenURL string
925+
expectRoute interface{}
926+
expectCode int
927+
}{
928+
{
929+
name: "404, route to static not found handler /a/c/xx",
930+
whenURL: "/a/c/xx",
931+
expectRoute: "GET /a/c/xx",
932+
expectCode: http.StatusNotFound,
933+
},
934+
{
935+
name: "404, route to path param not found handler /a/:file",
936+
whenURL: "/a/echo.exe",
937+
expectRoute: "GET /a/:file",
938+
expectCode: http.StatusNotFound,
939+
},
940+
{
941+
name: "404, route to any not found handler /*",
942+
whenURL: "/b/echo.exe",
943+
expectRoute: "GET /*",
944+
expectCode: http.StatusNotFound,
945+
},
946+
{
947+
name: "200, route /a/c/df to /a/c/df",
948+
whenURL: "/a/c/df",
949+
expectRoute: "GET /a/c/df",
950+
expectCode: http.StatusOK,
951+
},
952+
}
953+
954+
for _, tc := range testCases {
955+
t.Run(tc.name, func(t *testing.T) {
956+
e := New()
957+
958+
okHandler := func(c Context) error {
959+
return c.String(http.StatusOK, c.Request().Method+" "+c.Path())
960+
}
961+
notFoundHandler := func(c Context) error {
962+
return c.String(http.StatusNotFound, c.Request().Method+" "+c.Path())
963+
}
964+
965+
e.GET("/", okHandler)
966+
e.GET("/a/c/df", okHandler)
967+
e.GET("/a/b*", okHandler)
968+
e.PUT("/*", okHandler)
969+
970+
e.RouteNotFound("/a/c/xx", notFoundHandler) // static
971+
e.RouteNotFound("/a/:file", notFoundHandler) // param
972+
e.RouteNotFound("/*", notFoundHandler) // any
973+
974+
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
975+
rec := httptest.NewRecorder()
976+
977+
e.ServeHTTP(rec, req)
978+
979+
assert.Equal(t, tc.expectCode, rec.Code)
980+
assert.Equal(t, tc.expectRoute, rec.Body.String())
981+
})
982+
}
983+
}
984+
921985
func TestEchoNotFound(t *testing.T) {
922986
e := New()
923987
req := httptest.NewRequest(http.MethodGet, "/files", nil)

group.go

+7
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ func (g *Group) File(path, file string, middleware ...MiddlewareFunc) RouteInfo
157157
return g.Add(http.MethodGet, path, handler, middleware...)
158158
}
159159

160+
// RouteNotFound implements `Echo#RouteNotFound()` for sub-routes within the Group.
161+
//
162+
// Example: `g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
163+
func (g *Group) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
164+
return g.Add(RouteNotFound, path, h, m...)
165+
}
166+
160167
// Add implements `Echo#Add()` for sub-routes within the Group. Panics on error.
161168
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) RouteInfo {
162169
ri, err := g.AddRoute(Route{

group_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,71 @@ func TestGroup_TRACE(t *testing.T) {
303303
assert.Equal(t, `OK`, body)
304304
}
305305

306+
func TestGroup_RouteNotFound(t *testing.T) {
307+
var testCases = []struct {
308+
name string
309+
whenURL string
310+
expectRoute interface{}
311+
expectCode int
312+
}{
313+
{
314+
name: "404, route to static not found handler /group/a/c/xx",
315+
whenURL: "/group/a/c/xx",
316+
expectRoute: "GET /group/a/c/xx",
317+
expectCode: http.StatusNotFound,
318+
},
319+
{
320+
name: "404, route to path param not found handler /group/a/:file",
321+
whenURL: "/group/a/echo.exe",
322+
expectRoute: "GET /group/a/:file",
323+
expectCode: http.StatusNotFound,
324+
},
325+
{
326+
name: "404, route to any not found handler /group/*",
327+
whenURL: "/group/b/echo.exe",
328+
expectRoute: "GET /group/*",
329+
expectCode: http.StatusNotFound,
330+
},
331+
{
332+
name: "200, route /group/a/c/df to /group/a/c/df",
333+
whenURL: "/group/a/c/df",
334+
expectRoute: "GET /group/a/c/df",
335+
expectCode: http.StatusOK,
336+
},
337+
}
338+
339+
for _, tc := range testCases {
340+
t.Run(tc.name, func(t *testing.T) {
341+
e := New()
342+
g := e.Group("/group")
343+
344+
okHandler := func(c Context) error {
345+
return c.String(http.StatusOK, c.Request().Method+" "+c.Path())
346+
}
347+
notFoundHandler := func(c Context) error {
348+
return c.String(http.StatusNotFound, c.Request().Method+" "+c.Path())
349+
}
350+
351+
g.GET("/", okHandler)
352+
g.GET("/a/c/df", okHandler)
353+
g.GET("/a/b*", okHandler)
354+
g.PUT("/*", okHandler)
355+
356+
g.RouteNotFound("/a/c/xx", notFoundHandler) // static
357+
g.RouteNotFound("/a/:file", notFoundHandler) // param
358+
g.RouteNotFound("/*", notFoundHandler) // any
359+
360+
req := httptest.NewRequest(http.MethodGet, tc.whenURL, nil)
361+
rec := httptest.NewRecorder()
362+
363+
e.ServeHTTP(rec, req)
364+
365+
assert.Equal(t, tc.expectCode, rec.Code)
366+
assert.Equal(t, tc.expectRoute, rec.Body.String())
367+
})
368+
}
369+
}
370+
306371
func TestGroup_Any(t *testing.T) {
307372
e := New()
308373

middleware/basic_auth.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"bytes"
55
"encoding/base64"
66
"errors"
7-
"fmt"
7+
"net/http"
88
"strconv"
99
"strings"
1010

@@ -72,9 +72,11 @@ func (config BasicAuthConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
7272
continue
7373
}
7474

75+
// Invalid base64 shouldn't be treated as error
76+
// instead should be treated as invalid client input
7577
b, errDecode := base64.StdEncoding.DecodeString(auth[l+1:])
7678
if errDecode != nil {
77-
lastError = echo.ErrUnauthorized.WithInternal(fmt.Errorf("invalid basic auth value: %w", errDecode))
79+
lastError = echo.NewHTTPError(http.StatusBadRequest).WithInternal(errDecode)
7880
continue
7981
}
8082
idx := bytes.IndexByte(b, ':')

middleware/basic_auth_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func TestBasicAuth(t *testing.T) {
5656
name: "nok, not base64 Authorization header",
5757
givenConfig: defaultConfig,
5858
whenAuth: []string{strings.ToUpper(basic) + " NOT_BASE64"},
59-
expectErr: "code=401, message=Unauthorized, internal=invalid basic auth value: illegal base64 data at input byte 3",
59+
expectErr: "code=400, message=Bad Request, internal=illegal base64 data at input byte 3",
6060
},
6161
{
6262
name: "nok, missing Authorization header",

middleware/logger.go

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ type LoggerConfig struct {
2222
// Tags to construct the logger format.
2323
//
2424
// - time_unix
25+
// - time_unix_milli
26+
// - time_unix_micro
2527
// - time_unix_nano
2628
// - time_rfc3339
2729
// - time_rfc3339_nano
@@ -119,6 +121,10 @@ func (config LoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
119121
switch tag {
120122
case "time_unix":
121123
return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
124+
case "time_unix_milli":
125+
return buf.WriteString(strconv.FormatInt(time.Now().UnixMilli(), 10))
126+
case "time_unix_micro":
127+
return buf.WriteString(strconv.FormatInt(time.Now().UnixMicro(), 10))
122128
case "time_unix_nano":
123129
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
124130
case "time_rfc3339":

middleware/logger_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/http/httptest"
99
"net/url"
10+
"strconv"
1011
"strings"
1112
"testing"
1213
"time"
@@ -172,6 +173,52 @@ func TestLoggerCustomTimestamp(t *testing.T) {
172173
assert.Error(t, err)
173174
}
174175

176+
func TestLoggerTemplateWithTimeUnixMilli(t *testing.T) {
177+
buf := new(bytes.Buffer)
178+
179+
e := echo.New()
180+
e.Use(LoggerWithConfig(LoggerConfig{
181+
Format: `${time_unix_milli}`,
182+
Output: buf,
183+
}))
184+
185+
e.GET("/", func(c echo.Context) error {
186+
return c.String(http.StatusOK, "OK")
187+
})
188+
189+
req := httptest.NewRequest(http.MethodGet, "/", nil)
190+
191+
rec := httptest.NewRecorder()
192+
e.ServeHTTP(rec, req)
193+
194+
unixMillis, err := strconv.ParseInt(buf.String(), 10, 64)
195+
assert.NoError(t, err)
196+
assert.WithinDuration(t, time.Unix(unixMillis/1000, 0), time.Now(), 3*time.Second)
197+
}
198+
199+
func TestLoggerTemplateWithTimeUnixMicro(t *testing.T) {
200+
buf := new(bytes.Buffer)
201+
202+
e := echo.New()
203+
e.Use(LoggerWithConfig(LoggerConfig{
204+
Format: `${time_unix_micro}`,
205+
Output: buf,
206+
}))
207+
208+
e.GET("/", func(c echo.Context) error {
209+
return c.String(http.StatusOK, "OK")
210+
})
211+
212+
req := httptest.NewRequest(http.MethodGet, "/", nil)
213+
214+
rec := httptest.NewRecorder()
215+
e.ServeHTTP(rec, req)
216+
217+
unixMicros, err := strconv.ParseInt(buf.String(), 10, 64)
218+
assert.NoError(t, err)
219+
assert.WithinDuration(t, time.Unix(unixMicros/1000000, 0), time.Now(), 3*time.Second)
220+
}
221+
175222
func BenchmarkLoggerWithConfig_withoutMapFields(b *testing.B) {
176223
e := echo.New()
177224

0 commit comments

Comments
 (0)