Skip to content

Commit 5cb7cef

Browse files
committed
Bring over changes from master (latest commit f36d566)
1 parent 74b8c43 commit 5cb7cef

File tree

11 files changed

+244
-1015
lines changed

11 files changed

+244
-1015
lines changed

.github/workflows/checks.yml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Run checks
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read # to fetch code (actions/checkout)
14+
15+
env:
16+
# run static analysis only with the latest Go version
17+
LATEST_GO_VERSION: 1.19
18+
19+
jobs:
20+
check:
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Checkout Code
24+
uses: actions/checkout@v3
25+
26+
- name: Set up Go ${{ matrix.go }}
27+
uses: actions/setup-go@v3
28+
with:
29+
go-version: ${{ env.LATEST_GO_VERSION }}
30+
check-latest: true
31+
32+
- name: Run golint
33+
run: |
34+
go install golang.org/x/lint/golint@latest
35+
golint -set_exit_status ./...
36+
37+
- name: Run staticcheck
38+
run: |
39+
go install honnef.co/go/tools/cmd/staticcheck@latest
40+
staticcheck ./...
41+
42+
- name: Run govulncheck
43+
run: |
44+
go version
45+
go install golang.org/x/vuln/cmd/govulncheck@latest
46+
govulncheck ./...
47+

.github/workflows/echo.yml

+13-34
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,18 @@ on:
44
push:
55
branches:
66
- master
7-
paths:
8-
- '**.go'
9-
- 'go.*'
10-
- '_fixture/**'
11-
- '.github/**'
12-
- 'codecov.yml'
137
pull_request:
148
branches:
159
- master
16-
paths:
17-
- '**.go'
18-
- 'go.*'
19-
- '_fixture/**'
20-
- '.github/**'
21-
- 'codecov.yml'
2210
workflow_dispatch:
2311

12+
permissions:
13+
contents: read # to fetch code (actions/checkout)
14+
15+
env:
16+
# run coverage and benchmarks only with the latest Go version
17+
LATEST_GO_VERSION: 1.19
18+
2419
jobs:
2520
test:
2621
strategy:
@@ -30,14 +25,12 @@ jobs:
3025
# Echo tests with last four major releases (unless there are pressing vulnerabilities)
3126
# As we depend on `golang.org/x/` libraries which only support last 2 Go releases we could have situations when
3227
# we derive from last four major releases promise.
33-
go: [1.17, 1.18, 1.19]
28+
go: [1.18, 1.19]
3429
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
3530
runs-on: ${{ matrix.os }}
3631
steps:
3732
- name: Checkout Code
3833
uses: actions/checkout@v3
39-
with:
40-
ref: ${{ github.ref }}
4134

4235
- name: Set up Go ${{ matrix.go }}
4336
uses: actions/setup-go@v3
@@ -47,31 +40,17 @@ jobs:
4740
- name: Run Tests
4841
run: go test -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
4942

50-
- name: Install dependencies for checks
51-
run: |
52-
go install golang.org/x/lint/golint@latest
53-
go install honnef.co/go/tools/cmd/staticcheck@latest
54-
55-
- name: Run golint
56-
run: golint -set_exit_status ./...
57-
58-
- name: Run staticcheck
59-
run: staticcheck ./...
60-
6143
- name: Upload coverage to Codecov
62-
if: success() && matrix.go == 1.19 && matrix.os == 'ubuntu-latest'
44+
if: success() && matrix.go == env.LATEST_GO_VERSION && matrix.os == 'ubuntu-latest'
6345
uses: codecov/codecov-action@v3
6446
with:
6547
token:
6648
fail_ci_if_error: false
49+
6750
benchmark:
6851
needs: test
69-
strategy:
70-
matrix:
71-
os: [ubuntu-latest]
72-
go: [1.19]
73-
name: Benchmark comparison ${{ matrix.os }} @ Go ${{ matrix.go }}
74-
runs-on: ${{ matrix.os }}
52+
name: Benchmark comparison
53+
runs-on: ubuntu-latest
7554
steps:
7655
- name: Checkout Code (Previous)
7756
uses: actions/checkout@v3
@@ -87,7 +66,7 @@ jobs:
8766
- name: Set up Go ${{ matrix.go }}
8867
uses: actions/setup-go@v3
8968
with:
90-
go-version: ${{ matrix.go }}
69+
go-version: ${{ env.LATEST_GO_VERSION }}
9170

9271
- name: Install Dependencies
9372
run: go install golang.org/x/perf/cmd/benchstat@latest

echo.go

+26-7
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ import (
5555

5656
// Echo is the top-level framework instance.
5757
//
58-
// Note: replacing/nilling public fields is not coroutine/thread-safe and can cause data-races/panics. This is very likely
59-
// to happen when you access Echo instances through Context.Echo() method.
58+
// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
59+
// fields from handlers/middlewares and changing field values at the same time leads to data-races.
60+
// Same rule applies to adding new routes after server has been started - Adding a route is not Goroutine safe action.
6061
type Echo struct {
6162
// premiddleware are middlewares that are run for every request before routing is done
6263
premiddleware []MiddlewareFunc
@@ -90,6 +91,10 @@ type Echo struct {
9091
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
9192
// including `assets/images` as their prefix.
9293
Filesystem fs.FS
94+
95+
// OnAddRoute is called when Echo adds new route to specific host router. Handler is called for every router
96+
// and before route is added to the host router.
97+
OnAddRoute func(host string, route Routable) error
9398
}
9499

95100
// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
@@ -278,14 +283,22 @@ func (e *Echo) Router() Router {
278283
return e.router
279284
}
280285

281-
// Routers returns the map of host => router.
286+
// Routers returns the new map of host => router.
282287
func (e *Echo) Routers() map[string]Router {
283-
return e.routers
288+
result := make(map[string]Router)
289+
for host, r := range e.routers {
290+
result[host] = r
291+
}
292+
return result
284293
}
285294

286-
// RouterFor returns Router for given host.
287-
func (e *Echo) RouterFor(host string) Router {
288-
return e.routers[host]
295+
// RouterFor returns Router for given host. When host is left empty the default router is returned.
296+
func (e *Echo) RouterFor(host string) (Router, bool) {
297+
if host == "" {
298+
return e.router, true
299+
}
300+
router, ok := e.routers[host]
301+
return router, ok
289302
}
290303

291304
// ResetRouterCreator resets callback for creating new router instances.
@@ -549,6 +562,12 @@ func (e *Echo) AddRoute(route Routable) (RouteInfo, error) {
549562
}
550563

551564
func (e *Echo) add(host string, route Routable) (RouteInfo, error) {
565+
if e.OnAddRoute != nil {
566+
if err := e.OnAddRoute(host, route); err != nil {
567+
return nil, err
568+
}
569+
}
570+
552571
router := e.findRouter(host)
553572
ri, err := router.Add(route)
554573
if err != nil {

echo_test.go

+144
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,150 @@ func TestEchoMethodNotAllowed(t *testing.T) {
10131013
assert.Equal(t, "OPTIONS, GET", rec.Header().Get(HeaderAllow))
10141014
}
10151015

1016+
func TestEcho_OnAddRoute(t *testing.T) {
1017+
type rr struct {
1018+
host string
1019+
path string
1020+
}
1021+
exampleRoute := Route{
1022+
Method: http.MethodGet,
1023+
Path: "/api/files/:id",
1024+
Handler: notFoundHandler,
1025+
Middlewares: nil,
1026+
Name: "x",
1027+
}
1028+
1029+
var testCases = []struct {
1030+
name string
1031+
whenHost string
1032+
whenRoute Routable
1033+
whenError error
1034+
expectLen int
1035+
expectAdded []rr
1036+
expectError string
1037+
}{
1038+
{
1039+
name: "ok, for default host",
1040+
whenHost: "",
1041+
whenRoute: exampleRoute,
1042+
whenError: nil,
1043+
expectAdded: []rr{
1044+
{host: "", path: "/static"},
1045+
{host: "", path: "/api/files/:id"},
1046+
},
1047+
expectError: "",
1048+
expectLen: 2,
1049+
},
1050+
{
1051+
name: "ok, for specific host",
1052+
whenHost: "test.com",
1053+
whenRoute: exampleRoute,
1054+
whenError: nil,
1055+
expectAdded: []rr{
1056+
{host: "", path: "/static"},
1057+
{host: "test.com", path: "/api/files/:id"},
1058+
},
1059+
expectError: "",
1060+
expectLen: 1,
1061+
},
1062+
{
1063+
name: "nok, error is returned",
1064+
whenHost: "test.com",
1065+
whenRoute: exampleRoute,
1066+
whenError: errors.New("nope"),
1067+
expectAdded: []rr{
1068+
{host: "", path: "/static"},
1069+
},
1070+
expectError: "nope",
1071+
expectLen: 0,
1072+
},
1073+
}
1074+
for _, tc := range testCases {
1075+
t.Run(tc.name, func(t *testing.T) {
1076+
1077+
e := New()
1078+
1079+
added := make([]rr, 0)
1080+
cnt := 0
1081+
e.OnAddRoute = func(host string, route Routable) error {
1082+
if cnt > 0 && tc.whenError != nil { // we want to GET /static to succeed for nok tests
1083+
return tc.whenError
1084+
}
1085+
cnt++
1086+
added = append(added, rr{
1087+
host: host,
1088+
path: route.ToRoute().Path,
1089+
})
1090+
return nil
1091+
}
1092+
1093+
e.GET("/static", notFoundHandler)
1094+
1095+
var err error
1096+
if tc.whenHost != "" {
1097+
_, err = e.Host(tc.whenHost).AddRoute(tc.whenRoute)
1098+
} else {
1099+
_, err = e.AddRoute(tc.whenRoute)
1100+
}
1101+
1102+
if tc.expectError != "" {
1103+
assert.EqualError(t, err, tc.expectError)
1104+
} else {
1105+
assert.NoError(t, err)
1106+
}
1107+
1108+
r, _ := e.RouterFor(tc.whenHost)
1109+
assert.Len(t, r.Routes(), tc.expectLen)
1110+
assert.Equal(t, tc.expectAdded, added)
1111+
})
1112+
}
1113+
}
1114+
1115+
func TestEcho_RouterFor(t *testing.T) {
1116+
var testCases = []struct {
1117+
name string
1118+
whenHost string
1119+
expectLen int
1120+
expectOk bool
1121+
}{
1122+
{
1123+
name: "ok, default host",
1124+
whenHost: "",
1125+
expectLen: 2,
1126+
expectOk: true,
1127+
},
1128+
{
1129+
name: "ok, specific host with routes",
1130+
whenHost: "test.com",
1131+
expectLen: 1,
1132+
expectOk: true,
1133+
},
1134+
{
1135+
name: "ok, non-existent host",
1136+
whenHost: "oups.com",
1137+
expectLen: 0,
1138+
expectOk: false,
1139+
},
1140+
}
1141+
for _, tc := range testCases {
1142+
t.Run(tc.name, func(t *testing.T) {
1143+
e := New()
1144+
1145+
e.GET("/1", notFoundHandler)
1146+
e.GET("/2", notFoundHandler)
1147+
e.Host("test.com").GET("/3", notFoundHandler)
1148+
1149+
r, ok := e.RouterFor(tc.whenHost)
1150+
assert.Equal(t, tc.expectOk, ok)
1151+
if tc.expectLen > 0 {
1152+
assert.Len(t, r.Routes(), tc.expectLen)
1153+
} else {
1154+
assert.Nil(t, r)
1155+
}
1156+
})
1157+
}
1158+
}
1159+
10161160
func TestEchoContext(t *testing.T) {
10171161
e := New()
10181162
c := e.AcquireContext()

go.mod

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
module github.com/labstack/echo/v5
22

3-
go 1.17
3+
go 1.18
44

55
require (
6-
github.com/golang-jwt/jwt/v4 v4.4.3
76
github.com/stretchr/testify v1.8.1
87
github.com/valyala/fasttemplate v1.2.2
9-
golang.org/x/net v0.2.0
8+
golang.org/x/net v0.4.0
109
golang.org/x/time v0.3.0
1110
)
1211

1312
require (
1413
github.com/davecgh/go-spew v1.1.1 // indirect
1514
github.com/pmezard/go-difflib v1.0.0 // indirect
1615
github.com/valyala/bytebufferpool v1.0.0 // indirect
17-
golang.org/x/text v0.4.0 // indirect
16+
golang.org/x/text v0.5.0 // indirect
1817
gopkg.in/yaml.v3 v3.0.1 // indirect
1918
)

0 commit comments

Comments
 (0)