Exemplo n.º 1
0
        private static void DeleteEntityImpl(Uri uri, bool removeFromLibrary = true, bool useLocking = false)
        {
            HTTPCacheFileInfo info;
            bool inStats = library.TryGetValue(uri, out info);

            if (inStats)
            {
                info.Delete();
            }

            if (inStats && removeFromLibrary)
            {
                if (useLocking)
                {
                    rwLock.EnterWriteLock();
                }
                try
                {
                    library.Remove(uri);
                    UsedIndexes.Remove(info.MappedNameIDX);
                }
                finally
                {
                    if (useLocking)
                    {
                        rwLock.ExitWriteLock();
                    }
                }
            }

            PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
        }
Exemplo n.º 2
0
        public static void Remove(Uri uri, string name)
        {
            Load();

            rwLock.EnterWriteLock();
            try
            {
                for (int i = 0; i < Cookies.Count;)
                {
                    var cookie = Cookies[i];

                    if (cookie.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && uri.Host.IndexOf(cookie.Domain) != -1)
                    {
                        Cookies.RemoveAt(i);
                    }
                    else
                    {
                        i++;
                    }
                }
            }
            finally
            {
                rwLock.ExitWriteLock();
            }

            PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
        }
Exemplo n.º 3
0
        /// <summary>
        /// Will add a new, or overwrite an old cookie if already exists.
        /// </summary>
        public static void Set(Cookie cookie)
        {
            Load();

            rwLock.EnterWriteLock();
            try
            {
                int idx;
                Find(cookie, out idx);

                if (idx >= 0)
                {
                    Cookies[idx] = cookie;
                }
                else
                {
                    Cookies.Add(cookie);
                }
            }
            finally
            {
                rwLock.ExitWriteLock();
            }

            PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
        }
Exemplo n.º 4
0
        public void AddHeader(string name, string value)
        {
            name = name.ToLower();

            if (Headers == null)
            {
                Headers = new Dictionary <string, List <string> >();
            }

            List <string> values;

            if (!Headers.TryGetValue(name, out values))
            {
                Headers.Add(name, values = new List <string>(1));
            }

            values.Add(value);

            bool isFromCache = false;

#if !BESTHTTP_DISABLE_CACHING
            isFromCache = this.IsFromCache;
#endif
            if (!isFromCache && name.Equals("alt-svc", StringComparison.Ordinal))
            {
                PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(this.baseRequest.CurrentUri.Host, this)));
            }
        }
Exemplo n.º 5
0
        /// <summary>
        /// Deletes all expired or 'old' cookies, and will keep the sum size of cookies under the given size.
        /// </summary>
        internal static void Maintain(bool sendEvent)
        {
            // It's not the same as in the rfc:
            //  http://tools.ietf.org/html/rfc6265#section-5.3

            rwLock.EnterWriteLock();
            try
            {
                uint size = 0;

                for (int i = 0; i < Cookies.Count;)
                {
                    var cookie = Cookies[i];

                    // Remove expired or not used cookies
                    if (!cookie.WillExpireInTheFuture() || (cookie.LastAccess + AccessThreshold) < DateTime.UtcNow)
                    {
                        Cookies.RemoveAt(i);
                    }
                    else
                    {
                        if (!cookie.IsSession)
                        {
                            size += cookie.GuessSize();
                        }
                        i++;
                    }
                }

                if (size > HTTPManager.CookieJarSize)
                {
                    Cookies.Sort();

                    while (size > HTTPManager.CookieJarSize && Cookies.Count > 0)
                    {
                        var cookie = Cookies[0];
                        Cookies.RemoveAt(0);

                        size -= cookie.GuessSize();
                    }
                }
            }
            catch
            { }
            finally
            {
                rwLock.ExitWriteLock();
            }

            if (sendEvent)
            {
                PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
            }
        }
Exemplo n.º 6
0
        public static void AbortAll()
        {
            HTTPManager.Logger.Information("HTTPManager", "AbortAll called!");

            // This is an immediate shutdown request!

            RequestEventHelper.Clear();
            ConnectionEventHelper.Clear();
            PluginEventHelper.Clear();
            ProtocolEventHelper.Clear();

            HostManager.Shutdown();

            ProtocolEventHelper.CancelActiveProtocols();
        }
Exemplo n.º 7
0
        /// <summary>
        /// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function.
        /// </summary>
        public static void OnUpdate()
        {
            RequestEventHelper.ProcessQueue();
            ConnectionEventHelper.ProcessQueue();
            ProtocolEventHelper.ProcessQueue();
            PluginEventHelper.ProcessQueue();

            BestHTTP.Extensions.Timer.Process();

            if (heartbeats != null)
            {
                heartbeats.Update();
            }

            BufferPool.Maintain();
        }
Exemplo n.º 8
0
        private static void ClearImpl()
        {
            if (!IsSupported)
            {
                return;
            }

            CheckSetup();

            rwLock.EnterWriteLock();
            try
            {
                // GetFiles will return a string array that contains the files in the folder with the full path
                string[] cacheEntries = HTTPManager.IOService.GetFiles(CacheFolder);

                if (cacheEntries != null)
                {
                    for (int i = 0; i < cacheEntries.Length; ++i)
                    {
                        // We need a try-catch block because between the Directory.GetFiles call and the File.Delete calls a maintenance job, or other file operations can delete any file from the cache folder.
                        // So while there might be some problem with any file, we don't want to abort the whole for loop
                        try
                        {
                            HTTPManager.IOService.FileDelete(cacheEntries[i]);
                        }
                        catch
                        { }
                    }
                }
            }
            finally
            {
                UsedIndexes.Clear();
                library.Clear();
                NextNameIDX = 0x0001;

                InClearThread = false;

                rwLock.ExitWriteLock();

                PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
            }
        }
Exemplo n.º 9
0
        public static void OnQuit()
        {
            HTTPManager.Logger.Information("HTTPManager", "OnQuit called!");

            IsQuitting = true;

            AbortAll();

#if !BESTHTTP_DISABLE_CACHING
            PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
#endif

#if !BESTHTTP_DISABLE_COOKIES
            PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
#endif

            OnUpdate();

            HostManager.Clear();
        }
Exemplo n.º 10
0
        public static void TryStoreInCache(HTTPRequest request)
        {
            // if UseStreaming && !DisableCache then we already wrote the response to the cache
            if (!request.UseStreaming &&
                !request.DisableCache &&
                request.Response != null &&
                HTTPCacheService.IsSupported &&
                HTTPCacheService.IsCacheble(request.CurrentUri, request.MethodType, request.Response))
            {
                if (request.IsRedirected)
                {
                    HTTPCacheService.Store(request.Uri, request.MethodType, request.Response);
                }
                else
                {
                    HTTPCacheService.Store(request.CurrentUri, request.MethodType, request.Response);
                }

                PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
            }
        }
Exemplo n.º 11
0
        public void AddHeader(string name, string value)
        {
            name = name.ToLower();

            if (Headers == null)
            {
                Headers = new Dictionary <string, List <string> >();
            }

            List <string> values;

            if (!Headers.TryGetValue(name, out values))
            {
                Headers.Add(name, values = new List <string>(1));
            }

            values.Add(value);

            if (name.Equals("alt-svc", StringComparison.Ordinal))
            {
                PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(this.baseRequest.CurrentUri.Host, this)));
            }
        }
Exemplo n.º 12
0
        public static void HandleResponse(string context, HTTPRequest request, out bool resendRequest, out HTTPConnectionStates proposedConnectionState, ref KeepAliveHeader keepAlive)
        {
            resendRequest           = false;
            proposedConnectionState = HTTPConnectionStates.Processing;

            if (request.Response != null)
            {
#if !BESTHTTP_DISABLE_COOKIES
                // Try to store cookies before we do anything else, as we may remove the response deleting the cookies as well.
                if (request.IsCookiesEnabled && CookieJar.Set(request.Response))
                {
                    PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
                }
#endif

                switch (request.Response.StatusCode)
                {
                // Not authorized
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
                case 401:
                {
                    string authHeader = DigestStore.FindBest(request.Response.GetHeaderValues("www-authenticate"));
                    if (!string.IsNullOrEmpty(authHeader))
                    {
                        var digest = DigestStore.GetOrCreate(request.CurrentUri);
                        digest.ParseChallange(authHeader);

                        if (request.Credentials != null && digest.IsUriProtected(request.CurrentUri) && (!request.HasHeader("Authorization") || digest.Stale))
                        {
                            resendRequest = true;
                        }
                    }

                    goto default;
                }

                // Redirected
                case 301:     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
                case 302:     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3
                case 307:     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.8
                case 308:     // http://tools.ietf.org/html/rfc7238
                {
                    if (request.RedirectCount >= request.MaxRedirects)
                    {
                        goto default;
                    }
                    request.RedirectCount++;

                    string location = request.Response.GetFirstHeaderValue("location");
                    if (!string.IsNullOrEmpty(location))
                    {
                        Uri redirectUri = ConnectionHelper.GetRedirectUri(request, location);

                        if (HTTPManager.Logger.Level == Logger.Loglevels.All)
                        {
                            HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - Redirected to Location: '{1}' redirectUri: '{1}'", context, location, redirectUri));
                        }

                        // Let the user to take some control over the redirection
                        if (!request.CallOnBeforeRedirection(redirectUri))
                        {
                            HTTPManager.Logger.Information("HTTPConnection", string.Format("[{0}] OnBeforeRedirection returned False", context));
                            goto default;
                        }

                        // Remove the previously set Host header.
                        request.RemoveHeader("Host");

                        // Set the Referer header to the last Uri.
                        request.SetHeader("Referer", request.CurrentUri.ToString());

                        // Set the new Uri, the CurrentUri will return this while the IsRedirected property is true
                        request.RedirectUri = redirectUri;

                        // Discard the redirect response, we don't need it any more
                        request.Response = null;

                        request.IsRedirected = true;

                        resendRequest = true;
                    }
                    else
                    {
                        throw new Exception(string.Format("[{0}] Got redirect status({1}) without 'location' header!", context, request.Response.StatusCode.ToString()));
                    }

                    goto default;
                }

#if !BESTHTTP_DISABLE_CACHING
                case 304:
                    if (request.DisableCache)
                    {
                        break;
                    }

                    if (ConnectionHelper.LoadFromCache(context, request))
                    {
                        HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - HandleResponse - Loaded from cache successfully!", context));
                    }
                    else
                    {
                        HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - HandleResponse - Loaded from cache failed!", context));
                        resendRequest = true;
                    }

                    break;
#endif

                default:
#if !BESTHTTP_DISABLE_CACHING
                    ConnectionHelper.TryStoreInCache(request);
#endif
                    break;
                }

                // Closing the stream is done manually?
                if (!request.Response.IsClosedManually)
                {
                    // If we have a response and the server telling us that it closed the connection after the message sent to us, then
                    //  we will close the connection too.
                    bool closeByServer = request.Response.HasHeaderWithValue("connection", "close");
                    bool closeByClient = !request.IsKeepAlive;

                    if (closeByServer || closeByClient)
                    {
                        proposedConnectionState = HTTPConnectionStates.Closed;
                    }
                    else if (request.Response != null)
                    {
                        var keepAliveheaderValues = request.Response.GetHeaderValues("keep-alive");
                        if (keepAliveheaderValues != null && keepAliveheaderValues.Count > 0)
                        {
                            if (keepAlive == null)
                            {
                                keepAlive = new KeepAliveHeader();
                            }
                            keepAlive.Parse(keepAliveheaderValues);
                        }
                    }
                }
            }
        }
Exemplo n.º 13
0
        private static void MaintananceImpl(HTTPCacheMaintananceParams maintananceParam)
        {
            CheckSetup();

            rwLock.EnterWriteLock();
            try
            {
                // Delete cache entries older than the given time.
                DateTime deleteOlderAccessed             = DateTime.UtcNow - maintananceParam.DeleteOlder;
                List <HTTPCacheFileInfo> removedEntities = new List <HTTPCacheFileInfo>();
                foreach (var kvp in library)
                {
                    if (kvp.Value.LastAccess < deleteOlderAccessed)
                    {
                        DeleteEntityImpl(kvp.Key, false, false);
                        removedEntities.Add(kvp.Value);
                    }
                }

                for (int i = 0; i < removedEntities.Count; ++i)
                {
                    library.Remove(removedEntities[i].Uri);
                    UsedIndexes.Remove(removedEntities[i].MappedNameIDX);
                }
                removedEntities.Clear();

                ulong cacheSize = GetCacheSizeImpl();

                // This step will delete all entries starting with the oldest LastAccess property while the cache size greater then the MaxCacheSize in the given param.
                if (cacheSize > maintananceParam.MaxCacheSize)
                {
                    List <HTTPCacheFileInfo> fileInfos = new List <HTTPCacheFileInfo>(library.Count);

                    foreach (var kvp in library)
                    {
                        fileInfos.Add(kvp.Value);
                    }

                    fileInfos.Sort();

                    int idx = 0;
                    while (cacheSize >= maintananceParam.MaxCacheSize && idx < fileInfos.Count)
                    {
                        try
                        {
                            var   fi     = fileInfos[idx];
                            ulong length = (ulong)fi.BodyLength;

                            DeleteEntityImpl(fi.Uri);

                            cacheSize -= length;
                        }
                        catch
                        { }
                        finally
                        {
                            ++idx;
                        }
                    }
                }
            }
            finally
            {
                InMaintainenceThread = false;
                rwLock.ExitWriteLock();

                PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
            }
        }
Exemplo n.º 14
0
        public void RunHandler()
        {
            HTTPManager.Logger.Information("HTTP2Handler", "Processing thread up and running!", this.Context);

            Thread.CurrentThread.Name = "BestHTTP.HTTP2 Process";

            PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ReadThread);

            try
            {
                bool atLeastOneStreamHasAFrameToSend = true;

                this.HPACKEncoder = new HPACKEncoder(this, 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.InitiatedMySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] = (uint)(HTTPManager.HTTP2Settings.EnableConnectProtocol ? 1 : 0);
                    this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_PUSH]             = 0;
                    this.settings.SendChanges(this.outgoingFrames);
                    this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;

                    // The default window size for the whole connection is 65535 bytes,
                    // but we want to set it to the maximum possible value.
                    Int64 initialConnectionWindowSize = HTTPManager.HTTP2Settings.InitialConnectionWindowSize;

                    // yandex.ru returns with an FLOW_CONTROL_ERROR (3) error when the plugin tries to set the connection window to 2^31 - 1
                    // and works only with a maximum value of 2^31 - 10Mib (10 * 1024 * 1024).
                    if (initialConnectionWindowSize == HTTP2Handler.MaxValueFor31Bits)
                    {
                        initialConnectionWindowSize -= 10 * 1024 * 1024;
                    }

                    Int64 diff = initialConnectionWindowSize - 65535;
                    if (diff > 0)
                    {
                        this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)diff));
                    }

                    this.pingFrequency = HTTPManager.HTTP2Settings.PingFrequency;

                    while (this.isRunning)
                    {
                        DateTime now = DateTime.UtcNow;

                        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
                            //----|-----|---------------|--------|-------------------|
                            // lastInteraction                                  lastInteraction + MaxIdleTime

                            var sendPingAt                 = this.lastPingSent + this.pingFrequency;
                            var disconnectByIdleAt         = this.lastInteraction + HTTPManager.HTTP2Settings.MaxIdleTime;
                            var nextDueClientInteractionAt = sendPingAt < disconnectByIdleAt ? sendPingAt : disconnectByIdleAt;

                            int wait = (int)(nextDueClientInteractionAt - now).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.Context);
                                }
                                this.newFrameSignal.WaitOne(wait);

                                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
                                    if (HTTPManager.Logger.Level == Loglevels.All)
                                    {
                                        HTTPManager.Logger.Warning("HTTP2Handler", string.Format("No stream found for id: {0}! Can't deliver frame: {1}", header.StreamId, header), this.Context, http2Stream.Context);
                                    }
                                }
                            }
                            else
                            {
                                switch (header.Type)
                                {
                                case HTTP2FrameTypes.SETTINGS:
                                    this.settings.Process(header, this.outgoingFrames);

                                    PluginEventHelper.EnqueuePluginEvent(
                                        new PluginEventInfo(PluginEvents.HTTP2ConnectProtocol,
                                                            new HTTP2ConnectProtocolInfo(this.conn.LastProcessedUri.Host,
                                                                                         this.settings.MySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1 && this.settings.RemoteSettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1)));
                                    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(), this.Context);

                                    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);
                                    }
                                    this.clientInitiatedStreams.Clear();

                                    // 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;
                                }

                                if (header.Payload != null)
                                {
                                    BufferPool.Release(header.Payload);
                                }
                            }
                        }

                        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, 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.Context);
                            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.Context);

                            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.Context);
                            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(), this.Context);
                            }

                            // 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()), this.Context);
                                    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(), this.Context);
                            }

                            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.IsQuitting)
                {
                    HTTPManager.Logger.Exception("HTTP2Handler", "Sender thread", ex, this.Context);
                }
            }
            finally
            {
                TryToCleanup();

                HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing - cleaning up remaining request...", this.Context);

                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()), this.Context);
                    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", this.Context);
            }

            try
            {
                if (this.conn != null && this.conn.connector != null)
                {
                    // 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
            { }
        }
Exemplo n.º 15
0
        /// <summary>
        /// Will set or update all cookies from the response object.
        /// </summary>
        internal static bool Set(HTTPResponse response)
        {
            if (response == null)
            {
                return(false);
            }

            List <Cookie> newCookies       = new List <Cookie>();
            var           setCookieHeaders = response.GetHeaderValues("set-cookie");

            // No cookies. :'(
            if (setCookieHeaders == null)
            {
                return(false);
            }

            foreach (var cookieHeader in setCookieHeaders)
            {
                Cookie cookie = Cookie.Parse(cookieHeader, response.baseRequest.CurrentUri, response.baseRequest.Context);
                if (cookie != null)
                {
                    rwLock.EnterWriteLock();
                    try
                    {
                        int idx;
                        var old = Find(cookie, out idx);

                        // if no value for the cookie or already expired then the server asked us to delete the cookie
                        bool expired = string.IsNullOrEmpty(cookie.Value) || !cookie.WillExpireInTheFuture();

                        if (!expired)
                        {
                            // no old cookie, add it straight to the list
                            if (old == null)
                            {
                                Cookies.Add(cookie);

                                newCookies.Add(cookie);
                            }
                            else
                            {
                                // Update the creation-time of the newly created cookie to match the creation-time of the old-cookie.
                                cookie.Date  = old.Date;
                                Cookies[idx] = cookie;

                                newCookies.Add(cookie);
                            }
                        }
                        else if (idx != -1) // delete the cookie
                        {
                            Cookies.RemoveAt(idx);
                        }
                    }
                    catch
                    {
                        // Ignore cookie on error
                    }
                    finally
                    {
                        rwLock.ExitWriteLock();
                    }
                }
            }

            response.Cookies = newCookies;

            PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));

            return(true);
        }