private void TryToCleanup() { if (Interlocked.Increment(ref this.closedThreads) == 2) { ProtocolEventHelper.EnqueueProtocolEvent(new ProtocolEventInfo(this)); (newFrameSignal as IDisposable).Dispose(); newFrameSignal = null; } }
private void CloseWithError(HTTPRequestStates state, string message) { if (!string.IsNullOrEmpty(message)) { this.baseRequest.Exception = new Exception(message); } this.baseRequest.State = state; this.closed = true; HTTPManager.Heartbeats.Unsubscribe(this); HTTPUpdateDelegator.OnApplicationForegroundStateChanged -= OnApplicationForegroundStateChanged; CloseStream(); ProtocolEventHelper.EnqueueProtocolEvent(new ProtocolEventInfo(this)); }
private bool OnData(HTTPRequest request, HTTPResponse response, byte[] dataFragment, int dataFragmentLength) { if (this.State == States.Connecting) { string contentType = response.GetFirstHeaderValue("content-type"); bool IsUpgraded = response.StatusCode == 200 && !string.IsNullOrEmpty(contentType) && contentType.ToLower().StartsWith("text/event-stream"); if (IsUpgraded) { ProtocolEventHelper.AddProtocol(this); if (this.OnOpen != null) { try { this.OnOpen(this); } catch (Exception ex) { HTTPManager.Logger.Exception("EventSource", "OnOpen", ex, request.Context); } } this.RetryCount = 0; this.State = States.Open; } else { this.State = States.Closing; request.Abort(); } } if (this.State == States.Closing) { return(true); } if (FeedData(dataFragment, dataFragmentLength)) { ProtocolEventHelper.EnqueueProtocolEvent(new ProtocolEventInfo(this)); } return(true); }
private void ReceiveThreadFunc() { try { while (!closed) { try { WebSocketFrameReader frame = new WebSocketFrameReader(); frame.Read(this.Stream); if (HTTPManager.Logger.Level == Logger.Loglevels.All) { HTTPManager.Logger.Information("WebSocketResponse", "Frame received: " + frame.Type, this.Context); } lastMessage = DateTime.UtcNow; // A server MUST NOT mask any frames that it sends to the client. A client MUST close a connection if it detects a masked frame. // In this case, it MAY use the status code 1002 (protocol error) // (These rules might be relaxed in a future specification.) if (frame.HasMask) { HTTPManager.Logger.Warning("WebSocketResponse", "Protocol Error: masked frame received from server!", this.Context); Close(1002, "Protocol Error: masked frame received from server!"); continue; } if (!frame.IsFinal) { if (OnIncompleteFrame == null) { IncompleteFrames.Add(frame); } else { CompletedFrames.Enqueue(frame); } continue; } switch (frame.Type) { // For a complete documentation and rules on fragmentation see http://tools.ietf.org/html/rfc6455#section-5.4 // A fragmented Frame's last fragment's opcode is 0 (Continuation) and the FIN bit is set to 1. case WebSocketFrameTypes.Continuation: // Do an assemble pass only if OnFragment is not set. Otherwise put it in the CompletedFrames, we will handle it in the HandleEvent phase. if (OnIncompleteFrame == null) { frame.Assemble(IncompleteFrames); // Remove all incomplete frames IncompleteFrames.Clear(); // Control frames themselves MUST NOT be fragmented. So, its a normal text or binary frame. Go, handle it as usual. goto case WebSocketFrameTypes.Binary; } else { CompletedFrames.Enqueue(frame); ProtocolEventHelper.EnqueueProtocolEvent(new ProtocolEventInfo(this)); } break; case WebSocketFrameTypes.Text: case WebSocketFrameTypes.Binary: frame.DecodeWithExtensions(WebSocket); CompletedFrames.Enqueue(frame); ProtocolEventHelper.EnqueueProtocolEvent(new ProtocolEventInfo(this)); break; // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in response, unless it already received a Close frame. case WebSocketFrameTypes.Ping: if (!closeSent && !closed) { Send(new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.Pong, frame.Data)); } break; case WebSocketFrameTypes.Pong: waitingForPong = false; try { // Get the ticks from the frame's payload long ticksSent = BitConverter.ToInt64(frame.Data, 0); // the difference between the current time and the time when the ping message is sent TimeSpan diff = TimeSpan.FromTicks(lastMessage.Ticks - ticksSent); // add it to the buffer this.rtts.Add((int)diff.TotalMilliseconds); // and calculate the new latency this.Latency = CalculateLatency(); } catch { // https://tools.ietf.org/html/rfc6455#section-5.5 // A Pong frame MAY be sent unsolicited. This serves as a // unidirectional heartbeat. A response to an unsolicited Pong frame is // not expected. } break; // If an endpoint receives a Close frame and did not previously send a Close frame, the endpoint MUST send a Close frame in response. case WebSocketFrameTypes.ConnectionClose: HTTPManager.Logger.Information("WebSocketResponse", "ConnectionClose packet received!", this.Context); CloseFrame = frame; if (!closeSent) { Send(new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.ConnectionClose, null)); } closed = true; break; } } catch (Exception e) { if (HTTPUpdateDelegator.IsCreated) { this.baseRequest.Exception = e; this.baseRequest.State = HTTPRequestStates.Error; } else { this.baseRequest.State = HTTPRequestStates.Aborted; } closed = true; newFrameSignal.Set(); } } HTTPManager.Logger.Information("WebSocketResponse", "Ending Read thread! closed: " + closed, this.Context); } finally { HTTPManager.Heartbeats.Unsubscribe(this); HTTPUpdateDelegator.OnApplicationForegroundStateChanged -= OnApplicationForegroundStateChanged; HTTPManager.Logger.Information("WebSocketResponse", "ReceiveThread - Closed!", this.Context); TryToCleanup(); } }
void ParseLine(byte[] buffer, int count) { // If the line is empty (a blank line) => Dispatch the event if (count == 0) { if (CurrentMessage != null) { CompletedMessages.Add(CurrentMessage); ProtocolEventHelper.EnqueueProtocolEvent(new ProtocolEventInfo(this)); CurrentMessage = null; } return; } // If the line starts with a U+003A COLON character (:) => Ignore the line. if (buffer[0] == 0x3A) { return; } //If the line contains a U+003A COLON character (:) int colonIdx = -1; for (int i = 0; i < count && colonIdx == -1; ++i) { if (buffer[i] == 0x3A) { colonIdx = i; } } string field; string value; if (colonIdx != -1) { // Collect the characters on the line before the first U+003A COLON character (:), and let field be that string. field = Encoding.UTF8.GetString(buffer, 0, colonIdx); //Collect the characters on the line after the first U+003A COLON character (:), and let value be that string. If value starts with a U+0020 SPACE character, remove it from value. if (colonIdx + 1 < count && buffer[colonIdx + 1] == 0x20) { colonIdx++; } colonIdx++; // discarded because it is not followed by a blank line if (colonIdx >= count) { return; } value = Encoding.UTF8.GetString(buffer, colonIdx, count - colonIdx); } else { // Otherwise, the string is not empty but does not contain a U+003A COLON character (:) => // Process the field using the whole line as the field name, and the empty string as the field value. field = Encoding.UTF8.GetString(buffer, 0, count); value = string.Empty; } if (CurrentMessage == null) { CurrentMessage = new BestHTTP.ServerSentEvents.Message(); } switch (field) { // If the field name is "id" => Set the last event ID buffer to the field value. case "id": CurrentMessage.Id = value; break; // If the field name is "event" => Set the event type buffer to field value. case "event": CurrentMessage.Event = value; break; // If the field name is "data" => Append the field value to the data buffer, then append a single U+000A LINE FEED (LF) character to the data buffer. case "data": // Append a new line if we already have some data. This way we can skip step 3.) in the EventSource's OnMessageReceived. // We do only null check, because empty string can be valid payload if (CurrentMessage.Data != null) { CurrentMessage.Data += Environment.NewLine; } CurrentMessage.Data += value; break; // If the field name is "retry" => If the field value consists of only ASCII digits, then interpret the field value as an integer in base ten, // and set the event stream's reconnection time to that integer. Otherwise, ignore the field. case "retry": int result; if (int.TryParse(value, out result)) { CurrentMessage.Retry = TimeSpan.FromMilliseconds(result); } break; // Otherwise: The field is ignored. default: break; } }
private void SendThreadFunc() { try { using (WriteOnlyBufferedStream bufferedStream = new WriteOnlyBufferedStream(this.Stream, 16 * 1024)) { while (!closed && !closeSent) { //if (HTTPManager.Logger.Level <= Logger.Loglevels.All) // HTTPManager.Logger.Information("WebSocketResponse", "SendThread - Waiting..."); newFrameSignal.WaitOne(); try { //if (HTTPManager.Logger.Level <= Logger.Loglevels.All) // HTTPManager.Logger.Information("WebSocketResponse", "SendThread - Wait is over, about " + this.unsentFrames.Count.ToString() + " new frames!"); WebSocketFrame frame; while (this.unsentFrames.TryDequeue(out frame)) { if (!closeSent) { using (var rawData = frame.Get()) bufferedStream.Write(rawData.Data, 0, rawData.Length); BufferPool.Release(frame.Data); if (frame.Type == WebSocketFrameTypes.ConnectionClose) { closeSent = true; } } Interlocked.Add(ref this._bufferedAmount, -frame.DataLength); } bufferedStream.Flush(); } catch (Exception ex) { if (HTTPUpdateDelegator.IsCreated) { this.baseRequest.Exception = ex; this.baseRequest.State = HTTPRequestStates.Error; } else { this.baseRequest.State = HTTPRequestStates.Aborted; } closed = true; } } } } finally { Interlocked.Exchange(ref sendThreadCreated, 0); HTTPManager.Logger.Information("WebSocketResponse", "SendThread - Closed!"); if (Interlocked.Increment(ref this.closedThreads) == 2) { ProtocolEventHelper.EnqueueProtocolEvent(new ProtocolEventInfo(this)); } } }