/// <summary> /// Called when a Close frame is received /// Send a response close frame if applicable /// </summary> private async Task <WebSocketReceiveResult> RespondToCloseFrame(WebSocketFrame frame, ArraySegment <byte> buffer, CancellationToken token) { _closeStatus = frame.CloseStatus; _closeStatusDescription = frame.CloseStatusDescription; if (_state == WebSocketState.CloseSent) { // this is a response to close handshake initiated by this instance _state = WebSocketState.Closed; Events.Log.CloseHandshakeComplete(_guid); } else if (_state == WebSocketState.Open) { // this is in response to a close handshake initiated by the remote instance ArraySegment <byte> closePayload = new ArraySegment <byte>(buffer.Array, buffer.Offset, frame.Count); _state = WebSocketState.CloseReceived; Events.Log.CloseHandshakeRespond(_guid, frame.CloseStatus, frame.CloseStatusDescription); using (MemoryStream stream = _recycledStreamFactory()) { WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, closePayload, stream, true, _isClient); Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, closePayload.Count, false); await WriteStreamToNetwork(stream, token); } } else { Events.Log.CloseFrameReceivedInUnexpectedState(_guid, _state, frame.CloseStatus, frame.CloseStatusDescription); } return(new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Close, frame.IsFinBitSet, frame.CloseStatus, frame.CloseStatusDescription)); }
/// NOTE: pong payload must be 125 bytes or less /// Pong should contain the same payload as the ping private async Task SendPongAsync(ArraySegment <byte> payload, CancellationToken cancellationToken) { // as per websocket spec if (payload.Count > MAX_PING_PONG_PAYLOAD_LEN) { Exception ex = new InvalidOperationException($"Max ping message size {MAX_PING_PONG_PAYLOAD_LEN} exceeded: {payload.Count}"); await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, ex.Message, ex); throw ex; } try { if (_state == WebSocketState.Open) { using (MemoryStream stream = _recycledStreamFactory()) { WebSocketFrameWriter.Write(WebSocketOpCode.Pong, payload, stream, true, _isClient); Events.Log.SendingFrame(_guid, WebSocketOpCode.Pong, true, payload.Count, false); await WriteStreamToNetwork(stream, cancellationToken); } } } catch (Exception ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.EndpointUnavailable, "Unable to send Pong response", ex); throw; } }
/// <summary> /// Send data to the web socket /// </summary> /// <param name="buffer">the buffer containing data to send</param> /// <param name="messageType">The message type. Can be Text or Binary</param> /// <param name="endOfMessage">True if this message is a standalone message (this is the norm) /// If it is a multi-part message then false (and true for the last message)</param> /// <param name="cancellationToken">the cancellation token</param> public override async Task SendAsync(ArraySegment <byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) { using (MemoryStream stream = _recycledStreamFactory()) { WebSocketOpCode opCode = GetOppCode(messageType); if (_usePerMessageDeflate) { // NOTE: Compression is currently work in progress and should NOT be used in this library. // The code below is very inefficient for small messages. Ideally we would like to have some sort of moving window // of data to get the best compression. And we don't want to create new buffers which is bad for GC. using (MemoryStream temp = new MemoryStream()) { DeflateStream deflateStream = new DeflateStream(temp, CompressionMode.Compress); deflateStream.Write(buffer.Array, buffer.Offset, buffer.Count); deflateStream.Flush(); var compressedBuffer = new ArraySegment <byte>(temp.ToArray()); WebSocketFrameWriter.Write(opCode, compressedBuffer, stream, endOfMessage, _isClient); Events.Log.SendingFrame(_guid, opCode, endOfMessage, compressedBuffer.Count, true); } } else { WebSocketFrameWriter.Write(opCode, buffer, stream, endOfMessage, _isClient); Events.Log.SendingFrame(_guid, opCode, endOfMessage, buffer.Count, false); } await WriteStreamToNetwork(stream, cancellationToken); _isContinuationFrame = !endOfMessage; } }
/// <summary> /// Call this automatically from server side each keepAliveInterval period /// NOTE: ping payload must be 125 bytes or less /// </summary> public async Task SendPingAsync(ArraySegment <byte> payload, CancellationToken cancellationToken) { if (payload.Count > MAX_PING_PONG_PAYLOAD_LEN) { throw new InvalidOperationException($"Cannot send Ping: Max ping message size {MAX_PING_PONG_PAYLOAD_LEN} exceeded: {payload.Count}"); } if (_state == WebSocketState.Open) { using (MemoryStream stream = _recycledStreamFactory()) { WebSocketFrameWriter.Write(WebSocketOpCode.Ping, payload, stream, true, _isClient); Events.Log.SendingFrame(_guid, WebSocketOpCode.Ping, true, payload.Count, false); await WriteStreamToNetwork(stream, cancellationToken); } } }
/// <summary> /// Polite close (use the close handshake) /// </summary> public override async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) { if (_state == WebSocketState.Open) { using (MemoryStream stream = _recycledStreamFactory()) { ArraySegment <byte> buffer = BuildClosePayload(closeStatus, statusDescription); WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, buffer, stream, true, _isClient); Events.Log.CloseHandshakeStarted(_guid, closeStatus, statusDescription); Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, buffer.Count, true); await WriteStreamToNetwork(stream, cancellationToken); _state = WebSocketState.CloseSent; } } else { Events.Log.InvalidStateBeforeClose(_guid, _state); } }
/// <summary> /// Fire and forget close /// </summary> public override async Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) { if (_state == WebSocketState.Open) { _state = WebSocketState.Closed; // set this before we write to the network because the write may fail using (MemoryStream stream = _recycledStreamFactory()) { ArraySegment <byte> buffer = BuildClosePayload(closeStatus, statusDescription); WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, buffer, stream, true, _isClient); Events.Log.CloseOutputNoHandshake(_guid, closeStatus, statusDescription); Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, buffer.Count, true); await WriteStreamToNetwork(stream, cancellationToken); } } else { Events.Log.InvalidStateBeforeCloseOutput(_guid, _state); } // cancel pending reads _internalReadCts.Cancel(); }