예제 #1
0
            public Http2Stream(HttpRequestMessage request, Http2Connection connection, int streamId, int initialWindowSize)
            {
                _connection = connection;
                _streamId   = streamId;

                _state = StreamState.ExpectingStatus;

                _request = request;
                _shouldSendRequestBody = true;

                _disposed = false;

                _responseBuffer = new ArrayBuffer(InitialStreamBufferSize, usePool: true);

                _pendingWindowUpdate = 0;

                _streamWindow = new CreditManager(this, nameof(_streamWindow), initialWindowSize);

                _headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024;

                if (NetEventSource.IsEnabled)
                {
                    Trace($"{request}, {nameof(initialWindowSize)}={initialWindowSize}");
                }
            }
예제 #2
0
            internal void OnPingAckReceived(long payload, Http2Connection connection)
            {
                if (_state != State.PingSent && _state != State.TerminatingMayReceivePingAck)
                {
                    if (NetEventSource.Log.IsEnabled())
                    {
                        connection.Trace($"[FlowControl] Unexpected PING ACK in state {_state}");
                    }
                    ThrowProtocolError();
                }

                if (_state == State.TerminatingMayReceivePingAck)
                {
                    _state = State.Disabled;
                    return;
                }

                // RTT PINGs always carry negative payload, positive values indicate a response to KeepAlive PING.
                Debug.Assert(payload < 0);

                if (_pingCounter != payload)
                {
                    if (NetEventSource.Log.IsEnabled())
                    {
                        connection.Trace($"[FlowControl] Unexpected RTT PING ACK payload {payload}, should be {_pingCounter}.");
                    }
                    ThrowProtocolError();
                }

                RefreshRtt(connection);
                _state = State.Waiting;
            }
예제 #3
0
            internal void OnDataOrHeadersReceived(Http2Connection connection)
            {
                if (_state != State.Waiting)
                {
                    return;
                }

                long now     = Stopwatch.GetTimestamp();
                bool initial = _initialBurst > 0;

                if (initial || now - _pingSentTimestamp > PingIntervalInTicks)
                {
                    if (initial)
                    {
                        _initialBurst--;
                    }

                    // Send a PING
                    _pingCounter--;
                    if (NetEventSource.Log.IsEnabled())
                    {
                        connection.Trace($"[FlowControl] Sending RTT PING with payload {_pingCounter}");
                    }
                    connection.LogExceptions(connection.SendPingAsync(_pingCounter, isAck: false));
                    _pingSentTimestamp = now;
                    _state             = State.PingSent;
                }
            }
            private void AdjustWindowDynamic(int bytesConsumed, Http2Stream stream)
            {
                _deliveredBytes += bytesConsumed;

                if (_deliveredBytes < StreamWindowThreshold)
                {
                    return;
                }

                int             windowUpdateIncrement = _deliveredBytes;
                long            currentTime           = Stopwatch.GetTimestamp();
                Http2Connection connection            = stream.Connection;

                TimeSpan rtt = connection._rttEstimator.MinRtt;

                if (rtt > TimeSpan.Zero && _streamWindowSize < MaxStreamWindowSize)
                {
                    TimeSpan dt = Stopwatch.GetElapsedTime(_lastWindowUpdate, currentTime);

                    // We are detecting bursts in the amount of data consumed within a single 'dt' window update period.
                    // The value "_deliveredBytes / dt" correlates with the bandwidth of the connection.
                    // We need to extend the window, if the bandwidth-delay product grows over the current window size.
                    // To enable empirical fine tuning, we apply a configurable multiplier (_windowScaleThresholdMultiplier) to the window size, which defaults to 1.0
                    //
                    // The condition to extend the window is:
                    // (_deliveredBytes / dt) * rtt > _streamWindowSize * _windowScaleThresholdMultiplier
                    //
                    // Which is reordered into the form below, to avoid the division:
                    if (_deliveredBytes * (double)rtt.Ticks > _streamWindowSize * dt.Ticks * WindowScaleThresholdMultiplier)
                    {
                        int extendedWindowSize = Math.Min(MaxStreamWindowSize, _streamWindowSize * 2);
                        windowUpdateIncrement += extendedWindowSize - _streamWindowSize;
                        _streamWindowSize      = extendedWindowSize;

                        if (NetEventSource.Log.IsEnabled())
                        {
                            stream.Trace($"[FlowControl] Updated Stream Window. StreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}");
                        }

                        Debug.Assert(_streamWindowSize <= MaxStreamWindowSize);
                        if (_streamWindowSize == MaxStreamWindowSize)
                        {
                            if (NetEventSource.Log.IsEnabled())
                            {
                                stream.Trace($"[FlowControl] StreamWindowSize reached the configured maximum of {MaxStreamWindowSize}.");
                            }
                        }
                    }
                }

                _deliveredBytes = 0;

                Task sendWindowUpdateTask = connection.SendWindowUpdateAsync(stream.StreamId, windowUpdateIncrement);

                connection.LogExceptions(sendWindowUpdateTask);

                _lastWindowUpdate = currentTime;
            }
 internal void OnInitialSettingsAckReceived(Http2Connection connection)
 {
     if (_state == State.Disabled)
     {
         return;
     }
     RefreshRtt(connection);
     _state = State.Waiting;
 }
예제 #6
0
            public Http2Stream(Http2Connection connection)
            {
                _connection = connection;

                _streamId = connection.AddStream(this);

                _syncObject = new object();
                _disposed   = false;

                _responseBuffer = new ArrayBuffer(InitialBufferSize);
            }
            private void RefreshRtt(Http2Connection connection)
            {
                long     prevRtt    = _minRtt == 0 ? long.MaxValue : _minRtt;
                TimeSpan currentRtt = Stopwatch.GetElapsedTime(_pingSentTimestamp);
                long     minRtt     = Math.Min(prevRtt, currentRtt.Ticks);

                Interlocked.Exchange(ref _minRtt, minRtt); // MinRtt is being queried from another thread

                if (NetEventSource.Log.IsEnabled())
                {
                    connection.Trace($"[FlowControl] Updated MinRtt: {MinRtt.TotalMilliseconds} ms");
                }
            }
            public Http2StreamWindowManager(Http2Connection connection, Http2Stream stream)
            {
                HttpConnectionSettings settings = connection._pool.Settings;

                _streamWindowSize = settings._initialHttp2StreamWindowSize;
                _deliveredBytes   = 0;
                _lastWindowUpdate = default;

                if (NetEventSource.Log.IsEnabled())
                {
                    stream.Trace($"[FlowControl] InitialClientStreamWindowSize: {StreamWindowSize}, StreamWindowThreshold: {StreamWindowThreshold}, WindowScaleThresholdMultiplier: {WindowScaleThresholdMultiplier}");
                }
            }
            private void AjdustWindowStatic(int bytesConsumed, Http2Stream stream)
            {
                _deliveredBytes += bytesConsumed;
                if (_deliveredBytes < StreamWindowThreshold)
                {
                    return;
                }

                int windowUpdateIncrement = _deliveredBytes;

                _deliveredBytes = 0;

                Http2Connection connection           = stream.Connection;
                Task            sendWindowUpdateTask = connection.SendWindowUpdateAsync(stream.StreamId, windowUpdateIncrement);

                connection.LogExceptions(sendWindowUpdateTask);
            }
예제 #10
0
            public Http2Stream(HttpRequestMessage request, Http2Connection connection, int streamId, int initialWindowSize)
            {
                _connection = connection;
                _streamId   = streamId;

                _state = StreamState.ExpectingHeaders;

                _request = request;

                _disposed = false;

                _responseBuffer = new ArrayBuffer(InitialStreamBufferSize, usePool: true);

                _pendingWindowUpdate = 0;

                _streamWindow = new CreditManager(initialWindowSize);
            }
예제 #11
0
            public Http2Stream(HttpRequestMessage request, Http2Connection connection, int streamId, int initialWindowSize)
            {
                _connection = connection;
                _streamId   = streamId;

                _state = StreamState.ExpectingStatus;

                _request = request;
                _shouldSendRequestBody = true;

                _disposed = false;

                _responseBuffer = new ArrayBuffer(InitialStreamBufferSize, usePool: true);

                _pendingWindowUpdate = 0;

                _streamWindow = new CreditManager(initialWindowSize);

                _headerBudgetRemaining = connection._pool.Settings._maxResponseHeadersLength * 1024;
            }
예제 #12
0
            internal void OnPingAckReceived(long payload, Http2Connection connection)
            {
                if (_state != State.PingSent && _state != State.TerminatingMayReceivePingAck)
                {
                    ThrowProtocolError();
                }

                if (_state == State.TerminatingMayReceivePingAck)
                {
                    _state = State.Disabled;
                    return;
                }

                // RTT PINGs always carry negative payload, positive values indicate a response to KeepAlive PING.
                Debug.Assert(payload < 0);

                if (_pingCounter != payload)
                {
                    ThrowProtocolError();
                }

                RefreshRtt(connection);
                _state = State.Waiting;
            }
예제 #13
0
            internal void OnDataOrHeadersReceived(Http2Connection connection)
            {
                if (_state != State.Waiting)
                {
                    return;
                }

                long now     = Stopwatch.GetTimestamp();
                bool initial = _initialBurst > 0;

                if (initial || now - _pingSentTimestamp > PingIntervalInTicks)
                {
                    if (initial)
                    {
                        _initialBurst--;
                    }

                    // Send a PING
                    _pingCounter--;
                    connection.LogExceptions(connection.SendPingAsync(_pingCounter, isAck: false));
                    _pingSentTimestamp = now;
                    _state             = State.PingSent;
                }
            }
예제 #14
0
 public void InvalidateHttp2Connection(Http2Connection connection)
 {
     Debug.Assert(_http2Connection == connection);
     _http2Connection = null;
 }
예제 #15
0
        GetHttp2ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            Debug.Assert(_kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel);

            // See if we have an HTTP2 connection
            Http2Connection http2Connection = _http2Connection;

            if (http2Connection != null)
            {
                if (NetEventSource.IsEnabled)
                {
                    Trace("Using existing HTTP2 connection.");
                }
                return(http2Connection, false, null);
            }

            // Ensure that the connection creation semaphore is created
            if (_http2ConnectionCreateLock == null)
            {
                lock (SyncObj)
                {
                    if (_http2ConnectionCreateLock == null)
                    {
                        _http2ConnectionCreateLock = new SemaphoreSlim(1);
                    }
                }
            }

            // Try to establish an HTTP2 connection
            Socket           socket           = null;
            SslStream        sslStream        = null;
            TransportContext transportContext = null;

            // Serialize creation attempt
            await _http2ConnectionCreateLock.WaitAsync().ConfigureAwait(false);

            try
            {
                if (_http2Connection != null)
                {
                    // Someone beat us to it

                    if (NetEventSource.IsEnabled)
                    {
                        Trace("Using existing HTTP2 connection.");
                    }

                    return(_http2Connection, false, null);
                }

                // Recheck if HTTP2 has been disabled by a previous attempt.
                if (_http2Enabled)
                {
                    if (NetEventSource.IsEnabled)
                    {
                        Trace("Attempting new HTTP2 connection.");
                    }

                    Stream stream;
                    HttpResponseMessage failureResponse;
                    (socket, stream, transportContext, failureResponse) =
                        await ConnectAsync(request, true, cancellationToken).ConfigureAwait(false);

                    if (failureResponse != null)
                    {
                        return(null, true, failureResponse);
                    }

                    sslStream = (SslStream)stream;
                    if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2)
                    {
                        // The server accepted our request for HTTP2.

                        if (sslStream.SslProtocol < SslProtocols.Tls12)
                        {
                            throw new HttpRequestException(SR.net_http_invalid_response);
                        }

                        http2Connection = new Http2Connection(this, sslStream);
                        await http2Connection.SetupAsync().ConfigureAwait(false);

                        Debug.Assert(_http2Connection == null);
                        _http2Connection = http2Connection;

                        if (NetEventSource.IsEnabled)
                        {
                            Trace("New HTTP2 connection established.");
                        }

                        return(_http2Connection, true, null);
                    }
                }
            }
            finally
            {
                _http2ConnectionCreateLock.Release();
            }

            if (sslStream != null)
            {
                // We established an SSL connection, but the server denied our request for HTTP2.
                // Continue as an HTTP/1.1 connection.
                if (NetEventSource.IsEnabled)
                {
                    Trace("Server does not support HTTP2; disabling HTTP2 use and proceeding with HTTP/1.1 connection");
                }

                bool canUse = true;
                lock (SyncObj)
                {
                    _http2Enabled = false;

                    if (_associatedConnectionCount < _maxConnections)
                    {
                        IncrementConnectionCountNoLock();
                    }
                    else
                    {
                        // We are in the weird situation of having established a new HTTP 1.1 connection
                        // when we were already at the maximum for HTTP 1.1 connections.
                        // Just discard this connection and get another one from the pool.
                        // This should be a really rare situation to get into, since it would require
                        // the user to make multiple HTTP 1.1-only requests first before attempting an
                        // HTTP2 request, and the server failing to accept HTTP2.
                        canUse = false;
                    }
                }

                if (canUse)
                {
                    return(ConstructHttp11Connection(socket, sslStream, transportContext), true, null);
                }
                else
                {
                    if (NetEventSource.IsEnabled)
                    {
                        Trace("Discarding downgraded HTTP/1.1 connection because connection limit is exceeded");
                    }

                    sslStream.Close();
                }
            }

            // If we reach this point, it means we need to fall back to a (new or existing) HTTP/1.1 connection.
            return(await GetHttpConnectionAsync(request, cancellationToken));
        }