Skip to content

Commit 60e7567

Browse files
author
Alexandru Scvortov
committed
add support for AMQP URIs
.NET URI parser supports most of the AMQP URI spec. The only issue concerns the vhost. The URIs "amqp://foo/" and "amqp://foo" are supposed to be different; the first uses the default vhost, while the second uses an empty vhost. Since .NET automatically adds "/" if it's missing, both URIs look the same after parsing. The .NET client will interpret both as vhost "/".
1 parent 542e4f9 commit 60e7567

File tree

14 files changed

+212
-28
lines changed

14 files changed

+212
-28
lines changed

projects/client/RabbitMQ.Client/src/client/api/ConnectionFactory.cs

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,21 +167,48 @@ public AmqpTcpEndpoint Endpoint
167167
}
168168
}
169169

170-
public String Address
170+
public String Uri
171171
{
172-
get
173-
{
174-
String result = HostName;
175-
if(Port != AmqpTcpEndpoint.UseDefaultPort)
176-
{
177-
result += (":" + Port);
178-
}
179-
return result;
180-
}
181172
set
182173
{
183-
Endpoint = AmqpTcpEndpoint.Parse(Protocol, value);
184-
}
174+
Endpoint = new AmqpTcpEndpoint();
175+
176+
Uri uri = new Uri(value, UriKind.Absolute);
177+
178+
if ("amqp".CompareTo(uri.Scheme) != 0) {
179+
throw new ArgumentException("Wrong scheme in AMQP URI: " +
180+
value);
181+
}
182+
string host = uri.Host;
183+
if (!String.IsNullOrEmpty(host)) {
184+
HostName = host;
185+
}
186+
187+
int port = uri.Port;
188+
if (port != -1) {
189+
Port = port;
190+
}
191+
192+
string userInfo = uri.UserInfo;
193+
if (!String.IsNullOrEmpty(userInfo)) {
194+
string[] userPass = userInfo.Split(':');
195+
if (userPass.Length > 2) {
196+
throw new ArgumentException("Bad user info in AMQP URI: " + value);
197+
}
198+
UserName = UriDecode(userPass[0]);
199+
if (userPass.Length == 2) {
200+
Password = UriDecode(userPass[1]);
201+
}
202+
}
203+
204+
/* C# automatically changes URIs into a canonical form
205+
that has at least the path segment "/". */
206+
if (uri.Segments.Length > 2) {
207+
throw new ArgumentException("Multiple segments in path of AMQP URI: " + String.Join(", ", uri.Segments));
208+
} else if (uri.Segments.Length == 2) {
209+
VirtualHost = UriDecode(uri.Segments[1]);
210+
}
211+
}
185212
}
186213

187214
///<summary>Construct a fresh instance, with all fields set to
@@ -332,5 +359,10 @@ public AuthMechanismFactory AuthMechanismFactory(string[] mechs) {
332359

333360
return null;
334361
}
362+
363+
//<summary>Unescape a string, protecting '+'.</summary>
364+
private string UriDecode(string uri) {
365+
return Uri.UnescapeDataString(uri.Replace("+", "%2B"));
366+
}
335367
}
336368
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// This source code is dual-licensed under the Apache License, version
2+
// 2.0, and the Mozilla Public License, version 1.1.
3+
//
4+
// The APL v2.0:
5+
//
6+
//---------------------------------------------------------------------------
7+
// Copyright (C) 2011 VMware, Inc.
8+
//
9+
// Licensed under the Apache License, Version 2.0 (the "License");
10+
// you may not use this file except in compliance with the License.
11+
// You may obtain a copy of the License at
12+
//
13+
// http://www.apache.org/licenses/LICENSE-2.0
14+
//
15+
// Unless required by applicable law or agreed to in writing, software
16+
// distributed under the License is distributed on an "AS IS" BASIS,
17+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
// See the License for the specific language governing permissions and
19+
// limitations under the License.
20+
//---------------------------------------------------------------------------
21+
//
22+
// The MPL v1.1:
23+
//
24+
//---------------------------------------------------------------------------
25+
// The contents of this file are subject to the Mozilla Public License
26+
// Version 1.1 (the "License"); you may not use this file except in
27+
// compliance with the License. You may obtain a copy of the License
28+
// at http://www.mozilla.org/MPL/
29+
//
30+
// Software distributed under the License is distributed on an "AS IS"
31+
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
32+
// the License for the specific language governing rights and
33+
// limitations under the License.
34+
//
35+
// The Original Code is RabbitMQ.
36+
//
37+
// The Initial Developer of the Original Code is VMware, Inc.
38+
// Copyright (c) 2011 VMware, Inc. All rights reserved.
39+
//---------------------------------------------------------------------------
40+
41+
using NUnit.Framework;
42+
using System;
43+
using RabbitMQ.Client;
44+
45+
namespace RabbitMQ.Client.Unit
46+
{
47+
[TestFixture]
48+
public class TestAmqpUri
49+
{
50+
[Test]
51+
public void TestAmqpUriParse()
52+
{
53+
/* From the spec */
54+
ParseSuccess("amqp://user:pass@host:10000/vhost",
55+
"user", "pass", "host", 10000, "vhost");
56+
ParseSuccess("amqp://user%61:%61pass@host:10000/v%2fhost",
57+
"usera", "apass", "host", 10000, "v/host");
58+
ParseSuccess("amqp://", "guest", "guest", "localhost", 5672, "/");
59+
ParseSuccess("amqp://:@/", "", "", "localhost", 5672, "/");
60+
ParseSuccess("amqp://user@",
61+
"user", "guest", "localhost", 5672, "/");
62+
ParseSuccess("amqp://user:pass@",
63+
"user", "pass", "localhost", 5672, "/");
64+
ParseSuccess("amqp://host", "guest", "guest", "host", 5672, "/");
65+
ParseSuccess("amqp://:10000",
66+
"guest", "guest", "localhost", 10000, "/");
67+
ParseSuccess("amqp:///vhost",
68+
"guest", "guest", "localhost", 5672, "vhost");
69+
ParseSuccess("amqp://host/", "guest", "guest", "host", 5672, "/");
70+
ParseSuccess("amqp://host/%2f",
71+
"guest", "guest", "host", 5672, "/");
72+
ParseSuccess("amqp://[::1]", "guest", "guest",
73+
"[0000:0000:0000:0000:0000:0000:0000:0001]",
74+
5672, "/");
75+
76+
/* Various other success cases */
77+
ParseSuccess("amqp://host:100",
78+
"guest", "guest", "host", 100, "/");
79+
ParseSuccess("amqp://[::1]:100",
80+
"guest", "guest",
81+
"[0000:0000:0000:0000:0000:0000:0000:0001]",
82+
100, "/");
83+
84+
ParseSuccess("amqp://host/blah",
85+
"guest", "guest", "host", 5672, "blah");
86+
ParseSuccess("amqp://host:100/blah",
87+
"guest", "guest", "host", 100, "blah");
88+
ParseSuccess("amqp://:100/blah",
89+
"guest", "guest", "localhost", 100, "blah");
90+
ParseSuccess("amqp://[::1]/blah",
91+
"guest", "guest",
92+
"[0000:0000:0000:0000:0000:0000:0000:0001]",
93+
5672, "blah");
94+
ParseSuccess("amqp://[::1]:100/blah",
95+
"guest", "guest",
96+
"[0000:0000:0000:0000:0000:0000:0000:0001]",
97+
100, "blah");
98+
99+
ParseSuccess("amqp://user:pass@host",
100+
"user", "pass", "host", 5672, "/");
101+
ParseSuccess("amqp://user:pass@host:100",
102+
"user", "pass", "host", 100, "/");
103+
ParseSuccess("amqp://user:pass@:100",
104+
"user", "pass", "localhost", 100, "/");
105+
ParseSuccess("amqp://user:pass@[::1]",
106+
"user", "pass",
107+
"[0000:0000:0000:0000:0000:0000:0000:0001]",
108+
5672, "/");
109+
ParseSuccess("amqp://user:pass@[::1]:100",
110+
"user", "pass",
111+
"[0000:0000:0000:0000:0000:0000:0000:0001]",
112+
100, "/");
113+
114+
/* Various failure cases */
115+
ParseFail("http://www.rabbitmq.com");
116+
ParseFail("amqp://foo:bar:baz");
117+
ParseFail("amqp://foo[::1]");
118+
ParseFail("amqp://foo:[::1]");
119+
ParseFail("amqp://[::1]foo");
120+
ParseFail("amqp://foo:1000xyz");
121+
ParseFail("amqp://foo:1000000");
122+
ParseFail("amqp://foo/bar/baz");
123+
124+
ParseFail("amqp://foo%1");
125+
ParseFail("amqp://foo%1x");
126+
ParseFail("amqp://foo%xy");
127+
}
128+
129+
private void ParseSuccess(string uri, string user, string password,
130+
string host, int port, string vhost)
131+
{
132+
ConnectionFactory cf = new ConnectionFactory();
133+
cf.Uri = uri;
134+
Assert.AreEqual(user, cf.UserName);
135+
Assert.AreEqual(password, cf.Password);
136+
Assert.AreEqual(host, cf.HostName);
137+
Assert.AreEqual(port, cf.Port);
138+
Assert.AreEqual(vhost, cf.VirtualHost);
139+
}
140+
141+
private void ParseFail(string uri)
142+
{
143+
try {
144+
ConnectionFactory cf = new ConnectionFactory();
145+
cf.Uri = uri;
146+
Assert.Fail("URI parse didn't fail: '" + uri + "'");
147+
} catch (Exception e) {
148+
// whoosh!
149+
}
150+
}
151+
}
152+
}

projects/examples/client/AddClient/src/examples/AddClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ public static int Main(string[] args) {
5656
}
5757

5858
ConnectionFactory cf = new ConnectionFactory();
59-
cf.Address = args[0];
60-
59+
cf.Uri = args[0];
60+
6161
using (IConnection conn = cf.CreateConnection()) {
6262
using (IModel ch = conn.CreateModel()) {
6363

projects/examples/client/AddServer/src/examples/AddServer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static int Main(string[] args) {
5757
}
5858

5959
ConnectionFactory cf = new ConnectionFactory();
60-
cf.Address = args[0];
60+
cf.Uri = args[0];
6161
using (IConnection conn = cf.CreateConnection()) {
6262
using (IModel ch = conn.CreateModel()) {
6363
ch.QueueDeclare("AddServer", false, false, false, null);

projects/examples/client/DeclareQueue/src/examples/DeclareQueue.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public static int Main(string[] args) {
8484
string serverAddress = args[optionIndex++];
8585
string inputQueueName = args[optionIndex++];
8686
ConnectionFactory cf = new ConnectionFactory();
87-
cf.Address = serverAddress;
87+
cf.Uri = serverAddress;
8888

8989
using (IConnection conn = cf.CreateConnection())
9090
{

projects/examples/client/ExceptionTest/src/examples/ExceptionTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public static int Main(string[] args) {
5959

6060
string serverAddress = args[0];
6161
ConnectionFactory cf = new ConnectionFactory();
62-
cf.Address = serverAddress;
63-
62+
cf.Uri = serverAddress;
63+
6464
using (IConnection conn = cf.CreateConnection())
6565
{
6666
conn.ConnectionShutdown += new ConnectionShutdownEventHandler(First);

projects/examples/client/LogTail/src/examples/LogTail.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static int Main(string[] args) {
6565
string routingKey = args[3];
6666

6767
ConnectionFactory cf = new ConnectionFactory();
68-
cf.Address = serverAddress;
68+
cf.Uri = serverAddress;
6969

7070
using (IConnection conn = cf.CreateConnection())
7171
{

projects/examples/client/LowlevelLogTail/src/examples/LowlevelLogTail.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static int Main(string[] args) {
6565
string routingKey = args[3];
6666

6767
ConnectionFactory cf = new ConnectionFactory();
68-
cf.Address = serverAddress;
68+
cf.Uri = serverAddress;
6969
using (IConnection conn = cf.CreateConnection())
7070
{
7171
using (IModel ch = conn.CreateModel())

projects/examples/client/PerfTest/src/examples/PerfTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static int Main(string[] args) {
6060
int messageCount = int.Parse(args[1]);
6161

6262
ConnectionFactory cf = new ConnectionFactory();
63-
cf.Address = serverAddress;
63+
cf.Uri = serverAddress;
6464
using (IConnection conn = cf.CreateConnection())
6565
{
6666
Stopwatch sendTimer = new Stopwatch();

projects/examples/client/SendString/src/examples/SendString.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ public static int Main(string[] args) {
5757
string exchangeType = args[2];
5858
string routingKey = args[3];
5959
string message = args[4];
60-
60+
6161
ConnectionFactory cf = new ConnectionFactory();
62-
cf.Address = serverAddress;
63-
62+
cf.Uri = serverAddress;
63+
6464
using (IConnection conn = cf.CreateConnection())
6565
{
6666
using (IModel ch = conn.CreateModel()) {

0 commit comments

Comments
 (0)