Skip to content

Commit b996ee3

Browse files
authored
also adds some tests and extra features to the EchoApp test sample
1 parent c51aec5 commit b996ee3

File tree

7 files changed

+123
-42
lines changed

7 files changed

+123
-42
lines changed

samples/EchoApp/Startup.cs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public void ConfigureServices(IServiceCollection services)
2424
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
2525
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
2626
{
27-
loggerFactory.AddConsole();
27+
loggerFactory.AddConsole(LogLevel.Debug);
2828

2929
if (env.IsDevelopment())
3030
{
@@ -38,7 +38,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
3838
if (context.WebSockets.IsWebSocketRequest)
3939
{
4040
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
41-
await Echo(webSocket);
41+
await Echo(context, webSocket, loggerFactory.CreateLogger("Echo"));
4242
}
4343
else
4444
{
@@ -49,27 +49,57 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
4949
app.UseFileServer();
5050
}
5151

52-
private async Task Echo(WebSocket webSocket)
52+
private async Task Echo(HttpContext context, WebSocket webSocket, ILogger logger)
5353
{
5454
var buffer = new byte[1024 * 4];
5555
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
56+
LogFrame(logger, result, buffer);
5657
while (!result.CloseStatus.HasValue)
5758
{
5859
// If the client send "ServerClose", then they want a server-originated close to occur
59-
if(result.MessageType == WebSocketMessageType.Text)
60+
string content = "<<binary>>";
61+
if (result.MessageType == WebSocketMessageType.Text)
6062
{
61-
var str = Encoding.UTF8.GetString(buffer, 0, result.Count);
62-
if(str.Equals("ServerClose"))
63+
content = Encoding.UTF8.GetString(buffer, 0, result.Count);
64+
if (content.Equals("ServerClose"))
6365
{
6466
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing from Server", CancellationToken.None);
67+
logger.LogDebug($"Sent Frame Close: {WebSocketCloseStatus.NormalClosure} Closing from Server");
6568
return;
6669
}
70+
else if (content.Equals("ServerAbort"))
71+
{
72+
context.Abort();
73+
}
6774
}
6875

6976
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
77+
logger.LogDebug($"Sent Frame {result.MessageType}: Len={result.Count}, Fin={result.EndOfMessage}: {content}");
78+
7079
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
80+
LogFrame(logger, result, buffer);
7181
}
7282
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
7383
}
84+
85+
private void LogFrame(ILogger logger, WebSocketReceiveResult frame, byte[] buffer)
86+
{
87+
var close = frame.CloseStatus != null;
88+
string message;
89+
if (close)
90+
{
91+
message = $"Close: {frame.CloseStatus.Value} {frame.CloseStatusDescription}";
92+
}
93+
else
94+
{
95+
string content = "<<binary>>";
96+
if (frame.MessageType == WebSocketMessageType.Text)
97+
{
98+
content = Encoding.UTF8.GetString(buffer, 0, frame.Count);
99+
}
100+
message = $"{frame.MessageType}: Len={frame.Count}, Fin={frame.EndOfMessage}: {content}";
101+
}
102+
logger.LogDebug("Received Frame " + message);
103+
}
74104
}
75105
}

samples/EchoApp/wwwroot/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ <h1>WebSocket Test Page</h1>
2525
<button id="closeButton" disabled>Close Socket</button>
2626
</div>
2727

28-
<p>Note: When connected to the default server (i.e. the server in the address bar ;)), the message "ServerClose" will cause the server to close the connection</p>
28+
<p>Note: When connected to the default server (i.e. the server in the address bar ;)), the message "ServerClose" will cause the server to close the connection. Similarly, the message "ServerAbort" will cause the server to forcibly terminate the connection without a closing handshake</p>
2929

3030
<h2>Communication Log</h2>
3131
<table style="width: 800px">

src/Microsoft.AspNetCore.WebSockets/Internal/fx/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/ManagedWebSocket.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -615,12 +615,7 @@ private async Task<WebSocketReceiveResult> ReceiveAsyncPrivate(ArraySegment<byte
615615
// Make sure we have the first two bytes, which includes the start of the payload length.
616616
if (_receiveBufferCount < 2)
617617
{
618-
await EnsureBufferContainsAsync(2, cancellationToken, throwOnPrematureClosure: false).ConfigureAwait(false);
619-
if (_receiveBufferCount < 2)
620-
{
621-
// The connection closed; nothing more to read.
622-
return new WebSocketReceiveResult(0, WebSocketMessageType.Text, true);
623-
}
618+
await EnsureBufferContainsAsync(2, cancellationToken, throwOnPrematureClosure: true).ConfigureAwait(false);
624619
}
625620

626621
// Then make sure we have the full header based on the payload length.

test/Microsoft.AspNetCore.WebSockets.Test/BufferStream.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class BufferStream : Stream
1616
{
1717
private bool _disposed;
1818
private bool _aborted;
19+
private bool _terminated;
1920
private Exception _abortException;
2021
private ConcurrentQueue<byte[]> _bufferedData;
2122
private ArraySegment<byte> _topBuffer;
@@ -71,6 +72,14 @@ public override void SetLength(long value)
7172

7273
#endregion NotSupported
7374

75+
/// <summary>
76+
/// Ends the stream, meaning all future reads will return '0'.
77+
/// </summary>
78+
public void End()
79+
{
80+
_terminated = true;
81+
}
82+
7483
public override void Flush()
7584
{
7685
CheckDisposed();
@@ -95,6 +104,11 @@ public override Task FlushAsync(CancellationToken cancellationToken)
95104

96105
public override int Read(byte[] buffer, int offset, int count)
97106
{
107+
if(_terminated)
108+
{
109+
return 0;
110+
}
111+
98112
VerifyBuffer(buffer, offset, count, allowEmpty: false);
99113
_readLock.Wait();
100114
try
@@ -154,6 +168,11 @@ public override int EndRead(IAsyncResult asyncResult)
154168

155169
public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
156170
{
171+
if(_terminated)
172+
{
173+
return 0;
174+
}
175+
157176
VerifyBuffer(buffer, offset, count, allowEmpty: false);
158177
CancellationTokenRegistration registration = cancellationToken.Register(Abort);
159178
await _readLock.WaitAsync(cancellationToken);

test/Microsoft.AspNetCore.WebSockets.Test/DuplexStream.cs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,31 @@ namespace Microsoft.AspNetCore.WebSockets.Test
1010
// A duplex wrapper around a read and write stream.
1111
public class DuplexStream : Stream
1212
{
13-
private readonly Stream _readStream;
14-
private readonly Stream _writeStream;
13+
public BufferStream ReadStream { get; }
14+
public BufferStream WriteStream { get; }
1515

1616
public DuplexStream()
1717
: this (new BufferStream(), new BufferStream())
1818
{
1919
}
2020

21-
public DuplexStream(Stream readStream, Stream writeStream)
21+
public DuplexStream(BufferStream readStream, BufferStream writeStream)
2222
{
23-
_readStream = readStream;
24-
_writeStream = writeStream;
23+
ReadStream = readStream;
24+
WriteStream = writeStream;
2525
}
2626

2727
public DuplexStream CreateReverseDuplexStream()
2828
{
29-
return new DuplexStream(_writeStream, _readStream);
29+
return new DuplexStream(WriteStream, ReadStream);
3030
}
3131

32+
3233
#region Properties
3334

3435
public override bool CanRead
3536
{
36-
get { return _readStream.CanRead; }
37+
get { return ReadStream.CanRead; }
3738
}
3839

3940
public override bool CanSeek
@@ -43,12 +44,12 @@ public override bool CanSeek
4344

4445
public override bool CanTimeout
4546
{
46-
get { return _readStream.CanTimeout || _writeStream.CanTimeout; }
47+
get { return ReadStream.CanTimeout || WriteStream.CanTimeout; }
4748
}
4849

4950
public override bool CanWrite
5051
{
51-
get { return _writeStream.CanWrite; }
52+
get { return WriteStream.CanWrite; }
5253
}
5354

5455
public override long Length
@@ -64,14 +65,14 @@ public override long Position
6465

6566
public override int ReadTimeout
6667
{
67-
get { return _readStream.ReadTimeout; }
68-
set { _readStream.ReadTimeout = value; }
68+
get { return ReadStream.ReadTimeout; }
69+
set { ReadStream.ReadTimeout = value; }
6970
}
7071

7172
public override int WriteTimeout
7273
{
73-
get { return _writeStream.WriteTimeout; }
74-
set { _writeStream.WriteTimeout = value; }
74+
get { return WriteStream.WriteTimeout; }
75+
set { WriteStream.WriteTimeout = value; }
7576
}
7677

7778
#endregion Properties
@@ -90,33 +91,33 @@ public override void SetLength(long value)
9091

9192
public override int Read(byte[] buffer, int offset, int count)
9293
{
93-
return _readStream.Read(buffer, offset, count);
94+
return ReadStream.Read(buffer, offset, count);
9495
}
9596

9697
#if !NETCOREAPP1_0
9798
public override int ReadByte()
9899
{
99-
return _readStream.ReadByte();
100+
return ReadStream.ReadByte();
100101
}
101102

102103
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
103104
{
104-
return _readStream.BeginRead(buffer, offset, count, callback, state);
105+
return ReadStream.BeginRead(buffer, offset, count, callback, state);
105106
}
106107

107108
public override int EndRead(IAsyncResult asyncResult)
108109
{
109-
return _readStream.EndRead(asyncResult);
110+
return ReadStream.EndRead(asyncResult);
110111
}
111112

112113
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
113114
{
114-
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
115+
return ReadStream.ReadAsync(buffer, offset, count, cancellationToken);
115116
}
116117

117118
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
118119
{
119-
return _readStream.CopyToAsync(destination, bufferSize, cancellationToken);
120+
return ReadStream.CopyToAsync(destination, bufferSize, cancellationToken);
120121
}
121122
#endif
122123

@@ -126,39 +127,39 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio
126127

127128
public override void Write(byte[] buffer, int offset, int count)
128129
{
129-
_writeStream.Write(buffer, offset, count);
130+
WriteStream.Write(buffer, offset, count);
130131
}
131132

132133
#if !NETCOREAPP1_0
133134
public override void WriteByte(byte value)
134135
{
135-
_writeStream.WriteByte(value);
136+
WriteStream.WriteByte(value);
136137
}
137138

138139
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
139140
{
140-
return _writeStream.BeginWrite(buffer, offset, count, callback, state);
141+
return WriteStream.BeginWrite(buffer, offset, count, callback, state);
141142
}
142143

143144
public override void EndWrite(IAsyncResult asyncResult)
144145
{
145-
_writeStream.EndWrite(asyncResult);
146+
WriteStream.EndWrite(asyncResult);
146147
}
147148

148149
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
149150
{
150-
return _writeStream.WriteAsync(buffer, offset, count, cancellationToken);
151+
return WriteStream.WriteAsync(buffer, offset, count, cancellationToken);
151152
}
152153

153154
public override Task FlushAsync(CancellationToken cancellationToken)
154155
{
155-
return _writeStream.FlushAsync(cancellationToken);
156+
return WriteStream.FlushAsync(cancellationToken);
156157
}
157158
#endif
158159

159160
public override void Flush()
160161
{
161-
_writeStream.Flush();
162+
WriteStream.Flush();
162163
}
163164

164165
#endregion Write
@@ -167,8 +168,8 @@ protected override void Dispose(bool disposing)
167168
{
168169
if (disposing)
169170
{
170-
_readStream.Dispose();
171-
_writeStream.Dispose();
171+
ReadStream.Dispose();
172+
WriteStream.Dispose();
172173
}
173174
base.Dispose(disposing);
174175
}

test/Microsoft.AspNetCore.WebSockets.Test/SendReceiveTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,35 @@ public async Task ServerToClientBinaryMessage()
7373
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
7474
Assert.Equal(sendBuffer, receiveBuffer.Take(result.Count).ToArray());
7575
}
76+
77+
[Fact]
78+
public async Task ThrowsWhenUnderlyingStreamClosed()
79+
{
80+
var pair = WebSocketPair.Create();
81+
var sendBuffer = new byte[] { 0xde, 0xad, 0xbe, 0xef };
82+
83+
await pair.ServerSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Binary, endOfMessage: true, cancellationToken: CancellationToken.None);
84+
85+
var receiveBuffer = new byte[32];
86+
var result = await pair.ClientSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
87+
88+
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
89+
90+
// Close the client socket's read end
91+
pair.ClientStream.ReadStream.End();
92+
93+
// Assert.Throws doesn't support async :(
94+
try
95+
{
96+
await pair.ClientSocket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
97+
98+
// The exception should prevent this line from running
99+
Assert.False(true, "Expected an exception to be thrown!");
100+
}
101+
catch (WebSocketException ex)
102+
{
103+
Assert.Equal(WebSocketError.ConnectionClosedPrematurely, ex.WebSocketErrorCode);
104+
}
105+
}
76106
}
77107
}

test/Microsoft.AspNetCore.WebSockets.Test/WebSocketPair.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ internal class WebSocketPair
88
{
99
public WebSocket ClientSocket { get; }
1010
public WebSocket ServerSocket { get; }
11+
public DuplexStream ServerStream { get; }
12+
public DuplexStream ClientStream { get; }
1113

12-
public WebSocketPair(WebSocket clientSocket, WebSocket serverSocket)
14+
public WebSocketPair(DuplexStream serverStream, DuplexStream clientStream, WebSocket clientSocket, WebSocket serverSocket)
1315
{
16+
ClientStream = clientStream;
17+
ServerStream = serverStream;
1418
ClientSocket = clientSocket;
1519
ServerSocket = serverSocket;
1620
}
@@ -22,6 +26,8 @@ public static WebSocketPair Create()
2226
var clientStream = serverStream.CreateReverseDuplexStream();
2327

2428
return new WebSocketPair(
29+
serverStream,
30+
clientStream,
2531
clientSocket: WebSocketFactory.CreateClientWebSocket(clientStream, null, TimeSpan.FromMinutes(2), 1024),
2632
serverSocket: WebSocketFactory.CreateServerWebSocket(serverStream, null, TimeSpan.FromMinutes(2), 1024));
2733
}

0 commit comments

Comments
 (0)