Implement Close.

This commit is contained in:
Chris Ross 2014-03-05 09:28:09 -08:00
parent dbd084cb2c
commit 7004026b5e
2 changed files with 272 additions and 9 deletions

View File

@ -15,6 +15,9 @@ namespace Microsoft.Net.WebSockets
private readonly string _subProtocl;
private WebSocketState _state;
private WebSocketCloseStatus? _closeStatus;
private string _closeStatusDescription;
private byte[] _receiveBuffer;
private int _receiveOffset;
private int _receiveCount;
@ -32,12 +35,12 @@ namespace Microsoft.Net.WebSockets
public override WebSocketCloseStatus? CloseStatus
{
get { throw new NotImplementedException(); }
get { return _closeStatus; }
}
public override string CloseStatusDescription
{
get { throw new NotImplementedException(); }
get { return _closeStatusDescription; }
}
public override WebSocketState State
@ -94,9 +97,31 @@ namespace Microsoft.Net.WebSockets
}
WebSocketReceiveResult result;
// TODO: Close frame
// TODO: Ping or Pong frames
if (_frameInProgress.OpCode == Constants.OpCodes.CloseFrame)
{
// TOOD: This assumes the close message fits in the buffer.
// TODO: Assert at least two bytes remaining for the close status code.
await EnsureDataAvailableOrReadAsync((int)_frameBytesRemaining, CancellationToken.None);
_closeStatus = (WebSocketCloseStatus)((_receiveBuffer[_receiveOffset] << 8) | _receiveBuffer[_receiveOffset + 1]);
_closeStatusDescription = Encoding.UTF8.GetString(_receiveBuffer, _receiveOffset + 2, _receiveCount - 2) ?? string.Empty;
result = new WebSocketReceiveResult(0, WebSocketMessageType.Close, true, (WebSocketCloseStatus)_closeStatus, _closeStatusDescription);
if (State == WebSocketState.Open)
{
_state = WebSocketState.CloseReceived;
}
else if (State == WebSocketState.CloseSent)
{
_state = WebSocketState.Closed;
_stream.Dispose();
}
return result;
}
// Make sure there's at least some data in the buffer
if (_frameBytesRemaining > 0)
{
@ -163,9 +188,41 @@ namespace Microsoft.Net.WebSockets
}
}
public override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
public async override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
{
throw new NotImplementedException();
// TODO: Validate arguments
// TODO: Check state
// TODO: Check concurrent writes
// TODO: Check ping/pong state
if (State >= WebSocketState.Closed)
{
throw new InvalidOperationException("Already closed.");
}
if (State == WebSocketState.Open || State == WebSocketState.CloseReceived)
{
// Send a close message.
await CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
}
if (State == WebSocketState.CloseSent)
{
// Do a receiving drain
byte[] data = new byte[1024];
WebSocketReceiveResult result;
do
{
result = await ReceiveAsync(new ArraySegment<byte>(data), cancellationToken);
}
while (result.MessageType != WebSocketMessageType.Close);
_closeStatus = result.CloseStatus;
_closeStatusDescription = result.CloseStatusDescription;
}
_state = WebSocketState.Closed;
_stream.Dispose();
}
public override async Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
@ -174,14 +231,32 @@ namespace Microsoft.Net.WebSockets
// TODO: Check state
// TODO: Check concurrent writes
// TODO: Check ping/pong state
_state = WebSocketState.CloseSent;
if (State == WebSocketState.CloseSent || State >= WebSocketState.Closed)
{
throw new InvalidOperationException("Already closed.");
}
if (State == WebSocketState.Open)
{
_state = WebSocketState.CloseSent;
}
else if (State == WebSocketState.CloseReceived)
{
_state = WebSocketState.Closed;
}
byte[] descriptionBytes = Encoding.UTF8.GetBytes(statusDescription ?? string.Empty);
byte[] fullData = new byte[descriptionBytes.Length + 2];
fullData[0] = (byte)((int)closeStatus >> 8);
fullData[1] = (byte)closeStatus;
Array.Copy(descriptionBytes, 0, fullData, 2, descriptionBytes.Length);
// TODO: Masking
byte[] buffer = Encoding.UTF8.GetBytes(statusDescription);
FrameHeader frameHeader = new FrameHeader(true, Constants.OpCodes.CloseFrame, true, 0, buffer.Length);
FrameHeader frameHeader = new FrameHeader(true, Constants.OpCodes.CloseFrame, true, 0, fullData.Length);
ArraySegment<byte> segment = frameHeader.Buffer;
await _stream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken);
await _stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
await _stream.WriteAsync(fullData, 0, fullData.Length, cancellationToken);
}
public override void Abort()

View File

@ -280,5 +280,193 @@ namespace Microsoft.Net.WebSockets.Test
clientSocket.Dispose();
}
}
[Fact]
public async Task SendClose_Success()
{
using (HttpListener listener = new HttpListener())
{
listener.Prefixes.Add(ServerAddress);
listener.Start();
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
WebSocketClient client = new WebSocketClient();
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
HttpListenerContext serverContext = await serverAccept;
Assert.True(serverContext.Request.IsWebSocketRequest);
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
WebSocket clientSocket = await clientConnect;
string closeDescription = "Test Closed";
await clientSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
byte[] serverBuffer = new byte[1024];
WebSocketReceiveResult result = await serverWebSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
Assert.True(result.EndOfMessage);
Assert.Equal(0, result.Count);
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
Assert.Equal(closeDescription, result.CloseStatusDescription);
Assert.Equal(WebSocketState.CloseSent, clientSocket.State);
clientSocket.Dispose();
}
}
[Fact]
public async Task ReceiveClose_Success()
{
using (HttpListener listener = new HttpListener())
{
listener.Prefixes.Add(ServerAddress);
listener.Start();
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
WebSocketClient client = new WebSocketClient();
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
HttpListenerContext serverContext = await serverAccept;
Assert.True(serverContext.Request.IsWebSocketRequest);
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
WebSocket clientSocket = await clientConnect;
string closeDescription = "Test Closed";
await serverWebSocketContext.WebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
byte[] serverBuffer = new byte[1024];
WebSocketReceiveResult result = await clientSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
Assert.True(result.EndOfMessage);
Assert.Equal(0, result.Count);
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
Assert.Equal(closeDescription, result.CloseStatusDescription);
Assert.Equal(WebSocketState.CloseReceived, clientSocket.State);
clientSocket.Dispose();
}
}
[Fact]
public async Task CloseFromOpen_Success()
{
using (HttpListener listener = new HttpListener())
{
listener.Prefixes.Add(ServerAddress);
listener.Start();
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
WebSocketClient client = new WebSocketClient();
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
HttpListenerContext serverContext = await serverAccept;
Assert.True(serverContext.Request.IsWebSocketRequest);
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
WebSocket clientSocket = await clientConnect;
string closeDescription = "Test Closed";
Task closeTask = clientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
byte[] serverBuffer = new byte[1024];
WebSocketReceiveResult result = await serverWebSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
Assert.True(result.EndOfMessage);
Assert.Equal(0, result.Count);
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
Assert.Equal(closeDescription, result.CloseStatusDescription);
await serverWebSocketContext.WebSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
await closeTask;
Assert.Equal(WebSocketState.Closed, clientSocket.State);
clientSocket.Dispose();
}
}
[Fact]
public async Task CloseFromCloseSent_Success()
{
using (HttpListener listener = new HttpListener())
{
listener.Prefixes.Add(ServerAddress);
listener.Start();
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
WebSocketClient client = new WebSocketClient();
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
HttpListenerContext serverContext = await serverAccept;
Assert.True(serverContext.Request.IsWebSocketRequest);
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
WebSocket clientSocket = await clientConnect;
string closeDescription = "Test Closed";
await clientSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
Assert.Equal(WebSocketState.CloseSent, clientSocket.State);
byte[] serverBuffer = new byte[1024];
WebSocketReceiveResult result = await serverWebSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
Assert.True(result.EndOfMessage);
Assert.Equal(0, result.Count);
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
Assert.Equal(closeDescription, result.CloseStatusDescription);
await serverWebSocketContext.WebSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
await clientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
Assert.Equal(WebSocketState.Closed, clientSocket.State);
clientSocket.Dispose();
}
}
[Fact]
public async Task CloseFromCloseReceived_Success()
{
using (HttpListener listener = new HttpListener())
{
listener.Prefixes.Add(ServerAddress);
listener.Start();
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
WebSocketClient client = new WebSocketClient();
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
HttpListenerContext serverContext = await serverAccept;
Assert.True(serverContext.Request.IsWebSocketRequest);
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
WebSocket clientSocket = await clientConnect;
string closeDescription = "Test Closed";
await serverWebSocketContext.WebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeDescription, CancellationToken.None);
byte[] serverBuffer = new byte[1024];
WebSocketReceiveResult result = await clientSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
Assert.True(result.EndOfMessage);
Assert.Equal(0, result.Count);
Assert.Equal(WebSocketMessageType.Close, result.MessageType);
Assert.Equal(WebSocketCloseStatus.NormalClosure, result.CloseStatus);
Assert.Equal(closeDescription, result.CloseStatusDescription);
Assert.Equal(WebSocketState.CloseReceived, clientSocket.State);
await clientSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
Assert.Equal(WebSocketState.Closed, clientSocket.State);
clientSocket.Dispose();
}
}
}
}