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)); }
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)); }
/// <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)); }
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))); } }
/// <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)); } }
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(); }
/// <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(); }
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)); } }
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(); }
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)); } }
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))); } }
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); } } } } }
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)); } }
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 { } }
/// <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); }