Add additional properties to HttpOptions (#1557)

This commit is contained in:
James Newton-King 2018-03-13 09:46:34 +13:00 committed by GitHub
parent 489bd80b88
commit d816c6ef60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 379 additions and 26 deletions

View File

@ -4,8 +4,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Sockets;
using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Client.Http;
@ -19,6 +21,11 @@ namespace Microsoft.AspNetCore.SignalR.Client
public static readonly string HeadersKey = "Headers";
public static readonly string AccessTokenFactoryKey = "AccessTokenFactory";
public static readonly string WebSocketOptionsKey = "WebSocketOptions";
public static readonly string CookiesKey = "Cookies";
public static readonly string ProxyKey = "Proxy";
public static readonly string ClientCertificatesKey = "ClientCertificates";
public static readonly string CredentialsKey = "Credentials";
public static readonly string UseDefaultCredentialsKey = "UseDefaultCredentials";
public static IHubConnectionBuilder WithUrl(this IHubConnectionBuilder hubConnectionBuilder, string url)
{
@ -46,6 +53,11 @@ namespace Microsoft.AspNetCore.SignalR.Client
Headers = headers != null ? new ReadOnlyDictionary<string, string>(headers) : null,
AccessTokenFactory = hubConnectionBuilder.GetAccessTokenFactory(),
WebSocketOptions = hubConnectionBuilder.GetWebSocketOptions(),
Cookies = hubConnectionBuilder.GetCookies(),
Proxy = hubConnectionBuilder.GetProxy(),
UseDefaultCredentials = hubConnectionBuilder.GetUseDefaultCredentials(),
ClientCertificates = hubConnectionBuilder.GetClientCertificates(),
Credentials = hubConnectionBuilder.GetCredentials(),
};
return new HttpConnection(url,
@ -62,9 +74,15 @@ namespace Microsoft.AspNetCore.SignalR.Client
return hubConnectionBuilder;
}
public static IHubConnectionBuilder WithMessageHandler(this IHubConnectionBuilder hubConnectionBuilder, HttpMessageHandler httpMessageHandler)
/// <summary>
/// Sets a delegate for wrapping or replacing the <see cref="HttpMessageHandler"/> that will make HTTP requests the server.
/// </summary>
/// <param name="hubConnectionBuilder">The <see cref="IHubConnectionBuilder"/>.</param>
/// <param name="configurehttpMessageHandler">A delegate for wrapping or replacing the <see cref="HttpMessageHandler"/> that will make HTTP requests the server.</param>
/// <returns>The <see cref="IHubConnectionBuilder"/>.</returns>
public static IHubConnectionBuilder WithMessageHandler(this IHubConnectionBuilder hubConnectionBuilder, Func<HttpMessageHandler, HttpMessageHandler> configurehttpMessageHandler)
{
hubConnectionBuilder.AddSetting(HttpMessageHandlerKey, httpMessageHandler);
hubConnectionBuilder.AddSetting(HttpMessageHandlerKey, configurehttpMessageHandler);
return hubConnectionBuilder;
}
@ -87,6 +105,62 @@ namespace Microsoft.AspNetCore.SignalR.Client
return hubConnectionBuilder;
}
public static IHubConnectionBuilder WithUseDefaultCredentials(this IHubConnectionBuilder hubConnectionBuilder, bool useDefaultCredentials)
{
hubConnectionBuilder.AddSetting<bool?>(UseDefaultCredentialsKey, useDefaultCredentials);
return hubConnectionBuilder;
}
public static IHubConnectionBuilder WithCredentials(this IHubConnectionBuilder hubConnectionBuilder, ICredentials credentials)
{
hubConnectionBuilder.AddSetting(CredentialsKey, credentials);
return hubConnectionBuilder;
}
public static IHubConnectionBuilder WithProxy(this IHubConnectionBuilder hubConnectionBuilder, IWebProxy proxy)
{
hubConnectionBuilder.AddSetting(ProxyKey, proxy);
return hubConnectionBuilder;
}
public static IHubConnectionBuilder WithCookie(this IHubConnectionBuilder hubConnectionBuilder, Cookie cookie)
{
if (cookie == null)
{
throw new ArgumentNullException(nameof(cookie));
}
var cookies = hubConnectionBuilder.GetCookies();
if (cookies == null)
{
cookies = new CookieContainer();
hubConnectionBuilder.AddSetting(CookiesKey, cookies);
}
cookies.Add(cookie);
return hubConnectionBuilder;
}
public static IHubConnectionBuilder WithClientCertificate(this IHubConnectionBuilder hubConnectionBuilder, X509Certificate clientCertificate)
{
if (clientCertificate == null)
{
throw new ArgumentNullException(nameof(clientCertificate));
}
var clientCertificates = hubConnectionBuilder.GetClientCertificates();
if (clientCertificates == null)
{
clientCertificates = new X509CertificateCollection();
hubConnectionBuilder.AddSetting(ClientCertificatesKey, clientCertificates);
}
clientCertificates.Add(clientCertificate);
return hubConnectionBuilder;
}
public static IHubConnectionBuilder WithAccessToken(this IHubConnectionBuilder hubConnectionBuilder, Func<string> accessTokenFactory)
{
if (accessTokenFactory == null)
@ -121,9 +195,14 @@ namespace Microsoft.AspNetCore.SignalR.Client
return TransportType.All;
}
public static HttpMessageHandler GetMessageHandler(this IHubConnectionBuilder hubConnectionBuilder)
/// <summary>
/// Gets a delegate for wrapping or replacing the <see cref="HttpMessageHandler"/> that will make HTTP requests the server.
/// </summary>
/// <param name="hubConnectionBuilder">The <see cref="IHubConnectionBuilder"/>.</param>
/// <returns>A delegate for wrapping or replacing the <see cref="HttpMessageHandler"/> that will make HTTP requests the server.</returns>
public static Func<HttpMessageHandler, HttpMessageHandler> GetMessageHandler(this IHubConnectionBuilder hubConnectionBuilder)
{
hubConnectionBuilder.TryGetSetting<HttpMessageHandler>(HttpMessageHandlerKey, out var messageHandler);
hubConnectionBuilder.TryGetSetting<Func<HttpMessageHandler, HttpMessageHandler>>(HttpMessageHandlerKey, out var messageHandler);
return messageHandler;
}
@ -137,6 +216,56 @@ namespace Microsoft.AspNetCore.SignalR.Client
return null;
}
public static IWebProxy GetProxy(this IHubConnectionBuilder hubConnectionBuilder)
{
if (hubConnectionBuilder.TryGetSetting<IWebProxy>(ProxyKey, out var proxy))
{
return proxy;
}
return null;
}
public static bool? GetUseDefaultCredentials(this IHubConnectionBuilder hubConnectionBuilder)
{
if (hubConnectionBuilder.TryGetSetting<bool?>(UseDefaultCredentialsKey, out var useDefaultCredentials))
{
return useDefaultCredentials;
}
return null;
}
public static CookieContainer GetCookies(this IHubConnectionBuilder hubConnectionBuilder)
{
if (hubConnectionBuilder.TryGetSetting<CookieContainer>(CookiesKey, out var cookies))
{
return cookies;
}
return null;
}
public static ICredentials GetCredentials(this IHubConnectionBuilder hubConnectionBuilder)
{
if (hubConnectionBuilder.TryGetSetting<ICredentials>(CredentialsKey, out var credentials))
{
return credentials;
}
return null;
}
public static X509CertificateCollection GetClientCertificates(this IHubConnectionBuilder hubConnectionBuilder)
{
if (hubConnectionBuilder.TryGetSetting<X509CertificateCollection>(ClientCertificatesKey, out var clientCertificates))
{
return clientCertificates;
}
return null;
}
public static Func<string> GetAccessTokenFactory(this IHubConnectionBuilder hubConnectionBuilder)
{
if (hubConnectionBuilder.TryGetSetting<Func<string>>(AccessTokenFactoryKey, out var factory))

View File

@ -89,8 +89,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
_requestedTransportType = transportType;
if (_requestedTransportType != TransportType.WebSockets)
{
_httpClient = httpOptions?.HttpMessageHandler == null ? new HttpClient() : new HttpClient(httpOptions.HttpMessageHandler);
_httpClient.Timeout = HttpClientTimeout;
_httpClient = CreateHttpClient();
}
_transportFactory = new DefaultTransportFactory(transportType, _loggerFactory, _httpClient, httpOptions);
@ -98,14 +97,57 @@ namespace Microsoft.AspNetCore.Sockets.Client
_scopeDisposable = _logger.BeginScope(_logScope);
}
private HttpClient CreateHttpClient()
{
HttpMessageHandler httpMessageHandler = null;
if (_httpOptions != null)
{
var httpClientHandler = new HttpClientHandler();
if (_httpOptions.Proxy != null)
{
httpClientHandler.Proxy = _httpOptions.Proxy;
}
if (_httpOptions.Cookies != null)
{
httpClientHandler.CookieContainer = _httpOptions.Cookies;
}
if (_httpOptions.ClientCertificates != null)
{
httpClientHandler.ClientCertificates.AddRange(_httpOptions.ClientCertificates);
}
if (_httpOptions.UseDefaultCredentials != null)
{
httpClientHandler.UseDefaultCredentials = _httpOptions.UseDefaultCredentials.Value;
}
if (_httpOptions.Credentials != null)
{
httpClientHandler.Credentials = _httpOptions.Credentials;
}
httpMessageHandler = httpClientHandler;
if (_httpOptions.HttpMessageHandler != null)
{
httpMessageHandler = _httpOptions.HttpMessageHandler(httpClientHandler);
if (httpMessageHandler == null)
{
throw new InvalidOperationException("Configured HttpMessageHandler did not return a value.");
}
}
}
var httpClient = httpMessageHandler == null ? new HttpClient() : new HttpClient(httpMessageHandler);
httpClient.Timeout = HttpClientTimeout;
return httpClient;
}
public HttpConnection(Uri url, ITransportFactory transportFactory, ILoggerFactory loggerFactory, HttpOptions httpOptions)
{
Url = url ?? throw new ArgumentNullException(nameof(url));
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
_logger = _loggerFactory.CreateLogger<HttpConnection>();
_httpOptions = httpOptions;
_httpClient = _httpOptions?.HttpMessageHandler == null ? new HttpClient() : new HttpClient(_httpOptions?.HttpMessageHandler);
_httpClient.Timeout = HttpClientTimeout;
_httpClient = CreateHttpClient();
_transportFactory = transportFactory ?? throw new ArgumentNullException(nameof(transportFactory));
_logScope = new ConnectionLogScope();
_scopeDisposable = _logger.BeginScope(_logScope);

View File

@ -3,17 +3,29 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
namespace Microsoft.AspNetCore.Sockets.Client.Http
{
public class HttpOptions
{
public HttpMessageHandler HttpMessageHandler { get; set; }
/// <summary>
/// Gets or sets a delegate for wrapping or replacing the <see cref="HttpMessageHandler"/>
/// that will make HTTP requests the server.
/// </summary>
public Func<HttpMessageHandler, HttpMessageHandler> HttpMessageHandler { get; set; }
public IReadOnlyCollection<KeyValuePair<string, string>> Headers { get; set; }
public Func<string> AccessTokenFactory { get; set; }
public TimeSpan CloseTimeout { get; set; } = TimeSpan.FromSeconds(5);
public ICredentials Credentials { get; set; }
public X509CertificateCollection ClientCertificates { get; set; } = new X509CertificateCollection();
public CookieContainer Cookies { get; set; } = new CookieContainer();
public IWebProxy Proxy { get; set; }
public bool? UseDefaultCredentials { get; set; }
/// <summary>
/// Gets or sets a delegate that will be invoked with the <see cref="ClientWebSocketOptions"/> object used

View File

@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net;
using System.Net.WebSockets;
using System.Runtime.InteropServices;
using System.Threading;
@ -38,22 +39,51 @@ namespace Microsoft.AspNetCore.Sockets.Client
// Issue in ClientWebSocket prevents user-agent being set - https://github.com/dotnet/corefx/issues/26627
//_webSocket.Options.SetRequestHeader("User-Agent", Constants.UserAgentHeader.ToString());
if (httpOptions?.Headers != null)
if (httpOptions != null)
{
foreach (var header in httpOptions.Headers)
if (httpOptions.Headers != null)
{
_webSocket.Options.SetRequestHeader(header.Key, header.Value);
foreach (var header in httpOptions.Headers)
{
_webSocket.Options.SetRequestHeader(header.Key, header.Value);
}
}
if (httpOptions.Cookies != null)
{
_webSocket.Options.Cookies = httpOptions.Cookies;
}
if (httpOptions.ClientCertificates != null)
{
_webSocket.Options.ClientCertificates.AddRange(httpOptions.ClientCertificates);
}
if (httpOptions.Credentials != null)
{
_webSocket.Options.Credentials = httpOptions.Credentials;
}
if (httpOptions.Proxy != null)
{
_webSocket.Options.Proxy = httpOptions.Proxy;
}
if (httpOptions.UseDefaultCredentials != null)
{
_webSocket.Options.UseDefaultCredentials = httpOptions.UseDefaultCredentials.Value;
}
if (httpOptions.AccessTokenFactory != null)
{
_webSocket.Options.SetRequestHeader("Authorization", $"Bearer {httpOptions.AccessTokenFactory()}");
}
httpOptions.WebSocketOptions?.Invoke(_webSocket.Options);
_closeTimeout = httpOptions.CloseTimeout;
}
if (httpOptions?.AccessTokenFactory != null)
{
_webSocket.Options.SetRequestHeader("Authorization", $"Bearer {httpOptions.AccessTokenFactory()}");
}
httpOptions?.WebSocketOptions?.Invoke(_webSocket.Options);
_closeTimeout = httpOptions?.CloseTimeout ?? TimeSpan.FromSeconds(5);
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<WebSocketsTransport>();
}

View File

@ -16,11 +16,17 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
private static HttpConnection CreateConnection(HttpMessageHandler httpHandler = null, ILoggerFactory loggerFactory = null, string url = null, ITransport transport = null)
{
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
var httpOptions = new HttpOptions()
{
HttpMessageHandler = httpHandler ?? TestHttpMessageHandler.CreateDefault(),
HttpMessageHandler = (httpMessageHandler) => httpHandler ?? TestHttpMessageHandler.CreateDefault(),
};
return CreateConnection(httpOptions, loggerFactory, url, transport);
}
private static HttpConnection CreateConnection(HttpOptions httpOptions, ILoggerFactory loggerFactory = null, string url = null, ITransport transport = null)
{
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
var uri = new Uri(url ?? "http://fakeuri.org/");
var connection = (transport != null) ?

View File

@ -2,14 +2,21 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Client.Tests;
using Microsoft.AspNetCore.Sockets;
using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Client.Http;
using Microsoft.AspNetCore.Sockets.Features;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit;
using Xunit.Abstractions;
@ -140,5 +147,49 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
Assert.Equal(TransferMode.Binary, transferModeFeature.TransferMode);
});
}
[Fact]
public async Task HttpOptionsSetOntoHttpClientHandler()
{
var testHttpHandler = new TestHttpMessageHandler();
var negotiateUrlTcs = new TaskCompletionSource<string>();
testHttpHandler.OnNegotiate((request, cancellationToken) =>
{
negotiateUrlTcs.TrySetResult(request.RequestUri.ToString());
return ResponseUtils.CreateResponse(HttpStatusCode.OK,
ResponseUtils.CreateNegotiationContent());
});
HttpClientHandler httpClientHandler = null;
HttpOptions httpOptions = new HttpOptions();
httpOptions.HttpMessageHandler = inner =>
{
httpClientHandler = (HttpClientHandler)inner;
return testHttpHandler;
};
httpOptions.Cookies.Add(new Cookie("Name", "Value", string.Empty, "fakeuri.org"));
var clientCertificate = new X509Certificate();
httpOptions.ClientCertificates.Add(clientCertificate);
httpOptions.UseDefaultCredentials = false;
httpOptions.Credentials = Mock.Of<ICredentials>();
httpOptions.Proxy = Mock.Of<IWebProxy>();
await WithConnectionAsync(
CreateConnection(httpOptions, url: "http://fakeuri.org/"),
async (connection, closed) =>
{
await connection.StartAsync().OrTimeout();
});
Assert.NotNull(httpClientHandler);
Assert.Equal(1, httpClientHandler.CookieContainer.Count);
Assert.Single(httpClientHandler.ClientCertificates);
Assert.Same(clientCertificate, httpClientHandler.ClientCertificates[0]);
Assert.False(httpClientHandler.UseDefaultCredentials);
Assert.Same(httpOptions.Proxy, httpClientHandler.Proxy);
Assert.Same(httpOptions.Credentials, httpClientHandler.Credentials);
}
}
}

View File

@ -1,18 +1,68 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.AspNetCore.Sockets;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Moq;
using Xunit;
using TransportType = Microsoft.AspNetCore.Sockets.TransportType;
namespace Microsoft.AspNetCore.SignalR.Client.Tests
{
public class HubConnectionBuilderExtensionsTests
{
[Fact]
public void WithProxyRegistersGivenProxy()
{
var connectionBuilder = new HubConnectionBuilder();
var proxy = Mock.Of<IWebProxy>();
connectionBuilder.WithProxy(proxy);
Assert.Same(proxy, connectionBuilder.GetProxy());
}
[Fact]
public void WithCredentialsRegistersGivenCredentials()
{
var connectionBuilder = new HubConnectionBuilder();
var credentials = Mock.Of<ICredentials>();
connectionBuilder.WithCredentials(credentials);
Assert.Same(credentials, connectionBuilder.GetCredentials());
}
[Fact]
public void WithUseDefaultCredentialsRegistersGivenUseDefaultCredentials()
{
var connectionBuilder = new HubConnectionBuilder();
var useDefaultCredentials = true;
connectionBuilder.WithUseDefaultCredentials(useDefaultCredentials);
Assert.Equal(useDefaultCredentials, connectionBuilder.GetUseDefaultCredentials());
}
[Fact]
public void WithClientCertificateRegistersGivenClientCertificate()
{
var connectionBuilder = new HubConnectionBuilder();
var certificate = new X509Certificate();
connectionBuilder.WithClientCertificate(certificate);
Assert.Contains(certificate, connectionBuilder.GetClientCertificates().Cast<X509Certificate>());
}
[Fact]
public void WithCookieRegistersGivenCookie()
{
var connectionBuilder = new HubConnectionBuilder();
var cookie = new Cookie("Name!", "Value!", string.Empty, "www.contoso.com");
connectionBuilder.WithCookie(cookie);
Assert.Equal(1, connectionBuilder.GetCookies().Count);
}
[Fact]
public void WithHubProtocolRegistersGivenProtocol()
{
@ -60,10 +110,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
[Fact]
public void WithMsgHandlerRegistersGivenMessageHandler()
{
var messageHandler = new Func<HttpMessageHandler, HttpMessageHandler>(httpMessageHandler => default);
var connectionBuilder = new HubConnectionBuilder();
var msgHandler = Mock.Of<HttpMessageHandler>();
connectionBuilder.WithMessageHandler(msgHandler);
Assert.Same(msgHandler, connectionBuilder.GetMessageHandler());
connectionBuilder.WithMessageHandler(messageHandler);
Assert.Same(messageHandler, connectionBuilder.GetMessageHandler());
}
[Theory]

View File

@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
.Returns<HttpRequestMessage, CancellationToken>(
(request, cancellationToken) => Task.FromException<HttpResponseMessage>(new InvalidOperationException("HTTP requests should not be sent.")));
var connection = new HttpConnection(new Uri(url), TransportType.WebSockets, loggerFactory, new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
var connection = new HttpConnection(new Uri(url), TransportType.WebSockets, loggerFactory, new HttpOptions { HttpMessageHandler = (httpMessageHandler) => mockHttpHandler.Object });
try
{

View File

@ -3,9 +3,14 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.WebSockets;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Sockets;
@ -35,6 +40,33 @@ namespace Microsoft.AspNetCore.SignalR.Tests
_serverFixture = serverFixture;
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2, SkipReason = "No WebSockets Client for this platform")]
public void HttpOptionsSetOntoWebSocketOptions()
{
ClientWebSocketOptions webSocketsOptions = null;
HttpOptions httpOptions = new HttpOptions();
httpOptions.Cookies.Add(new Cookie("Name", "Value", string.Empty, "fakeuri.org"));
var clientCertificate = new X509Certificate();
httpOptions.ClientCertificates.Add(clientCertificate);
httpOptions.UseDefaultCredentials = false;
httpOptions.Credentials = Mock.Of<ICredentials>();
httpOptions.Proxy = Mock.Of<IWebProxy>();
httpOptions.WebSocketOptions = options => webSocketsOptions = options;
var webSocketsTransport = new WebSocketsTransport(httpOptions: httpOptions, loggerFactory: null);
Assert.NotNull(webSocketsTransport);
Assert.NotNull(webSocketsOptions);
Assert.Equal(1, webSocketsOptions.Cookies.Count);
Assert.Single(webSocketsOptions.ClientCertificates);
Assert.Same(clientCertificate, webSocketsOptions.ClientCertificates[0]);
Assert.False(webSocketsOptions.UseDefaultCredentials);
Assert.Same(httpOptions.Proxy, webSocketsOptions.Proxy);
Assert.Same(httpOptions.Credentials, webSocketsOptions.Credentials);
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2, SkipReason = "No WebSockets Client for this platform")]
public async Task WebSocketsTransportStopsSendAndReceiveLoopsWhenTransportIsStopped()