[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
This commit is contained in:
Mark van Renswoude 2019-08-18 11:06:33 +02:00
parent 7075baeb80
commit 84ee6f090d
7 changed files with 155 additions and 19 deletions

View File

@ -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<string, string>
{
{ "example", "01 - Publish Subscribe" }
}
}
})
{

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Configuration;
using System.Configuration;
using System.Linq;
namespace Tapeti
@ -19,6 +18,7 @@ namespace Tapeti
/// <item><description>rabbitmq:password</description></item>
/// <item><description>rabbitmq:prefetchcount</description></item>
/// <item><description>rabbitmq:managementport</description></item>
/// <item><description>rabbitmq:clientproperty:*</description></item>
/// </list>
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;
}
}
/// <inheritdoc />
@ -37,21 +51,35 @@ namespace Tapeti
/// <param name="prefix">The prefix to apply to the keys. Defaults to "rabbitmq:"</param>
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<string> 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));
}
}
}

View File

@ -1,14 +1,18 @@
using System;
using System.Collections.Generic;
// ReSharper disable UnusedMember.Global
namespace Tapeti
{
/// <summary>
///
/// Defines the connection parameters.
/// </summary>
public class TapetiConnectionParams
{
private IDictionary<string, string> clientProperties;
/// <summary>
/// The hostname to connect to. Defaults to localhost.
/// </summary>
@ -46,10 +50,24 @@ namespace Tapeti
/// </summary>
public int ManagementPort { get; set; } = 15672;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public IDictionary<string, string> ClientProperties {
get => clientProperties ?? (clientProperties = new Dictionary<string, string>());
set => clientProperties = value;
}
/// <inheritdoc />
public TapetiConnectionParams()
{
{
}
/// <summary>

View File

@ -148,4 +148,73 @@ To send a message, get a reference to IPublisher using dependency injection and
$"It was last seen in {message.LastKnownHutch}."
});
}
}
}
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
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="rabbitmq:hostname" value="localhost" />
<add key="rabbitmq:port" value="5672" />
<add key="rabbitmq:virtualhost" value="/" />
<add key="rabbitmq:username" value="guest" />
<add key="rabbitmq:password" value="guest" />
<add key="rabbitmq:prefetchcount" value="50" />
<add key="rabbitmq:managementport" value="15672" />
<add key="rabbitmq:clientproperty:application" value="Example" />
</appSettings>
</configuration>
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 <http://easynetq.com/>`_. 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.

View File

@ -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.
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.