Skip to content

Commit 930385e

Browse files
implement bindings and un-bindings (#19)
* implement bindings and unbindings --------- Signed-off-by: Gabriele Santomaggio <[email protected]> Co-authored-by: Luke Bakken <[email protected]>
1 parent 7eb6a19 commit 930385e

17 files changed

+662
-60
lines changed

.ci/ubuntu/gha-setup.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
88
readonly script_dir
99
echo "[INFO] script_dir: '$script_dir'"
1010

11-
readonly rabbitmq_image="${RABBITMQ_IMAGE:-pivotalrabbitmq/rabbitmq:main}"
11+
if [[ $3 == 'arm' ]]
12+
then
13+
readonly rabbitmq_image="${RABBITMQ_IMAGE:-pivotalrabbitmq/rabbitmq-arm64:main}"
14+
else
15+
readonly rabbitmq_image="${RABBITMQ_IMAGE:-pivotalrabbitmq/rabbitmq:main}"
16+
fi
17+
18+
1219
readonly docker_name_prefix='rabbitmq-amqp-dotnet-client'
1320
readonly docker_network_name="$docker_name_prefix-network"
1421

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ build:
99
test: build
1010
dotnet test -c Debug $(CURDIR)/Tests/Tests.csproj --no-build --logger:"console;verbosity=detailed" /p:AltCover=true
1111

12-
rabbitmq-server-start:
13-
./.ci/ubuntu/gha-setup.sh start
12+
rabbitmq-server-start-arm:
13+
./.ci/ubuntu/gha-setup.sh start pull arm
1414

1515
rabbitmq-server-stop:
1616
./.ci/ubuntu/gha-setup.sh stop

RabbitMQ.AMQP.Client/IEntities.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@ public interface IEntityInfo
55
}
66

77
/// <summary>
8-
/// Generic interface for declaring entities
8+
/// Generic interface for declaring entities with result of type T
99
/// </summary>
1010
/// <typeparam name="T"></typeparam>
11-
public interface IEntityDeclaration<T> where T : IEntityInfo
11+
public interface IEntityInfoDeclaration<T> where T : IEntityInfo
1212
{
1313
Task<T> Declare();
1414
}
1515

16-
public interface IQueueSpecification : IEntityDeclaration<IQueueInfo>
16+
/// <summary>
17+
/// Generic interface for declaring entities without result
18+
/// </summary>
19+
public interface IEntityDeclaration
20+
{
21+
Task Declare();
22+
}
23+
24+
public interface IQueueSpecification : IEntityInfoDeclaration<IQueueInfo>
1725
{
1826
IQueueSpecification Name(string name);
1927
public string Name();
@@ -44,22 +52,39 @@ public interface IQueueDeletion
4452
Task<IEntityInfo> Delete(string name);
4553
}
4654

47-
public interface IExchangeSpecification : IEntityDeclaration<IExchangeInfo>
55+
public interface IExchangeSpecification : IEntityDeclaration
4856
{
4957
IExchangeSpecification Name(string name);
5058

5159
IExchangeSpecification AutoDelete(bool autoDelete);
5260

5361
IExchangeSpecification Type(ExchangeType type);
5462

55-
IExchangeSpecification Type(string type);
63+
IExchangeSpecification Type(string type); // TODO: Add this
5664

5765
IExchangeSpecification Argument(string key, object value);
5866
}
5967

60-
6168
public interface IExchangeDeletion
6269
{
6370
// TODO consider returning a ExchangeStatus object with some info after deletion
64-
Task<IEntityInfo> Delete(string name);
71+
Task Delete(string name);
72+
}
73+
74+
public interface IBindingSpecification
75+
{
76+
IBindingSpecification SourceExchange(string exchange);
77+
78+
IBindingSpecification DestinationQueue(string queue);
79+
80+
IBindingSpecification DestinationExchange(string exchange);
81+
82+
IBindingSpecification Key(string key);
83+
84+
IBindingSpecification Argument(string key, object value);
85+
86+
IBindingSpecification Arguments(Dictionary<string, object> arguments);
87+
88+
Task Bind();
89+
Task Unbind();
6590
}

RabbitMQ.AMQP.Client/IEntitiesInfo.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,3 @@ public enum ExchangeType
3939
HEADERS
4040
}
4141

42-
public interface IExchangeInfo : IEntityInfo
43-
{
44-
}

RabbitMQ.AMQP.Client/IManagement.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public interface IManagement : IClosable
1717

1818
IExchangeDeletion ExchangeDeletion();
1919

20+
IBindingSpecification Binding();
21+
2022
ITopologyListener TopologyListener();
2123
}
22-
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using Amqp.Types;
2+
3+
namespace RabbitMQ.AMQP.Client.Impl;
4+
5+
public abstract class BindingSpecificationBase
6+
{
7+
protected string Source = "";
8+
protected string Destination = "";
9+
protected string RoutingKey = "";
10+
protected bool ToQueue = true;
11+
protected Dictionary<string, object> _arguments = new();
12+
13+
protected Map ArgsToMap()
14+
{
15+
Map argMap = new();
16+
17+
foreach ((string key, object value) in _arguments)
18+
{
19+
argMap[key] = value;
20+
}
21+
22+
return argMap;
23+
}
24+
}
25+
26+
public class AmqpBindingSpecification(AmqpManagement management) : BindingSpecificationBase, IBindingSpecification
27+
{
28+
private AmqpManagement Management { get; } = management;
29+
30+
public async Task Bind()
31+
{
32+
var kv = new Map
33+
{
34+
{ "source", Source },
35+
{ "binding_key", RoutingKey },
36+
{ "arguments", ArgsToMap() },
37+
{ ToQueue ? "destination_queue" : "destination_exchange", Destination }
38+
};
39+
40+
await Management.Request(kv, $"/{Consts.Bindings}",
41+
AmqpManagement.Post,
42+
[
43+
AmqpManagement.Code204,
44+
]).ConfigureAwait(false);
45+
}
46+
47+
public async Task Unbind()
48+
{
49+
string destinationCharacter = ToQueue ? "dstq" : "dste";
50+
if (_arguments.Count == 0)
51+
{
52+
string target =
53+
$"/{Consts.Bindings}/src={Utils.EncodePathSegment(Source)};" +
54+
$"{($"{destinationCharacter}={Utils.EncodePathSegment(Destination)};" +
55+
$"key={Utils.EncodePathSegment(RoutingKey)};args=")}";
56+
57+
await Management.Request(
58+
null, target,
59+
AmqpManagement.Delete, new[] { AmqpManagement.Code204 }).ConfigureAwait(false);
60+
}
61+
else
62+
{
63+
string path = BindingsTarget(destinationCharacter, Source, Destination, RoutingKey);
64+
List<Map> bindings = await GetBindings(path).ConfigureAwait(false);
65+
string? uri = MatchBinding(bindings, RoutingKey, ArgsToMap());
66+
if (uri != null)
67+
{
68+
await Management.Request(
69+
null, uri,
70+
AmqpManagement.Delete, new[] { AmqpManagement.Code204 }).ConfigureAwait(false);
71+
}
72+
}
73+
}
74+
75+
public IBindingSpecification SourceExchange(string exchange)
76+
{
77+
ToQueue = false;
78+
Source = exchange;
79+
return this;
80+
}
81+
82+
public IBindingSpecification DestinationQueue(string queue)
83+
{
84+
ToQueue = true;
85+
Destination = queue;
86+
return this;
87+
}
88+
89+
public IBindingSpecification DestinationExchange(string exchange)
90+
{
91+
Destination = exchange;
92+
return this;
93+
}
94+
95+
public IBindingSpecification Key(string key)
96+
{
97+
RoutingKey = key;
98+
return this;
99+
}
100+
101+
public IBindingSpecification Argument(string key, object value)
102+
{
103+
_arguments[key] = value;
104+
return this;
105+
}
106+
107+
public IBindingSpecification Arguments(Dictionary<string, object> arguments)
108+
{
109+
_arguments = arguments;
110+
return this;
111+
}
112+
113+
private string BindingsTarget(
114+
string destinationField, string source, string destination, string key)
115+
{
116+
return "/bindings?src="
117+
+ Utils.EncodeHttpParameter(source)
118+
+ "&"
119+
+ destinationField
120+
+ "="
121+
+ Utils.EncodeHttpParameter(destination)
122+
+ "&key="
123+
+ Utils.EncodeHttpParameter(key);
124+
}
125+
126+
private async Task<List<Map>> GetBindings(string path)
127+
{
128+
var result = await Management.Request(
129+
null, path,
130+
AmqpManagement.Get, new[] { AmqpManagement.Code200 }).ConfigureAwait(false);
131+
132+
if (result.Body is not List list)
133+
{
134+
return [];
135+
}
136+
137+
138+
var l = new List<Map>() { };
139+
foreach (object o in list)
140+
{
141+
if (o is Map item)
142+
{
143+
l.Add(item);
144+
}
145+
}
146+
147+
return l;
148+
}
149+
150+
private string? MatchBinding(List<Map> bindings, string key, Map arguments)
151+
{
152+
string? uri = null;
153+
foreach (Map binding in bindings)
154+
{
155+
string bindingKey = (string)binding["binding_key"];
156+
Map bindingArguments = (Map)binding["arguments"];
157+
if ((key == null && bindingKey == null) || (key != null && key.Equals(bindingKey)))
158+
{
159+
if ((arguments == null && bindingArguments == null) ||
160+
(arguments != null && Utils.CompareMap(arguments, bindingArguments)))
161+
{
162+
uri = binding["location"].ToString();
163+
break;
164+
}
165+
}
166+
}
167+
168+
return uri;
169+
}
170+
}

RabbitMQ.AMQP.Client/Impl/AmqpConnection.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class AmqpConnection : AbstractClosable, IConnection
4545

4646
private readonly AmqpManagement _management = new();
4747
private readonly RecordingTopologyListener _recordingTopologyListener = new();
48+
private readonly TaskCompletionSource<bool> _connectionCloseTaskCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
4849

4950
private readonly ConnectionSettings _connectionSettings;
5051
internal readonly AmqpSessionManagement NativePubSubSessions;
@@ -287,6 +288,8 @@ await _recordingTopologyListener.Accept(visitor)
287288
{
288289
_semaphoreClose.Release();
289290
}
291+
292+
_connectionCloseTaskCompletionSource.SetResult(true);
290293
};
291294
}
292295

@@ -334,6 +337,11 @@ await _management.CloseAsync()
334337
{
335338
_semaphoreClose.Release();
336339
}
340+
341+
await _connectionCloseTaskCompletionSource.Task.WaitAsync(TimeSpan.FromSeconds(10))
342+
.ConfigureAwait(false);
343+
344+
OnNewStatus(State.Closed, null);
337345
}
338346

339347

RabbitMQ.AMQP.Client/Impl/AmqpExchangeSpecification.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33

44
namespace RabbitMQ.AMQP.Client.Impl;
55

6-
public class AmqpExchangeInfo : IExchangeInfo
7-
{
8-
}
9-
106
public class AmqpExchangeSpecification(AmqpManagement management) : IExchangeSpecification
117
{
128
private string _name = "";
@@ -15,7 +11,7 @@ public class AmqpExchangeSpecification(AmqpManagement management) : IExchangeSpe
1511
private string _typeString = ""; // TODO: add this
1612
private readonly Map _arguments = new();
1713

18-
public async Task<IExchangeInfo> Declare()
14+
public async Task Declare()
1915
{
2016
if (string.IsNullOrEmpty(_name))
2117
{
@@ -34,15 +30,13 @@ public async Task<IExchangeInfo> Declare()
3430
// TODO: encodePathSegment(queues)
3531
// Message request = await management.Request(kv, $"/{Consts.Exchanges}/{_name}",
3632
// for the moment we won't use the message response
37-
await management.Request(kv, $"/{Consts.Exchanges}/{_name}",
33+
await management.Request(kv, $"/{Consts.Exchanges}/{Utils.EncodePathSegment(_name)}",
3834
AmqpManagement.Put,
3935
[
4036
AmqpManagement.Code204,
4137
AmqpManagement.Code201,
4238
AmqpManagement.Code409
4339
]).ConfigureAwait(false);
44-
45-
return new AmqpExchangeInfo();
4640
}
4741

4842
public IExchangeSpecification Name(string name)
@@ -76,18 +70,13 @@ public IExchangeSpecification Argument(string key, object value)
7670
}
7771
}
7872

79-
public class DefaultExchangeDeletionInfo : IEntityInfo
80-
{
81-
}
82-
8373
public class AmqpExchangeDeletion(AmqpManagement management) : IExchangeDeletion
8474
{
85-
public async Task<IEntityInfo> Delete(string name)
75+
public async Task Delete(string name)
8676
{
8777
await management
88-
.Request(null, $"/{Consts.Exchanges}/{name}", AmqpManagement.Delete, new[] { AmqpManagement.Code204, })
78+
.Request(null, $"/{Consts.Exchanges}/{Utils.EncodePathSegment(name)}", AmqpManagement.Delete,
79+
new[] { AmqpManagement.Code204, })
8980
.ConfigureAwait(false);
90-
91-
return new DefaultExchangeDeletionInfo();
9281
}
9382
}

RabbitMQ.AMQP.Client/Impl/AmqpManagement.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public class AmqpManagement : AbstractClosable, IManagement // TODO: Implement T
2929
internal const int Code204 = 204; // TODO: handle 204
3030
internal const int Code409 = 409;
3131
internal const string Put = "PUT";
32+
internal const string Get = "GET";
33+
internal const string Post = "POST";
34+
3235
internal const string Delete = "DELETE";
3336

3437
private const string ReplyTo = "$me";
@@ -74,6 +77,11 @@ public IExchangeDeletion ExchangeDeletion()
7477
return new AmqpExchangeDeletion(this);
7578
}
7679

80+
public IBindingSpecification Binding()
81+
{
82+
return new AmqpBindingSpecification(this);
83+
}
84+
7785
public ITopologyListener TopologyListener()
7886
{
7987
return _recordingTopologyListener!;

0 commit comments

Comments
 (0)