internal WebSocketImplementation(Guid guid, Func <MemoryStream> recycledStreamFactory, System.IO.Stream stream, TimeSpan keepAliveInterval, string secWebSocketExtensions, bool includeExceptionInCloseResponse, bool isClient, string subProtocol) { _guid = guid; _recycledStreamFactory = recycledStreamFactory; _stream = stream; _isClient = isClient; _subProtocol = subProtocol; _internalReadCts = new CancellationTokenSource(); _state = WebSocketState.Open; _readCursor = new WebSocketReadCursor(null, 0, 0); if (secWebSocketExtensions?.IndexOf("permessage-deflate") >= 0) { _usePerMessageDeflate = true; } KeepAliveInterval = keepAliveInterval; _includeExceptionInCloseResponse = includeExceptionInCloseResponse; if (keepAliveInterval.Ticks < 0) { throw new InvalidOperationException("KeepAliveInterval must be Zero or positive"); } if (keepAliveInterval != TimeSpan.Zero) { _pingPongManager = new PingPongManager(guid, this, keepAliveInterval, _internalReadCts.Token); } }
/// <summary> /// The last read could not be completed because the read buffer was too small. /// We need to continue reading bytes off the stream. /// Not to be confused with a continuation frame /// </summary> /// <param name="fromStream">The stream to read from</param> /// <param name="intoBuffer">The buffer to read into</param> /// <param name="readCursor">The previous partial websocket frame read plus cursor information</param> /// <param name="cancellationToken">the cancellation token</param> /// <returns>A websocket frame</returns> public static async Task <WebSocketReadCursor> ReadFromCursorAsync(System.IO.Stream fromStream, ArraySegment <byte> intoBuffer, WebSocketReadCursor readCursor, CancellationToken cancellationToken) { var remainingFrame = readCursor.WebSocketFrame; var minCount = CalculateNumBytesToRead(readCursor.NumBytesLeftToRead, intoBuffer.Count); await BinaryReaderWriter.ReadExactly(minCount, fromStream, intoBuffer, cancellationToken); if (remainingFrame.MaskKey.Count > 0) { ArraySegment <byte> payloadToMask = new ArraySegment <byte>(intoBuffer.Array, intoBuffer.Offset, minCount); WebSocketFrameCommon.ToggleMask(remainingFrame.MaskKey, payloadToMask); } return(new WebSocketReadCursor(remainingFrame, minCount, readCursor.NumBytesLeftToRead - minCount)); }
/// <summary> /// Receive web socket result /// </summary> /// <param name="buffer">The buffer to copy data into</param> /// <param name="cancellationToken">The cancellation token</param> /// <returns>The web socket result details</returns> public override async Task <WebSocketReceiveResult> ReceiveAsync(ArraySegment <byte> buffer, CancellationToken cancellationToken) { try { // we may receive control frames so reading needs to happen in an infinite loop while (true) { // allow this operation to be cancelled from iniside OR outside this instance using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_internalReadCts.Token, cancellationToken)) { WebSocketFrame frame; try { if (_readCursor.NumBytesLeftToRead > 0) { // If the buffer used to read the frame was too small to fit the whole frame then we need to "remember" this frame // and return what we have. Subsequent calls to the read function will simply continue reading off the stream without // decoding the first few bytes as a websocket header. _readCursor = await WebSocketFrameReader.ReadFromCursorAsync(_stream, buffer, _readCursor, linkedCts.Token); frame = _readCursor.WebSocketFrame; } else { _readCursor = await WebSocketFrameReader.ReadAsync(_stream, buffer, linkedCts.Token); frame = _readCursor.WebSocketFrame; } } catch (InternalBufferOverflowException ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.MessageTooBig, "Frame too large to fit in buffer. Use message fragmentation", ex); throw; } catch (ArgumentOutOfRangeException ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, "Payload length out of range", ex); throw; } catch (EndOfStreamException ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InvalidPayloadData, "Unexpected end of stream encountered", ex); throw; } catch (OperationCanceledException ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.EndpointUnavailable, "Operation cancelled", ex); throw; } catch (Exception ex) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InternalServerError, "Error reading WebSocket frame", ex); throw; } var endOfMessage = frame.IsFinBitSet && _readCursor.NumBytesLeftToRead == 0; switch (frame.OpCode) { case WebSocketOpCode.ConnectionClose: return(await RespondToCloseFrame(frame, buffer, linkedCts.Token)); case WebSocketOpCode.Ping: ArraySegment <byte> pingPayload = new ArraySegment <byte>(buffer.Array, buffer.Offset, _readCursor.NumBytesRead); await SendPongAsync(pingPayload, linkedCts.Token); break; case WebSocketOpCode.Pong: ArraySegment <byte> pongBuffer = new ArraySegment <byte>(buffer.Array, _readCursor.NumBytesRead, buffer.Offset); Pong?.Invoke(this, new PongEventArgs(pongBuffer)); break; case WebSocketOpCode.TextFrame: if (!frame.IsFinBitSet) { // continuation frames will follow, record the message type Text _continuationFrameMessageType = WebSocketMessageType.Text; } return(new WebSocketReceiveResult(_readCursor.NumBytesRead, WebSocketMessageType.Text, endOfMessage)); case WebSocketOpCode.BinaryFrame: if (!frame.IsFinBitSet) { // continuation frames will follow, record the message type Binary _continuationFrameMessageType = WebSocketMessageType.Binary; } return(new WebSocketReceiveResult(_readCursor.NumBytesRead, WebSocketMessageType.Binary, endOfMessage)); case WebSocketOpCode.ContinuationFrame: return(new WebSocketReceiveResult(_readCursor.NumBytesRead, _continuationFrameMessageType, endOfMessage)); default: Exception ex = new NotSupportedException($"Unknown WebSocket opcode {frame.OpCode}"); await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, ex.Message, ex); throw ex; } } } } catch (Exception catchAll) { // Most exceptions will be caught closer to their source to send an appropriate close message (and set the WebSocketState) // However, if an unhandled exception is encountered and a close message not sent then send one here if (_state == WebSocketState.Open) { await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InternalServerError, "Unexpected error reading from WebSocket", catchAll); } throw; } }