Text Protocol Formatter (#187)
This commit is contained in:
parent
9889ab1bd7
commit
a728e1da41
|
|
@ -14,6 +14,7 @@ env:
|
||||||
global:
|
global:
|
||||||
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||||
- DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
- DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||||
|
- SIGNALR_TESTS_VERBOSE: 1
|
||||||
mono:
|
mono:
|
||||||
- 4.0.5
|
- 4.0.5
|
||||||
python:
|
python:
|
||||||
|
|
|
||||||
10
SignalR.sln
10
SignalR.sln
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26127.0
|
VisualStudioVersion = 15.0.26208.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA69F624-5398-4884-87E4-B816698CDE65}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA69F624-5398-4884-87E4-B816698CDE65}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -58,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Signal
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Sockets.Common", "src\Microsoft.AspNetCore.Sockets.Common\Microsoft.AspNetCore.Sockets.Common.csproj", "{F3EFFD9F-DD85-48A2-9B11-83A133ECC099}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Sockets.Common", "src\Microsoft.AspNetCore.Sockets.Common\Microsoft.AspNetCore.Sockets.Common.csproj", "{F3EFFD9F-DD85-48A2-9B11-83A133ECC099}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Sockets.Common.Tests", "test\Microsoft.AspNetCore.Sockets.Common.Tests\Microsoft.AspNetCore.Sockets.Common.Tests.csproj", "{B0D32729-48AA-4841-B52A-2A61B60EED61}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -152,6 +153,10 @@ Global
|
||||||
{F3EFFD9F-DD85-48A2-9B11-83A133ECC099}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{F3EFFD9F-DD85-48A2-9B11-83A133ECC099}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{F3EFFD9F-DD85-48A2-9B11-83A133ECC099}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{F3EFFD9F-DD85-48A2-9B11-83A133ECC099}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F3EFFD9F-DD85-48A2-9B11-83A133ECC099}.Release|Any CPU.Build.0 = Release|Any CPU
|
{F3EFFD9F-DD85-48A2-9B11-83A133ECC099}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B0D32729-48AA-4841-B52A-2A61B60EED61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B0D32729-48AA-4841-B52A-2A61B60EED61}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B0D32729-48AA-4841-B52A-2A61B60EED61}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B0D32729-48AA-4841-B52A-2A61B60EED61}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -179,5 +184,6 @@ Global
|
||||||
{354335AB-CEE9-4434-A641-78058F6EFE56} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
{354335AB-CEE9-4434-A641-78058F6EFE56} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
||||||
{455B68D2-C5B6-4BF4-A685-964B07AFAAF8} = {6A35B453-52EC-48AF-89CA-D4A69800F131}
|
{455B68D2-C5B6-4BF4-A685-964B07AFAAF8} = {6A35B453-52EC-48AF-89CA-D4A69800F131}
|
||||||
{F3EFFD9F-DD85-48A2-9B11-83A133ECC099} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
{F3EFFD9F-DD85-48A2-9B11-83A133ECC099} = {DA69F624-5398-4884-87E4-B816698CDE65}
|
||||||
|
{B0D32729-48AA-4841-B52A-2A61B60EED61} = {6A35B453-52EC-48AF-89CA-D4A69800F131}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ namespace ClientSample
|
||||||
var line = Console.ReadLine();
|
var line = Console.ReadLine();
|
||||||
logger.LogInformation("Sending: {0}", line);
|
logger.LogInformation("Sending: {0}", line);
|
||||||
|
|
||||||
await connection.SendAsync(Encoding.UTF8.GetBytes("Hello World"), Format.Text);
|
await connection.SendAsync(Encoding.UTF8.GetBytes("Hello World"), MessageType.Text);
|
||||||
}
|
}
|
||||||
logger.LogInformation("Send loop terminated");
|
logger.LogInformation("Send loop terminated");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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;
|
||||||
|
|
@ -39,7 +39,7 @@ namespace SocialWeather
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
await formatter.WriteAsync(data, ms);
|
await formatter.WriteAsync(data, ms);
|
||||||
var buffer = ReadableBuffer.Create(ms.ToArray()).Preserve();
|
var buffer = ReadableBuffer.Create(ms.ToArray()).Preserve();
|
||||||
await connection.Transport.Output.WriteAsync(new Message(buffer, Format.Binary, endOfMessage: true));
|
await connection.Transport.Output.WriteAsync(new Message(buffer, MessageType.Binary, endOfMessage: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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;
|
||||||
|
|
@ -26,7 +26,7 @@ namespace SocialWeather.Pipe
|
||||||
Weather weather = (Weather)(-1);
|
Weather weather = (Weather)(-1);
|
||||||
string zipCode = tokens.Length > 3 ? tokens[3] : string.Empty;
|
string zipCode = tokens.Length > 3 ? tokens[3] : string.Empty;
|
||||||
|
|
||||||
if(tokens.Length == 0 || !int.TryParse(tokens[0], out temperature))
|
if (tokens.Length == 0 || !int.TryParse(tokens[0], out temperature))
|
||||||
{
|
{
|
||||||
temperature = int.MinValue;
|
temperature = int.MinValue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||||
// source: WeatherReport.proto
|
// source: WeatherReport.proto
|
||||||
#pragma warning disable 1591, 0612, 3021
|
#pragma warning disable 1591, 0612, 3021
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -29,7 +29,7 @@ namespace SocketsSample.EndPoints
|
||||||
using (message)
|
using (message)
|
||||||
{
|
{
|
||||||
// We can avoid the copy here but we'll deal with that later
|
// We can avoid the copy here but we'll deal with that later
|
||||||
await Broadcast(message.Payload.Buffer, message.MessageFormat, message.EndOfMessage);
|
await Broadcast(message.Payload.Buffer, message.Type, message.EndOfMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,10 +44,10 @@ namespace SocketsSample.EndPoints
|
||||||
|
|
||||||
private Task Broadcast(string text)
|
private Task Broadcast(string text)
|
||||||
{
|
{
|
||||||
return Broadcast(ReadableBuffer.Create(Encoding.UTF8.GetBytes(text)), Format.Text, endOfMessage: true);
|
return Broadcast(ReadableBuffer.Create(Encoding.UTF8.GetBytes(text)), MessageType.Text, endOfMessage: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Broadcast(ReadableBuffer payload, Format format, bool endOfMessage)
|
private Task Broadcast(ReadableBuffer payload, MessageType format, bool endOfMessage)
|
||||||
{
|
{
|
||||||
var tasks = new List<Task>(Connections.Count);
|
var tasks = new List<Task>(Connections.Count);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
||||||
_logger.LogInformation("Sending Invocation #{0}", descriptor.Id);
|
_logger.LogInformation("Sending Invocation #{0}", descriptor.Id);
|
||||||
|
|
||||||
// TODO: Format.Text - who, where and when decides about the format of outgoing messages
|
// TODO: Format.Text - who, where and when decides about the format of outgoing messages
|
||||||
await _connection.SendAsync(ms.ToArray(), Format.Text, cancellationToken);
|
await _connection.SendAsync(ms.ToArray(), MessageType.Text, cancellationToken);
|
||||||
|
|
||||||
_logger.LogInformation("Sending Invocation #{0} complete", descriptor.Id);
|
_logger.LogInformation("Sending Invocation #{0} complete", descriptor.Id);
|
||||||
|
|
||||||
|
|
@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
||||||
ReceiveData receiveData = new ReceiveData();
|
ReceiveData receiveData = new ReceiveData();
|
||||||
while (await _connection.ReceiveAsync(receiveData, cancellationToken))
|
while (await _connection.ReceiveAsync(receiveData, cancellationToken))
|
||||||
{
|
{
|
||||||
var message
|
var message
|
||||||
= await _adapter.ReadMessageAsync(new MemoryStream(receiveData.Data), _binder, cancellationToken);
|
= await _adapter.ReadMessageAsync(new MemoryStream(receiveData.Data), _binder, cancellationToken);
|
||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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;
|
||||||
|
|
@ -278,7 +278,7 @@ namespace Microsoft.AspNetCore.SignalR.Redis
|
||||||
private async Task WriteAsync(Connection connection, byte[] data)
|
private async Task WriteAsync(Connection connection, byte[] data)
|
||||||
{
|
{
|
||||||
var buffer = ReadableBuffer.Create(data).Preserve();
|
var buffer = ReadableBuffer.Create(data).Preserve();
|
||||||
var message = new Message(buffer, Format.Text, endOfMessage: true);
|
var message = new Message(buffer, MessageType.Text, endOfMessage: true);
|
||||||
|
|
||||||
while (await connection.Transport.Output.WaitToWriteAsync())
|
while (await connection.Transport.Output.WaitToWriteAsync())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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;
|
||||||
|
|
@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
await invocationAdapter.WriteMessageAsync(invocation, stream);
|
await invocationAdapter.WriteMessageAsync(invocation, stream);
|
||||||
|
|
||||||
var buffer = ReadableBuffer.Create(stream.ToArray()).Preserve();
|
var buffer = ReadableBuffer.Create(stream.ToArray()).Preserve();
|
||||||
var message = new Message(buffer, Format.Text, endOfMessage: true);
|
var message = new Message(buffer, MessageType.Text, endOfMessage: true);
|
||||||
|
|
||||||
while (await connection.Transport.Output.WaitToWriteAsync())
|
while (await connection.Transport.Output.WaitToWriteAsync())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// 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;
|
||||||
|
|
@ -233,7 +233,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
await invocationAdapter.WriteMessageAsync(result, outStream);
|
await invocationAdapter.WriteMessageAsync(result, outStream);
|
||||||
|
|
||||||
var buffer = ReadableBuffer.Create(outStream.ToArray()).Preserve();
|
var buffer = ReadableBuffer.Create(outStream.ToArray()).Preserve();
|
||||||
var outMessage = new Message(buffer, Format.Text, endOfMessage: true);
|
var outMessage = new Message(buffer, MessageType.Text, endOfMessage: true);
|
||||||
|
|
||||||
while (await connection.Transport.Output.WaitToWriteAsync())
|
while (await connection.Transport.Output.WaitToWriteAsync())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
{
|
{
|
||||||
using (message)
|
using (message)
|
||||||
{
|
{
|
||||||
receiveData.Format = message.MessageFormat;
|
receiveData.MessageType = message.Type;
|
||||||
receiveData.Data = message.Payload.Buffer.ToArray();
|
receiveData.Data = message.Payload.Buffer.ToArray();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -80,14 +80,14 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> SendAsync(byte[] data, Format format)
|
public Task<bool> SendAsync(byte[] data, MessageType type)
|
||||||
{
|
{
|
||||||
return SendAsync(data, format, CancellationToken.None);
|
return SendAsync(data, type, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SendAsync(byte[] data, Format format, CancellationToken cancellationToken)
|
public async Task<bool> SendAsync(byte[] data, MessageType type, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var message = new Message(ReadableBuffer.Create(data).Preserve(), format);
|
var message = new Message(ReadableBuffer.Create(data).Preserve(), type);
|
||||||
|
|
||||||
while (await Output.WaitToWriteAsync(cancellationToken))
|
while (await Output.WaitToWriteAsync(cancellationToken))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
await response.Content.CopyToAsync(ms);
|
await response.Content.CopyToAsync(ms);
|
||||||
var message = new Message(ReadableBuffer.Create(ms.ToArray()).Preserve(), Format.Text);
|
var message = new Message(ReadableBuffer.Create(ms.ToArray()).Preserve(), MessageType.Text);
|
||||||
|
|
||||||
while (await _application.Output.WaitToWriteAsync(cancellationToken))
|
while (await _application.Output.WaitToWriteAsync(cancellationToken))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
||||||
{
|
{
|
||||||
public byte[] Data { get; set; }
|
public byte[] Data { get; set; }
|
||||||
|
|
||||||
public Format Format { get; set; }
|
public MessageType MessageType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,28 @@
|
||||||
// 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;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Formatting;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Sockets
|
namespace Microsoft.AspNetCore.Sockets
|
||||||
{
|
{
|
||||||
public struct Message : IDisposable
|
public struct Message : IDisposable
|
||||||
{
|
{
|
||||||
public bool EndOfMessage { get; }
|
public bool EndOfMessage { get; }
|
||||||
public Format MessageFormat { get; }
|
public MessageType Type { get; }
|
||||||
public PreservedBuffer Payload { get; }
|
public PreservedBuffer Payload { get; }
|
||||||
|
|
||||||
public Message(PreservedBuffer payload, Format messageFormat)
|
public Message(PreservedBuffer payload, MessageType type)
|
||||||
: this(payload, messageFormat, endOfMessage: true)
|
: this(payload, type, endOfMessage: true)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message(PreservedBuffer payload, Format messageFormat, bool endOfMessage)
|
public Message(PreservedBuffer payload, MessageType type, bool endOfMessage)
|
||||||
{
|
{
|
||||||
MessageFormat = messageFormat;
|
Type = type;
|
||||||
EndOfMessage = endOfMessage;
|
EndOfMessage = endOfMessage;
|
||||||
Payload = payload;
|
Payload = payload;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Sockets
|
||||||
|
{
|
||||||
|
public enum MessageFormat
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
Binary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Sockets
|
||||||
|
{
|
||||||
|
public static class MessageFormatter
|
||||||
|
{
|
||||||
|
public static bool TryFormatMessage(Message message, Span<byte> buffer, MessageFormat format, out int bytesWritten)
|
||||||
|
{
|
||||||
|
if (!message.EndOfMessage)
|
||||||
|
{
|
||||||
|
// This is a truely exceptional condition since we EXPECT callers to have already
|
||||||
|
// buffered incomplete messages and synthesized the correct, complete message before
|
||||||
|
// giving it to us. Hence we throw, instead of returning false.
|
||||||
|
throw new InvalidOperationException("Cannot format message where endOfMessage is false using this format");
|
||||||
|
}
|
||||||
|
return format == MessageFormat.Text ?
|
||||||
|
TextMessageFormatter.TryFormatMessage(message, buffer, out bytesWritten) :
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParseMessage(ReadOnlySpan<byte> buffer, MessageFormat format, out Message message, out int bytesConsumed)
|
||||||
|
{
|
||||||
|
return format == MessageFormat.Text ?
|
||||||
|
TextMessageFormatter.TryParseMessage(buffer, out message, out bytesConsumed) :
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
// 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.
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Sockets
|
namespace Microsoft.AspNetCore.Sockets
|
||||||
{
|
{
|
||||||
public enum Format
|
public enum MessageType
|
||||||
{
|
{
|
||||||
Text,
|
Text,
|
||||||
Binary
|
Binary,
|
||||||
|
Close,
|
||||||
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="0.1.0-*" />
|
<PackageReference Include="System.IO.Pipelines" Version="0.1.0-*" />
|
||||||
|
<PackageReference Include="System.Text.Primitives" Version="0.1.0-*" />
|
||||||
<PackageReference Include="System.Threading.Tasks.Channels" Version="0.1.0-*" />
|
<PackageReference Include="System.Threading.Tasks.Channels" Version="0.1.0-*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
// 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.IO.Pipelines;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Sockets
|
||||||
|
{
|
||||||
|
internal static class TextMessageFormatter
|
||||||
|
{
|
||||||
|
private const byte FieldDelimiter = (byte)':';
|
||||||
|
private const byte MessageDelimiter = (byte)';';
|
||||||
|
private const byte TextTypeFlag = (byte)'T';
|
||||||
|
private const byte BinaryTypeFlag = (byte)'B';
|
||||||
|
private const byte CloseTypeFlag = (byte)'C';
|
||||||
|
private const byte ErrorTypeFlag = (byte)'E';
|
||||||
|
|
||||||
|
public static bool TryFormatMessage(Message message, Span<byte> buffer, out int bytesWritten)
|
||||||
|
{
|
||||||
|
// Calculate the length, it's the number of characters for text messages, but number of base64 characters for binary
|
||||||
|
var length = message.Payload.Buffer.Length;
|
||||||
|
if (message.Type == MessageType.Binary)
|
||||||
|
{
|
||||||
|
length = (int)(4 * Math.Ceiling(((double)message.Payload.Buffer.Length / 3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the length as a string
|
||||||
|
int written = 0;
|
||||||
|
if (!length.TryFormat(buffer, out int lengthLen, default(TextFormat), EncodingData.InvariantUtf8))
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
written += lengthLen;
|
||||||
|
buffer = buffer.Slice(lengthLen);
|
||||||
|
|
||||||
|
// We need at least 4 more characters of space (':', type flag, ':', and eventually the terminating ';')
|
||||||
|
// We'll still need to double-check that we have space for the terminator after we write the payload,
|
||||||
|
// but this way we can exit early if the buffer is way too small.
|
||||||
|
if (buffer.Length < 4)
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buffer[0] = FieldDelimiter;
|
||||||
|
if (!TryFormatType(message.Type, buffer.Slice(1, 1)))
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buffer[2] = FieldDelimiter;
|
||||||
|
buffer = buffer.Slice(3);
|
||||||
|
written += 3;
|
||||||
|
|
||||||
|
// Payload
|
||||||
|
if (message.Type == MessageType.Binary)
|
||||||
|
{
|
||||||
|
// Encode the payload. For now, we make it an array and use the old-fashioned types because we need to mirror packages
|
||||||
|
// I've filed https://github.com/aspnet/SignalR/issues/192 to update this. -anurse
|
||||||
|
var payload = Convert.ToBase64String(message.Payload.Buffer.ToArray());
|
||||||
|
if (!TextEncoder.Utf8.TryEncodeString(payload, buffer, out int payloadWritten))
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
written += payloadWritten;
|
||||||
|
buffer = buffer.Slice(payloadWritten);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (buffer.Length < message.Payload.Buffer.Length)
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
message.Payload.Buffer.CopyTo(buffer.Slice(0, message.Payload.Buffer.Length));
|
||||||
|
written += message.Payload.Buffer.Length;
|
||||||
|
buffer = buffer.Slice(message.Payload.Buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminator
|
||||||
|
if (buffer.Length < 1)
|
||||||
|
{
|
||||||
|
bytesWritten = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buffer[0] = MessageDelimiter;
|
||||||
|
bytesWritten = written + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool TryParseMessage(ReadOnlySpan<byte> buffer, out Message message, out int bytesConsumed)
|
||||||
|
{
|
||||||
|
// Read until the first ':' to find the length
|
||||||
|
var consumedSoFar = 0;
|
||||||
|
var colonIndex = buffer.IndexOf(FieldDelimiter);
|
||||||
|
if (colonIndex < 0)
|
||||||
|
{
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
consumedSoFar += colonIndex;
|
||||||
|
var lengthSpan = buffer.Slice(0, colonIndex);
|
||||||
|
buffer = buffer.Slice(colonIndex);
|
||||||
|
|
||||||
|
// Parse the length
|
||||||
|
if (!PrimitiveParser.TryParseInt32(lengthSpan, out var length, out var consumedByLength, EncodingData.InvariantUtf8) || consumedByLength < lengthSpan.Length)
|
||||||
|
{
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's enough space in the buffer to even bother continuing
|
||||||
|
// There are at least 4 characters we still expect to see: ':', type flag, ':', ';'.
|
||||||
|
if (buffer.Length < 4)
|
||||||
|
{
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we have the ':' after the type flag.
|
||||||
|
if (buffer[0] != FieldDelimiter)
|
||||||
|
{
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We already know that index 0 is the ':', so next is the type flag at index '1'.
|
||||||
|
if (!TryParseType(buffer[1], out var messageType))
|
||||||
|
{
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we have the ':' after the type flag.
|
||||||
|
if (buffer[2] != FieldDelimiter)
|
||||||
|
{
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice off ':[Type]:' and check the remaining length
|
||||||
|
buffer = buffer.Slice(3);
|
||||||
|
consumedSoFar += 3;
|
||||||
|
|
||||||
|
// We expect to see <length>+1 more characters. Since <length> is the exact number of bytes in the text (even if base64-encoded)
|
||||||
|
// and we expect to see the ';'
|
||||||
|
if (buffer.Length < length + 1)
|
||||||
|
{
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the payload buffer
|
||||||
|
var payloadBuffer = buffer.Slice(0, length);
|
||||||
|
buffer = buffer.Slice(length);
|
||||||
|
consumedSoFar += length;
|
||||||
|
|
||||||
|
// Parse the payload. For now, we make it an array and use the old-fashioned types.
|
||||||
|
// I've filed https://github.com/aspnet/SignalR/issues/192 to update this. -anurse
|
||||||
|
var payloadArray = payloadBuffer.ToArray();
|
||||||
|
PreservedBuffer payload;
|
||||||
|
if (messageType == MessageType.Binary)
|
||||||
|
{
|
||||||
|
byte[] decoded;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var str = Encoding.UTF8.GetString(payloadArray);
|
||||||
|
decoded = Convert.FromBase64String(str);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Decoding failure
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
payload = ReadableBuffer.Create(decoded).Preserve();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
payload = ReadableBuffer.Create(payloadArray).Preserve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the trailer
|
||||||
|
if (buffer.Length < 1 || buffer[0] != MessageDelimiter)
|
||||||
|
{
|
||||||
|
message = default(Message);
|
||||||
|
bytesConsumed = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = new Message(payload, messageType);
|
||||||
|
bytesConsumed = consumedSoFar + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseType(byte type, out MessageType messageType)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case TextTypeFlag:
|
||||||
|
messageType = MessageType.Text;
|
||||||
|
return true;
|
||||||
|
case BinaryTypeFlag:
|
||||||
|
messageType = MessageType.Binary;
|
||||||
|
return true;
|
||||||
|
case CloseTypeFlag:
|
||||||
|
messageType = MessageType.Close;
|
||||||
|
return true;
|
||||||
|
case ErrorTypeFlag:
|
||||||
|
messageType = MessageType.Error;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
messageType = default(MessageType);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryFormatType(MessageType type, Span<byte> buffer)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MessageType.Text:
|
||||||
|
buffer[0] = TextTypeFlag;
|
||||||
|
return true;
|
||||||
|
case MessageType.Binary:
|
||||||
|
buffer[0] = BinaryTypeFlag;
|
||||||
|
return true;
|
||||||
|
case MessageType.Close:
|
||||||
|
buffer[0] = CloseTypeFlag;
|
||||||
|
return true;
|
||||||
|
case MessageType.Error:
|
||||||
|
buffer[0] = ErrorTypeFlag;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -221,8 +221,8 @@ namespace Microsoft.AspNetCore.Sockets
|
||||||
{
|
{
|
||||||
var format =
|
var format =
|
||||||
string.Equals(context.Request.Query["format"], "binary", StringComparison.OrdinalIgnoreCase)
|
string.Equals(context.Request.Query["format"], "binary", StringComparison.OrdinalIgnoreCase)
|
||||||
? Format.Binary
|
? MessageType.Binary
|
||||||
: Format.Text;
|
: MessageType.Text;
|
||||||
|
|
||||||
var state = _manager.CreateConnection();
|
var state = _manager.CreateConnection();
|
||||||
state.Connection.User = context.User;
|
state.Connection.User = context.User;
|
||||||
|
|
@ -327,8 +327,8 @@ namespace Microsoft.AspNetCore.Sockets
|
||||||
|
|
||||||
var format =
|
var format =
|
||||||
string.Equals(context.Request.Query["format"], "binary", StringComparison.OrdinalIgnoreCase)
|
string.Equals(context.Request.Query["format"], "binary", StringComparison.OrdinalIgnoreCase)
|
||||||
? Format.Binary
|
? MessageType.Binary
|
||||||
: Format.Text;
|
: MessageType.Text;
|
||||||
|
|
||||||
var message = new Message(
|
var message = new Message(
|
||||||
ReadableBuffer.Create(buffer).Preserve(),
|
ReadableBuffer.Create(buffer).Preserve(),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
using System;
|
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ namespace Microsoft.AspNetCore.Sockets.Transports
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a Message for the frame
|
// Create a Message for the frame
|
||||||
var message = new Message(frame.Payload.Preserve(), effectiveOpcode == WebSocketOpcode.Binary ? Format.Binary : Format.Text, frame.EndOfMessage);
|
var message = new Message(frame.Payload.Preserve(), effectiveOpcode == WebSocketOpcode.Binary ? MessageType.Binary : MessageType.Text, frame.EndOfMessage);
|
||||||
|
|
||||||
// Write the message to the channel
|
// Write the message to the channel
|
||||||
return _application.Output.WriteAsync(message);
|
return _application.Output.WriteAsync(message);
|
||||||
|
|
@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Sockets.Transports
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var opcode = message.MessageFormat == Format.Binary ?
|
var opcode = message.Type == MessageType.Binary ?
|
||||||
WebSocketOpcode.Binary :
|
WebSocketOpcode.Binary :
|
||||||
WebSocketOpcode.Text;
|
WebSocketOpcode.Text;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
using System;
|
// 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.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.SignalR.Tests.Common
|
namespace Microsoft.AspNetCore.SignalR.Tests.Common
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
var transport = new LongPollingTransport(httpClient, loggerFactory);
|
var transport = new LongPollingTransport(httpClient, loggerFactory);
|
||||||
using (var connection = await ClientConnection.ConnectAsync(new Uri(baseUrl + "/echo"), transport, httpClient, loggerFactory))
|
using (var connection = await ClientConnection.ConnectAsync(new Uri(baseUrl + "/echo"), transport, httpClient, loggerFactory))
|
||||||
{
|
{
|
||||||
await connection.SendAsync(Encoding.UTF8.GetBytes(message), Format.Text);
|
await connection.SendAsync(Encoding.UTF8.GetBytes(message), MessageType.Text);
|
||||||
|
|
||||||
var receiveData = new ReceiveData();
|
var receiveData = new ReceiveData();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
_loggerFactory.AddConsole();
|
_loggerFactory.AddConsole();
|
||||||
}
|
}
|
||||||
if(Debugger.IsAttached)
|
if (Debugger.IsAttached)
|
||||||
{
|
{
|
||||||
_loggerFactory.AddDebug();
|
_loggerFactory.AddDebug();
|
||||||
}
|
}
|
||||||
|
|
@ -67,10 +67,10 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
var t = Task.Run(() => host.Start());
|
var t = Task.Run(() => host.Start());
|
||||||
Console.WriteLine("Starting test server...");
|
Console.WriteLine("Starting test server...");
|
||||||
lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
|
||||||
if(!lifetime.ApplicationStarted.WaitHandle.WaitOne(TimeSpan.FromSeconds(1)))
|
if (!lifetime.ApplicationStarted.WaitHandle.WaitOne(TimeSpan.FromSeconds(1)))
|
||||||
{
|
{
|
||||||
// t probably faulted
|
// t probably faulted
|
||||||
if(t.IsFaulted)
|
if (t.IsFaulted)
|
||||||
{
|
{
|
||||||
throw t.Exception.InnerException;
|
throw t.Exception.InnerException;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
stream);
|
stream);
|
||||||
|
|
||||||
var buffer = ReadableBuffer.Create(stream.ToArray()).Preserve();
|
var buffer = ReadableBuffer.Create(stream.ToArray()).Preserve();
|
||||||
await Application.Output.WriteAsync(new Message(buffer, Format.Binary, endOfMessage: true));
|
await Application.Output.WriteAsync(new Message(buffer, MessageType.Binary, endOfMessage: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> Read<T>() where T : InvocationMessage
|
public async Task<T> Read<T>() where T : InvocationMessage
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
using Microsoft.AspNetCore.SignalR.Tests;
|
// 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 Microsoft.AspNetCore.SignalR.Tests;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
using (var connection = await Connection.ConnectAsync(new Uri("http://fakeuri.org/"), longPollingTransport, httpClient))
|
using (var connection = await Connection.ConnectAsync(new Uri("http://fakeuri.org/"), longPollingTransport, httpClient))
|
||||||
{
|
{
|
||||||
var data = new byte[] { 1, 1, 2, 3, 5, 8 };
|
var data = new byte[] { 1, 1, 2, 3, 5, 8 };
|
||||||
await connection.SendAsync(data, Format.Binary);
|
await connection.SendAsync(data, MessageType.Binary);
|
||||||
|
|
||||||
Assert.Equal(data, await sendTcs.Task.OrTimeout());
|
Assert.Equal(data, await sendTcs.Task.OrTimeout());
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
using (var connection = await Connection.ConnectAsync(new Uri("http://fakeuri.org/"), longPollingTransport, httpClient))
|
using (var connection = await Connection.ConnectAsync(new Uri("http://fakeuri.org/"), longPollingTransport, httpClient))
|
||||||
{
|
{
|
||||||
await connection.StopAsync();
|
await connection.StopAsync();
|
||||||
Assert.False(await connection.SendAsync(new byte[] { 1, 1, 3, 5, 8 }, Format.Binary));
|
Assert.False(await connection.SendAsync(new byte[] { 1, 1, 3, 5, 8 }, MessageType.Binary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +217,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
||||||
allowPollTcs.TrySetResult(null);
|
allowPollTcs.TrySetResult(null);
|
||||||
await Assert.ThrowsAsync<HttpRequestException>(async () => await receiveTask);
|
await Assert.ThrowsAsync<HttpRequestException>(async () => await receiveTask);
|
||||||
|
|
||||||
Assert.False(await connection.SendAsync(new byte[] { 1, 1, 3, 5, 8 }, Format.Binary));
|
Assert.False(await connection.SendAsync(new byte[] { 1, 1, 3, 5, 8 }, MessageType.Binary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
// 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.IO.Pipelines;
|
||||||
|
using System.Text;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
|
{
|
||||||
|
public class MessageFormatterTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void WriteMultipleMessages()
|
||||||
|
{
|
||||||
|
const string expectedEncoding = "0:B:;14:T:Hello,\r\nWorld!;1:C:A;12:E:Server Error;";
|
||||||
|
var messages = new[]
|
||||||
|
{
|
||||||
|
CreateMessage(new byte[0]),
|
||||||
|
CreateMessage("Hello,\r\nWorld!",MessageType.Text),
|
||||||
|
CreateMessage("A", MessageType.Close),
|
||||||
|
CreateMessage("Server Error", MessageType.Error)
|
||||||
|
};
|
||||||
|
|
||||||
|
var array = new byte[256];
|
||||||
|
var buffer = array.Slice();
|
||||||
|
var totalConsumed = 0;
|
||||||
|
foreach (var message in messages)
|
||||||
|
{
|
||||||
|
Assert.True(MessageFormatter.TryFormatMessage(message, buffer, MessageFormat.Text, out var consumed));
|
||||||
|
buffer = buffer.Slice(consumed);
|
||||||
|
totalConsumed += consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(expectedEncoding, Encoding.UTF8.GetString(array, 0, totalConsumed));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("0:B:;", new byte[0])]
|
||||||
|
[InlineData("8:B:q83vEg==;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })]
|
||||||
|
public void WriteBinaryMessage(string encoded, byte[] payload)
|
||||||
|
{
|
||||||
|
var message = CreateMessage(payload);
|
||||||
|
var buffer = new byte[256];
|
||||||
|
|
||||||
|
Assert.True(MessageFormatter.TryFormatMessage(message, buffer, MessageFormat.Text, out var bytesWritten));
|
||||||
|
|
||||||
|
var encodedSpan = buffer.Slice(0, bytesWritten);
|
||||||
|
Assert.Equal(encoded, Encoding.UTF8.GetString(encodedSpan.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("0:T:;", MessageType.Text, "")]
|
||||||
|
[InlineData("3:T:ABC;", MessageType.Text, "ABC")]
|
||||||
|
[InlineData("11:T:A\nR\rC\r\n;DEF;", MessageType.Text, "A\nR\rC\r\n;DEF")]
|
||||||
|
[InlineData("0:C:;", MessageType.Close, "")]
|
||||||
|
[InlineData("17:C:Connection Closed;", MessageType.Close, "Connection Closed")]
|
||||||
|
[InlineData("0:E:;", MessageType.Error, "")]
|
||||||
|
[InlineData("12:E:Server Error;", MessageType.Error, "Server Error")]
|
||||||
|
public void WriteTextMessage(string encoded, MessageType messageType, string payload)
|
||||||
|
{
|
||||||
|
var message = CreateMessage(payload, messageType);
|
||||||
|
var buffer = new byte[256];
|
||||||
|
|
||||||
|
Assert.True(MessageFormatter.TryFormatMessage(message, buffer, MessageFormat.Text, out var bytesWritten));
|
||||||
|
|
||||||
|
var encodedSpan = buffer.Slice(0, bytesWritten);
|
||||||
|
Assert.Equal(encoded, Encoding.UTF8.GetString(encodedSpan.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WriteInvalidMessages()
|
||||||
|
{
|
||||||
|
var message = new Message(ReadableBuffer.Create(new byte[0]).Preserve(), MessageType.Binary, endOfMessage: false);
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
MessageFormatter.TryFormatMessage(message, Span<byte>.Empty, MessageFormat.Text, out var written));
|
||||||
|
Assert.Equal("Cannot format message where endOfMessage is false using this format", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("0:T:;", MessageType.Text, "")]
|
||||||
|
[InlineData("3:T:ABC;", MessageType.Text, "ABC")]
|
||||||
|
[InlineData("11:T:A\nR\rC\r\n;DEF;", MessageType.Text, "A\nR\rC\r\n;DEF")]
|
||||||
|
[InlineData("0:C:;", MessageType.Close, "")]
|
||||||
|
[InlineData("17:C:Connection Closed;", MessageType.Close, "Connection Closed")]
|
||||||
|
[InlineData("0:E:;", MessageType.Error, "")]
|
||||||
|
[InlineData("12:E:Server Error;", MessageType.Error, "Server Error")]
|
||||||
|
public void ReadTextMessage(string encoded, MessageType messageType, string payload)
|
||||||
|
{
|
||||||
|
var buffer = Encoding.UTF8.GetBytes(encoded);
|
||||||
|
|
||||||
|
Assert.True(MessageFormatter.TryParseMessage(buffer, MessageFormat.Text, out var message, out var consumed));
|
||||||
|
Assert.Equal(consumed, buffer.Length);
|
||||||
|
|
||||||
|
AssertMessage(message, messageType, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("0:B:;", new byte[0])]
|
||||||
|
[InlineData("8:B:q83vEg==;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })]
|
||||||
|
public void ReadBinaryMessage(string encoded, byte[] payload)
|
||||||
|
{
|
||||||
|
var buffer = Encoding.UTF8.GetBytes(encoded);
|
||||||
|
|
||||||
|
Assert.True(MessageFormatter.TryParseMessage(buffer, MessageFormat.Text, out var message, out var consumed));
|
||||||
|
Assert.Equal(consumed, buffer.Length);
|
||||||
|
|
||||||
|
AssertMessage(message, MessageType.Binary, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadMultipleMessages()
|
||||||
|
{
|
||||||
|
const string encoded = "0:B:;14:T:Hello,\r\nWorld!;1:C:A;12:E:Server Error;";
|
||||||
|
var buffer = (Span<byte>)Encoding.UTF8.GetBytes(encoded);
|
||||||
|
|
||||||
|
var messages = new List<Message>();
|
||||||
|
var consumedTotal = 0;
|
||||||
|
while (MessageFormatter.TryParseMessage(buffer, MessageFormat.Text, out var message, out var consumed))
|
||||||
|
{
|
||||||
|
messages.Add(message);
|
||||||
|
consumedTotal += consumed;
|
||||||
|
buffer = buffer.Slice(consumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(consumedTotal, Encoding.UTF8.GetByteCount(encoded));
|
||||||
|
|
||||||
|
Assert.Equal(4, messages.Count);
|
||||||
|
AssertMessage(messages[0], MessageType.Binary, new byte[0]);
|
||||||
|
AssertMessage(messages[1], MessageType.Text, "Hello,\r\nWorld!");
|
||||||
|
AssertMessage(messages[2], MessageType.Close, "A");
|
||||||
|
AssertMessage(messages[3], MessageType.Error, "Server Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData("ABC")]
|
||||||
|
[InlineData("1230450945")]
|
||||||
|
[InlineData("12ab34:")]
|
||||||
|
[InlineData("1:asdf")]
|
||||||
|
[InlineData("1::")]
|
||||||
|
[InlineData("1:AB:")]
|
||||||
|
[InlineData("5:T:A")]
|
||||||
|
[InlineData("5:T:ABCDE")]
|
||||||
|
[InlineData("5:T:ABCDEF")]
|
||||||
|
[InlineData("5:X:ABCDEF")]
|
||||||
|
[InlineData("1029348109238412903849023841290834901283409128349018239048102394:X:ABCDEF")]
|
||||||
|
public void ReadInvalidMessages(string encoded)
|
||||||
|
{
|
||||||
|
var buffer = Encoding.UTF8.GetBytes(encoded);
|
||||||
|
Assert.False(MessageFormatter.TryParseMessage(buffer, MessageFormat.Text, out var message, out var consumed));
|
||||||
|
Assert.Equal(0, consumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertMessage(Message message, MessageType messageType, byte[] payload)
|
||||||
|
{
|
||||||
|
Assert.True(message.EndOfMessage);
|
||||||
|
Assert.Equal(messageType, message.Type);
|
||||||
|
Assert.Equal(payload, message.Payload.Buffer.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertMessage(Message message, MessageType messageType, string payload)
|
||||||
|
{
|
||||||
|
Assert.True(message.EndOfMessage);
|
||||||
|
Assert.Equal(messageType, message.Type);
|
||||||
|
Assert.Equal(payload, Encoding.UTF8.GetString(message.Payload.Buffer.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Message CreateMessage(byte[] payload, MessageType type = MessageType.Binary)
|
||||||
|
{
|
||||||
|
return new Message(
|
||||||
|
ReadableBuffer.Create(payload).Preserve(),
|
||||||
|
type,
|
||||||
|
endOfMessage: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Message CreateMessage(string payload, MessageType type)
|
||||||
|
{
|
||||||
|
return new Message(
|
||||||
|
ReadableBuffer.Create(Encoding.UTF8.GetBytes(payload)).Preserve(),
|
||||||
|
type,
|
||||||
|
endOfMessage: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<Import Project="..\..\build\common.props" />
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netcoreapp1.1;net46</TargetFrameworks>
|
||||||
|
<!-- TODO remove when https://github.com/Microsoft/vstest/issues/428 is resolved -->
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Sockets.Common\Microsoft.AspNetCore.Sockets.Common.csproj" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-*" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-*" />
|
||||||
|
<PackageReference Include="xunit" Version="2.2.0-*" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
var buffer = ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve();
|
var buffer = ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve();
|
||||||
|
|
||||||
// Write to the transport so the poll yields
|
// Write to the transport so the poll yields
|
||||||
await state.Connection.Transport.Output.WriteAsync(new Message(buffer, Format.Text, endOfMessage: true));
|
await state.Connection.Transport.Output.WriteAsync(new Message(buffer, MessageType.Text, endOfMessage: true));
|
||||||
|
|
||||||
await task;
|
await task;
|
||||||
|
|
||||||
|
|
@ -279,7 +279,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
var buffer = ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve();
|
var buffer = ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve();
|
||||||
|
|
||||||
// Write to the application
|
// Write to the application
|
||||||
await state.Application.Output.WriteAsync(new Message(buffer, Format.Text, endOfMessage: true));
|
await state.Application.Output.WriteAsync(new Message(buffer, MessageType.Text, endOfMessage: true));
|
||||||
|
|
||||||
await task;
|
await task;
|
||||||
|
|
||||||
|
|
@ -304,7 +304,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
var buffer = ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve();
|
var buffer = ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve();
|
||||||
|
|
||||||
// Write to the application
|
// Write to the application
|
||||||
await state.Application.Output.WriteAsync(new Message(buffer, Format.Text, endOfMessage: true));
|
await state.Application.Output.WriteAsync(new Message(buffer, MessageType.Text, endOfMessage: true));
|
||||||
|
|
||||||
await task;
|
await task;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
|
|
||||||
await channel.Out.WriteAsync(new Message(
|
await channel.Out.WriteAsync(new Message(
|
||||||
ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve(),
|
ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve(),
|
||||||
Format.Text,
|
MessageType.Text,
|
||||||
endOfMessage: true));
|
endOfMessage: true));
|
||||||
|
|
||||||
Assert.True(channel.Out.TryComplete());
|
Assert.True(channel.Out.TryComplete());
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
|
|
||||||
await channel.Out.WriteAsync(new Message(
|
await channel.Out.WriteAsync(new Message(
|
||||||
ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve(),
|
ReadableBuffer.Create(Encoding.UTF8.GetBytes("Hello World")).Preserve(),
|
||||||
Format.Text,
|
MessageType.Text,
|
||||||
endOfMessage: true));
|
endOfMessage: true));
|
||||||
|
|
||||||
Assert.True(channel.Out.TryComplete());
|
Assert.True(channel.Out.TryComplete());
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
public class WebSocketsTests
|
public class WebSocketsTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(Format.Text, WebSocketOpcode.Text)]
|
[InlineData(MessageType.Text, WebSocketOpcode.Text)]
|
||||||
[InlineData(Format.Binary, WebSocketOpcode.Binary)]
|
[InlineData(MessageType.Binary, WebSocketOpcode.Binary)]
|
||||||
public async Task ReceivedFramesAreWrittenToChannel(Format format, WebSocketOpcode opcode)
|
public async Task ReceivedFramesAreWrittenToChannel(MessageType format, WebSocketOpcode opcode)
|
||||||
{
|
{
|
||||||
var transportToApplication = Channel.CreateUnbounded<Message>();
|
var transportToApplication = Channel.CreateUnbounded<Message>();
|
||||||
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
||||||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
using (var message = await applicationSide.Input.In.ReadAsync())
|
using (var message = await applicationSide.Input.In.ReadAsync())
|
||||||
{
|
{
|
||||||
Assert.True(message.EndOfMessage);
|
Assert.True(message.EndOfMessage);
|
||||||
Assert.Equal(format, message.MessageFormat);
|
Assert.Equal(format, message.Type);
|
||||||
Assert.Equal("Hello", Encoding.UTF8.GetString(message.Payload.Buffer.ToArray()));
|
Assert.Equal("Hello", Encoding.UTF8.GetString(message.Payload.Buffer.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,9 +66,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(Format.Text, WebSocketOpcode.Text)]
|
[InlineData(MessageType.Text, WebSocketOpcode.Text)]
|
||||||
[InlineData(Format.Binary, WebSocketOpcode.Binary)]
|
[InlineData(MessageType.Binary, WebSocketOpcode.Binary)]
|
||||||
public async Task MultiFrameMessagesArePropagatedToTheChannel(Format format, WebSocketOpcode opcode)
|
public async Task MultiFrameMessagesArePropagatedToTheChannel(MessageType format, WebSocketOpcode opcode)
|
||||||
{
|
{
|
||||||
var transportToApplication = Channel.CreateUnbounded<Message>();
|
var transportToApplication = Channel.CreateUnbounded<Message>();
|
||||||
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
||||||
|
|
@ -101,14 +101,14 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
using (var message1 = await applicationSide.Input.In.ReadAsync())
|
using (var message1 = await applicationSide.Input.In.ReadAsync())
|
||||||
{
|
{
|
||||||
Assert.False(message1.EndOfMessage);
|
Assert.False(message1.EndOfMessage);
|
||||||
Assert.Equal(format, message1.MessageFormat);
|
Assert.Equal(format, message1.Type);
|
||||||
Assert.Equal("Hello", Encoding.UTF8.GetString(message1.Payload.Buffer.ToArray()));
|
Assert.Equal("Hello", Encoding.UTF8.GetString(message1.Payload.Buffer.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var message2 = await applicationSide.Input.In.ReadAsync())
|
using (var message2 = await applicationSide.Input.In.ReadAsync())
|
||||||
{
|
{
|
||||||
Assert.True(message2.EndOfMessage);
|
Assert.True(message2.EndOfMessage);
|
||||||
Assert.Equal(format, message2.MessageFormat);
|
Assert.Equal(format, message2.Type);
|
||||||
Assert.Equal("World", Encoding.UTF8.GetString(message2.Payload.Buffer.ToArray()));
|
Assert.Equal("World", Encoding.UTF8.GetString(message2.Payload.Buffer.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,9 +125,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(Format.Text, WebSocketOpcode.Text)]
|
[InlineData(MessageType.Text, WebSocketOpcode.Text)]
|
||||||
[InlineData(Format.Binary, WebSocketOpcode.Binary)]
|
[InlineData(MessageType.Binary, WebSocketOpcode.Binary)]
|
||||||
public async Task IncompleteMessagesAreWrittenAsMultiFrameWebSocketMessages(Format format, WebSocketOpcode opcode)
|
public async Task IncompleteMessagesAreWrittenAsMultiFrameWebSocketMessages(MessageType format, WebSocketOpcode opcode)
|
||||||
{
|
{
|
||||||
var transportToApplication = Channel.CreateUnbounded<Message>();
|
var transportToApplication = Channel.CreateUnbounded<Message>();
|
||||||
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
||||||
|
|
@ -173,9 +173,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(Format.Text, WebSocketOpcode.Text)]
|
[InlineData(MessageType.Text, WebSocketOpcode.Text)]
|
||||||
[InlineData(Format.Binary, WebSocketOpcode.Binary)]
|
[InlineData(MessageType.Binary, WebSocketOpcode.Binary)]
|
||||||
public async Task DataWrittenToOutputPipelineAreSentAsFrames(Format format, WebSocketOpcode opcode)
|
public async Task DataWrittenToOutputPipelineAreSentAsFrames(MessageType format, WebSocketOpcode opcode)
|
||||||
{
|
{
|
||||||
var transportToApplication = Channel.CreateUnbounded<Message>();
|
var transportToApplication = Channel.CreateUnbounded<Message>();
|
||||||
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
||||||
|
|
@ -214,9 +214,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(Format.Text, WebSocketOpcode.Text)]
|
[InlineData(MessageType.Text, WebSocketOpcode.Text)]
|
||||||
[InlineData(Format.Binary, WebSocketOpcode.Binary)]
|
[InlineData(MessageType.Binary, WebSocketOpcode.Binary)]
|
||||||
public async Task FrameReceivedAfterServerCloseSent(Format format, WebSocketOpcode opcode)
|
public async Task FrameReceivedAfterServerCloseSent(MessageType format, WebSocketOpcode opcode)
|
||||||
{
|
{
|
||||||
var transportToApplication = Channel.CreateUnbounded<Message>();
|
var transportToApplication = Channel.CreateUnbounded<Message>();
|
||||||
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
var applicationToTransport = Channel.CreateUnbounded<Message>();
|
||||||
|
|
@ -250,7 +250,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
||||||
using (var message = await applicationSide.Input.In.ReadAsync())
|
using (var message = await applicationSide.Input.In.ReadAsync())
|
||||||
{
|
{
|
||||||
Assert.True(message.EndOfMessage);
|
Assert.True(message.EndOfMessage);
|
||||||
Assert.Equal(format, message.MessageFormat);
|
Assert.Equal(format, message.Type);
|
||||||
Assert.Equal("Hello", Encoding.UTF8.GetString(message.Payload.Buffer.ToArray()));
|
Assert.Equal("Hello", Encoding.UTF8.GetString(message.Payload.Buffer.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue