From 84ee6f090d4eda5570bfb339bed05f0ca9073a01 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sun, 18 Aug 2019 11:06:33 +0200 Subject: [PATCH] [skip appveyor] Added support for ClientProperties (manual and in the AppSettings) Added support for managementport in the ConnectionStringParser Added documentation on setting the connection parameters --- Examples/01-PublishSubscribe/Program.cs | 9 ++- Tapeti/Connection/TapetiClient.cs | 11 ++++ Tapeti/Helpers/ConnectionstringParser.cs | 1 + Tapeti/TapetiAppSettingsConnectionParams.cs | 56 ++++++++++++---- Tapeti/TapetiConnectionParams.cs | 22 ++++++- docs/gettingstarted.rst | 71 ++++++++++++++++++++- docs/introduction.rst | 4 +- 7 files changed, 155 insertions(+), 19 deletions(-) diff --git a/Examples/01-PublishSubscribe/Program.cs b/Examples/01-PublishSubscribe/Program.cs index eb777d3..75eb7c8 100644 --- a/Examples/01-PublishSubscribe/Program.cs +++ b/Examples/01-PublishSubscribe/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using ExampleLib; using SimpleInjector; @@ -42,7 +43,13 @@ namespace _01_PublishSubscribe { HostName = "localhost", Username = "guest", - Password = "guest" + Password = "guest", + + // These properties allow you to identify the connection in the RabbitMQ Management interface + ClientProperties = new Dictionary + { + { "example", "01 - Publish Subscribe" } + } } }) { diff --git a/Tapeti/Connection/TapetiClient.cs b/Tapeti/Connection/TapetiClient.cs index 9f3487c..ec71b06 100644 --- a/Tapeti/Connection/TapetiClient.cs +++ b/Tapeti/Connection/TapetiClient.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; @@ -481,6 +482,16 @@ namespace Tapeti.Connection RequestedHeartbeat = 30 }; + if (connectionParams.ClientProperties != null) + foreach (var pair in connectionParams.ClientProperties) + { + if (connectionFactory.ClientProperties.ContainsKey(pair.Key)) + connectionFactory.ClientProperties[pair.Key] = Encoding.UTF8.GetBytes(pair.Value); + else + connectionFactory.ClientProperties.Add(pair.Key, Encoding.UTF8.GetBytes(pair.Value)); + } + + while (true) { try diff --git a/Tapeti/Helpers/ConnectionstringParser.cs b/Tapeti/Helpers/ConnectionstringParser.cs index 57422da..4bab299 100644 --- a/Tapeti/Helpers/ConnectionstringParser.cs +++ b/Tapeti/Helpers/ConnectionstringParser.cs @@ -123,6 +123,7 @@ namespace Tapeti.Helpers case "username": result.Username = value; break; case "password": result.Password = value; break; case "prefetchcount": result.PrefetchCount = ushort.Parse(value); break; + case "managementport": result.ManagementPort = int.Parse(value); break; } } } diff --git a/Tapeti/TapetiAppSettingsConnectionParams.cs b/Tapeti/TapetiAppSettingsConnectionParams.cs index d5e6dfb..a18285b 100644 --- a/Tapeti/TapetiAppSettingsConnectionParams.cs +++ b/Tapeti/TapetiAppSettingsConnectionParams.cs @@ -1,5 +1,4 @@ -using System; -using System.Configuration; +using System.Configuration; using System.Linq; namespace Tapeti @@ -19,6 +18,7 @@ namespace Tapeti /// rabbitmq:password /// rabbitmq:prefetchcount /// rabbitmq:managementport + /// rabbitmq:clientproperty:* /// public class TapetiAppSettingsConnectionParams : TapetiConnectionParams { @@ -30,6 +30,20 @@ namespace Tapeti private const string KeyPassword = "password"; private const string KeyPrefetchCount = "prefetchcount"; private const string KeyManagementPort = "managementport"; + private const string KeyClientProperty = "clientproperty:"; + + + private struct AppSettingsKey + { + public readonly string Entry; + public readonly string Parameter; + + public AppSettingsKey(string entry, string parameter) + { + Entry = entry; + Parameter = parameter; + } + } /// @@ -37,21 +51,35 @@ namespace Tapeti /// The prefix to apply to the keys. Defaults to "rabbitmq:" public TapetiAppSettingsConnectionParams(string prefix = DefaultPrefix) { - var keys = ConfigurationManager.AppSettings.AllKeys; + var keys = !string.IsNullOrEmpty(prefix) + ? ConfigurationManager.AppSettings.AllKeys.Where(k => k.StartsWith(prefix)).Select(k => new AppSettingsKey(k, k.Substring(prefix.Length))) + : ConfigurationManager.AppSettings.AllKeys.Select(k => new AppSettingsKey(k, k)); - void GetAppSetting(string key, Action setValue) + + + foreach (var key in keys) { - if (keys.Contains(prefix + key)) setValue(ConfigurationManager.AppSettings[prefix + key]); + var value = ConfigurationManager.AppSettings[key.Entry]; + + if (key.Parameter.StartsWith(KeyClientProperty)) + { + ClientProperties.Add(key.Parameter.Substring(KeyClientProperty.Length), value); + } + else + { + // ReSharper disable once SwitchStatementMissingSomeCases - don't fail if we encounter an unknown value + switch (key.Parameter) + { + case KeyHostname: HostName = value; break; + case KeyPort: Port = int.Parse(value); break; + case KeyVirtualHost: VirtualHost = value; break; + case KeyUsername: Username = value; break; + case KeyPassword: Password = value; break; + case KeyPrefetchCount: PrefetchCount = ushort.Parse(value); break; + case KeyManagementPort: ManagementPort = int.Parse(value); break; + } + } } - - - GetAppSetting(KeyHostname, value => HostName = value); - GetAppSetting(KeyPort, value => Port = int.Parse(value)); - GetAppSetting(KeyVirtualHost, value => VirtualHost = value); - GetAppSetting(KeyUsername, value => Username = value); - GetAppSetting(KeyPassword, value => Password = value); - GetAppSetting(KeyPrefetchCount, value => PrefetchCount = ushort.Parse(value)); - GetAppSetting(KeyManagementPort, value => ManagementPort = int.Parse(value)); } } } diff --git a/Tapeti/TapetiConnectionParams.cs b/Tapeti/TapetiConnectionParams.cs index 162fbaf..ac739b1 100644 --- a/Tapeti/TapetiConnectionParams.cs +++ b/Tapeti/TapetiConnectionParams.cs @@ -1,14 +1,18 @@ using System; +using System.Collections.Generic; // ReSharper disable UnusedMember.Global namespace Tapeti { /// - /// + /// Defines the connection parameters. /// public class TapetiConnectionParams { + private IDictionary clientProperties; + + /// /// The hostname to connect to. Defaults to localhost. /// @@ -46,10 +50,24 @@ namespace Tapeti /// public int ManagementPort { get; set; } = 15672; + /// + /// Key-value pair of properties that are set on the connection. These will be visible in the RabbitMQ Management interface. + /// Note that you can either set a new dictionary entirely, to allow for inline declaration, or use this property directly + /// and use the auto-created dictionary. + /// + /// + /// If any of the default keys used by the RabbitMQ Client library (product, version) are specified their value + /// will be overwritten. See DefaultClientProperties in Connection.cs in the RabbitMQ .NET client source for the default values. + /// + public IDictionary ClientProperties { + get => clientProperties ?? (clientProperties = new Dictionary()); + set => clientProperties = value; + } + /// public TapetiConnectionParams() - { + { } /// diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst index 445928d..f913f1b 100644 --- a/docs/gettingstarted.rst +++ b/docs/gettingstarted.rst @@ -148,4 +148,73 @@ To send a message, get a reference to IPublisher using dependency injection and $"It was last seen in {message.LastKnownHutch}." }); } - } \ No newline at end of file + } + + +Connection parameters +--------------------- +If you don't want to use the default connection parameters, which is probably a good idea in a production environment, you can manually specify the properties for TapetiConnectionParams or get them from your configuration of choice. Tapeti provides with two helpers. + +App.config / Web.config +^^^^^^^^^^^^^^^^^^^^^^^ +The TapetiAppSettingsConnectionParams class can be used to load the connection parameters from the AppSettings: + +:: + + using (var connection = new TapetiConnection(config) + { + Params = new TapetiAppSettingsConnectionParams() + }) + +The constructor accepts a prefix parameter, which defaults to "rabbitmq:". You can then specify the values in the appSettings block of your App.config or Web.config. Any omitted parameters will use the default value. + +.. code-block:: xml + + + + + + + + + + + + + + + + +The last entry is special: any setting which starts with "clientproperty:", after the configured prefix, will be added to the ClientProperties set. These properties are visible in the RabbitMQ Management interface and can be used to identify the connection. + +ConnectionString +^^^^^^^^^^^^^^^^ +Tapeti also includes a helper which can parse a connection string style value which is mainly for compatibility with `EasyNetQ `_. It made porting our applications slightly easier. EasyNetQ is a very capable library which includes high- and low-level wrappers for the RabbitMQ Client as well as a Management API client, and is worth checking out if you have a use case that is not suited to Tapeti. + +To parse a connection string, use the ConnectionStringParser.Parse method. You can of course still load the value from the AppSettings easily: + +:: + + using (var connection = new TapetiConnection(config) + { + Params = Tapeti.Helpers.ConnectionStringParser.Parse( + ConfigurationManager.AppSettings["RabbitMQ.ConnectionString"]) + }) + +An example connection string: + +:: + + host=localhost;username=guest;password=prefetchcount=5 + +Supported keys are: + +- hostname +- port +- virtualhost +- username +- password +- prefetchcount +- managementport + +Any keys in the connection string which are not supported will be silently ignored. \ No newline at end of file diff --git a/docs/introduction.rst b/docs/introduction.rst index 07798c0..a36c41d 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -21,4 +21,6 @@ Key features What it is not -------------- -Tapeti is not a general purpose RabbitMQ client. Although some behaviour can be overridden by implementing various interfaces, it enforces it's style of messaging and assumes everyone on the bus speaks the same language. \ No newline at end of file +Tapeti is not a general purpose RabbitMQ client. Although some behaviour can be overridden by implementing various interfaces, it enforces it's style of messaging and assumes everyone on the bus speaks the same language. + +There is no support for TLS connections, nor are there any plans to support it. The author is of the opinion the message bus should be considered an internal, highly available, service and recommends self-hosted REST API's behind an SSL proxy for communicating over public interfaces. \ No newline at end of file