protected override bool AdvanceFrame() { if (this.currentFrameIdx >= this.frames.Count - 1) { return(false); } this.currentFrameIdx++; HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx]; this.data = frame.Payload; switch (frame.Type) { case HTTP2FrameTypes.HEADERS: var header = HTTP2FrameHelper.ReadHeadersFrame(frame); this.offset = header.HeaderBlockFragmentIdx; this.maxOffset = this.offset + header.HeaderBlockFragmentLength; break; case HTTP2FrameTypes.CONTINUATION: this.offset = 0; this.maxOffset = frame.PayloadLength; break; } return(true); }
internal void Process(HTTP2FrameHeaderAndPayload frame, List <HTTP2FrameHeaderAndPayload> outgoingFrames) { if (frame.Type != HTTP2FrameTypes.SETTINGS) { return; } HTTP2SettingsFrame settingsFrame = HTTP2FrameHelper.ReadSettings(frame); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HTTP2SettingsManager", "Processing Settings frame: " + settingsFrame.ToString(), this.Parent.Context); } if ((settingsFrame.Flags & HTTP2SettingsFlags.ACK) == HTTP2SettingsFlags.ACK) { this.MySettings.Merge(this.InitiatedMySettings); this.SettingsChangesSentAt = DateTime.MinValue; } else { this.RemoteSettings.Merge(settingsFrame.Settings); outgoingFrames.Add(HTTP2FrameHelper.CreateACKSettingsFrame()); } }
protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame) { switch (frame.Type) { case HTTP2FrameTypes.HEADERS: return(HTTP2FrameHelper.ReadHeadersFrame(frame).HeaderBlockFragmentLength); case HTTP2FrameTypes.CONTINUATION: return(frame.PayloadLength); } return(0); }
protected override bool AdvanceFrame() { if (this.currentFrameIdx >= this.frames.Count - 1) { return(false); } this.currentFrameIdx++; HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx]; HTTP2DataFrame dataFrame = HTTP2FrameHelper.ReadDataFrame(frame); this.data = frame.Payload; this.offset = dataFrame.DataIdx; this.maxOffset = dataFrame.DataIdx + dataFrame.DataLength; return(true); }
internal HTTP2FrameHeaderAndPayload CreateFrame() { List <KeyValuePair <HTTP2Settings, UInt32> > keyValuePairs = new List <KeyValuePair <HTTP2Settings, uint> >(HTTP2SettingsManager.SettingsCount); for (int i = 1; i < HTTP2SettingsManager.SettingsCount; ++i) { if (this.changeFlags[i]) { keyValuePairs.Add(new KeyValuePair <HTTP2Settings, uint>((HTTP2Settings)i, this[(HTTP2Settings)i])); this.changeFlags[i] = false; } } this.IsChanged = false; return(HTTP2FrameHelper.CreateSettingsFrame(keyValuePairs)); }
public void Process(List <HTTP2FrameHeaderAndPayload> outgoingFrames) { if (this.AssignedRequest.IsCancellationRequested && !this.isRSTFrameSent) { // These two are already set in HTTPRequest's Abort(). //this.AssignedRequest.Response = null; //this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted; this.outgoing.Clear(); if (this.State != HTTP2StreamStates.Idle) { this.outgoing.Enqueue(HTTP2FrameHelper.CreateRSTFrame(this.Id, HTTP2ErrorCodes.CANCEL)); } // We can close the stream if already received headers, or not even sent one if (this.State == HTTP2StreamStates.HalfClosedRemote || this.State == HTTP2StreamStates.Idle) { this.State = HTTP2StreamStates.Closed; } this.isRSTFrameSent = true; } // 1.) Go through incoming frames ProcessIncomingFrames(outgoingFrames); // 2.) Create outgoing frames based on the stream's state and the request processing state. ProcessState(outgoingFrames); // 3.) Send one frame per Process call if (this.outgoing.Count > 0) { HTTP2FrameHeaderAndPayload frame = this.outgoing.Dequeue(); outgoingFrames.Add(frame); // If END_Stream in header or data frame is present => half closed local if ((frame.Type == HTTP2FrameTypes.HEADERS && (frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0) || (frame.Type == HTTP2FrameTypes.DATA && (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0)) { this.State = HTTP2StreamStates.HalfClosedLocal; } } }
protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame) { return(HTTP2FrameHelper.ReadDataFrame(frame).DataLength); }
private void ProcessIncomingFrames(List <HTTP2FrameHeaderAndPayload> outgoingFrames) { UInt32 windowUpdate = 0; while (this.incomingFrames.Count > 0) { HTTP2FrameHeaderAndPayload frame = this.incomingFrames.Dequeue(); if (this.isRSTFrameSent) { BufferPool.Release(frame.Payload); continue; } if (/*HTTPManager.Logger.Level == Logger.Loglevels.All && */ frame.Type != HTTP2FrameTypes.DATA && frame.Type != HTTP2FrameTypes.WINDOW_UPDATE) { HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Process - processing frame: {1}", this.Id, frame.ToString())); } switch (frame.Type) { case HTTP2FrameTypes.HEADERS: case HTTP2FrameTypes.CONTINUATION: if (this.State != HTTP2StreamStates.HalfClosedLocal) { // ERROR! continue; } // payload will be released by the view frame.DontUseMemPool = true; if (this.currentFramesView == null) { this.currentFramesView = new FramesAsStreamView(new HeaderFrameView()); } this.currentFramesView.AddFrame(frame); if ((frame.Flags & (byte)HTTP2HeadersFlags.END_HEADERS) != 0) { List <KeyValuePair <string, string> > headers = new List <KeyValuePair <string, string> >(); try { this.encoder.Decode(this, this.currentFramesView, headers); } catch (Exception ex) { HTTPManager.Logger.Exception("HTTP2Stream", string.Format("[{0}] ProcessIncomingFrames", this.Id), ex); } this.response = new HTTP2Response(this.AssignedRequest, false); this.response.AddHeaders(headers); this.AssignedRequest.Response = this.response; this.currentFramesView.Close(); this.currentFramesView = null; if (frame.Type == HTTP2FrameTypes.HEADERS && (frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0) { PlatformSupport.Threading.ThreadedRunner.RunShortLiving <HTTPRequest, HTTP2Response, FramesAsStreamView>(FinishRequest, this.AssignedRequest, this.response, this.currentFramesView); if (this.State == HTTP2StreamStates.HalfClosedLocal) { this.State = HTTP2StreamStates.Closed; } else { this.State = HTTP2StreamStates.HalfClosedRemote; } } } break; case HTTP2FrameTypes.DATA: if (this.State != HTTP2StreamStates.HalfClosedLocal) { // ERROR! continue; } this.downloaded += frame.PayloadLength; if (this.isStreamedDownload && frame.Payload != null && frame.PayloadLength > 0) { this.response.ProcessData(frame.Payload, (int)frame.PayloadLength); } // frame's buffer will be released by the frames view frame.DontUseMemPool = true; if (this.currentFramesView == null && !this.isStreamedDownload) { this.currentFramesView = new FramesAsStreamView(new DataFrameView()); } if (!this.isStreamedDownload) { this.currentFramesView.AddFrame(frame); } // Track received data, and if necessary(local window getting too low), send a window update frame if (this.localWindow < frame.PayloadLength) { HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] Frame's PayloadLength ({1:N0}) is larger then local window ({2:N0}). Frame: {3}", this.Id, frame.PayloadLength, this.localWindow, frame)); } else { this.localWindow -= frame.PayloadLength; } bool isFinalDataFrame = (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0; // Window update logic. // 1.) We could use a logic to only send window update(s) after a threshold is reached. // When the initial window size is high enough to contain the whole or most of the result, // sending back two window updates (connection and stream) after every data frame is pointless. // 2.) On the other hand, window updates are cheap and works even when initial window size is low. // ( if (isFinalDataFrame || this.localWindow <= this.windowUpdateThreshold) { windowUpdate += this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] - this.localWindow - windowUpdate; } if (isFinalDataFrame) { if (this.isStreamedDownload) { this.response.FinishProcessData(); } HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded)); // create a short living thread to process the downloaded data: PlatformSupport.Threading.ThreadedRunner.RunShortLiving <HTTPRequest, HTTP2Response, FramesAsStreamView>(FinishRequest, this.AssignedRequest, this.response, this.currentFramesView); this.currentFramesView = null; if (this.State == HTTP2StreamStates.HalfClosedLocal) { this.State = HTTP2StreamStates.Closed; } else { this.State = HTTP2StreamStates.HalfClosedRemote; } } else if (this.AssignedRequest.OnDownloadProgress != null) { RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.DownloadProgress, downloaded, this.response.ExpectedContentLength)); } break; case HTTP2FrameTypes.WINDOW_UPDATE: HTTP2WindowUpdateFrame windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(frame); if (HTTPManager.Logger.Level == Logger.Loglevels.All) { HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Received Window Update: {1:N0}, new remoteWindow: {2:N0}, initial remote window: {3:N0}, total data sent: {4:N0}", this.Id, windowUpdateFrame.WindowSizeIncrement, this.remoteWindow + windowUpdateFrame.WindowSizeIncrement, this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE], this.sentData)); } this.remoteWindow += windowUpdateFrame.WindowSizeIncrement; break; case HTTP2FrameTypes.RST_STREAM: // https://httpwg.org/specs/rfc7540.html#RST_STREAM var rstStreamFrame = HTTP2FrameHelper.ReadRST_StreamFrame(frame); HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] RST Stream frame ({1}) received in state {2}!", this.Id, rstStreamFrame, this.State)); Abort(string.Format("RST_STREAM frame received! Error code: {0}({1})", rstStreamFrame.Error.ToString(), rstStreamFrame.ErrorCode)); break; default: HTTPManager.Logger.Warning("HTTP2Stream", string.Format("[{0}] Unexpected frame ({1}) in state {2}!", this.Id, frame, this.State)); break; } if (!frame.DontUseMemPool) { BufferPool.Release(frame.Payload); } } if (windowUpdate > 0) { if (HTTPManager.Logger.Level <= Logger.Loglevels.All) { HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Sending window update: {1:N0}, current window: {2:N0}, initial window size: {3:N0}", this.Id, windowUpdate, this.localWindow, this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE])); } this.localWindow += windowUpdate; outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, windowUpdate)); } }
public void RunHandler() { HTTPManager.Logger.Information("HTTP2Handler", "Processing thread up and running!"); Thread.CurrentThread.Name = "HTTP2 Process"; PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ReadThread); try { bool atLeastOneStreamHasAFrameToSend = true; this.HPACKEncoder = new HPACKEncoder(this.settings); // https://httpwg.org/specs/rfc7540.html#InitialWindowSize // The connection flow-control window is also 65,535 octets. this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE]; // we want to pack as many data as we can in one tcp segment, but setting the buffer's size too high // we might keep data too long and send them in bursts instead of in a steady stream. // Keeping it too low might result in a full tcp segment and one with very low payload // Is it possible that one full tcp segment sized buffer would be the best, or multiple of it. // It would keep the network busy without any fragments. The ethernet layer has a maximum of 1500 bytes, // but there's two layers of 20 byte headers each, so as a theoretical maximum it's 1500-20-20 bytes. // On the other hand, if the buffer is small (1-2), that means that for larger data, we have to do a lot // of system calls, in that case a larger buffer might be better. Still, if we are not cpu bound, // a well saturated network might serve us better. using (WriteOnlyBufferedStream bufferedStream = new WriteOnlyBufferedStream(this.conn.connector.Stream, 1024 * 1024 /*1500 - 20 - 20*/)) { // The client connection preface starts with a sequence of 24 octets bufferedStream.Write(MAGIC, 0, MAGIC.Length); // This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty. // The client sends the client connection preface immediately upon receipt of a // 101 (Switching Protocols) response (indicating a successful upgrade) // or as the first application data octets of a TLS connection // Set streams' initial window size to its maximum. this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] = HTTPManager.HTTP2Settings.InitialStreamWindowSize; this.settings.InitiatedMySettings[HTTP2Settings.MAX_CONCURRENT_STREAMS] = HTTPManager.HTTP2Settings.MaxConcurrentStreams; this.settings.SendChanges(this.outgoingFrames); // The default window size for the whole connection is 65535 bytes, // but we want to set it to the maximum possible value. Int64 diff = HTTPManager.HTTP2Settings.InitialConnectionWindowSize - 65535; if (diff > 0) { this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)diff)); } this.pingFrequency = HTTPManager.HTTP2Settings.PingFrequency; while (this.isRunning) { if (!atLeastOneStreamHasAFrameToSend) { // buffered stream will call flush automatically if its internal buffer is full. // But we have to make it sure that we flush remaining data before we go to sleep. bufferedStream.Flush(); // Wait until we have to send the next ping, OR a new frame is received on the read thread. // Sent Now Sent+frequency //-----|--------|----|------------ int wait = (int)((this.lastPingSent + this.pingFrequency) - DateTime.UtcNow).TotalMilliseconds; wait = (int)Math.Min(wait, this.MaxGoAwayWaitTime.TotalMilliseconds); if (wait >= 1) { if (HTTPManager.Logger.Level <= Logger.Loglevels.All) { HTTPManager.Logger.Information("HTTP2Handler", string.Format("Sleeping for {0:N0}ms", wait)); } this.newFrameSignal.WaitOne(wait); } } DateTime now = DateTime.UtcNow; if (now - this.lastPingSent >= this.pingFrequency) { this.lastPingSent = now; var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.None); BufferHelper.SetLong(frame.Payload, 0, now.Ticks); this.outgoingFrames.Add(frame); } // Process received frames HTTP2FrameHeaderAndPayload header; while (this.newFrames.TryDequeue(out header)) { if (header.StreamId > 0) { HTTP2Stream http2Stream = FindStreamById(header.StreamId); // Add frame to the stream, so it can process it when its Process function is called if (http2Stream != null) { http2Stream.AddFrame(header, this.outgoingFrames); } else { // Error? It's possible that we closed and removed the stream while the server was in the middle of sending frames //HTTPManager.Logger.Warning("HTTP2Handler", string.Format("No stream found for id: {0}! Can't deliver frame: {1}", header.StreamId, header)); } } else { switch (header.Type) { case HTTP2FrameTypes.SETTINGS: this.settings.Process(header, this.outgoingFrames); break; case HTTP2FrameTypes.PING: var pingFrame = HTTP2FrameHelper.ReadPingFrame(header); // if it wasn't an ack for our ping, we have to send one if ((pingFrame.Flags & HTTP2PingFlags.ACK) == 0) { var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.ACK); Array.Copy(pingFrame.OpaqueData, 0, frame.Payload, 0, pingFrame.OpaqueDataLength); this.outgoingFrames.Add(frame); } break; case HTTP2FrameTypes.WINDOW_UPDATE: var windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(header); this.remoteWindow += windowUpdateFrame.WindowSizeIncrement; break; case HTTP2FrameTypes.GOAWAY: // parse the frame, so we can print out detailed information HTTP2GoAwayFrame goAwayFrame = HTTP2FrameHelper.ReadGoAwayFrame(header); HTTPManager.Logger.Information("HTTP2Handler", "Received GOAWAY frame: " + goAwayFrame.ToString()); string msg = string.Format("Server closing the connection! Error code: {0} ({1})", goAwayFrame.Error, goAwayFrame.ErrorCode); for (int i = 0; i < this.clientInitiatedStreams.Count; ++i) { this.clientInitiatedStreams[i].Abort(msg); } // set the running flag to false, so the thread can exit this.isRunning = false; this.conn.State = HTTPConnectionStates.Closed; break; case HTTP2FrameTypes.ALT_SVC: //HTTP2AltSVCFrame altSvcFrame = HTTP2FrameHelper.ReadAltSvcFrame(header); // Implement //HTTPManager.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(altSvcFrame.Origin, )) break; } } } UInt32 maxConcurrentStreams = Math.Min(HTTPManager.HTTP2Settings.MaxConcurrentStreams, this.settings.RemoteSettings[HTTP2Settings.MAX_CONCURRENT_STREAMS]); // pre-test stream count to lock only when truly needed. if (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.isRunning) { // grab requests from queue HTTPRequest request; while (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.requestQueue.TryDequeue(out request)) { // create a new stream var newStream = new HTTP2Stream((UInt32)Interlocked.Add(ref LastStreamId, 2), this.settings, this.HPACKEncoder); // process the request newStream.Assign(request); this.clientInitiatedStreams.Add(newStream); } } // send any settings changes this.settings.SendChanges(this.outgoingFrames); atLeastOneStreamHasAFrameToSend = false; // process other streams // Room for improvement Streams should be processed by their priority! for (int i = 0; i < this.clientInitiatedStreams.Count; ++i) { var stream = this.clientInitiatedStreams[i]; stream.Process(this.outgoingFrames); // remove closed, empty streams (not enough to check the closed flag, a closed stream still can contain frames to send) if (stream.State == HTTP2StreamStates.Closed && !stream.HasFrameToSend) { this.clientInitiatedStreams.RemoveAt(i--); stream.Removed(); } atLeastOneStreamHasAFrameToSend |= stream.HasFrameToSend; this.lastInteraction = DateTime.UtcNow; } // If we encounter a data frame that too large for the current remote window, we have to stop // sending all data frames as we could send smaller data frames before the large ones. // Room for improvement: An improvement would be here to stop data frame sending per-stream. bool haltDataSending = false; if (this.ShutdownType == ShutdownTypes.Running && now - this.lastInteraction >= HTTPManager.HTTP2Settings.MaxIdleTime) { this.lastInteraction = DateTime.UtcNow; HTTPManager.Logger.Information("HTTP2Handler", "Reached idle time, sending GoAway frame!"); this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR)); this.goAwaySentAt = DateTime.UtcNow; } // https://httpwg.org/specs/rfc7540.html#GOAWAY // Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote peer can know whether a stream has been partially processed or not. if (this.ShutdownType == ShutdownTypes.Gentle) { HTTPManager.Logger.Information("HTTP2Handler", "Connection abort requested, sending GoAway frame!"); this.outgoingFrames.Clear(); this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR)); this.goAwaySentAt = DateTime.UtcNow; } if (this.isRunning && now - goAwaySentAt >= this.MaxGoAwayWaitTime) { HTTPManager.Logger.Information("HTTP2Handler", "No GoAway frame received back. Really quitting now!"); this.isRunning = false; conn.State = HTTPConnectionStates.Closed; } uint streamWindowUpdates = 0; // Go through all the collected frames and send them. for (int i = 0; i < this.outgoingFrames.Count; ++i) { var frame = this.outgoingFrames[i]; if (HTTPManager.Logger.Level <= Logger.Loglevels.All && frame.Type != HTTP2FrameTypes.DATA /*&& frame.Type != HTTP2FrameTypes.PING*/) { HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString()); } // post process frames switch (frame.Type) { case HTTP2FrameTypes.DATA: if (haltDataSending) { continue; } // if the tracked remoteWindow is smaller than the frame's payload, we stop sending // data frames until we receive window-update frames if (frame.PayloadLength > this.remoteWindow) { haltDataSending = true; HTTPManager.Logger.Warning("HTTP2Handler", string.Format("Data sending halted for this round. Remote Window: {0:N0}, frame: {1}", this.remoteWindow, frame.ToString())); continue; } break; case HTTP2FrameTypes.WINDOW_UPDATE: if (frame.StreamId > 0) { streamWindowUpdates += BufferHelper.ReadUInt31(frame.Payload, 0); } break; } this.outgoingFrames.RemoveAt(i--); using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame)) bufferedStream.Write(buffer.Data, 0, buffer.Length); if (frame.PayloadLength > 0) { bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength); if (!frame.DontUseMemPool) { BufferPool.Release(frame.Payload); } } if (frame.Type == HTTP2FrameTypes.DATA) { this.remoteWindow -= frame.PayloadLength; } } if (streamWindowUpdates > 0) { var frame = HTTP2FrameHelper.CreateWindowUpdateFrame(0, streamWindowUpdates); if (HTTPManager.Logger.Level <= Logger.Loglevels.All) { HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString()); } using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame)) bufferedStream.Write(buffer.Data, 0, buffer.Length); bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength); } } // while (this.isRunning) bufferedStream.Flush(); } } catch (Exception ex) { // Log out the exception if it's a non-expected one. if (this.ShutdownType == ShutdownTypes.Running && this.goAwaySentAt == DateTime.MaxValue) { HTTPManager.Logger.Exception("HTTP2Handler", "Sender thread", ex); } } finally { // First thread closing notifies the ConnectionEventHelper if (Interlocked.Increment(ref this.threadExitCount) == 1) { ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed)); } HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing - cleaning up remaining request..."); for (int i = 0; i < this.clientInitiatedStreams.Count; ++i) { this.clientInitiatedStreams[i].Abort("Connection closed unexpectedly"); } this.clientInitiatedStreams.Clear(); HTTPRequest request = null; while (this.requestQueue.TryDequeue(out request)) { HTTPManager.Logger.Information("HTTP2Handler", string.Format("Request '{0}' IsCancellationRequested: {1}", request.CurrentUri.ToString(), request.IsCancellationRequested.ToString())); if (request.IsCancellationRequested) { request.Response = null; request.State = HTTPRequestStates.Aborted; } else { RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend)); } } HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing"); } try { // Works in the new runtime if (this.conn.connector.TopmostStream != null) { using (this.conn.connector.TopmostStream) { } } // Works in the old runtime if (this.conn.connector.Stream != null) { using (this.conn.connector.Stream) { } } } catch { } }
private void ReadThread() { try { Thread.CurrentThread.Name = "HTTP2 Read"; HTTPManager.Logger.Information("HTTP2Handler", "Reader thread up and running!"); using (ReadOnlyBufferedStream bufferedStream = new ReadOnlyBufferedStream(this.conn.connector.Stream, 32 * 1024)) { while (this.isRunning) { // TODO: // 1. Set the local window to a reasonable size // 2. stop reading when the local window is about to be 0. // 3. HTTP2FrameHeaderAndPayload header = HTTP2FrameHelper.ReadHeader(bufferedStream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information && header.Type != HTTP2FrameTypes.DATA /*&& header.Type != HTTP2FrameTypes.PING*/) { HTTPManager.Logger.Information("HTTP2Handler", "New frame received: " + header.ToString()); } // Add the new frame to the queue. Processing it on the write thread gives us the advantage that // we don't have to deal with too much locking. this.newFrames.Enqueue(header); // ping write thread to process the new frame this.newFrameSignal.Set(); switch (header.Type) { // Handle pongs on the read thread, so no additional latency is added to the rtt calculation. case HTTP2FrameTypes.PING: var pingFrame = HTTP2FrameHelper.ReadPingFrame(header); if ((pingFrame.Flags & HTTP2PingFlags.ACK) != 0) { // it was an ack, payload must contain what we sent var ticks = BufferHelper.ReadLong(pingFrame.OpaqueData, 0); // the difference between the current time and the time when the ping message is sent TimeSpan diff = TimeSpan.FromTicks(DateTime.UtcNow.Ticks - ticks); // add it to the buffer this.rtts.Add(diff.TotalMilliseconds); // and calculate the new latency this.Latency = CalculateLatency(); HTTPManager.Logger.Verbose("HTTP2Handler", string.Format("Latency: {0:F2}ms, RTT buffer: {1}", this.Latency, this.rtts.ToString())); } break; case HTTP2FrameTypes.GOAWAY: // Just exit from this thread. The processing thread will handle the frame too. return; } } } } catch //(Exception ex) { //HTTPManager.Logger.Exception("HTTP2Handler", "", ex); this.isRunning = false; this.newFrameSignal.Set(); } finally { // First thread closing notifies the ConnectionEventHelper if (Interlocked.Increment(ref this.threadExitCount) == 1) { ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed)); } HTTPManager.Logger.Information("HTTP2Handler", "Reader thread closing"); } }