Merge branch 'release/2.1' into dev
This commit is contained in:
commit
8d8a546a3f
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@aspnet/signalr-protocol-msgpack",
|
"name": "@aspnet/signalr-protocol-msgpack",
|
||||||
"version": "1.0.0-preview3-t000",
|
"version": "1.0.0-rc1-t000",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@aspnet/signalr",
|
"name": "@aspnet/signalr",
|
||||||
"version": "1.0.0-preview3-t000",
|
"version": "1.0.0-rc1-t000",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -288,13 +288,73 @@ describe("HttpConnection", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets inherentKeepAlive feature when using LongPolling", async (done) => {
|
it("authorization header removed when token factory returns null and using LongPolling", async (done) => {
|
||||||
const availableTransport = { transport: "LongPolling", transferFormats: ["Text"] };
|
const availableTransport = { transport: "LongPolling", transferFormats: ["Text"] };
|
||||||
|
|
||||||
|
var httpClientGetCount = 0;
|
||||||
|
var accessTokenFactoryCount = 0;
|
||||||
const options: IHttpConnectionOptions = {
|
const options: IHttpConnectionOptions = {
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
httpClient: new TestHttpClient()
|
httpClient: new TestHttpClient()
|
||||||
.on("POST", (r) => ({ connectionId: "42", availableTransports: [availableTransport] })),
|
.on("POST", (r) => ({ connectionId: "42", availableTransports: [availableTransport] }))
|
||||||
|
.on("GET", (r) => {
|
||||||
|
httpClientGetCount++;
|
||||||
|
const authorizationValue = r.headers["Authorization"];
|
||||||
|
if (httpClientGetCount == 1) {
|
||||||
|
if (authorizationValue) {
|
||||||
|
fail("First long poll request should have a authorization header.");
|
||||||
|
}
|
||||||
|
// First long polling request must succeed so start completes
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
// Check second long polling request has its header removed
|
||||||
|
if (authorizationValue) {
|
||||||
|
fail("Second long poll request should have no authorization header.");
|
||||||
|
}
|
||||||
|
throw new Error("fail");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
accessTokenFactory: () => {
|
||||||
|
accessTokenFactoryCount++;
|
||||||
|
if (accessTokenFactoryCount == 1) {
|
||||||
|
return "A token value";
|
||||||
|
} else {
|
||||||
|
// Return a null value after the first call to test the header being removed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} as IHttpConnectionOptions;
|
||||||
|
|
||||||
|
const connection = new HttpConnection("http://tempuri.org", options);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connection.start(TransferFormat.Text);
|
||||||
|
expect(httpClientGetCount).toBeGreaterThanOrEqual(2);
|
||||||
|
expect(accessTokenFactoryCount).toBeGreaterThanOrEqual(2);
|
||||||
|
done();
|
||||||
|
} catch (e) {
|
||||||
|
fail(e);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets inherentKeepAlive feature when using LongPolling", async (done) => {
|
||||||
|
const availableTransport = { transport: "LongPolling", transferFormats: ["Text"] };
|
||||||
|
|
||||||
|
var httpClientGetCount = 0;
|
||||||
|
const options: IHttpConnectionOptions = {
|
||||||
|
...commonOptions,
|
||||||
|
httpClient: new TestHttpClient()
|
||||||
|
.on("POST", (r) => ({ connectionId: "42", availableTransports: [availableTransport] }))
|
||||||
|
.on("GET", (r) => {
|
||||||
|
httpClientGetCount++;
|
||||||
|
if (httpClientGetCount == 1) {
|
||||||
|
// First long polling request must succeed so start completes
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
throw new Error("fail");
|
||||||
|
}
|
||||||
|
}),
|
||||||
} as IHttpConnectionOptions;
|
} as IHttpConnectionOptions;
|
||||||
|
|
||||||
const connection = new HttpConnection("http://tempuri.org", options);
|
const connection = new HttpConnection("http://tempuri.org", options);
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export class LongPollingTransport implements ITransport {
|
||||||
this.logMessageContent = logMessageContent;
|
this.logMessageContent = logMessageContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public connect(url: string, transferFormat: TransferFormat): Promise<void> {
|
public async connect(url: string, transferFormat: TransferFormat): Promise<void> {
|
||||||
Arg.isRequired(url, "url");
|
Arg.isRequired(url, "url");
|
||||||
Arg.isRequired(transferFormat, "transferFormat");
|
Arg.isRequired(transferFormat, "transferFormat");
|
||||||
Arg.isIn(transferFormat, TransferFormat, "transferFormat");
|
Arg.isIn(transferFormat, TransferFormat, "transferFormat");
|
||||||
|
|
@ -45,13 +45,6 @@ export class LongPollingTransport implements ITransport {
|
||||||
throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");
|
throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.poll(this.url, transferFormat);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async poll(url: string, transferFormat: TransferFormat): Promise<void> {
|
|
||||||
this.running = true;
|
|
||||||
|
|
||||||
const pollOptions: HttpRequest = {
|
const pollOptions: HttpRequest = {
|
||||||
abortSignal: this.pollAbort.signal,
|
abortSignal: this.pollAbort.signal,
|
||||||
headers: {},
|
headers: {},
|
||||||
|
|
@ -62,15 +55,49 @@ export class LongPollingTransport implements ITransport {
|
||||||
pollOptions.responseType = "arraybuffer";
|
pollOptions.responseType = "arraybuffer";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const token = await this.accessTokenFactory();
|
||||||
|
this.updateHeaderToken(pollOptions, token);
|
||||||
|
|
||||||
let closeError: Error;
|
let closeError: Error;
|
||||||
|
|
||||||
|
// Make initial long polling request
|
||||||
|
// Server uses first long polling request to finish initializing connection and it returns without data
|
||||||
|
const pollUrl = `${url}&_=${Date.now()}`;
|
||||||
|
this.logger.log(LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}`);
|
||||||
|
const response = await this.httpClient.get(pollUrl, pollOptions);
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
this.logger.log(LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}`);
|
||||||
|
|
||||||
|
// Mark running as false so that the poll immediately ends and runs the close logic
|
||||||
|
closeError = new HttpError(response.statusText, response.statusCode);
|
||||||
|
this.running = false;
|
||||||
|
} else {
|
||||||
|
this.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.poll(this.url, pollOptions, closeError);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateHeaderToken(request: HttpRequest, token: string) {
|
||||||
|
if (token) {
|
||||||
|
// tslint:disable-next-line:no-string-literal
|
||||||
|
request.headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:no-string-literal
|
||||||
|
if (request.headers["Authorization"]) {
|
||||||
|
// tslint:disable-next-line:no-string-literal
|
||||||
|
delete request.headers["Authorization"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async poll(url: string, pollOptions: HttpRequest, closeError: Error): Promise<void> {
|
||||||
try {
|
try {
|
||||||
while (this.running) {
|
while (this.running) {
|
||||||
// We have to get the access token on each poll, in case it changes
|
// We have to get the access token on each poll, in case it changes
|
||||||
const token = await this.accessTokenFactory();
|
const token = await this.accessTokenFactory();
|
||||||
if (token) {
|
this.updateHeaderToken(pollOptions, token);
|
||||||
// tslint:disable-next-line:no-string-literal
|
|
||||||
pollOptions.headers["Authorization"] = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pollUrl = `${url}&_=${Date.now()}`;
|
const pollUrl = `${url}&_=${Date.now()}`;
|
||||||
|
|
@ -142,14 +169,11 @@ export class LongPollingTransport implements ITransport {
|
||||||
this.running = false;
|
this.running = false;
|
||||||
this.logger.log(LogLevel.Trace, `(LongPolling transport) sending DELETE request to ${this.url}.`);
|
this.logger.log(LogLevel.Trace, `(LongPolling transport) sending DELETE request to ${this.url}.`);
|
||||||
|
|
||||||
const deleteOptions: HttpRequest = {};
|
const deleteOptions: HttpRequest = {
|
||||||
|
headers: {},
|
||||||
|
};
|
||||||
const token = await this.accessTokenFactory();
|
const token = await this.accessTokenFactory();
|
||||||
if (token) {
|
this.updateHeaderToken(deleteOptions, token);
|
||||||
// tslint:disable-next-line:no-string-literal
|
|
||||||
deleteOptions.headers = {
|
|
||||||
["Authorization"]: `Bearer ${token}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const response = await this.httpClient.delete(this.url, deleteOptions);
|
const response = await this.httpClient.delete(this.url, deleteOptions);
|
||||||
|
|
||||||
this.logger.log(LogLevel.Trace, "(LongPolling transport) DELETE request accepted.");
|
this.logger.log(LogLevel.Trace, "(LongPolling transport) DELETE request accepted.");
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<LongPollingTransport>();
|
_logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<LongPollingTransport>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(Uri url, TransferFormat transferFormat)
|
public async Task StartAsync(Uri url, TransferFormat transferFormat)
|
||||||
{
|
{
|
||||||
if (transferFormat != TransferFormat.Binary && transferFormat != TransferFormat.Text)
|
if (transferFormat != TransferFormat.Binary && transferFormat != TransferFormat.Text)
|
||||||
{
|
{
|
||||||
|
|
@ -49,6 +49,14 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
|
|
||||||
Log.StartTransport(_logger, transferFormat);
|
Log.StartTransport(_logger, transferFormat);
|
||||||
|
|
||||||
|
// Make initial long polling request
|
||||||
|
// Server uses first long polling request to finish initializing connection and it returns without data
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
using (var response = await _httpClient.SendAsync(request))
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
// Create the pipe pair (Application's writer is connected to Transport's reader, and vice versa)
|
// Create the pipe pair (Application's writer is connected to Transport's reader, and vice versa)
|
||||||
var options = ClientPipeOptions.DefaultOptions;
|
var options = ClientPipeOptions.DefaultOptions;
|
||||||
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
var pair = DuplexPipe.CreateConnectionPair(options, options);
|
||||||
|
|
@ -57,8 +65,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
_application = pair.Application;
|
_application = pair.Application;
|
||||||
|
|
||||||
Running = ProcessAsync(url);
|
Running = ProcessAsync(url);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessAsync(Uri url)
|
private async Task ProcessAsync(Uri url)
|
||||||
|
|
@ -105,6 +111,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
{
|
{
|
||||||
Log.TransportStopping(_logger);
|
Log.TransportStopping(_logger);
|
||||||
|
|
||||||
|
if (_application == null)
|
||||||
|
{
|
||||||
|
// We never started
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_application.Input.CancelPendingRead();
|
_application.Input.CancelPendingRead();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
{
|
{
|
||||||
Log.TransportStopping(_logger);
|
Log.TransportStopping(_logger);
|
||||||
|
|
||||||
|
if (_application == null)
|
||||||
|
{
|
||||||
|
// We never started
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_transport.Output.Complete();
|
_transport.Output.Complete();
|
||||||
_transport.Input.Complete();
|
_transport.Input.Complete();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
|
||||||
{
|
{
|
||||||
Log.TransportStopping(_logger);
|
Log.TransportStopping(_logger);
|
||||||
|
|
||||||
|
if (_application == null)
|
||||||
|
{
|
||||||
|
// We never started
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_transport.Output.Complete();
|
_transport.Output.Complete();
|
||||||
_transport.Input.Complete();
|
_transport.Input.Complete();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
using (connection.Cancellation)
|
using (connection.Cancellation)
|
||||||
{
|
{
|
||||||
// Cancel the previous request
|
// Cancel the previous request
|
||||||
connection.Cancellation.Cancel();
|
connection.Cancellation?.Cancel();
|
||||||
|
|
||||||
// Wait for the previous request to drain
|
// Wait for the previous request to drain
|
||||||
await connection.TransportTask;
|
await connection.TransportTask;
|
||||||
|
|
@ -228,29 +228,38 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
Log.EstablishedConnection(_logger);
|
Log.EstablishedConnection(_logger);
|
||||||
|
|
||||||
connection.ApplicationTask = ExecuteApplication(connectionDelegate, connection);
|
connection.ApplicationTask = ExecuteApplication(connectionDelegate, connection);
|
||||||
|
|
||||||
|
context.Response.ContentType = "application/octet-stream";
|
||||||
|
|
||||||
|
// This request has no content
|
||||||
|
context.Response.ContentLength = 0;
|
||||||
|
|
||||||
|
// On the first poll, we flush the response immediately to mark the poll as "initialized" so future
|
||||||
|
// requests can be made safely
|
||||||
|
connection.TransportTask = context.Response.Body.FlushAsync();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.ResumingConnection(_logger);
|
Log.ResumingConnection(_logger);
|
||||||
|
|
||||||
|
// REVIEW: Performance of this isn't great as this does a bunch of per request allocations
|
||||||
|
connection.Cancellation = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var timeoutSource = new CancellationTokenSource();
|
||||||
|
var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(connection.Cancellation.Token, context.RequestAborted, timeoutSource.Token);
|
||||||
|
|
||||||
|
// Dispose these tokens when the request is over
|
||||||
|
context.Response.RegisterForDispose(timeoutSource);
|
||||||
|
context.Response.RegisterForDispose(tokenSource);
|
||||||
|
|
||||||
|
var longPolling = new LongPollingTransport(timeoutSource.Token, connection.Application.Input, _loggerFactory);
|
||||||
|
|
||||||
|
// Start the transport
|
||||||
|
connection.TransportTask = longPolling.ProcessRequestAsync(context, tokenSource.Token);
|
||||||
|
|
||||||
|
// Start the timeout after we return from creating the transport task
|
||||||
|
timeoutSource.CancelAfter(options.LongPolling.PollTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// REVIEW: Performance of this isn't great as this does a bunch of per request allocations
|
|
||||||
connection.Cancellation = new CancellationTokenSource();
|
|
||||||
|
|
||||||
var timeoutSource = new CancellationTokenSource();
|
|
||||||
var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(connection.Cancellation.Token, context.RequestAborted, timeoutSource.Token);
|
|
||||||
|
|
||||||
// Dispose these tokens when the request is over
|
|
||||||
context.Response.RegisterForDispose(timeoutSource);
|
|
||||||
context.Response.RegisterForDispose(tokenSource);
|
|
||||||
|
|
||||||
var longPolling = new LongPollingTransport(timeoutSource.Token, connection.Application.Input, _loggerFactory);
|
|
||||||
|
|
||||||
// Start the transport
|
|
||||||
connection.TransportTask = longPolling.ProcessRequestAsync(context, tokenSource.Token);
|
|
||||||
|
|
||||||
// Start the timeout after we return from creating the transport task
|
|
||||||
timeoutSource.CancelAfter(options.LongPolling.PollTimeout);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -302,7 +311,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal
|
||||||
connection.Status = HttpConnectionStatus.Inactive;
|
connection.Status = HttpConnectionStatus.Inactive;
|
||||||
|
|
||||||
// Dispose the cancellation token
|
// Dispose the cancellation token
|
||||||
connection.Cancellation.Dispose();
|
connection.Cancellation?.Dispose();
|
||||||
|
|
||||||
connection.Cancellation = null;
|
connection.Cancellation = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -636,6 +636,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
|
|
||||||
// Start a poll
|
// Start a poll
|
||||||
var task = dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
|
var task = dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
|
||||||
|
Assert.True(task.IsCompleted);
|
||||||
|
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||||
|
|
||||||
|
task = dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
|
||||||
|
|
||||||
// Send to the application
|
// Send to the application
|
||||||
var buffer = Encoding.UTF8.GetBytes("Hello World");
|
var buffer = Encoding.UTF8.GetBytes("Hello World");
|
||||||
|
|
@ -745,7 +749,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(HttpTransportType.LongPolling, 204)]
|
[InlineData(HttpTransportType.LongPolling, 200)]
|
||||||
[InlineData(HttpTransportType.WebSockets, 404)]
|
[InlineData(HttpTransportType.WebSockets, 404)]
|
||||||
[InlineData(HttpTransportType.ServerSentEvents, 404)]
|
[InlineData(HttpTransportType.ServerSentEvents, 404)]
|
||||||
public async Task EndPointThatOnlySupportsLongPollingRejectsOtherTransports(HttpTransportType transportType, int status)
|
public async Task EndPointThatOnlySupportsLongPollingRejectsOtherTransports(HttpTransportType transportType, int status)
|
||||||
|
|
@ -869,6 +873,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
var builder = new ConnectionBuilder(services.BuildServiceProvider());
|
var builder = new ConnectionBuilder(services.BuildServiceProvider());
|
||||||
builder.UseConnectionHandler<ImmediatelyCompleteConnectionHandler>();
|
builder.UseConnectionHandler<ImmediatelyCompleteConnectionHandler>();
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
// First poll will 200
|
||||||
|
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
|
||||||
|
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||||
|
|
||||||
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
|
await dispatcher.ExecuteAsync(context, new HttpConnectionDispatcherOptions(), app);
|
||||||
|
|
||||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||||
|
|
@ -998,6 +1006,9 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
var options = new HttpConnectionDispatcherOptions();
|
var options = new HttpConnectionDispatcherOptions();
|
||||||
var request1 = dispatcher.ExecuteAsync(context1, options, app);
|
var request1 = dispatcher.ExecuteAsync(context1, options, app);
|
||||||
|
Assert.True(request1.IsCompleted);
|
||||||
|
|
||||||
|
request1 = dispatcher.ExecuteAsync(context1, options, app);
|
||||||
var request2 = dispatcher.ExecuteAsync(context2, options, app);
|
var request2 = dispatcher.ExecuteAsync(context2, options, app);
|
||||||
|
|
||||||
await request1;
|
await request1;
|
||||||
|
|
@ -1132,7 +1143,14 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
builder.UseConnectionHandler<BlockingConnectionHandler>();
|
builder.UseConnectionHandler<BlockingConnectionHandler>();
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
var options = new HttpConnectionDispatcherOptions();
|
var options = new HttpConnectionDispatcherOptions();
|
||||||
|
|
||||||
|
// Initial poll
|
||||||
var task = dispatcher.ExecuteAsync(context, options, app);
|
var task = dispatcher.ExecuteAsync(context, options, app);
|
||||||
|
Assert.True(task.IsCompleted);
|
||||||
|
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||||
|
|
||||||
|
// Real long running poll
|
||||||
|
task = dispatcher.ExecuteAsync(context, options, app);
|
||||||
|
|
||||||
var buffer = Encoding.UTF8.GetBytes("Hello World");
|
var buffer = Encoding.UTF8.GetBytes("Hello World");
|
||||||
|
|
||||||
|
|
@ -1166,7 +1184,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
var options = new HttpConnectionDispatcherOptions();
|
var options = new HttpConnectionDispatcherOptions();
|
||||||
|
|
||||||
var context1 = MakeRequest("/foo", connection);
|
var context1 = MakeRequest("/foo", connection);
|
||||||
|
// This is the initial poll to make sure things are setup
|
||||||
var task1 = dispatcher.ExecuteAsync(context1, options, app);
|
var task1 = dispatcher.ExecuteAsync(context1, options, app);
|
||||||
|
Assert.True(task1.IsCompleted);
|
||||||
|
task1 = dispatcher.ExecuteAsync(context1, options, app);
|
||||||
var context2 = MakeRequest("/foo", connection);
|
var context2 = MakeRequest("/foo", connection);
|
||||||
var task2 = dispatcher.ExecuteAsync(context2, options, app);
|
var task2 = dispatcher.ExecuteAsync(context2, options, app);
|
||||||
|
|
||||||
|
|
@ -1363,8 +1384,11 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "name") }));
|
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "name") }));
|
||||||
|
|
||||||
var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
||||||
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello, World")).AsTask().OrTimeout();
|
await connectionHandlerTask.OrTimeout();
|
||||||
|
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||||
|
|
||||||
|
connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
||||||
|
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello, World")).AsTask().OrTimeout();
|
||||||
await connectionHandlerTask.OrTimeout();
|
await connectionHandlerTask.OrTimeout();
|
||||||
|
|
||||||
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||||
|
|
@ -1444,7 +1468,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
new Claim(ClaimTypes.StreetAddress, "12345 123rd St. NW")
|
new Claim(ClaimTypes.StreetAddress, "12345 123rd St. NW")
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// First poll
|
||||||
var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
||||||
|
Assert.True(connectionHandlerTask.IsCompleted);
|
||||||
|
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||||
|
|
||||||
|
connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
||||||
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello, World")).AsTask().OrTimeout();
|
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello, World")).AsTask().OrTimeout();
|
||||||
|
|
||||||
await connectionHandlerTask.OrTimeout();
|
await connectionHandlerTask.OrTimeout();
|
||||||
|
|
@ -1502,7 +1531,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
// "authorize" user
|
// "authorize" user
|
||||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "name") }));
|
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "name") }));
|
||||||
|
|
||||||
|
// Initial poll
|
||||||
var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
var connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
||||||
|
Assert.True(connectionHandlerTask.IsCompleted);
|
||||||
|
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||||
|
|
||||||
|
connectionHandlerTask = dispatcher.ExecuteAsync(context, options, app);
|
||||||
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello, World")).AsTask().OrTimeout();
|
await connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes("Hello, World")).AsTask().OrTimeout();
|
||||||
|
|
||||||
await connectionHandlerTask.OrTimeout();
|
await connectionHandlerTask.OrTimeout();
|
||||||
|
|
@ -1660,6 +1694,10 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests
|
||||||
var options = new HttpConnectionDispatcherOptions();
|
var options = new HttpConnectionDispatcherOptions();
|
||||||
|
|
||||||
var pollTask = dispatcher.ExecuteAsync(context, options, app);
|
var pollTask = dispatcher.ExecuteAsync(context, options, app);
|
||||||
|
Assert.True(pollTask.IsCompleted);
|
||||||
|
|
||||||
|
// Now send the second poll
|
||||||
|
pollTask = dispatcher.ExecuteAsync(context, options, app);
|
||||||
|
|
||||||
// Issue the delete request and make sure the poll completes
|
// Issue the delete request and make sure the poll completes
|
||||||
var deleteContext = new DefaultHttpContext();
|
var deleteContext = new DefaultHttpContext();
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,20 @@ using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Connections;
|
using Microsoft.AspNetCore.Http.Connections;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Client;
|
using Microsoft.AspNetCore.Http.Connections.Client;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
|
using Microsoft.AspNetCore.Http.Connections.Client.Internal;
|
||||||
|
using Microsoft.AspNetCore.SignalR.Tests;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
public partial class HttpConnectionTests
|
public partial class HttpConnectionTests
|
||||||
{
|
{
|
||||||
public class Transport
|
public class Transport : VerifiableLoggedTest
|
||||||
{
|
{
|
||||||
|
public Transport(ITestOutputHelper output) : base(output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(HttpTransportType.LongPolling)]
|
[InlineData(HttpTransportType.LongPolling)]
|
||||||
[InlineData(HttpTransportType.ServerSentEvents)]
|
[InlineData(HttpTransportType.ServerSentEvents)]
|
||||||
|
|
@ -30,11 +36,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var requestsExecuted = false;
|
var requestsExecuted = false;
|
||||||
var callCount = 0;
|
var callCount = 0;
|
||||||
|
|
||||||
testHttpHandler.OnRequest((request, next, token) =>
|
|
||||||
{
|
|
||||||
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent));
|
|
||||||
});
|
|
||||||
|
|
||||||
testHttpHandler.OnNegotiate((_, cancellationToken) =>
|
testHttpHandler.OnNegotiate((_, cancellationToken) =>
|
||||||
{
|
{
|
||||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent());
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent());
|
||||||
|
|
@ -52,6 +53,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
return await next();
|
return await next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testHttpHandler.OnRequest((request, next, token) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent));
|
||||||
|
});
|
||||||
|
|
||||||
Task<string> AccessTokenProvider()
|
Task<string> AccessTokenProvider()
|
||||||
{
|
{
|
||||||
callCount++;
|
callCount++;
|
||||||
|
|
@ -75,28 +81,25 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
[InlineData(HttpTransportType.ServerSentEvents, false)]
|
[InlineData(HttpTransportType.ServerSentEvents, false)]
|
||||||
public async Task HttpConnectionSetsInherentKeepAliveFeature(HttpTransportType transportType, bool expectedValue)
|
public async Task HttpConnectionSetsInherentKeepAliveFeature(HttpTransportType transportType, bool expectedValue)
|
||||||
{
|
{
|
||||||
var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false);
|
using (StartVerifableLog(out var loggerFactory, testName: $"HttpConnectionSetsInherentKeepAliveFeature_{transportType}_{expectedValue}"))
|
||||||
|
|
||||||
testHttpHandler.OnRequest((request, next, token) =>
|
|
||||||
{
|
{
|
||||||
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent));
|
var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false);
|
||||||
});
|
|
||||||
|
|
||||||
testHttpHandler.OnNegotiate((_, cancellationToken) =>
|
testHttpHandler.OnNegotiate((_, cancellationToken) => ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent()));
|
||||||
{
|
|
||||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent());
|
|
||||||
});
|
|
||||||
|
|
||||||
await WithConnectionAsync(
|
testHttpHandler.OnRequest((request, next, token) => Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent)));
|
||||||
CreateConnection(testHttpHandler, transportType: transportType),
|
|
||||||
async (connection) =>
|
|
||||||
{
|
|
||||||
await connection.StartAsync(TransferFormat.Text).OrTimeout();
|
|
||||||
|
|
||||||
var feature = connection.Features.Get<IConnectionInherentKeepAliveFeature>();
|
await WithConnectionAsync(
|
||||||
Assert.NotNull(feature);
|
CreateConnection(testHttpHandler, transportType: transportType, loggerFactory: loggerFactory),
|
||||||
Assert.Equal(expectedValue, feature.HasInherentKeepAlive);
|
async (connection) =>
|
||||||
});
|
{
|
||||||
|
await connection.StartAsync(TransferFormat.Text).OrTimeout();
|
||||||
|
|
||||||
|
var feature = connection.Features.Get<IConnectionInherentKeepAliveFeature>();
|
||||||
|
Assert.NotNull(feature);
|
||||||
|
Assert.Equal(expectedValue, feature.HasInherentKeepAlive);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -107,10 +110,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false);
|
var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false);
|
||||||
var requestsExecuted = false;
|
var requestsExecuted = false;
|
||||||
|
|
||||||
testHttpHandler.OnRequest((request, next, token) =>
|
|
||||||
{
|
|
||||||
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent));
|
|
||||||
});
|
|
||||||
|
|
||||||
testHttpHandler.OnNegotiate((_, cancellationToken) =>
|
testHttpHandler.OnNegotiate((_, cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
|
@ -135,6 +134,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
return await next();
|
return await next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testHttpHandler.OnRequest((request, next, token) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent));
|
||||||
|
});
|
||||||
|
|
||||||
await WithConnectionAsync(
|
await WithConnectionAsync(
|
||||||
CreateConnection(testHttpHandler, transportType: transportType),
|
CreateConnection(testHttpHandler, transportType: transportType),
|
||||||
async (connection) =>
|
async (connection) =>
|
||||||
|
|
@ -154,11 +158,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false);
|
var testHttpHandler = new TestHttpMessageHandler(autoNegotiate: false);
|
||||||
var requestsExecuted = false;
|
var requestsExecuted = false;
|
||||||
|
|
||||||
testHttpHandler.OnRequest((request, next, token) =>
|
|
||||||
{
|
|
||||||
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent));
|
|
||||||
});
|
|
||||||
|
|
||||||
testHttpHandler.OnNegotiate((_, cancellationToken) =>
|
testHttpHandler.OnNegotiate((_, cancellationToken) =>
|
||||||
{
|
{
|
||||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent());
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent());
|
||||||
|
|
@ -175,6 +174,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
return await next();
|
return await next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testHttpHandler.OnRequest((request, next, token) =>
|
||||||
|
{
|
||||||
|
return Task.FromResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent));
|
||||||
|
});
|
||||||
|
|
||||||
await WithConnectionAsync(
|
await WithConnectionAsync(
|
||||||
CreateConnection(testHttpHandler, transportType: transportType),
|
CreateConnection(testHttpHandler, transportType: transportType),
|
||||||
async (connection) =>
|
async (connection) =>
|
||||||
|
|
|
||||||
|
|
@ -109,15 +109,20 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
if (requests == 0)
|
if (requests == 0)
|
||||||
{
|
{
|
||||||
requests++;
|
requests++;
|
||||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK, "Hello");
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
}
|
}
|
||||||
else if (requests == 1)
|
else if (requests == 1)
|
||||||
|
{
|
||||||
|
requests++;
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK, "Hello");
|
||||||
|
}
|
||||||
|
else if (requests == 2)
|
||||||
{
|
{
|
||||||
requests++;
|
requests++;
|
||||||
// Time out
|
// Time out
|
||||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
}
|
}
|
||||||
else if (requests == 2)
|
else if (requests == 3)
|
||||||
{
|
{
|
||||||
requests++;
|
requests++;
|
||||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK, "World");
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK, "World");
|
||||||
|
|
@ -147,7 +152,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task LongPollingTransportStopsWhenPollRequestFails()
|
public async Task LongPollingTransportStartAsyncFailsIfFirstRequestFails()
|
||||||
{
|
{
|
||||||
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
mockHttpHandler.Protected()
|
mockHttpHandler.Protected()
|
||||||
|
|
@ -158,6 +163,39 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
return ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError);
|
return ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
|
{
|
||||||
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => longPollingTransport.StartAsync(TestUri, TransferFormat.Binary));
|
||||||
|
Assert.Contains(" 500 ", exception.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await longPollingTransport.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LongPollingTransportStopsWhenPollRequestFails()
|
||||||
|
{
|
||||||
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
|
var firstPoll = true;
|
||||||
|
mockHttpHandler.Protected()
|
||||||
|
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
|
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
if (firstPoll)
|
||||||
|
{
|
||||||
|
firstPoll = false;
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError);
|
||||||
|
});
|
||||||
|
|
||||||
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
using (var httpClient = new HttpClient(mockHttpHandler.Object))
|
||||||
{
|
{
|
||||||
var longPollingTransport = new LongPollingTransport(httpClient);
|
var longPollingTransport = new LongPollingTransport(httpClient);
|
||||||
|
|
@ -314,7 +352,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var message1Payload = new[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
|
var message1Payload = new[] { (byte)'H', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
|
||||||
|
|
||||||
var firstCall = true;
|
var requests = 0;
|
||||||
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
var sentRequests = new List<HttpRequestMessage>();
|
var sentRequests = new List<HttpRequestMessage>();
|
||||||
mockHttpHandler.Protected()
|
mockHttpHandler.Protected()
|
||||||
|
|
@ -325,9 +363,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
|
|
||||||
if (firstCall)
|
if (requests == 0)
|
||||||
{
|
{
|
||||||
firstCall = false;
|
requests++;
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
else if (requests == 1)
|
||||||
|
{
|
||||||
|
requests++;
|
||||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK, message1Payload);
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK, message1Payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,7 +392,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var message = await longPollingTransport.Input.ReadAllAsync();
|
var message = await longPollingTransport.Input.ReadAllAsync();
|
||||||
|
|
||||||
// Check the provided request
|
// Check the provided request
|
||||||
Assert.Equal(2, sentRequests.Count);
|
Assert.Equal(3, sentRequests.Count);
|
||||||
|
|
||||||
// Check the messages received
|
// Check the messages received
|
||||||
Assert.Equal(message1Payload, message);
|
Assert.Equal(message1Payload, message);
|
||||||
|
|
@ -366,6 +409,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
var sentRequests = new List<byte[]>();
|
var sentRequests = new List<byte[]>();
|
||||||
var tcs = new TaskCompletionSource<HttpResponseMessage>();
|
var tcs = new TaskCompletionSource<HttpResponseMessage>();
|
||||||
|
var firstPoll = true;
|
||||||
|
|
||||||
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
mockHttpHandler.Protected()
|
mockHttpHandler.Protected()
|
||||||
|
|
@ -380,6 +424,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
}
|
}
|
||||||
else if (request.Method == HttpMethod.Get)
|
else if (request.Method == HttpMethod.Get)
|
||||||
{
|
{
|
||||||
|
// First poll completes immediately
|
||||||
|
if (firstPoll)
|
||||||
|
{
|
||||||
|
firstPoll = false;
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
|
cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken));
|
||||||
// This is the poll task
|
// This is the poll task
|
||||||
return await tcs.Task;
|
return await tcs.Task;
|
||||||
|
|
@ -426,6 +477,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
var sentRequests = new List<byte[]>();
|
var sentRequests = new List<byte[]>();
|
||||||
var pollTcs = new TaskCompletionSource<HttpResponseMessage>();
|
var pollTcs = new TaskCompletionSource<HttpResponseMessage>();
|
||||||
var deleteTcs = new TaskCompletionSource<object>();
|
var deleteTcs = new TaskCompletionSource<object>();
|
||||||
|
var firstPoll = true;
|
||||||
|
|
||||||
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||||
mockHttpHandler.Protected()
|
mockHttpHandler.Protected()
|
||||||
|
|
@ -440,6 +492,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
}
|
}
|
||||||
else if (request.Method == HttpMethod.Get)
|
else if (request.Method == HttpMethod.Get)
|
||||||
{
|
{
|
||||||
|
// First poll completes immediately
|
||||||
|
if (firstPoll)
|
||||||
|
{
|
||||||
|
firstPoll = false;
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
cancellationToken.Register(() => pollTcs.TrySetCanceled(cancellationToken));
|
cancellationToken.Register(() => pollTcs.TrySetCanceled(cancellationToken));
|
||||||
// This is the poll task
|
// This is the poll task
|
||||||
return await pollTcs.Task;
|
return await pollTcs.Task;
|
||||||
|
|
@ -538,7 +597,13 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
|
|
||||||
if (Interlocked.Increment(ref numPolls) < 3)
|
if (numPolls == 0)
|
||||||
|
{
|
||||||
|
numPolls++;
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numPolls++ < 3)
|
||||||
{
|
{
|
||||||
throw new OperationCanceledException();
|
throw new OperationCanceledException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
|
@ -39,6 +40,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
public static bool IsLongPollRequest(HttpRequestMessage request)
|
public static bool IsLongPollRequest(HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
return request.Method == HttpMethod.Get &&
|
return request.Method == HttpMethod.Get &&
|
||||||
|
!IsServerSentEventsRequest(request) &&
|
||||||
(request.RequestUri.PathAndQuery.Contains("?id=") || request.RequestUri.PathAndQuery.Contains("&id="));
|
(request.RequestUri.PathAndQuery.Contains("?id=") || request.RequestUri.PathAndQuery.Contains("&id="));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,6 +50,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
(request.RequestUri.PathAndQuery.Contains("?id=") || request.RequestUri.PathAndQuery.Contains("&id="));
|
(request.RequestUri.PathAndQuery.Contains("?id=") || request.RequestUri.PathAndQuery.Contains("&id="));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsServerSentEventsRequest(HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
return request.Method == HttpMethod.Get && request.Headers.Accept.Any(h => h.MediaType == "text/event-stream");
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsSocketSendRequest(HttpRequestMessage request)
|
public static bool IsSocketSendRequest(HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
return request.Method == HttpMethod.Post &&
|
return request.Method == HttpMethod.Post &&
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,14 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
{
|
{
|
||||||
|
delegate Task<HttpResponseMessage> RequestDelegate(HttpRequestMessage requestMessage, CancellationToken cancellationToken);
|
||||||
|
|
||||||
public class TestHttpMessageHandler : HttpMessageHandler
|
public class TestHttpMessageHandler : HttpMessageHandler
|
||||||
{
|
{
|
||||||
private List<HttpRequestMessage> _receivedRequests = new List<HttpRequestMessage>();
|
private List<HttpRequestMessage> _receivedRequests = new List<HttpRequestMessage>();
|
||||||
private Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handler;
|
private RequestDelegate _app;
|
||||||
|
|
||||||
|
private List<Func<RequestDelegate, RequestDelegate>> _middleware = new List<Func<RequestDelegate, RequestDelegate>>();
|
||||||
|
|
||||||
public IReadOnlyList<HttpRequestMessage> ReceivedRequests
|
public IReadOnlyList<HttpRequestMessage> ReceivedRequests
|
||||||
{
|
{
|
||||||
|
|
@ -23,14 +27,29 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestHttpMessageHandler(bool autoNegotiate = true)
|
public TestHttpMessageHandler(bool autoNegotiate = true, bool handleFirstPoll = true)
|
||||||
{
|
{
|
||||||
_handler = BaseHandler;
|
|
||||||
|
|
||||||
if (autoNegotiate)
|
if (autoNegotiate)
|
||||||
{
|
{
|
||||||
OnNegotiate((_, cancellationToken) => ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent()));
|
OnNegotiate((_, cancellationToken) => ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationContent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (handleFirstPoll)
|
||||||
|
{
|
||||||
|
var firstPoll = true;
|
||||||
|
OnRequest(async (request, next, cancellationToken) =>
|
||||||
|
{
|
||||||
|
if (ResponseUtils.IsLongPollRequest(request) && firstPoll)
|
||||||
|
{
|
||||||
|
firstPoll = false;
|
||||||
|
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return await next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
|
|
@ -40,9 +59,21 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
lock (_receivedRequests)
|
lock (_receivedRequests)
|
||||||
{
|
{
|
||||||
_receivedRequests.Add(request);
|
_receivedRequests.Add(request);
|
||||||
|
|
||||||
|
if (_app == null)
|
||||||
|
{
|
||||||
|
_middleware.Reverse();
|
||||||
|
RequestDelegate handler = BaseHandler;
|
||||||
|
foreach (var middleware in _middleware)
|
||||||
|
{
|
||||||
|
handler = middleware(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
_app = handler;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await _handler(request, cancellationToken);
|
return await _app(request, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TestHttpMessageHandler CreateDefault()
|
public static TestHttpMessageHandler CreateDefault()
|
||||||
|
|
@ -80,8 +111,18 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||||
|
|
||||||
public void OnRequest(Func<HttpRequestMessage, Func<Task<HttpResponseMessage>>, CancellationToken, Task<HttpResponseMessage>> handler)
|
public void OnRequest(Func<HttpRequestMessage, Func<Task<HttpResponseMessage>>, CancellationToken, Task<HttpResponseMessage>> handler)
|
||||||
{
|
{
|
||||||
var nextHandler = _handler;
|
void OnRequestCore(Func<RequestDelegate, RequestDelegate> middleware)
|
||||||
_handler = (request, cancellationToken) => handler(request, () => nextHandler(request, cancellationToken), cancellationToken);
|
{
|
||||||
|
_middleware.Add(middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnRequestCore(next =>
|
||||||
|
{
|
||||||
|
return (request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
return handler(request, () => next(request, cancellationToken), cancellationToken);
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnGet(string pathAndQuery, Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handler) => OnRequest(HttpMethod.Get, pathAndQuery, handler);
|
public void OnGet(string pathAndQuery, Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handler) => OnRequest(HttpMethod.Get, pathAndQuery, handler);
|
||||||
|
|
|
||||||
|
|
@ -51,32 +51,44 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanStartAndStopConnectionUsingDefaultTransport()
|
public async Task CanStartAndStopConnectionUsingDefaultTransport()
|
||||||
{
|
{
|
||||||
var url = _serverFixture.Url + "/echo";
|
using (StartVerifableLog(out var loggerFactory))
|
||||||
// The test should connect to the server using WebSockets transport on Windows 8 and newer.
|
{
|
||||||
// On Windows 7/2008R2 it should use ServerSentEvents transport to connect to the server.
|
var url = _serverFixture.Url + "/echo";
|
||||||
var connection = new HttpConnection(new Uri(url));
|
// The test should connect to the server using WebSockets transport on Windows 8 and newer.
|
||||||
await connection.StartAsync(TransferFormat.Binary).OrTimeout();
|
// On Windows 7/2008R2 it should use ServerSentEvents transport to connect to the server.
|
||||||
await connection.DisposeAsync().OrTimeout();
|
var connection = new HttpConnection(new Uri(url), HttpTransports.All, loggerFactory);
|
||||||
|
await connection.StartAsync(TransferFormat.Binary).OrTimeout();
|
||||||
|
await connection.DisposeAsync().OrTimeout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TransportThatFallsbackCreatesNewConnection()
|
public async Task TransportThatFallsbackCreatesNewConnection()
|
||||||
{
|
{
|
||||||
var url = _serverFixture.Url + "/echo";
|
bool ExpectedErrors(WriteContext writeContext)
|
||||||
// The test should connect to the server using WebSockets transport on Windows 8 and newer.
|
{
|
||||||
// On Windows 7/2008R2 it should use ServerSentEvents transport to connect to the server.
|
return writeContext.LoggerName == typeof(HttpConnection).FullName &&
|
||||||
|
writeContext.EventId.Name == "ErrorStartingTransport";
|
||||||
|
}
|
||||||
|
|
||||||
// The test logic lives in the TestTransportFactory and FakeTransport.
|
using (StartVerifableLog(out var loggerFactory, expectedErrorsFilter: ExpectedErrors))
|
||||||
var connection = new HttpConnection(new HttpConnectionOptions { Url = new Uri(url) }, null, new TestTransportFactory());
|
{
|
||||||
await connection.StartAsync(TransferFormat.Text).OrTimeout();
|
var url = _serverFixture.Url + "/echo";
|
||||||
await connection.DisposeAsync().OrTimeout();
|
// The test should connect to the server using WebSockets transport on Windows 8 and newer.
|
||||||
|
// On Windows 7/2008R2 it should use ServerSentEvents transport to connect to the server.
|
||||||
|
|
||||||
|
// The test logic lives in the TestTransportFactory and FakeTransport.
|
||||||
|
var connection = new HttpConnection(new HttpConnectionOptions { Url = new Uri(url) }, loggerFactory, new TestTransportFactory());
|
||||||
|
await connection.StartAsync(TransferFormat.Text).OrTimeout();
|
||||||
|
await connection.DisposeAsync().OrTimeout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory(Skip = "https://github.com/aspnet/SignalR/issues/2031")]
|
[Theory]
|
||||||
[MemberData(nameof(TransportTypes))]
|
[MemberData(nameof(TransportTypes))]
|
||||||
public async Task CanStartAndStopConnectionUsingGivenTransport(HttpTransportType transportType)
|
public async Task CanStartAndStopConnectionUsingGivenTransport(HttpTransportType transportType)
|
||||||
{
|
{
|
||||||
using (StartVerifableLog(out var loggerFactory, testName: $"CanStartAndStopConnectionUsingGivenTransport_{transportType}"))
|
using (StartVerifableLog(out var loggerFactory, minLogLevel: LogLevel.Trace, testName: $"CanStartAndStopConnectionUsingGivenTransport_{transportType}"))
|
||||||
{
|
{
|
||||||
var url = _serverFixture.Url + "/echo";
|
var url = _serverFixture.Url + "/echo";
|
||||||
var connection = new HttpConnection(new Uri(url), transportType, loggerFactory);
|
var connection = new HttpConnection(new Uri(url), transportType, loggerFactory);
|
||||||
|
|
@ -532,7 +544,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
|
|
||||||
if (_tries < availableTransports)
|
if (_tries < availableTransports)
|
||||||
{
|
{
|
||||||
throw new Exception();
|
return Task.FromException(new Exception());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue