Skip to content

Commit fd9994a

Browse files
authored
normalize route creation in xresolver (#494)
* add check if port is empty * updated changelog * add unit tests
1 parent fa6a57d commit fd9994a

File tree

3 files changed

+107
-5
lines changed

3 files changed

+107
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88

9+
## [v1.10.3]
10+
### Fixed
11+
- Fixed xresolver failing to create route when default port is used. [#494](https://github.com/xmidt-org/webpa-common/pull/494)
12+
913
## [v1.10.2]
1014
### Fixed
1115
- Fixed `ConsulWatch` in xresolver by storing and watching the correct part of the url. [#490](https://github.com/xmidt-org/webpa-common/pull/490)
@@ -106,7 +110,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
106110
- The first official release. We will be better about documenting changes
107111
moving forward.
108112

109-
[Unreleased]: https://github.com/xmidt-org/webpa-common/compare/v1.10.2...HEAD
113+
[Unreleased]: https://github.com/xmidt-org/webpa-common/compare/v1.10.3...HEAD
114+
[v1.10.2]: https://github.com/xmidt-org/webpa-common/compare/v1.10.2...v1.10.3
110115
[v1.10.2]: https://github.com/xmidt-org/webpa-common/compare/v1.10.1...v1.10.2
111116
[v1.10.1]: https://github.com/xmidt-org/webpa-common/compare/v1.10.0...v1.10.1
112117
[v1.10.0]: https://github.com/xmidt-org/webpa-common/compare/v1.9.0...v1.10.0

xresolver/types.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package xresolver
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"net"
78
"net/url"
9+
"regexp"
810
"strconv"
11+
"strings"
912
"sync"
1013
)
1114

@@ -40,17 +43,73 @@ type Route struct {
4043
Port int
4144
}
4245

46+
// instancePattern is what NormalizeInstance expects to be matched. This pattern is intentionally liberal, and allows
47+
// URIs that are disallowed under https://www.ietf.org/rfc/rfc2396.txt
48+
var instancePattern = regexp.MustCompile("^((?P<scheme>.+)://)?(?P<address>[^:]+)(:(?P<port>[0-9]+))?$")
49+
50+
// NormalizeRoute canonicalizes a route string from a backend
51+
//
52+
// This function performs the following on the instance:
53+
// (1) If instance is a blank string, e.g. contains only whitespace or is empty, an empty string is returned with an error
54+
// (2) If the instance with whitespace trimmed off is not a valid instance, an error is returned with the trimmed instance string.
55+
// This function is intentionally lenient on what is a valid instance string, e.g. "foobar.com", "foobar.com:8080", "asdf://foobar.com", etc
56+
// (3) If there was no scheme prepended to the route, http will be used
57+
func NormalizeRoute(route string) (string, error) {
58+
route = strings.TrimSpace(route)
59+
if len(route) == 0 {
60+
return route, errors.New("empty route is not allowed")
61+
}
62+
63+
submatches := instancePattern.FindStringSubmatch(route)
64+
if len(submatches) == 0 {
65+
return route, fmt.Errorf("invalid route: %s", route)
66+
}
67+
68+
var (
69+
scheme = submatches[2]
70+
address = submatches[3]
71+
)
72+
73+
if len(scheme) == 0 {
74+
scheme = "http"
75+
}
76+
77+
var port int
78+
if portValue := submatches[5]; len(portValue) > 0 {
79+
var err error
80+
port, err = strconv.Atoi(submatches[5])
81+
if err != nil {
82+
// NOTE: Shouldn't ever hit this case, because the port is constrained by the regexp to be numeric
83+
return route, err
84+
}
85+
return fmt.Sprintf("%s://%s:%d", scheme, address, port), nil
86+
87+
}
88+
return fmt.Sprintf("%s://%s", scheme, address), nil
89+
}
90+
4391
func CreateRoute(route string) (Route, error) {
92+
route, err := NormalizeRoute(route)
93+
if err != nil {
94+
return Route{}, err
95+
}
4496
path, err := url.Parse(route)
4597
if err != nil {
4698
return Route{}, err
4799
}
48-
port, err := strconv.Atoi(path.Port())
49-
return Route{
100+
101+
newRoute := Route{
50102
Scheme: path.Scheme,
51103
Host: path.Hostname(),
52-
Port: port,
53-
}, err
104+
}
105+
if path.Port() != "" {
106+
port, err := strconv.Atoi(path.Port())
107+
newRoute.Port = port
108+
if err != nil {
109+
return newRoute, err
110+
}
111+
}
112+
return newRoute, nil
54113
}
55114

56115
func (r Route) String() string {

xresolver/types_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,41 @@ func IPv4Address(ipInt int64) string {
287287

288288
return b0 + "." + b1 + "." + b2 + "." + b3
289289
}
290+
291+
func TestRouteCreation(t *testing.T) {
292+
var (
293+
testData = []struct {
294+
name string
295+
route string
296+
expected Route
297+
expectsError bool
298+
}{
299+
{"empty route", "", Route{Scheme: "", Host: "", Port: 0}, true},
300+
{"whitespace characters", " \t\n\r", Route{Scheme: "", Host: "", Port: 0}, true},
301+
{"bad url", "blah:blah:blah", Route{Scheme: "", Host: "", Port: 0}, true},
302+
{"bad url with whitespace", " blah:blah:blah ", Route{Scheme: "", Host: "", Port: 0}, true},
303+
{"no scheme with port", "somehost.com:8080", Route{Scheme: "http", Host: "somehost.com", Port: 8080}, false},
304+
{"no scheme with port with whitespace", " somehost.com:8080 ", Route{Scheme: "http", Host: "somehost.com", Port: 8080}, false},
305+
{"no scheme no port", "somehost.com", Route{Scheme: "http", Host: "somehost.com", Port: 0}, false},
306+
{"ftp with port", "ftp://somehost.com:6060", Route{Scheme: "ftp", Host: "somehost.com", Port: 6060}, false},
307+
{"http", "http://foobar.com", Route{Scheme: "http", Host: "foobar.com", Port: 0}, false},
308+
{"https", "https://foobar.com", Route{Scheme: "https", Host: "foobar.com", Port: 0}, false},
309+
}
310+
)
311+
312+
for _, record := range testData {
313+
t.Run(record.name, func(t *testing.T) {
314+
var (
315+
assert = assert.New(t)
316+
actual, err = CreateRoute(record.route)
317+
)
318+
319+
assert.Equal(record.expected, actual)
320+
if record.expectsError {
321+
assert.Error(err)
322+
} else {
323+
assert.NoError(err)
324+
}
325+
})
326+
}
327+
}

0 commit comments

Comments
 (0)