Skip to content

Commit 6ff915d

Browse files
authored
Tidy up IPv6 handling. (#63)
* Remove normalization and prefer `.build`. * Add release notes.
1 parent 332d1ac commit 6ff915d

File tree

3 files changed

+106
-5
lines changed

3 files changed

+106
-5
lines changed

lib/async/redis/endpoint.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ def self.local_endpoint(**options)
1919

2020
# Represents a way to connect to a remote Redis server.
2121
class Endpoint < ::IO::Endpoint::Generic
22-
LOCALHOST = URI.parse("redis://localhost").freeze
22+
LOCALHOST = URI::Generic.build(scheme: "redis", host: "localhost").freeze
2323

2424
def self.local(**options)
2525
self.new(LOCALHOST, **options)
2626
end
2727

2828
def self.remote(host, port = 6379, **options)
29-
self.new(URI.parse("redis://#{host}:#{port}"), **options)
29+
# URI::Generic.build automatically handles IPv6 addresses correctly:
30+
self.new(URI::Generic.build(scheme: "redis", host: host, port: port), **options)
3031
end
3132

3233
SCHEMES = {
@@ -35,7 +36,7 @@ def self.remote(host, port = 6379, **options)
3536
}
3637

3738
def self.parse(string, endpoint = nil, **options)
38-
url = URI.parse(string).normalize
39+
url = URI.parse(string)
3940

4041
return self.new(url, endpoint, **options)
4142
end
@@ -45,7 +46,7 @@ def self.parse(string, endpoint = nil, **options)
4546
# @parameter scheme [String] The scheme to use, e.g. "redis" or "rediss".
4647
# @parameter hostname [String] The hostname to connect to (or bind to).
4748
# @parameter options [Hash] Additional options, passed to {#initialize}.
48-
def self.for(scheme, hostname, credentials: nil, port: nil, database: nil, **options)
49+
def self.for(scheme, host, credentials: nil, port: nil, database: nil, **options)
4950
uri_klass = SCHEMES.fetch(scheme.downcase) do
5051
raise ArgumentError, "Unsupported scheme: #{scheme.inspect}"
5152
end
@@ -55,7 +56,13 @@ def self.for(scheme, hostname, credentials: nil, port: nil, database: nil, **opt
5556
end
5657

5758
self.new(
58-
uri_klass.new(scheme, credentials&.join(":"), hostname, port, nil, path, nil, nil, nil).normalize,
59+
uri_klass.build(
60+
scheme: scheme,
61+
userinfo: credentials&.join(":"),
62+
host: host,
63+
port: port,
64+
path: path,
65+
),
5966
**options
6067
)
6168
end

releases.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Releases
22

3+
## Unreleased
4+
5+
- Fix handling of IPv6 address literals, including those returned by Redis Cluster / Sentinel.
6+
37
## v0.11.1
48

59
- Correctly pass `@options` to `Async::Redis::Client` instances created by `Async::Redis::ClusterClient`.

test/async/redis/endpoint.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,94 @@
5353
end
5454
end
5555
end
56+
57+
with ".remote" do
58+
it "handles IPv4 addresses correctly" do
59+
endpoint = Async::Redis::Endpoint.remote("127.0.0.1", 6380)
60+
expect(endpoint.url.to_s).to be == "redis://127.0.0.1:6380"
61+
expect(endpoint.url.host).to be == "127.0.0.1"
62+
expect(endpoint.url.hostname).to be == "127.0.0.1"
63+
end
64+
65+
it "handles IPv6 addresses correctly" do
66+
endpoint = Async::Redis::Endpoint.remote("::1", 6380)
67+
expect(endpoint.url.to_s).to be == "redis://[::1]:6380"
68+
expect(endpoint.url.host).to be == "[::1]"
69+
expect(endpoint.url.hostname).to be == "::1"
70+
end
71+
72+
it "handles expanded IPv6 addresses correctly" do
73+
ipv6 = "2600:1f28:372:c404:5c2d:ce68:3620:cc4b"
74+
endpoint = Async::Redis::Endpoint.remote(ipv6, 6380)
75+
expect(endpoint.url.to_s).to be == "redis://[#{ipv6}]:6380"
76+
expect(endpoint.url.host).to be == "[#{ipv6}]"
77+
expect(endpoint.url.hostname).to be == ipv6
78+
end
79+
end
80+
81+
with ".for" do
82+
it "handles IPv4 addresses correctly" do
83+
endpoint = Async::Redis::Endpoint.for("redis", "127.0.0.1", port: 6380)
84+
expect(endpoint.url.to_s).to be == "redis://127.0.0.1:6380"
85+
expect(endpoint.url.host).to be == "127.0.0.1"
86+
expect(endpoint.url.hostname).to be == "127.0.0.1"
87+
expect(endpoint.port).to be == 6380
88+
end
89+
90+
it "handles IPv6 addresses correctly" do
91+
endpoint = Async::Redis::Endpoint.for("redis", "::1", port: 6380)
92+
expect(endpoint.url.to_s).to be == "redis://[::1]:6380"
93+
expect(endpoint.url.host).to be == "[::1]"
94+
expect(endpoint.url.hostname).to be == "::1"
95+
expect(endpoint.port).to be == 6380
96+
end
97+
98+
it "handles expanded IPv6 addresses correctly" do
99+
ipv6 = "2600:1f28:372:c404:5c2d:ce68:3620:cc4b"
100+
endpoint = Async::Redis::Endpoint.for("redis", ipv6, port: 6380)
101+
expect(endpoint.url.to_s).to be == "redis://[#{ipv6}]:6380"
102+
expect(endpoint.url.host).to be == "[#{ipv6}]"
103+
expect(endpoint.url.hostname).to be == ipv6
104+
expect(endpoint.port).to be == 6380
105+
end
106+
107+
it "handles credentials correctly" do
108+
endpoint = Async::Redis::Endpoint.for("redis", "localhost", credentials: ["user", "pass"], port: 6380)
109+
expect(endpoint.url.to_s).to be == "redis://user:pass@localhost:6380"
110+
expect(endpoint.url.userinfo).to be == "user:pass"
111+
expect(endpoint.credentials).to be == ["user", "pass"]
112+
end
113+
114+
it "handles database selection correctly" do
115+
endpoint = Async::Redis::Endpoint.for("redis", "localhost", database: 2)
116+
expect(endpoint.url.to_s).to be == "redis://localhost/2"
117+
expect(endpoint.url.path).to be == "/2"
118+
expect(endpoint.database).to be == 2
119+
end
120+
121+
it "handles secure connections correctly" do
122+
endpoint = Async::Redis::Endpoint.for("rediss", "localhost")
123+
expect(endpoint.url.to_s).to be == "rediss://localhost"
124+
expect(endpoint).to be(:secure?)
125+
end
126+
127+
it "handles all parameters together correctly" do
128+
ipv6 = "2600:1f28:372:c404:5c2d:ce68:3620:cc4b"
129+
endpoint = Async::Redis::Endpoint.for("rediss", ipv6,
130+
credentials: ["user", "pass"],
131+
port: 6380,
132+
database: 3
133+
)
134+
expect(endpoint.url.to_s).to be == "rediss://user:pass@[#{ipv6}]:6380/3"
135+
expect(endpoint.url.scheme).to be == "rediss"
136+
expect(endpoint.url.host).to be == "[#{ipv6}]"
137+
expect(endpoint.url.hostname).to be == ipv6
138+
expect(endpoint.url.userinfo).to be == "user:pass"
139+
expect(endpoint.url.port).to be == 6380
140+
expect(endpoint.url.path).to be == "/3"
141+
expect(endpoint).to be(:secure?)
142+
expect(endpoint.credentials).to be == ["user", "pass"]
143+
expect(endpoint.database).to be == 3
144+
end
145+
end
56146
end

0 commit comments

Comments
 (0)