public async Task<Http2Response> Send (CancellationToken cancelToken, Uri uri, HttpMethod method, NameValueCollection headers = null, Stream data = null) { var semaphoreClose = new SemaphoreSlim(0); await connection.Connect (); var stream = await streamManager.Get (); stream.OnFrameReceived += async (frame) => { // Check for an end of stream state if (stream.State == StreamState.HalfClosedRemote || stream.State == StreamState.Closed) semaphoreClose.Release (); }; var allHeaders = new NameValueCollection (); allHeaders.Add (":method", method.Method.ToUpperInvariant ()); allHeaders.Add (":path", uri.PathAndQuery); allHeaders.Add (":scheme", uri.Scheme); allHeaders.Add (":authority", uri.Authority); if (headers != null && headers.Count > 0) allHeaders.Add (headers); var headerData = Util.PackHeaders (allHeaders, connection.Settings.HeaderTableSize); var numFrames = Math.Ceiling ((double)headerData.Length / (double)connection.Settings.MaxFrameSize); for (int i = 0; i < numFrames; i++) { // First item is headers frame, others are continuation IFrameContainsHeaders frame = (i == 0) ? (IFrameContainsHeaders)new HeadersFrame (stream.StreamIdentifer) : (IFrameContainsHeaders)new ContinuationFrame (stream.StreamIdentifer); // Set end flag if this is the last item if (i == numFrames - 1) frame.EndHeaders = true; var maxFrameSize = connection.Settings.MaxFrameSize; var amt = maxFrameSize; if ( i * maxFrameSize + amt > headerData.Length) amt = (uint)headerData.Length - (uint)(i * maxFrameSize); frame.HeaderBlockFragment = new byte[amt]; Array.Copy (headerData, i * maxFrameSize, frame.HeaderBlockFragment, 0, amt); await connection.QueueFrame (frame); } // TODO: Need to send multiple frames if the frame content length is too big // This will require checking what the actual if (data != null) { var sentEndOfStream = false; var supportsPosLength = true; // Keep track of if we threw exceptions trying pos/len of stream // Break stream up into data frames within allowed size var dataFrameBuffer = new byte[connection.Settings.MaxFrameSize]; while (true) { var rd = await data.ReadAsync (dataFrameBuffer, 0, dataFrameBuffer.Length); if (rd <= 0) break; var dataFrame = new DataFrame (stream.StreamIdentifer); dataFrameBuffer.CopyTo (dataFrame.Data, 0); try { // See if the stream supports Length / Position to try and detect EOS // we also want to see if we previously had an exception trying this // and not try again if we did, since throwing exceptions every single // read operation is wasteful if (supportsPosLength && data.Position >= data.Length) { dataFrame.EndStream = true; sentEndOfStream = true; } } catch { supportsPosLength = false; sentEndOfStream = false; } await connection.QueueFrame (dataFrame); } // Send an empty frame with end of stream flag if (!sentEndOfStream) await connection.QueueFrame (new DataFrame (stream.StreamIdentifer) { EndStream = true }); } if (!await semaphoreClose.WaitAsync (ConnectionSettings.ConnectionTimeout, cancelToken)) throw new TimeoutException (); var responseData = new List<byte> (); var rxHeaderData = new List<byte> (); foreach (var f in stream.ReceivedFrames) { if (f.Type == FrameType.Headers || f.Type == FrameType.Continuation) { // Get the header data and add it to our buffer var fch = (IFrameContainsHeaders)f; if (fch.HeaderBlockFragment != null && fch.HeaderBlockFragment.Length > 0) rxHeaderData.AddRange (fch.HeaderBlockFragment); } else if (f.Type == FrameType.PushPromise) { // TODO: In the future we need to implement PushPromise beyond grabbing header data var fch = (IFrameContainsHeaders)f; if (fch.HeaderBlockFragment != null && fch.HeaderBlockFragment.Length > 0) rxHeaderData.AddRange (fch.HeaderBlockFragment); } else if (f.Type == FrameType.Data) { responseData.AddRange ((f as DataFrame).Data); } else if (f.Type == FrameType.GoAway) { var fga = f as GoAwayFrame; if (fga != null && fga.AdditionalDebugData != null && fga.AdditionalDebugData.Length > 0) responseData.AddRange (fga.AdditionalDebugData); } } var responseHeaders = Util.UnpackHeaders (rxHeaderData.ToArray (), connection.Settings.MaxHeaderListSize.HasValue ? (int)connection.Settings.MaxHeaderListSize.Value : 8192, (int)connection.Settings.HeaderTableSize); var strStatus = "500"; if (responseHeaders [":status"] != null) strStatus = responseHeaders [":status"]; var statusCode = HttpStatusCode.OK; Enum.TryParse<HttpStatusCode> (strStatus, out statusCode); // Remove the stream from being tracked since we're done with it await streamManager.Cleanup (stream.StreamIdentifer); return new Http2Response { Status = statusCode, Stream = stream, Headers = responseHeaders, Body = responseData.ToArray () }; }
public async Task<Http2Response> Send (Uri uri, HttpMethod method, NameValueCollection headers = null, byte[] data = null) { var semaphoreClose = new SemaphoreSlim(0); await connection.Connect (); var stream = await connection.CreateStream (); stream.OnFrameReceived += (frame) => { if (stream.State == StreamState.Closed) semaphoreClose.Release(); }; //await connection.SendFrame(new SettingsFrame()); var headersFrame = new HeadersFrame (stream.StreamIdentifer); headersFrame.Headers.Add (":method", method.Method.ToUpperInvariant ()); headersFrame.Headers.Add (":path", uri.PathAndQuery); headersFrame.Headers.Add (":scheme", uri.Scheme); headersFrame.Headers.Add (":authority", uri.Authority); if (headers != null && headers.Count > 0) headersFrame.Headers.Add (headers); headersFrame.EndHeaders = true; if (data != null && data.Length > 0) { await connection.SendFrame (headersFrame); var dataFrame = new DataFrame (stream.StreamIdentifer); dataFrame.Data = data; dataFrame.EndStream = true; await connection.SendFrame (dataFrame); } else { headersFrame.EndStream = true; await connection.SendFrame (headersFrame); } if (!await semaphoreClose.WaitAsync (connection.ConnectionTimeout)) throw new TimeoutException (); var responseData = new List<byte> (); var responseHeaders = new NameValueCollection (); foreach (var f in stream.Frames) { if (f.Type == FrameType.Headers) { responseHeaders = (f as HeadersFrame)?.Headers ?? new NameValueCollection (); } else if (f.Type == FrameType.Data) { responseData.AddRange ((f as DataFrame).Data); } else if (f.Type == FrameType.Continuation) { var h = (f as ContinuationFrame).Headers ?? new NameValueCollection (); responseHeaders.Add (h); } else if (f.Type == FrameType.GoAway) { var fga = f as GoAwayFrame; if (fga != null && fga.AdditionalDebugData != null && fga.AdditionalDebugData.Length > 0) responseData.AddRange (fga.AdditionalDebugData); } } var strStatus = "500"; if (responseHeaders [":status"] != null) strStatus = responseHeaders [":status"]; var statusCode = HttpStatusCode.OK; Enum.TryParse<HttpStatusCode> (strStatus, out statusCode); return new Http2Response { Status = statusCode, Stream = stream, Headers = responseHeaders, Body = responseData.ToArray () }; }
async void read () { int rx = 0; byte[] b = new byte[4096]; while (true) { try { rx = await clientStream.ReadAsync(b, 0, b.Length); } catch { rx = -1; } if (rx > 0) { Console.WriteLine ("RX: {0} bytes", rx); for (int i = 0; i < rx; i++) buffer.Add (b [i]); while (true) { // We need at least 9 bytes to process the frame // 9 octets is the frame header length if (buffer.Count < 9) break; // Find out the frame length // which is a 24 bit uint, so we need to convert this as c# uint is 32 bit var flen = new byte[4]; flen [0] = 0x0; flen [1] = buffer.ElementAt (0); flen [2] = buffer.ElementAt (1); flen [3] = buffer.ElementAt (2); var frameLength = BitConverter.ToUInt32 (flen.EnsureBigEndian (), 0); // If we are expecting a payload that's bigger than what's in our buffer // we should keep reading from the stream if (buffer.Count - 9 < frameLength) break; // If we made it this far, the buffer has all the data we need, let's get it out to process var data = buffer.GetRange (0, (int)frameLength + 9).ToArray (); // remove the processed info from the buffer buffer.RemoveRange (0, (int)frameLength + 9); // Get the Frame Type so we can instantiate the right subclass var frameType = data [3]; // 4th byte in frame header is TYPE // Don't need the flags yet //var frameFlags = data [4]; // 5th byte is FLAGS // we need to turn the stream id into a uint var frameStreamIdData = new byte[4]; Array.Copy (data, 5, frameStreamIdData, 0, 4); uint frameStreamId = Util.ConvertFromUInt31 (frameStreamIdData.EnsureBigEndian ()); Frame frame = null; var ft = (FrameType)frameType; switch (ft) { case FrameType.Data: frame = new DataFrame (); break; case FrameType.Headers: frame = new HeadersFrame (); break; case FrameType.Priority: frame = new PriorityFrame (); break; case FrameType.RstStream: frame = new RstStreamFrame (); break; case FrameType.Settings: frame = new SettingsFrame (); break; case FrameType.PushPromise: frame = new PushPromiseFrame (); break; case FrameType.Ping: frame = new PingFrame (); break; case FrameType.GoAway: frame = new GoAwayFrame (); break; case FrameType.WindowUpdate: frame = new WindowUpdateFrame (); break; case FrameType.Continuation: frame = new ContinuationFrame (); break; } try { // Call the specific subclass implementation to parse if (frame != null) frame.Parse (data); } catch (Exception ex) { Console.WriteLine ("Parsing Frame Failed: " + ex); throw ex; } // If we are waiting on the connection preface from server // and it's the right frame type, set our resetevent if (ft == FrameType.Settings && resetEventConnectionSettingsFrame != null && !resetEventConnectionSettingsFrame.IsSet) { var settingsFrame = frame as SettingsFrame; // ack the settings from the server settingsFrame.Ack = true; await SendFrame (settingsFrame); resetEventConnectionSettingsFrame.Set (); } try { if (frameStreamId == 0) { foreach (var s in Streams) { if (s.Value.State != StreamState.Closed) s.Value.ProcessFrame(frame); } } else { var stream = await GetStream(frameStreamId); stream.ProcessFrame(frame); } } catch (Exception ex) { Console.WriteLine ("Error Processing Frame: " + ex); throw ex; } } } else { // TODO: Connection error //throw new Exception ("Connection Error"); break; } } Disconnect(); }