Example #1
0
        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));
            }
        }
        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");
            }
        }