Adding support for JWT in the C# client

Fixes: #1018

(Bonus: also enabling passing headers)
This commit is contained in:
Pawel Kadluczka 2017-11-07 22:08:34 -08:00 committed by Pawel Kadluczka
parent fadd6f89fd
commit 0bafb304c2
20 changed files with 363 additions and 81 deletions

View File

@ -9,6 +9,7 @@
<InternalAspNetCoreSdkPackageVersion>2.1.0-preview1-15549</InternalAspNetCoreSdkPackageVersion> <InternalAspNetCoreSdkPackageVersion>2.1.0-preview1-15549</InternalAspNetCoreSdkPackageVersion>
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion> <MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreAuthenticationCorePackageVersion> <MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
<MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>2.1.0-preview1-27475</MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>
<MicrosoftAspNetCoreAuthorizationPackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreAuthorizationPackageVersion> <MicrosoftAspNetCoreAuthorizationPackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreAuthorizationPackageVersion>
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion> <MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
<MicrosoftAspNetCoreCorsPackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreCorsPackageVersion> <MicrosoftAspNetCoreCorsPackageVersion>2.1.0-preview1-27579</MicrosoftAspNetCoreCorsPackageVersion>

View File

@ -6,7 +6,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -42,9 +42,11 @@ namespace Microsoft.AspNetCore.SignalR.Client
throw new InvalidOperationException("Cannot create IConnection instance. The connection factory was not configured."); throw new InvalidOperationException("Cannot create IConnection instance. The connection factory was not configured.");
} }
IHubConnectionBuilder builder = this;
var connection = _connectionFactoryDelegate(); var connection = _connectionFactoryDelegate();
var loggerFactory = ((IHubConnectionBuilder)this).GetLoggerFactory();
var hubProtocol = ((IHubConnectionBuilder)this).GetHubProtocol(); var loggerFactory = builder.GetLoggerFactory();
var hubProtocol = builder.GetHubProtocol();
return new HubConnection(connection, hubProtocol ?? new JsonHubProtocol(), loggerFactory); return new HubConnection(connection, hubProtocol ?? new JsonHubProtocol(), loggerFactory);
} }

View File

@ -2,9 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net.Http; using System.Net.Http;
using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets;
using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Client.Http;
namespace Microsoft.AspNetCore.SignalR.Client namespace Microsoft.AspNetCore.SignalR.Client
{ {
@ -12,6 +15,8 @@ namespace Microsoft.AspNetCore.SignalR.Client
{ {
public static readonly string TransportTypeKey = "TransportType"; public static readonly string TransportTypeKey = "TransportType";
public static readonly string HttpMessageHandlerKey = "HttpMessageHandler"; public static readonly string HttpMessageHandlerKey = "HttpMessageHandler";
public static readonly string HeadersKey = "Headers";
public static readonly string JwtBearerTokenFactoryKey = "JwtBearerTokenFactory";
public static IHubConnectionBuilder WithUrl(this IHubConnectionBuilder hubConnectionBuilder, string url) public static IHubConnectionBuilder WithUrl(this IHubConnectionBuilder hubConnectionBuilder, string url)
{ {
@ -32,10 +37,18 @@ namespace Microsoft.AspNetCore.SignalR.Client
hubConnectionBuilder.ConfigureConnectionFactory(() => hubConnectionBuilder.ConfigureConnectionFactory(() =>
{ {
var headers = hubConnectionBuilder.GetHeaders();
var httpOptions = new HttpOptions
{
HttpMessageHandler = hubConnectionBuilder.GetMessageHandler(),
Headers = headers != null ? new ReadOnlyDictionary<string, string>(headers) : null,
JwtBearerTokenFactory = hubConnectionBuilder.GetJwtBearerTokenFactory()
};
return new HttpConnection(url, return new HttpConnection(url,
hubConnectionBuilder.GetTransport(), hubConnectionBuilder.GetTransport(),
hubConnectionBuilder.GetLoggerFactory(), hubConnectionBuilder.GetLoggerFactory(),
hubConnectionBuilder.GetMessageHandler()); httpOptions);
}); });
return hubConnectionBuilder; return hubConnectionBuilder;
} }
@ -52,6 +65,37 @@ namespace Microsoft.AspNetCore.SignalR.Client
return hubConnectionBuilder; return hubConnectionBuilder;
} }
public static IHubConnectionBuilder WithHeader(this IHubConnectionBuilder hubConnectionBuilder, string name, string value)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Header name cannot be null or empty string.", nameof(name));
}
var headers = hubConnectionBuilder.GetHeaders();
if (headers == null)
{
headers = new Dictionary<string, string>();
hubConnectionBuilder.AddSetting(HeadersKey, headers);
}
headers.Add(name, value);
return hubConnectionBuilder;
}
public static IHubConnectionBuilder WithJwtBearer(this IHubConnectionBuilder hubConnectionBuilder, Func<string> jwtBearerTokenFactory)
{
if (jwtBearerTokenFactory == null)
{
throw new ArgumentNullException(nameof(jwtBearerTokenFactory));
}
hubConnectionBuilder.AddSetting(JwtBearerTokenFactoryKey, jwtBearerTokenFactory);
return hubConnectionBuilder;
}
public static TransportType GetTransport(this IHubConnectionBuilder hubConnectionBuilder) public static TransportType GetTransport(this IHubConnectionBuilder hubConnectionBuilder)
{ {
if (hubConnectionBuilder.TryGetSetting<TransportType>(TransportTypeKey, out var transportType)) if (hubConnectionBuilder.TryGetSetting<TransportType>(TransportTypeKey, out var transportType))
@ -67,5 +111,25 @@ namespace Microsoft.AspNetCore.SignalR.Client
hubConnectionBuilder.TryGetSetting<HttpMessageHandler>(HttpMessageHandlerKey, out var messageHandler); hubConnectionBuilder.TryGetSetting<HttpMessageHandler>(HttpMessageHandlerKey, out var messageHandler);
return messageHandler; return messageHandler;
} }
public static IDictionary<string, string> GetHeaders(this IHubConnectionBuilder hubConnectionBuilder)
{
if (hubConnectionBuilder.TryGetSetting<IDictionary<string, string>>(HeadersKey, out var headers))
{
return headers;
}
return null;
}
public static Func<string> GetJwtBearerTokenFactory(this IHubConnectionBuilder hubConnectionBuilder)
{
if (hubConnectionBuilder.TryGetSetting<Func<string>>(JwtBearerTokenFactoryKey, out var factory))
{
return factory;
}
return null;
}
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Net.Http; using System.Net.Http;
using Microsoft.AspNetCore.Sockets.Client.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Sockets.Client namespace Microsoft.AspNetCore.Sockets.Client
@ -10,11 +11,12 @@ namespace Microsoft.AspNetCore.Sockets.Client
public class DefaultTransportFactory : ITransportFactory public class DefaultTransportFactory : ITransportFactory
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly HttpOptions _httpOptions;
private readonly TransportType _requestedTransportType; private readonly TransportType _requestedTransportType;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private static volatile bool _websocketsSupported = true; private static volatile bool _websocketsSupported = true;
public DefaultTransportFactory(TransportType requestedTransportType, ILoggerFactory loggerFactory, HttpClient httpClient) public DefaultTransportFactory(TransportType requestedTransportType, ILoggerFactory loggerFactory, HttpClient httpClient, HttpOptions httpOptions)
{ {
if (requestedTransportType <= 0 || requestedTransportType > TransportType.All) if (requestedTransportType <= 0 || requestedTransportType > TransportType.All)
{ {
@ -29,6 +31,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
_requestedTransportType = requestedTransportType; _requestedTransportType = requestedTransportType;
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_httpClient = httpClient; _httpClient = httpClient;
_httpOptions = httpOptions;
} }
public ITransport CreateTransport(TransportType availableServerTransports) public ITransport CreateTransport(TransportType availableServerTransports)
@ -37,7 +40,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
{ {
try try
{ {
return new WebSocketsTransport(_loggerFactory); return new WebSocketsTransport(_httpOptions, _loggerFactory);
} }
catch (PlatformNotSupportedException) catch (PlatformNotSupportedException)
{ {
@ -47,12 +50,12 @@ namespace Microsoft.AspNetCore.Sockets.Client
if ((availableServerTransports & TransportType.ServerSentEvents & _requestedTransportType) == TransportType.ServerSentEvents) if ((availableServerTransports & TransportType.ServerSentEvents & _requestedTransportType) == TransportType.ServerSentEvents)
{ {
return new ServerSentEventsTransport(_httpClient, _loggerFactory); return new ServerSentEventsTransport(_httpClient, _httpOptions, _loggerFactory);
} }
if ((availableServerTransports & TransportType.LongPolling & _requestedTransportType) == TransportType.LongPolling) if ((availableServerTransports & TransportType.LongPolling & _requestedTransportType) == TransportType.LongPolling)
{ {
return new LongPollingTransport(_httpClient, _loggerFactory); return new LongPollingTransport(_httpClient, _httpOptions, _loggerFactory);
} }
throw new InvalidOperationException("No requested transports available on the server."); throw new InvalidOperationException("No requested transports available on the server.");

View File

@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
private volatile int _connectionState = ConnectionState.Initial; private volatile int _connectionState = ConnectionState.Initial;
private volatile ChannelConnection<byte[], SendMessage> _transportChannel; private volatile ChannelConnection<byte[], SendMessage> _transportChannel;
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly HttpOptions _httpOptions;
private volatile ITransport _transport; private volatile ITransport _transport;
private volatile Task _receiveLoopTask; private volatile Task _receiveLoopTask;
private TaskCompletionSource<object> _startTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource<object> _startTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
@ -54,49 +55,47 @@ namespace Microsoft.AspNetCore.Sockets.Client
: this(url, TransportType.All) : this(url, TransportType.All)
{ } { }
public HttpConnection(Uri url, HttpMessageHandler httpMessageHandler)
: this(url, TransportType.All, loggerFactory: null, httpMessageHandler: httpMessageHandler)
{ }
public HttpConnection(Uri url, TransportType transportType) public HttpConnection(Uri url, TransportType transportType)
: this(url, transportType, loggerFactory: null) : this(url, transportType, loggerFactory: null)
{ {
} }
public HttpConnection(Uri url, ILoggerFactory loggerFactory) public HttpConnection(Uri url, ILoggerFactory loggerFactory)
: this(url, TransportType.All, loggerFactory, httpMessageHandler: null) : this(url, TransportType.All, loggerFactory, httpOptions: null)
{ {
} }
public HttpConnection(Uri url, TransportType transportType, ILoggerFactory loggerFactory) public HttpConnection(Uri url, TransportType transportType, ILoggerFactory loggerFactory)
: this(url, transportType, loggerFactory, httpMessageHandler: null) : this(url, transportType, loggerFactory, httpOptions: null)
{ {
} }
public HttpConnection(Uri url, TransportType transportType, ILoggerFactory loggerFactory, HttpMessageHandler httpMessageHandler) public HttpConnection(Uri url, TransportType transportType, ILoggerFactory loggerFactory, HttpOptions httpOptions)
{ {
Url = url ?? throw new ArgumentNullException(nameof(url)); Url = url ?? throw new ArgumentNullException(nameof(url));
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
_logger = _loggerFactory.CreateLogger<HttpConnection>(); _logger = _loggerFactory.CreateLogger<HttpConnection>();
_httpOptions = httpOptions;
_requestedTransportType = transportType; _requestedTransportType = transportType;
if (_requestedTransportType != TransportType.WebSockets) if (_requestedTransportType != TransportType.WebSockets)
{ {
_httpClient = httpMessageHandler == null ? new HttpClient() : new HttpClient(httpMessageHandler); _httpClient = httpOptions?.HttpMessageHandler == null ? new HttpClient() : new HttpClient(httpOptions.HttpMessageHandler);
_httpClient.Timeout = HttpClientTimeout; _httpClient.Timeout = HttpClientTimeout;
} }
_transportFactory = new DefaultTransportFactory(transportType, _loggerFactory, _httpClient); _transportFactory = new DefaultTransportFactory(transportType, _loggerFactory, _httpClient, httpOptions);
} }
public HttpConnection(Uri url, ITransportFactory transportFactory, ILoggerFactory loggerFactory, HttpMessageHandler httpMessageHandler) public HttpConnection(Uri url, ITransportFactory transportFactory, ILoggerFactory loggerFactory, HttpOptions httpOptions)
{ {
Url = url ?? throw new ArgumentNullException(nameof(url)); Url = url ?? throw new ArgumentNullException(nameof(url));
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
_logger = _loggerFactory.CreateLogger<HttpConnection>(); _logger = _loggerFactory.CreateLogger<HttpConnection>();
_httpClient = httpMessageHandler == null ? new HttpClient() : new HttpClient(httpMessageHandler); _httpOptions = httpOptions;
_httpClient.Timeout = HttpClientTimeout; _httpClient = _httpOptions?.HttpMessageHandler == null ? new HttpClient() : new HttpClient(_httpOptions?.HttpMessageHandler);
_httpClient.Timeout = HttpClientTimeout;
_transportFactory = transportFactory ?? throw new ArgumentNullException(nameof(transportFactory)); _transportFactory = transportFactory ?? throw new ArgumentNullException(nameof(transportFactory));
} }
@ -214,7 +213,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
} }
} }
private async static Task<NegotiationResponse> Negotiate(Uri url, HttpClient httpClient, ILogger logger) private async Task<NegotiationResponse> Negotiate(Uri url, HttpClient httpClient, ILogger logger)
{ {
try try
{ {
@ -229,7 +228,8 @@ namespace Microsoft.AspNetCore.Sockets.Client
using (var request = new HttpRequestMessage(HttpMethod.Post, urlBuilder.Uri)) using (var request = new HttpRequestMessage(HttpMethod.Post, urlBuilder.Uri))
{ {
request.Headers.UserAgent.Add(Constants.UserAgentHeader); SendUtils.PrepareHttpRequest(request, _httpOptions);
using (var response = await httpClient.SendAsync(request)) using (var response = await httpClient.SendAsync(request))
{ {
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();

View File

@ -0,0 +1,16 @@
// 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.Collections.Generic;
using System.Net.Http;
namespace Microsoft.AspNetCore.Sockets.Client.Http
{
public class HttpOptions
{
public HttpMessageHandler HttpMessageHandler { get; set; }
public IReadOnlyCollection<KeyValuePair<string, string>> Headers { get; set; }
public Func<string> JwtBearerTokenFactory { get; set; }
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
@ -17,6 +18,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
public class LongPollingTransport : ITransport public class LongPollingTransport : ITransport
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly HttpOptions _httpOptions;
private readonly ILogger _logger; private readonly ILogger _logger;
private Channel<byte[], SendMessage> _application; private Channel<byte[], SendMessage> _application;
private Task _sender; private Task _sender;
@ -30,12 +32,13 @@ namespace Microsoft.AspNetCore.Sockets.Client
public TransferMode? Mode { get; private set; } public TransferMode? Mode { get; private set; }
public LongPollingTransport(HttpClient httpClient) public LongPollingTransport(HttpClient httpClient)
: this(httpClient, null) : this(httpClient, null, null)
{ } { }
public LongPollingTransport(HttpClient httpClient, ILoggerFactory loggerFactory) public LongPollingTransport(HttpClient httpClient, HttpOptions httpOptions, ILoggerFactory loggerFactory)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_httpOptions = httpOptions;
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<LongPollingTransport>(); _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<LongPollingTransport>();
} }
@ -54,7 +57,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
// Start sending and polling (ask for binary if the server supports it) // Start sending and polling (ask for binary if the server supports it)
_poller = Poll(url, _transportCts.Token); _poller = Poll(url, _transportCts.Token);
_sender = SendUtils.SendMessages(url, _application, _httpClient, _transportCts, _logger, _connectionId); _sender = SendUtils.SendMessages(url, _application, _httpClient, _httpOptions, _transportCts, _logger, _connectionId);
Running = Task.WhenAll(_sender, _poller).ContinueWith(t => Running = Task.WhenAll(_sender, _poller).ContinueWith(t =>
{ {
@ -90,7 +93,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var request = new HttpRequestMessage(HttpMethod.Get, pollUrl); var request = new HttpRequestMessage(HttpMethod.Get, pollUrl);
request.Headers.UserAgent.Add(Constants.UserAgentHeader); SendUtils.PrepareHttpRequest(request, _httpOptions);
HttpResponseMessage response; HttpResponseMessage response;

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
internal static class SendUtils internal static class SendUtils
{ {
public static async Task SendMessages(Uri sendUrl, Channel<byte[], SendMessage> application, HttpClient httpClient, public static async Task SendMessages(Uri sendUrl, Channel<byte[], SendMessage> application, HttpClient httpClient,
CancellationTokenSource transportCts, ILogger logger, string connectionId) HttpOptions httpOptions, CancellationTokenSource transportCts, ILogger logger, string connectionId)
{ {
logger.SendStarted(connectionId); logger.SendStarted(connectionId);
IList<SendMessage> messages = null; IList<SendMessage> messages = null;
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
// Send them in a single post // Send them in a single post
var request = new HttpRequestMessage(HttpMethod.Post, sendUrl); var request = new HttpRequestMessage(HttpMethod.Post, sendUrl);
request.Headers.UserAgent.Add(Constants.UserAgentHeader); PrepareHttpRequest(request, httpOptions);
// TODO: We can probably use a pipeline here or some kind of pooled memory. // TODO: We can probably use a pipeline here or some kind of pooled memory.
// But where do we get the pool from? ArrayBufferPool.Instance? // But where do we get the pool from? ArrayBufferPool.Instance?
@ -107,5 +107,22 @@ namespace Microsoft.AspNetCore.Sockets.Client
logger.SendStopped(connectionId); logger.SendStopped(connectionId);
} }
public static void PrepareHttpRequest(HttpRequestMessage request, HttpOptions httpOptions)
{
if (httpOptions?.Headers != null)
{
foreach (var header in httpOptions.Headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
request.Headers.UserAgent.Add(Constants.UserAgentHeader);
if (httpOptions?.JwtBearerTokenFactory != null)
{
request.Headers.Add("Authorization", $"Bearer {httpOptions.JwtBearerTokenFactory()}");
}
}
} }
} }

View File

@ -9,6 +9,7 @@ using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Channels; using System.Threading.Channels;
using Microsoft.AspNetCore.Sockets.Client.Http;
using Microsoft.AspNetCore.Sockets.Client.Internal; using Microsoft.AspNetCore.Sockets.Client.Internal;
using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Microsoft.AspNetCore.Sockets.Internal.Formatters;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
{ {
private static readonly MemoryPool _memoryPool = new MemoryPool(); private static readonly MemoryPool _memoryPool = new MemoryPool();
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly HttpOptions _httpOptions;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly CancellationTokenSource _transportCts = new CancellationTokenSource(); private readonly CancellationTokenSource _transportCts = new CancellationTokenSource();
private readonly ServerSentEventsMessageParser _parser = new ServerSentEventsMessageParser(); private readonly ServerSentEventsMessageParser _parser = new ServerSentEventsMessageParser();
@ -32,10 +34,10 @@ namespace Microsoft.AspNetCore.Sockets.Client
public TransferMode? Mode { get; private set; } public TransferMode? Mode { get; private set; }
public ServerSentEventsTransport(HttpClient httpClient) public ServerSentEventsTransport(HttpClient httpClient)
: this(httpClient, null) : this(httpClient, null, null)
{ } { }
public ServerSentEventsTransport(HttpClient httpClient, ILoggerFactory loggerFactory) public ServerSentEventsTransport(HttpClient httpClient, HttpOptions httpOptions, ILoggerFactory loggerFactory)
{ {
if (httpClient == null) if (httpClient == null)
{ {
@ -43,6 +45,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
} }
_httpClient = httpClient; _httpClient = httpClient;
_httpOptions = httpOptions;
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<ServerSentEventsTransport>(); _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<ServerSentEventsTransport>();
} }
@ -59,7 +62,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
_logger.StartTransport(_connectionId, Mode.Value); _logger.StartTransport(_connectionId, Mode.Value);
var sendTask = SendUtils.SendMessages(url, _application, _httpClient, _transportCts, _logger, _connectionId); var sendTask = SendUtils.SendMessages(url, _application, _httpClient, _httpOptions, _transportCts, _logger, _connectionId);
var receiveTask = OpenConnection(_application, url, _transportCts.Token); var receiveTask = OpenConnection(_application, url, _transportCts.Token);
Running = Task.WhenAll(sendTask, receiveTask).ContinueWith(t => Running = Task.WhenAll(sendTask, receiveTask).ContinueWith(t =>
@ -78,6 +81,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
_logger.StartReceive(_connectionId); _logger.StartReceive(_connectionId);
var request = new HttpRequestMessage(HttpMethod.Get, url); var request = new HttpRequestMessage(HttpMethod.Get, url);
SendUtils.PrepareHttpRequest(request, _httpOptions);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);

View File

@ -6,8 +6,9 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Sockets.Client.Http;
using Microsoft.AspNetCore.Sockets.Client.Internal; using Microsoft.AspNetCore.Sockets.Client.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
{ {
public class WebSocketsTransport : ITransport public class WebSocketsTransport : ITransport
{ {
private readonly ClientWebSocket _webSocket = new ClientWebSocket(); private readonly ClientWebSocket _webSocket;
private Channel<byte[], SendMessage> _application; private Channel<byte[], SendMessage> _application;
private readonly CancellationTokenSource _transportCts = new CancellationTokenSource(); private readonly CancellationTokenSource _transportCts = new CancellationTokenSource();
private readonly CancellationTokenSource _receiveCts = new CancellationTokenSource(); private readonly CancellationTokenSource _receiveCts = new CancellationTokenSource();
@ -28,12 +29,26 @@ namespace Microsoft.AspNetCore.Sockets.Client
public TransferMode? Mode { get; private set; } public TransferMode? Mode { get; private set; }
public WebSocketsTransport() public WebSocketsTransport()
: this(null) : this(null, null)
{ {
} }
public WebSocketsTransport(ILoggerFactory loggerFactory) public WebSocketsTransport(HttpOptions httpOptions, ILoggerFactory loggerFactory)
{ {
_webSocket = new ClientWebSocket();
if (httpOptions?.Headers != null)
{
foreach (var header in httpOptions.Headers)
{
_webSocket.Options.SetRequestHeader(header.Key, header.Value);
}
}
if (httpOptions?.JwtBearerTokenFactory != null)
{
_webSocket.Options.SetRequestHeader("Authorization", $"Bearer {httpOptions.JwtBearerTokenFactory()}");
}
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<WebSocketsTransport>(); _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<WebSocketsTransport>();
} }

View File

@ -6,8 +6,8 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Channels; using System.Threading.Channels;
@ -555,6 +557,70 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
} }
} }
[Theory]
[MemberData(nameof(TransportTypes))]
public async Task ClientCanUseJwtBearerTokenForAuthentication(TransportType transportType)
{
using (StartLog(out var loggerFactory))
{
var httpResponse = await new HttpClient().GetAsync(_serverFixture.Url + "/generateJwtToken");
httpResponse.EnsureSuccessStatusCode();
var token = await httpResponse.Content.ReadAsStringAsync();
var hubConnection = new HubConnectionBuilder()
.WithUrl(_serverFixture.Url + "/authorizedhub")
.WithTransport(transportType)
.WithLoggerFactory(loggerFactory)
.WithJwtBearer(() => token)
.Build();
try
{
await hubConnection.StartAsync().OrTimeout();
var message = await hubConnection.InvokeAsync<string>("Echo", "Hello, World!").OrTimeout();
Assert.Equal("Hello, World!", message);
}
catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await hubConnection.DisposeAsync().OrTimeout();
}
}
}
[Theory]
[MemberData(nameof(TransportTypes))]
public async Task ClientCanSendHeaders(TransportType transportType)
{
using (StartLog(out var loggerFactory))
{
var hubConnection = new HubConnectionBuilder()
.WithUrl(_serverFixture.Url + "/default")
.WithTransport(transportType)
.WithLoggerFactory(loggerFactory)
.WithHeader("X-test", "42")
.WithHeader("X-42", "test")
.Build();
try
{
await hubConnection.StartAsync().OrTimeout();
var headerValues = await hubConnection.InvokeAsync<string[]>("GetHeaderValues", new object[] { new[] { "X-test", "X-42" } }).OrTimeout();
Assert.Equal(new[] { "42", "test" }, headerValues);
}
catch (Exception ex)
{
loggerFactory.CreateLogger<HubConnectionTests>().LogError(ex, "Exception from test");
throw;
}
finally
{
await hubConnection.DisposeAsync().OrTimeout();
}
}
}
public static IEnumerable<object[]> HubProtocolsAndTransportsAndHubPaths public static IEnumerable<object[]> HubProtocolsAndTransportsAndHubPaths
{ {
@ -562,7 +628,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{ {
foreach (var protocol in HubProtocols) foreach (var protocol in HubProtocols)
{ {
foreach (var transport in TransportTypes()) foreach (var transport in TransportTypes().SelectMany(t => t))
{ {
foreach (var hubPath in HubPaths) foreach (var hubPath in HubPaths)
{ {
@ -582,14 +648,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
new MessagePackHubProtocol(), new MessagePackHubProtocol(),
}; };
public static IEnumerable<TransportType> TransportTypes() public static IEnumerable<object[]> TransportTypes()
{ {
if (TestHelpers.IsWebSocketsSupported()) if (TestHelpers.IsWebSocketsSupported())
{ {
yield return TransportType.WebSockets; yield return new object[] { TransportType.WebSockets };
} }
yield return TransportType.ServerSentEvents; yield return new object[] { TransportType.ServerSentEvents };
yield return TransportType.LongPolling; yield return new object[] { TransportType.LongPolling };
} }
} }
} }

View File

@ -2,10 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{ {
@ -30,6 +33,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{ {
await Clients.Client(Context.ConnectionId).InvokeAsync("NoClientHandler"); await Clients.Client(Context.ConnectionId).InvokeAsync("NoClientHandler");
} }
public IEnumerable<string> GetHeaderValues(string[] headerNames)
{
var headers = Context.Connection.GetHttpContext().Request.Headers;
return headerNames.Select(h => (string)headers[h]);
}
} }
public class DynamicTestHub : DynamicHub public class DynamicTestHub : DynamicHub
@ -111,4 +120,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
Task Send(string message); Task Send(string message);
Task NoClientHandler(); Task NoClientHandler();
} }
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
public class HubWithAuthorization : Hub
{
public string Echo(string message) => TestHubMethodsImpl.Echo(message);
}
} }

View File

@ -23,6 +23,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(MicrosoftAspNetCoreDiagnosticsPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(MicrosoftAspNetCoreHostingPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="$(MicrosoftAspNetCoreHttpPackageVersion)" />

View File

@ -1,26 +1,77 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{ {
public class Startup public class Startup
{ {
private readonly SymmetricSecurityKey SecurityKey = new SymmetricSecurityKey(Guid.NewGuid().ToByteArray());
private readonly JwtSecurityTokenHandler JwtTokenHandler = new JwtSecurityTokenHandler();
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddSignalR(); services.AddSignalR();
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.NameIdentifier);
});
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
});
} }
public void Configure(IApplicationBuilder app) public void Configure(IApplicationBuilder app)
{ {
app.UseAuthentication();
app.UseSignalR(routes => app.UseSignalR(routes =>
{ {
routes.MapHub<TestHub>("default"); routes.MapHub<TestHub>("default");
routes.MapHub<DynamicTestHub>("dynamic"); routes.MapHub<DynamicTestHub>("dynamic");
routes.MapHub<TestHubT>("hubT"); routes.MapHub<TestHubT>("hubT");
routes.MapHub<HubWithAuthorization>("authorizedhub");
}); });
app.Run(async (context) =>
{
if (context.Request.Path.StartsWithSegments("/generateJwtToken"))
{
await context.Response.WriteAsync(GenerateJwtToken());
return;
}
});
}
private string GenerateJwtToken()
{
var claims = new[] { new Claim(ClaimTypes.NameIdentifier, "testuser") };
var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("SignalRTestServer", "SignalRTests", claims, expires: DateTime.Now.AddSeconds(5), signingCredentials: credentials);
return JwtTokenHandler.WriteToken(token);
} }
} }
} }

View File

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using System.Threading.Channels; using System.Threading.Channels;
using Microsoft.AspNetCore.Client.Tests; using Microsoft.AspNetCore.Client.Tests;
using Microsoft.AspNetCore.SignalR.Tests.Common; using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets.Client.Http;
using Microsoft.AspNetCore.Sockets.Features; using Microsoft.AspNetCore.Sockets.Features;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Moq; using Moq;
@ -58,7 +59,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK); : ResponseUtils.CreateResponse(HttpStatusCode.OK);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
try try
{ {
await connection.StartAsync(); await connection.StartAsync();
@ -87,7 +89,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK); : ResponseUtils.CreateResponse(HttpStatusCode.OK);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
await connection.StartAsync(); await connection.StartAsync();
await connection.DisposeAsync(); await connection.DisposeAsync();
@ -139,7 +142,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
var transport = new Mock<ITransport>(); var transport = new Mock<ITransport>();
transport.Setup(t => t.StopAsync()).Returns(async () => { await releaseDisposeTcs.Task; }); transport.Setup(t => t.StopAsync()).Returns(async () => { await releaseDisposeTcs.Task; });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(transport.Object), loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(transport.Object), loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
var startTask = connection.StartAsync(); var startTask = connection.StartAsync();
await allowDisposeTcs.Task; await allowDisposeTcs.Task;
@ -178,7 +182,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK); : ResponseUtils.CreateResponse(HttpStatusCode.OK);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
await connection.StartAsync(); await connection.StartAsync();
await connection.DisposeAsync(); await connection.DisposeAsync();
@ -202,7 +207,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK); : ResponseUtils.CreateResponse(HttpStatusCode.OK);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
await connection.StartAsync().OrTimeout(); await connection.StartAsync().OrTimeout();
@ -228,7 +234,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK); : ResponseUtils.CreateResponse(HttpStatusCode.OK);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
try try
{ {
@ -274,7 +281,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
}); });
mockTransport.SetupGet(t => t.Mode).Returns(TransferMode.Text); mockTransport.SetupGet(t => t.Mode).Returns(TransferMode.Text);
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
var onReceivedInvoked = false; var onReceivedInvoked = false;
connection.OnReceived( _ => connection.OnReceived( _ =>
@ -321,7 +329,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
var callbackInvokedTcs = new TaskCompletionSource<object>(); var callbackInvokedTcs = new TaskCompletionSource<object>();
var closedTcs = new TaskCompletionSource<object>(); var closedTcs = new TaskCompletionSource<object>();
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
connection.OnReceived(_ => connection.OnReceived(_ =>
{ {
@ -376,7 +385,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
var blockReceiveCallbackTcs = new TaskCompletionSource<object>(); var blockReceiveCallbackTcs = new TaskCompletionSource<object>();
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
connection.OnReceived(_ => blockReceiveCallbackTcs.Task); connection.OnReceived(_ => blockReceiveCallbackTcs.Task);
await connection.StartAsync(); await connection.StartAsync();
@ -420,7 +430,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
var callbackInvokedTcs = new TaskCompletionSource<object>(); var callbackInvokedTcs = new TaskCompletionSource<object>();
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
connection.OnReceived( _ => connection.OnReceived( _ =>
{ {
throw new OperationCanceledException(); throw new OperationCanceledException();
@ -461,8 +472,9 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
using (var httpClient = new HttpClient(mockHttpHandler.Object)) using (var httpClient = new HttpClient(mockHttpHandler.Object))
{ {
var longPollingTransport = new LongPollingTransport(httpClient, new LoggerFactory()); var longPollingTransport = new LongPollingTransport(httpClient, null, new LoggerFactory());
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(longPollingTransport), loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(longPollingTransport), loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
try try
{ {
@ -504,7 +516,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
return ResponseUtils.CreateResponse(HttpStatusCode.OK); return ResponseUtils.CreateResponse(HttpStatusCode.OK);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
try try
{ {
await connection.StartAsync(); await connection.StartAsync();
@ -550,7 +563,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK, content); : ResponseUtils.CreateResponse(HttpStatusCode.OK, content);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
await connection.StartAsync(); await connection.StartAsync();
await connection.DisposeAsync(); await connection.DisposeAsync();
@ -578,7 +592,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK); : ResponseUtils.CreateResponse(HttpStatusCode.OK);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
await connection.StartAsync(); await connection.StartAsync();
var exception = await Assert.ThrowsAsync<HttpRequestException>( var exception = await Assert.ThrowsAsync<HttpRequestException>(
@ -609,7 +624,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK, content); : ResponseUtils.CreateResponse(HttpStatusCode.OK, content);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
try try
{ {
var receiveTcs = new TaskCompletionSource<string>(); var receiveTcs = new TaskCompletionSource<string>();
@ -664,7 +680,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK, content); : ResponseUtils.CreateResponse(HttpStatusCode.OK, content);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
try try
{ {
var receiveTcs = new TaskCompletionSource<string>(); var receiveTcs = new TaskCompletionSource<string>();
@ -727,7 +744,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK, content); : ResponseUtils.CreateResponse(HttpStatusCode.OK, content);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
try try
{ {
var receiveTcs = new TaskCompletionSource<string>(); var receiveTcs = new TaskCompletionSource<string>();
@ -785,7 +803,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
: ResponseUtils.CreateResponse(HttpStatusCode.OK); : ResponseUtils.CreateResponse(HttpStatusCode.OK);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
try try
{ {
@ -818,7 +837,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
return ResponseUtils.CreateResponse(HttpStatusCode.OK, negotiatePayload); return ResponseUtils.CreateResponse(HttpStatusCode.OK, negotiatePayload);
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
var exception = await Assert.ThrowsAsync<FormatException>( var exception = await Assert.ThrowsAsync<FormatException>(
() => connection.StartAsync()); () => connection.StartAsync());
@ -838,7 +858,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
ResponseUtils.CreateNegotiationResponse(connectionId: null)); ResponseUtils.CreateNegotiationResponse(connectionId: null));
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
var exception = await Assert.ThrowsAsync<FormatException>( var exception = await Assert.ThrowsAsync<FormatException>(
() => connection.StartAsync()); () => connection.StartAsync());
@ -858,7 +879,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
ResponseUtils.CreateNegotiationResponse(transportTypes: null)); ResponseUtils.CreateNegotiationResponse(transportTypes: null));
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
var exception = await Assert.ThrowsAsync<FormatException>( var exception = await Assert.ThrowsAsync<FormatException>(
() => connection.StartAsync()); () => connection.StartAsync());
@ -880,7 +902,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
ResponseUtils.CreateNegotiationResponse(transportTypes: serverTransports)); ResponseUtils.CreateNegotiationResponse(transportTypes: serverTransports));
}); });
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
var exception = await Assert.ThrowsAsync<InvalidOperationException>( var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => connection.StartAsync()); () => connection.StartAsync());
@ -918,7 +941,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
mockTransport.SetupGet(t => t.Mode).Returns(TransferMode.Binary); mockTransport.SetupGet(t => t.Mode).Returns(TransferMode.Binary);
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object), var connection = new HttpConnection(new Uri("http://fakeuri.org/"), new TestTransportFactory(mockTransport.Object),
loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); loggerFactory: null, httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
await connection.StartAsync().OrTimeout(); await connection.StartAsync().OrTimeout();
var transferModeFeature = connection.Features.Get<ITransferModeFeature>(); var transferModeFeature = connection.Features.Get<ITransferModeFeature>();
@ -950,7 +973,8 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
ResponseUtils.CreateNegotiationResponse()); ResponseUtils.CreateNegotiationResponse());
}); });
var connection = new HttpConnection(new Uri(requested), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object); var connection = new HttpConnection(new Uri(requested), TransportType.LongPolling, loggerFactory: null,
httpOptions: new HttpOptions { HttpMessageHandler = mockHttpHandler.Object });
await connection.StartAsync().OrTimeout(); await connection.StartAsync().OrTimeout();
await connection.DisposeAsync().OrTimeout(); await connection.DisposeAsync().OrTimeout();
} }

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public void DefaultTransportFactoryCannotBeCreatedWithInvalidTransportType(TransportType transportType) public void DefaultTransportFactoryCannotBeCreatedWithInvalidTransportType(TransportType transportType)
{ {
Assert.Throws<ArgumentOutOfRangeException>( Assert.Throws<ArgumentOutOfRangeException>(
() => new DefaultTransportFactory(transportType, new LoggerFactory(), new HttpClient())); () => new DefaultTransportFactory(transportType, new LoggerFactory(), new HttpClient(), httpOptions: null));
} }
[Theory] [Theory]
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public void DefaultTransportFactoryCannotBeCreatedWithoutHttpClient(TransportType transportType) public void DefaultTransportFactoryCannotBeCreatedWithoutHttpClient(TransportType transportType)
{ {
var exception = Assert.Throws<ArgumentNullException>( var exception = Assert.Throws<ArgumentNullException>(
() => new DefaultTransportFactory(transportType, new LoggerFactory(), httpClient: null)); () => new DefaultTransportFactory(transportType, new LoggerFactory(), httpClient: null, httpOptions: null));
Assert.Equal("httpClient", exception.ParamName); Assert.Equal("httpClient", exception.ParamName);
} }
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
[Fact] [Fact]
public void DefaultTransportFactoryCanBeCreatedWithoutHttpClientIfWebSocketsTransportRequestedExplicitly() public void DefaultTransportFactoryCanBeCreatedWithoutHttpClientIfWebSocketsTransportRequestedExplicitly()
{ {
new DefaultTransportFactory(TransportType.WebSockets, new LoggerFactory(), httpClient: null); new DefaultTransportFactory(TransportType.WebSockets, new LoggerFactory(), httpClient: null, httpOptions: null);
} }
[ConditionalTheory] [ConditionalTheory]
@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
[OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2, SkipReason = "No WebSockets Client for this platform")] [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2, SkipReason = "No WebSockets Client for this platform")]
public void DefaultTransportFactoryCreatesRequestedTransportIfAvailable(TransportType requestedTransport, Type expectedTransportType) public void DefaultTransportFactoryCreatesRequestedTransportIfAvailable(TransportType requestedTransport, Type expectedTransportType)
{ {
var transportFactory = new DefaultTransportFactory(requestedTransport, loggerFactory: null, httpClient: new HttpClient()); var transportFactory = new DefaultTransportFactory(requestedTransport, loggerFactory: null, httpClient: new HttpClient(), httpOptions: null);
Assert.IsType(expectedTransportType, Assert.IsType(expectedTransportType,
transportFactory.CreateTransport(TransportType.All)); transportFactory.CreateTransport(TransportType.All));
} }
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public void DefaultTransportFactoryThrowsIfItCannotCreateRequestedTransport(TransportType requestedTransport) public void DefaultTransportFactoryThrowsIfItCannotCreateRequestedTransport(TransportType requestedTransport)
{ {
var transportFactory = var transportFactory =
new DefaultTransportFactory(requestedTransport, loggerFactory: null, httpClient: new HttpClient()); new DefaultTransportFactory(requestedTransport, loggerFactory: null, httpClient: new HttpClient(), httpOptions: null);
var ex = Assert.Throws<InvalidOperationException>( var ex = Assert.Throws<InvalidOperationException>(
() => transportFactory.CreateTransport(~requestedTransport)); () => transportFactory.CreateTransport(~requestedTransport));
@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public void DefaultTransportFactoryCreatesWebSocketsTransportIfAvailable() public void DefaultTransportFactoryCreatesWebSocketsTransportIfAvailable()
{ {
Assert.IsType<WebSocketsTransport>( Assert.IsType<WebSocketsTransport>(
new DefaultTransportFactory(TransportType.All, loggerFactory: null, httpClient: new HttpClient()) new DefaultTransportFactory(TransportType.All, loggerFactory: null, httpClient: new HttpClient(), httpOptions: null)
.CreateTransport(TransportType.All)); .CreateTransport(TransportType.All));
} }
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
{ {
if (!TestHelpers.IsWebSocketsSupported()) if (!TestHelpers.IsWebSocketsSupported())
{ {
var transportFactory = new DefaultTransportFactory(requestedTransport, loggerFactory: null, httpClient: new HttpClient()); var transportFactory = new DefaultTransportFactory(requestedTransport, loggerFactory: null, httpClient: new HttpClient(), httpOptions: null);
Assert.IsType(expectedTransportType, Assert.IsType(expectedTransportType,
transportFactory.CreateTransport(TransportType.All)); transportFactory.CreateTransport(TransportType.All));
} }
@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
if (!TestHelpers.IsWebSocketsSupported()) if (!TestHelpers.IsWebSocketsSupported())
{ {
var transportFactory = var transportFactory =
new DefaultTransportFactory(requestedTransport, loggerFactory: null, httpClient: new HttpClient()); new DefaultTransportFactory(requestedTransport, loggerFactory: null, httpClient: new HttpClient(), httpOptions: null);
var ex = Assert.Throws<InvalidOperationException>( var ex = Assert.Throws<InvalidOperationException>(
() => transportFactory.CreateTransport(TransportType.All)); () => transportFactory.CreateTransport(TransportType.All));

View File

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Tests.Common; using Microsoft.AspNetCore.SignalR.Tests.Common;
using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets;
using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Client;
using Microsoft.AspNetCore.Sockets.Client.Http;
using Microsoft.AspNetCore.Sockets.Features; using Microsoft.AspNetCore.Sockets.Features;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -114,7 +115,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
.Returns<HttpRequestMessage, CancellationToken>( .Returns<HttpRequestMessage, CancellationToken>(
(request, cancellationToken) => Task.FromException<HttpResponseMessage>(new InvalidOperationException("HTTP requests should not be sent."))); (request, cancellationToken) => Task.FromException<HttpResponseMessage>(new InvalidOperationException("HTTP requests should not be sent.")));
var connection = new HttpConnection(new Uri(url), TransportType.WebSockets, loggerFactory, mockHttpHandler.Object); var connection = new HttpConnection(new Uri(url), TransportType.WebSockets, loggerFactory, new HttpOptions { HttpMessageHandler = mockHttpHandler.Object});
try try
{ {

View File

@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var transportToConnection = Channel.CreateUnbounded<byte[]>(); var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection); var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
var webSocketsTransport = new WebSocketsTransport(loggerFactory); var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection, await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection,
TransferMode.Binary, connectionId: string.Empty).OrTimeout(); TransferMode.Binary, connectionId: string.Empty).OrTimeout();
await webSocketsTransport.StopAsync().OrTimeout(); await webSocketsTransport.StopAsync().OrTimeout();
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var transportToConnection = Channel.CreateUnbounded<byte[]>(); var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection); var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
var webSocketsTransport = new WebSocketsTransport(loggerFactory); var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection, await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection,
TransferMode.Binary, connectionId: string.Empty); TransferMode.Binary, connectionId: string.Empty);
connectionToTransport.Writer.TryComplete(); connectionToTransport.Writer.TryComplete();
@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var transportToConnection = Channel.CreateUnbounded<byte[]>(); var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection); var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
var webSocketsTransport = new WebSocketsTransport(loggerFactory); var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection, transferMode, connectionId: string.Empty); await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection, transferMode, connectionId: string.Empty);
var sendTcs = new TaskCompletionSource<object>(); var sendTcs = new TaskCompletionSource<object>();
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var transportToConnection = Channel.CreateUnbounded<byte[]>(); var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection); var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
var webSocketsTransport = new WebSocketsTransport(loggerFactory); var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
Assert.Null(webSocketsTransport.Mode); Assert.Null(webSocketsTransport.Mode);
await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection, await webSocketsTransport.StartAsync(new Uri(_serverFixture.WebSocketsUrl + "/echo"), channelConnection,
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var transportToConnection = Channel.CreateUnbounded<byte[]>(); var transportToConnection = Channel.CreateUnbounded<byte[]>();
var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection); var channelConnection = new ChannelConnection<SendMessage, byte[]>(connectionToTransport, transportToConnection);
var webSocketsTransport = new WebSocketsTransport(loggerFactory); var webSocketsTransport = new WebSocketsTransport(httpOptions: null, loggerFactory: loggerFactory);
var exception = await Assert.ThrowsAsync<ArgumentException>(() => var exception = await Assert.ThrowsAsync<ArgumentException>(() =>
webSocketsTransport.StartAsync(new Uri("http://fakeuri.org"), channelConnection, TransferMode.Text | TransferMode.Binary, connectionId: string.Empty)); webSocketsTransport.StartAsync(new Uri("http://fakeuri.org"), channelConnection, TransferMode.Text | TransferMode.Binary, connectionId: string.Empty));