Skip to content
This repository has been archived by the owner on Apr 27, 2019. It is now read-only.

Commit

Permalink
Fix config so it can be reused between clients
Browse files Browse the repository at this point in the history
Fixes #53 by making clients hold a HttpHandler and subscribe to Events
that tell it when to update the local handler, based on config changes
from the main ConsulClientConfiguration.
  • Loading branch information
highlyunavailable committed May 17, 2016
1 parent 94223ad commit cc8c37b
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 24 deletions.
34 changes: 32 additions & 2 deletions Consul.Test/ClientTest.cs
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
Expand Down Expand Up @@ -30,8 +31,6 @@ public void Client_DefaultConfig_env()
Assert.Equal("username", config.HttpAuth.UserName);
Assert.Equal("password", config.HttpAuth.Password);
Assert.Equal("https", config.Address.Scheme);
Assert.True((config.Handler as WebRequestHandler).ServerCertificateValidationCallback(null, null, null,
SslPolicyErrors.RemoteCertificateChainErrors));

Environment.SetEnvironmentVariable("CONSUL_HTTP_ADDR", string.Empty);
Environment.SetEnvironmentVariable("CONSUL_HTTP_TOKEN", string.Empty);
Expand All @@ -42,6 +41,9 @@ public void Client_DefaultConfig_env()

var client = new ConsulClient(config);

Assert.True((client.Handler as WebRequestHandler).ServerCertificateValidationCallback(null, null, null,
SslPolicyErrors.RemoteCertificateChainErrors));

Assert.NotNull(client);
}

Expand Down Expand Up @@ -138,5 +140,33 @@ public async Task Client_DisposeBehavior()

await Assert.ThrowsAsync<ObjectDisposedException>(() => request.Execute());
}

[Fact]
public async Task Client_ReuseAndUpdateConfig()
{
var config = new ConsulClientConfiguration();

using (var client = new ConsulClient(config))
{
config.DisableServerCertificateValidation = true;
await client.KV.Put(new KVPair("kv/reuseconfig") { Flags = 1000 });
Assert.Equal<ulong>(1000, (await client.KV.Get("kv/reuseconfig")).Response.Flags);
Assert.True((client.Handler as WebRequestHandler).ServerCertificateValidationCallback(null, null, null,
SslPolicyErrors.RemoteCertificateChainErrors));
}

using (var client = new ConsulClient(config))
{
config.DisableServerCertificateValidation = false;
await client.KV.Put(new KVPair("kv/reuseconfig") { Flags = 2000 });
Assert.Equal<ulong>(2000, (await client.KV.Get("kv/reuseconfig")).Response.Flags);
Assert.Null((client.Handler as WebRequestHandler).ServerCertificateValidationCallback);
}

using (var client = new ConsulClient(config))
{
await client.KV.Delete("kv/reuseconfig");
}
}
}
}
96 changes: 74 additions & 22 deletions Consul/Client.cs
Expand Up @@ -40,9 +40,24 @@ public class ConsulConfigurationException : Exception
public class ConsulClientConfiguration
{
private NetworkCredential _httpauth;

private bool _disableServerCertificateValidation;
private X509Certificate2 _clientCertificate;

internal event EventHandler Updated;

internal bool DisableServerCertificateValidation
{
get
{
return _disableServerCertificateValidation;
}
set
{
_disableServerCertificateValidation = value;
OnUpdated(new EventArgs());
}
}

/// <summary>
/// The Uri to connect to the Consul agent.
/// </summary>
Expand All @@ -66,10 +81,7 @@ internal get
set
{
_httpauth = value;
if (_httpauth != null)
{
(Handler as WebRequestHandler).Credentials = _httpauth;
}
OnUpdated(new EventArgs());
}
}

Expand All @@ -86,12 +98,7 @@ internal get
set
{
_clientCertificate = value;
if (_clientCertificate != null)
{
var handler = (Handler as WebRequestHandler);
handler.ClientCertificates.Add(_clientCertificate);
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
}
OnUpdated(new EventArgs());
}
}

Expand All @@ -106,19 +113,13 @@ internal get
/// </summary>
public TimeSpan? WaitTime { get; set; }

/// <summary>
/// A handler used to bypass HTTPS certificate validation if validation is disabled.
/// </summary>
internal HttpMessageHandler Handler;

/// <summary>
/// Creates a new instance of a Consul client configuration.
/// </summary>
/// <exception cref="ConsulConfigurationException">An error that occured while building the configuration.</exception>
public ConsulClientConfiguration()
{
UriBuilder consulAddress = new UriBuilder("http://127.0.0.1:8500");
Handler = new WebRequestHandler();
ConfigureFromEnvironment(consulAddress);

Address = consulAddress.Uri;
Expand Down Expand Up @@ -178,8 +179,7 @@ private void ConfigureFromEnvironment(UriBuilder consulAddress)
{
if (verifySsl == "0" || bool.Parse(verifySsl))
{
(Handler as WebRequestHandler).ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
DisableServerCertificateValidation = true;
}
}
catch (Exception ex)
Expand Down Expand Up @@ -210,6 +210,21 @@ private void ConfigureFromEnvironment(UriBuilder consulAddress)
Token = Environment.GetEnvironmentVariable("CONSUL_HTTP_TOKEN");
}
}

internal virtual void OnUpdated(EventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler handler = Updated;

// Event will be null if there are no subscribers
if (handler != null)
{
// Use the () operator to raise the event.
handler(this, e);
}
}
}

/// <summary>
Expand Down Expand Up @@ -401,6 +416,7 @@ public partial class ConsulClient : IDisposable
private object _lock = new object();
private bool skipClientDispose;
internal HttpClient HttpClient { get; set; }
internal HttpMessageHandler Handler;
internal ConsulClientConfiguration Config { get; set; }

internal readonly JsonSerializer serializer = new JsonSerializer();
Expand All @@ -417,7 +433,10 @@ public partial class ConsulClient : IDisposable
public ConsulClient(ConsulClientConfiguration config)
{
Config = config;
HttpClient = new HttpClient(config.Handler);
config.Updated += HandleConfigUpdateEvent;
Handler = new WebRequestHandler();
ApplyConfig(config);
HttpClient = new HttpClient(Handler);
HttpClient.Timeout = TimeSpan.FromMinutes(15);
HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpClient.DefaultRequestHeaders.Add("Keep-Alive", "true");
Expand Down Expand Up @@ -449,13 +468,14 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Config.Updated -= HandleConfigUpdateEvent;
if (HttpClient != null && !skipClientDispose)
{
HttpClient.Dispose();
}
if (Config.Handler != null)
if (Handler != null)
{
Config.Handler.Dispose();
Handler.Dispose();
}
}

Expand Down Expand Up @@ -486,6 +506,38 @@ public void CheckDisposed()
}
#endregion

void HandleConfigUpdateEvent(object sender, EventArgs e)
{
ApplyConfig(sender as ConsulClientConfiguration);

}
void ApplyConfig(ConsulClientConfiguration config)
{
var handler = (Handler as WebRequestHandler);
if (config.HttpAuth != null)
{
handler.Credentials = config.HttpAuth;
}
if (config.ClientCertificate != null)
{
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(config.ClientCertificate);
}
else
{
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Clear();
}
if (config.DisableServerCertificateValidation)
{
handler.ServerCertificateValidationCallback += (certSender, cert, chain, sslPolicyErrors) => true;
}
else
{
handler.ServerCertificateValidationCallback = null;
}
}

internal GetRequest<T> Get<T>(string path, QueryOptions opts = null)
{
return new GetRequest<T>(this, path, opts ?? QueryOptions.Default);
Expand Down

0 comments on commit cc8c37b

Please sign in to comment.