Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7122509

Browse files
committedFeb 5, 2021
feat: support username in URI
1 parent c7d80a7 commit 7122509

File tree

4 files changed

+114
-26
lines changed

4 files changed

+114
-26
lines changed
 

‎README.md

+29-20
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ You can also specify connection options as a [`redis://` URL](http://www.iana.or
134134
```javascript
135135
// Connect to 127.0.0.1:6380, db 4, using password "authpassword":
136136
new Redis("redis://:authpassword@127.0.0.1:6380/4");
137+
138+
// Username can also be passed via URI.
139+
// It's worth to noticing that for compatibility reasons `allowUsernameInURI`
140+
// need to be provided, otherwise the username part will be ignored.
141+
new Redis(
142+
"redis://username:authpassword@127.0.0.1:6380/4?allowUsernameInURI=true"
143+
);
137144
```
138145

139146
See [API Documentation](API.md#new_Redis) for all available options.
@@ -680,7 +687,6 @@ This feature is useful when using Amazon ElastiCache instances with Auto-failove
680687

681688
On ElastiCache insances with Auto-failover enabled, `reconnectOnError` does not execute. Instead of returning a Redis error, AWS closes all connections to the master endpoint until the new primary node is ready. ioredis reconnects via `retryStrategy` instead of `reconnectOnError` after about a minute. On ElastiCache insances with Auto-failover enabled, test failover events with the `Failover primary` option in the AWS console.
682689

683-
684690
## Connection Events
685691

686692
The Redis instance will emit some events about the state of the connection to the Redis server.
@@ -923,13 +929,17 @@ Sometimes you may want to send a command to multiple nodes (masters or slaves) o
923929
```javascript
924930
// Send `FLUSHDB` command to all slaves:
925931
const slaves = cluster.nodes("slave");
926-
Promise.all(slaves.map(node => node.flushdb()))
932+
Promise.all(slaves.map((node) => node.flushdb()));
927933

928934
// Get keys of all the masters:
929935
const masters = cluster.nodes("master");
930-
Promise.all(masters.map(node => node.keys()).then(keys => {
931-
// keys: [['key1', 'key2'], ['key3', 'key4']]
932-
}));
936+
Promise.all(
937+
masters
938+
.map((node) => node.keys())
939+
.then((keys) => {
940+
// keys: [['key1', 'key2'], ['key3', 'key4']]
941+
})
942+
);
933943
```
934944

935945
### NAT Mapping
@@ -1064,7 +1074,7 @@ const cluster = new Redis.Cluster(
10641074

10651075
## Autopipelining
10661076

1067-
In standard mode, when you issue multiple commands, ioredis sends them to the server one by one. As described in Redis pipeline documentation, this is a suboptimal use of the network link, especially when such link is not very performant.
1077+
In standard mode, when you issue multiple commands, ioredis sends them to the server one by one. As described in Redis pipeline documentation, this is a suboptimal use of the network link, especially when such link is not very performant.
10681078

10691079
The TCP and network overhead negatively affects performance. Commands are stuck in the send queue until the previous ones are correctly delivered to the server. This is a problem known as Head-Of-Line blocking (HOL).
10701080

@@ -1076,38 +1086,39 @@ This feature can dramatically improve throughput and avoids HOL blocking. In our
10761086

10771087
While an automatic pipeline is executing, all new commands will be enqueued in a new pipeline which will be executed as soon as the previous finishes.
10781088

1079-
When using Redis Cluster, one pipeline per node is created. Commands are assigned to pipelines according to which node serves the slot.
1089+
When using Redis Cluster, one pipeline per node is created. Commands are assigned to pipelines according to which node serves the slot.
10801090

1081-
A pipeline will thus contain commands using different slots but that ultimately are assigned to the same node.
1091+
A pipeline will thus contain commands using different slots but that ultimately are assigned to the same node.
10821092

10831093
Note that the same slot limitation within a single command still holds, as it is a Redis limitation.
10841094

1085-
10861095
### Example of automatic pipeline enqueuing
10871096

10881097
This sample code uses ioredis with automatic pipeline enabled.
10891098

10901099
```javascript
1091-
const Redis = require('./built');
1092-
const http = require('http');
1100+
const Redis = require("./built");
1101+
const http = require("http");
10931102

10941103
const db = new Redis({ enableAutoPipelining: true });
10951104

10961105
const server = http.createServer((request, response) => {
1097-
const key = new URL(request.url, 'https://localhost:3000/').searchParams.get('key');
1106+
const key = new URL(request.url, "https://localhost:3000/").searchParams.get(
1107+
"key"
1108+
);
10981109

10991110
db.get(key, (err, value) => {
1100-
response.writeHead(200, { 'Content-Type': 'text/plain' });
1111+
response.writeHead(200, { "Content-Type": "text/plain" });
11011112
response.end(value);
11021113
});
1103-
})
1114+
});
11041115

11051116
server.listen(3000);
11061117
```
11071118

11081119
When Node receives requests, it schedules them to be processed in one or more iterations of the events loop.
11091120

1110-
All commands issued by requests processing during one iteration of the loop will be wrapped in a pipeline automatically created by ioredis.
1121+
All commands issued by requests processing during one iteration of the loop will be wrapped in a pipeline automatically created by ioredis.
11111122

11121123
In the example above, the pipeline will have the following contents:
11131124

@@ -1129,24 +1140,22 @@ This approach increases the utilization of the network link, reduces the TCP ove
11291140

11301141
### Benchmarks
11311142

1132-
Here's some of the results of our tests for a single node.
1143+
Here's some of the results of our tests for a single node.
11331144

11341145
Each iteration of the test runs 1000 random commands on the server.
11351146

11361147
| | Samples | Result | Tolerance |
1137-
|---------------------------|---------|---------------|-----------|
1148+
| ------------------------- | ------- | ------------- | --------- |
11381149
| default | 1000 | 174.62 op/sec | ± 0.45 % |
11391150
| enableAutoPipelining=true | 1500 | 233.33 op/sec | ± 0.88 % |
11401151

1141-
11421152
And here's the same test for a cluster of 3 masters and 3 replicas:
11431153

11441154
| | Samples | Result | Tolerance |
1145-
|---------------------------|---------|---------------|-----------|
1155+
| ------------------------- | ------- | ------------- | --------- |
11461156
| default | 1000 | 164.05 op/sec | ± 0.42 % |
11471157
| enableAutoPipelining=true | 3000 | 235.31 op/sec | ± 0.94 % |
11481158

1149-
11501159
# Error Handling
11511160

11521161
All the errors returned by the Redis server are instances of `ReplyError`, which can be accessed via `Redis`:

‎lib/utils/index.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,19 @@ export function parseURL(url) {
255255
parsed = urllibParse(url, true, true);
256256
}
257257

258+
const options = parsed.query || {};
259+
const allowUsernameInURI =
260+
options.allowUsernameInURI && options.allowUsernameInURI !== "false";
261+
delete options.allowUsernameInURI;
262+
258263
const result: any = {};
259264
if (parsed.auth) {
260-
const index = parsed.auth.indexOf(":")
261-
result.password = index === -1 ? '' : parsed.auth.slice(index + 1)
265+
const index = parsed.auth.indexOf(":");
266+
if (allowUsernameInURI) {
267+
result.username =
268+
index === -1 ? parsed.auth : parsed.auth.slice(0, index);
269+
}
270+
result.password = index === -1 ? "" : parsed.auth.slice(index + 1);
262271
}
263272
if (parsed.pathname) {
264273
if (parsed.protocol === "redis:" || parsed.protocol === "rediss:") {
@@ -275,7 +284,7 @@ export function parseURL(url) {
275284
if (parsed.port) {
276285
result.port = parsed.port;
277286
}
278-
defaults(result, parsed.query);
287+
defaults(result, options);
279288

280289
return result;
281290
}

‎test/functional/auth.ts

+19
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,25 @@ describe("auth", function () {
170170
redis = new Redis({ port: 17379, username, password });
171171
});
172172

173+
it("should handle auth with Redis URL string with username and password (Redis >=6) (redis://foo:bar@baz.com/) correctly", function (done) {
174+
let username = "user";
175+
let password = "pass";
176+
let redis;
177+
new MockServer(17379, function (argv) {
178+
if (
179+
argv[0] === "auth" &&
180+
argv[1] === username &&
181+
argv[2] === password
182+
) {
183+
redis.disconnect();
184+
done();
185+
}
186+
});
187+
redis = new Redis(
188+
`redis://user:pass@localhost:17379/?allowUsernameInURI=true`
189+
);
190+
});
191+
173192
it('should not emit "error" when the Redis >=6 server doesn\'t need auth', function (done) {
174193
new MockServer(17379, function (argv) {
175194
if (argv[0] === "auth" && argv[1] === "pass") {

‎test/unit/utils.ts

+54-3
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,7 @@ describe("utils", function () {
204204
password: "pass:word",
205205
key: "value",
206206
});
207-
expect(
208-
utils.parseURL("redis://user@127.0.0.1:6380/4?key=value")
209-
).to.eql({
207+
expect(utils.parseURL("redis://user@127.0.0.1:6380/4?key=value")).to.eql({
210208
host: "127.0.0.1",
211209
port: "6380",
212210
db: "4",
@@ -226,6 +224,59 @@ describe("utils", function () {
226224
key: "value",
227225
});
228226
});
227+
228+
it("supports allowUsernameInURI", function () {
229+
expect(
230+
utils.parseURL(
231+
"redis://user:pass@127.0.0.1:6380/4?allowUsernameInURI=true"
232+
)
233+
).to.eql({
234+
host: "127.0.0.1",
235+
port: "6380",
236+
db: "4",
237+
username: "user",
238+
password: "pass",
239+
});
240+
expect(
241+
utils.parseURL(
242+
"redis://user:pass@127.0.0.1:6380/4?allowUsernameInURI=false"
243+
)
244+
).to.eql({
245+
host: "127.0.0.1",
246+
port: "6380",
247+
db: "4",
248+
password: "pass",
249+
});
250+
expect(
251+
utils.parseURL(
252+
"redis://user:pass:word@127.0.0.1:6380/4?key=value&allowUsernameInURI=true"
253+
)
254+
).to.eql({
255+
host: "127.0.0.1",
256+
port: "6380",
257+
db: "4",
258+
username: "user",
259+
password: "pass:word",
260+
key: "value",
261+
});
262+
expect(
263+
utils.parseURL(
264+
"redis://user@127.0.0.1:6380/4?key=value&allowUsernameInURI=true"
265+
)
266+
).to.eql({
267+
host: "127.0.0.1",
268+
port: "6380",
269+
db: "4",
270+
username: "user",
271+
password: "",
272+
key: "value",
273+
});
274+
expect(
275+
utils.parseURL("redis://127.0.0.1/?allowUsernameInURI=true")
276+
).to.eql({
277+
host: "127.0.0.1",
278+
});
279+
});
229280
});
230281

231282
describe(".sample", function () {

0 commit comments

Comments
 (0)
Please sign in to comment.