public static HTTP2RSTStreamFrame ReadRST_StreamFrame(HTTP2FrameHeaderAndPayload header) { // https://httpwg.org/specs/rfc7540.html#RST_STREAM HTTP2RSTStreamFrame frame = new HTTP2RSTStreamFrame(header); frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 0); return(frame); }
public static HTTP2WindowUpdateFrame ReadWindowUpdateFrame(HTTP2FrameHeaderAndPayload header) { // https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE HTTP2WindowUpdateFrame frame = new HTTP2WindowUpdateFrame(header); frame.ReservedBit = BufferHelper.ReadBit(header.Payload[0], 0); frame.WindowSizeIncrement = BufferHelper.ReadUInt31(header.Payload, 0); return(frame); }
public static PooledBuffer HeaderAsBinary(HTTP2FrameHeaderAndPayload header) { // https://httpwg.org/specs/rfc7540.html#FrameHeader var buffer = BufferPool.Get(9, true); BufferHelper.SetUInt24(buffer, 0, header.PayloadLength); buffer[3] = (byte)header.Type; buffer[4] = header.Flags; BufferHelper.SetUInt31(buffer, 5, header.StreamId); return(new PooledBuffer { Data = buffer, Length = 9 }); }
public static HTTP2FrameHeaderAndPayload CreateRSTFrame(UInt32 streamId, HTTP2ErrorCodes errorCode) { // https://httpwg.org/specs/rfc7540.html#RST_STREAM HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload(); frame.Type = HTTP2FrameTypes.RST_STREAM; frame.Flags = 0; frame.StreamId = streamId; frame.Payload = BufferPool.Get(4, true); frame.PayloadLength = 4; BufferHelper.SetUInt32(frame.Payload, 0, (UInt32)errorCode); return(frame); }
public static HTTP2HeadersFrame ReadHeadersFrame(HTTP2FrameHeaderAndPayload header) { // https://httpwg.org/specs/rfc7540.html#HEADERS HTTP2HeadersFrame frame = new HTTP2HeadersFrame(header); frame.HeaderBlockFragmentLength = header.PayloadLength; bool isPadded = (frame.Flags & HTTP2HeadersFlags.PADDED) != 0; bool isPriority = (frame.Flags & HTTP2HeadersFlags.PRIORITY) != 0; int payloadIdx = 0; if (isPadded) { frame.PadLength = header.Payload[payloadIdx++]; uint subLength = (uint)(1 + (frame.PadLength ?? 0)); if (subLength <= frame.HeaderBlockFragmentLength) { frame.HeaderBlockFragmentLength -= subLength; } //else // throw PROTOCOL_ERROR; } if (isPriority) { frame.IsExclusive = BufferHelper.ReadBit(header.Payload[payloadIdx], 0); frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, payloadIdx); payloadIdx += 4; frame.Weight = header.Payload[payloadIdx++]; uint subLength = 5; if (subLength <= frame.HeaderBlockFragmentLength) { frame.HeaderBlockFragmentLength -= subLength; } //else // throw PROTOCOL_ERROR; } frame.HeaderBlockFragmentIdx = (UInt32)payloadIdx; frame.HeaderBlockFragment = header.Payload; return(frame); }
public static HTTP2FrameHeaderAndPayload CreateGoAwayFrame(UInt32 lastStreamId, HTTP2ErrorCodes error) { // https://httpwg.org/specs/rfc7540.html#GOAWAY HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload(); frame.Type = HTTP2FrameTypes.GOAWAY; frame.Flags = 0; frame.StreamId = 0; frame.Payload = BufferPool.Get(8, true); frame.PayloadLength = 8; BufferHelper.SetUInt31(frame.Payload, 0, lastStreamId); BufferHelper.SetUInt31(frame.Payload, 4, (UInt32)error); return(frame); }
public static HTTP2FrameHeaderAndPayload CreateWindowUpdateFrame(UInt32 streamId, UInt32 windowSizeIncrement) { // https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload(); frame.Type = HTTP2FrameTypes.WINDOW_UPDATE; frame.Flags = 0; frame.StreamId = streamId; frame.Payload = BufferPool.Get(4, true); frame.PayloadLength = 4; BufferHelper.SetBit(0, 0, 0); BufferHelper.SetUInt31(frame.Payload, 0, windowSizeIncrement); return(frame); }
public static HTTP2PriorityFrame ReadPriorityFrame(HTTP2FrameHeaderAndPayload header) { // https://httpwg.org/specs/rfc7540.html#PRIORITY if (header.PayloadLength != 5) { //throw FRAME_SIZE_ERROR } HTTP2PriorityFrame frame = new HTTP2PriorityFrame(header); frame.IsExclusive = BufferHelper.ReadBit(header.Payload[0], 0); frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, 0); frame.Weight = header.Payload[4]; return(frame); }
public static HTTP2FrameHeaderAndPayload CreateSettingsFrame(List <KeyValuePair <HTTP2Settings, UInt32> > settings) { HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload(); frame.Type = HTTP2FrameTypes.SETTINGS; frame.Flags = 0; frame.PayloadLength = (UInt32)settings.Count * 6; frame.Payload = BufferPool.Get(frame.PayloadLength, true); for (int i = 0; i < settings.Count; ++i) { BufferHelper.SetUInt16(frame.Payload, i * 6, (UInt16)settings[i].Key); BufferHelper.SetUInt32(frame.Payload, (i * 6) + 2, settings[i].Value); } return(frame); }
public static HTTP2GoAwayFrame ReadGoAwayFrame(HTTP2FrameHeaderAndPayload header) { // https://httpwg.org/specs/rfc7540.html#GOAWAY HTTP2GoAwayFrame frame = new HTTP2GoAwayFrame(header); frame.ReservedBit = BufferHelper.ReadBit(header.Payload[0], 0); frame.LastStreamId = BufferHelper.ReadUInt31(header.Payload, 0); frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 4); frame.AdditionalDebugDataLength = header.PayloadLength - 8; if (frame.AdditionalDebugDataLength > 0) { frame.AdditionalDebugData = BufferPool.Get(frame.AdditionalDebugDataLength, true); Array.Copy(header.Payload, 0, frame.AdditionalDebugData, 0, frame.AdditionalDebugDataLength); } return(frame); }
public static HTTP2FrameHeaderAndPayload ReadHeader(Stream stream) { byte[] buffer = BufferPool.Get(9, true); StreamRead(stream, buffer, 0, 9); HTTP2FrameHeaderAndPayload header = new HTTP2FrameHeaderAndPayload(); header.PayloadLength = BufferHelper.ReadUInt24(buffer, 0); header.Type = (HTTP2FrameTypes)buffer[3]; header.Flags = buffer[4]; header.StreamId = BufferHelper.ReadUInt31(buffer, 5); BufferPool.Release(buffer); header.Payload = BufferPool.Get(header.PayloadLength, true); StreamRead(stream, header.Payload, 0, header.PayloadLength); return(header); }
public static HTTP2SettingsFrame ReadSettings(HTTP2FrameHeaderAndPayload header) { HTTP2SettingsFrame frame = new HTTP2SettingsFrame(header); if (header.PayloadLength > 0) { int kvpCount = (int)(header.PayloadLength / 6); frame.Settings = new List <KeyValuePair <HTTP2Settings, uint> >(kvpCount); for (int i = 0; i < kvpCount; ++i) { HTTP2Settings key = (HTTP2Settings)BufferHelper.ReadUInt16(header.Payload, i * 6); UInt32 value = BufferHelper.ReadUInt32(header.Payload, (i * 6) + 2); frame.Settings.Add(new KeyValuePair <HTTP2Settings, uint>(key, value)); } } return(frame); }
// This method encodes a string without huffman encoding private static void EncodeRawStringTo(string str, byte[] buffer, ref UInt32 offset) { uint requiredBytesForStr = (uint)System.Text.Encoding.UTF8.GetByteCount(str); int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7); UInt32 originalOffset = offset; buffer[offset] = 0; EncodeInteger(requiredBytesForStr, 7, buffer, ref offset); // Zero out the huffman flag buffer[originalOffset] = BufferHelper.SetBit(buffer[originalOffset], 0, false); if (offset != originalOffset + requiredBytesForLengthPrefix) { throw new Exception(string.Format("offset({0}) != originalOffset({1}) + requiredBytesForLengthPrefix({1})", offset, originalOffset, requiredBytesForLengthPrefix)); } System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, (int)offset); offset += requiredBytesForStr; }
private static void AddCodePointToBuffer(HuffmanEncoder.TableEntry code, byte[] buffer, ref UInt32 offset, ref byte bufferBitIdx, bool finishOnBoundary = false) { for (byte codeBitIdx = 1; codeBitIdx <= code.Bits; ++codeBitIdx) { byte bit = code.GetBitAtIdx(codeBitIdx); buffer[offset] = BufferHelper.SetBit(buffer[offset], bufferBitIdx, bit); // octet boundary reached, proceed to the next octet if (++bufferBitIdx == 8) { if (++offset < buffer.Length) { buffer[offset] = 0; } if (finishOnBoundary) { return; } bufferBitIdx = 0; } } }
public void Decode(HTTP2Stream context, Stream stream, List <KeyValuePair <string, string> > to) { int headerType = stream.ReadByte(); while (headerType != -1) { byte firstDataByte = (byte)headerType; // https://http2.github.io/http2-spec/compression.html#indexed.header.representation if (BufferHelper.ReadBit(firstDataByte, 0) == 1) { var header = ReadIndexedHeader(firstDataByte, stream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - IndexedHeader: {1}", context.Id, header.ToString())); } to.Add(header); } else if (BufferHelper.ReadValue(firstDataByte, 0, 1) == 1) { // https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing if (BufferHelper.ReadValue(firstDataByte, 2, 7) == 0) { // Literal Header Field with Incremental Indexing — New Name var header = ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(firstDataByte, stream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_NewName: {1}", context.Id, header.ToString())); } this.responseTable.Add(header); to.Add(header); } else { // Literal Header Field with Incremental Indexing — Indexed Name var header = ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(firstDataByte, stream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_IndexedName: {1}", context.Id, header.ToString())); } this.responseTable.Add(header); to.Add(header); } } else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 0) { // https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0) { // Literal Header Field without Indexing — New Name var header = ReadLiteralHeaderFieldwithoutIndexing_NewName(firstDataByte, stream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_NewName: {1}", context.Id, header.ToString())); } to.Add(header); } else { // Literal Header Field without Indexing — Indexed Name var header = ReadLiteralHeaderFieldwithoutIndexing_IndexedName(firstDataByte, stream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_IndexedName: {1}", context.Id, header.ToString())); } to.Add(header); } } else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 1) { // https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0) { // Literal Header Field Never Indexed — New Name var header = ReadLiteralHeaderFieldNeverIndexed_NewName(firstDataByte, stream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_NewName: {1}", context.Id, header.ToString())); } to.Add(header); } else { // Literal Header Field Never Indexed — Indexed Name var header = ReadLiteralHeaderFieldNeverIndexed_IndexedName(firstDataByte, stream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_IndexedName: {1}", context.Id, header.ToString())); } to.Add(header); } } else if (BufferHelper.ReadValue(firstDataByte, 0, 2) == 1) { // https://http2.github.io/http2-spec/compression.html#encoding.context.update UInt32 newMaxSize = DecodeInteger(5, firstDataByte, stream); if (HTTPManager.Logger.Level <= Logger.Loglevels.Information) { HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - Dynamic Table Size Update: {1}", context.Id, newMaxSize)); } //this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE] = (UInt16)newMaxSize; this.responseTable.MaxDynamicTableSize = (UInt16)newMaxSize; } else { // ERROR } headerType = stream.ReadByte(); } }
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"); } }
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 string DecodeString(Stream stream) { byte start = (byte)stream.ReadByte(); bool rawString = BufferHelper.ReadBit(start, 0) == 0; UInt32 stringLength = DecodeInteger(7, start, stream); if (rawString) { byte[] buffer = BufferPool.Get(stringLength, true); stream.Read(buffer, 0, (int)stringLength); BufferPool.Release(buffer); return(System.Text.Encoding.UTF8.GetString(buffer, 0, (int)stringLength)); } else { var node = HuffmanEncoder.GetRoot(); byte currentByte = (byte)stream.ReadByte(); byte bitIdx = 0; // 0..7 using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream()) { do { byte bitValue = BufferHelper.ReadBit(currentByte, bitIdx); if (++bitIdx > 7) { stringLength--; if (stringLength > 0) { bitIdx = 0; currentByte = (byte)stream.ReadByte(); } } node = HuffmanEncoder.GetNext(node, bitValue); if (node.Value != 0) { if (node.Value != HuffmanEncoder.EOS) { bufferStream.WriteByte((byte)node.Value); } node = HuffmanEncoder.GetRoot(); } } while (stringLength > 0); byte[] buffer = bufferStream.ToArray(true); string result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)bufferStream.Length); BufferPool.Release(buffer); return(result); } } }