void OnAborted() { HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnAborted", string.Empty, this.Context); CurrentRequest.Response = null; CurrentRequest.State = HTTPRequestStates.Aborted; ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); }
void OnTimeout() { HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnResponse", string.Empty); CurrentRequest.Response = null; CurrentRequest.State = HTTPRequestStates.TimedOut; ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); }
protected override void ThreadFunc() { try { // Step 1 : create a stream with header information // Step 2 : create a stream from the file // Step 3 : create a StreamList // Step 4 : create a HTTPResponse object // Step 5 : call the Receive function of the response object using (System.IO.Stream fs = HTTPManager.IOService.CreateFileStream(this.CurrentRequest.CurrentUri.LocalPath, FileStreamModes.OpenRead)) using (StreamList stream = new StreamList(new BufferPoolMemoryStream(), fs)) { // This will write to the MemoryStream stream.Write("HTTP/1.1 200 Ok\r\n"); stream.Write("Content-Type: application/octet-stream\r\n"); stream.Write("Content-Length: " + fs.Length.ToString() + "\r\n"); stream.Write("\r\n"); stream.Seek(0, System.IO.SeekOrigin.Begin); base.CurrentRequest.Response = new HTTPResponse(base.CurrentRequest, stream, base.CurrentRequest.UseStreaming, false); if (!CurrentRequest.Response.Receive()) { CurrentRequest.Response = null; } } } catch (Exception e) { CurrentRequest.Response = null; if (!CurrentRequest.IsCancellationRequested) { CurrentRequest.Exception = e; CurrentRequest.State = HTTPRequestStates.Error; } } finally { if (this.CurrentRequest.IsCancellationRequested) { this.CurrentRequest.Response = null; this.CurrentRequest.State = this.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted; } else if (this.CurrentRequest.Response == null) { this.CurrentRequest.State = HTTPRequestStates.Error; } else { this.CurrentRequest.State = HTTPRequestStates.Finished; } ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); } }
void OnError(string error) { HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnError", error, this.Context); LastProcessTime = DateTime.UtcNow; CurrentRequest.Response = null; CurrentRequest.Exception = new Exception(error); CurrentRequest.State = HTTPRequestStates.Error; ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); }
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 void TryToCleanup() { // First thread closing notifies the ConnectionEventHelper int counter = Interlocked.Increment(ref this.threadExitCount); if (counter == 1) { ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed)); } // Last thread closes the AutoResetEvent if (counter == 2) { if (this.newFrameSignal != null) { this.newFrameSignal.Close(); } this.newFrameSignal = null; } }
public void RunHandler() { HTTPManager.Logger.Information("HTTP1Handler", string.Format("[{0}] started processing request '{1}'", this, this.conn.CurrentRequest.CurrentUri.ToString()), this.Context, this.conn.CurrentRequest.Context); System.Threading.Thread.CurrentThread.Name = "BestHTTP.HTTP1 R&W"; HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing; bool resendRequest = false; try { if (this.conn.CurrentRequest.IsCancellationRequested) { return; } #if !BESTHTTP_DISABLE_CACHING // Setup cache control headers before we send out the request if (!this.conn.CurrentRequest.DisableCache) { HTTPCacheService.SetHeaders(this.conn.CurrentRequest); } #endif // Write the request to the stream this.conn.CurrentRequest.QueuedAt = DateTime.MinValue; this.conn.CurrentRequest.ProcessingStarted = DateTime.UtcNow; this.conn.CurrentRequest.SendOutTo(this.conn.connector.Stream); this.conn.CurrentRequest.Timing.Add(TimingEventNames.Request_Sent); if (this.conn.CurrentRequest.IsCancellationRequested) { return; } this.conn.CurrentRequest.OnCancellationRequested += OnCancellationRequested; // Receive response from the server bool received = Receive(this.conn.CurrentRequest); this.conn.CurrentRequest.Timing.Add(TimingEventNames.Response_Received); if (this.conn.CurrentRequest.IsCancellationRequested) { return; } if (!received && this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries) { proposedConnectionState = HTTPConnectionStates.Closed; this.conn.CurrentRequest.Retries++; resendRequest = true; return; } ConnectionHelper.HandleResponse(this.conn.ToString(), this.conn.CurrentRequest, out resendRequest, out proposedConnectionState, ref this._keepAlive, this.conn.Context, this.conn.CurrentRequest.Context); } catch (TimeoutException e) { this.conn.CurrentRequest.Response = null; // Do nothing here if Abort() got called on the request, its State is already set. if (!this.conn.CurrentRequest.IsTimedOut) { // We will try again only once if (this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries) { this.conn.CurrentRequest.Retries++; resendRequest = true; } else { this.conn.CurrentRequest.Exception = e; this.conn.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; } } proposedConnectionState = HTTPConnectionStates.Closed; } catch (Exception e) { if (this.ShutdownType == ShutdownTypes.Immediate) { return; } string exceptionMessage = string.Empty; if (e == null) { exceptionMessage = "null"; } else { System.Text.StringBuilder sb = new System.Text.StringBuilder(); Exception exception = e; int counter = 1; while (exception != null) { sb.AppendFormat("{0}: {1} {2}", counter++.ToString(), exception.Message, exception.StackTrace); exception = exception.InnerException; if (exception != null) { sb.AppendLine(); } } exceptionMessage = sb.ToString(); } HTTPManager.Logger.Verbose("HTTP1Handler", exceptionMessage, this.Context, this.conn.CurrentRequest.Context); #if !BESTHTTP_DISABLE_CACHING if (this.conn.CurrentRequest.UseStreaming) { HTTPCacheService.DeleteEntity(this.conn.CurrentRequest.CurrentUri); } #endif // Something gone bad, Response must be null! this.conn.CurrentRequest.Response = null; // Do nothing here if Abort() got called on the request, its State is already set. if (!this.conn.CurrentRequest.IsCancellationRequested) { this.conn.CurrentRequest.Exception = e; this.conn.CurrentRequest.State = HTTPRequestStates.Error; } proposedConnectionState = HTTPConnectionStates.Closed; } finally { this.conn.CurrentRequest.OnCancellationRequested -= OnCancellationRequested; // Exit ASAP if (this.ShutdownType != ShutdownTypes.Immediate) { if (this.conn.CurrentRequest.IsCancellationRequested) { // we don't know what stage the request is canceled, we can't safely reuse the tcp channel. proposedConnectionState = HTTPConnectionStates.Closed; this.conn.CurrentRequest.Response = null; // The request's State already set, or going to be set soon in RequestEvents.cs. //this.conn.CurrentRequest.State = this.conn.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted; } else if (resendRequest) { // Here introducing a ClosedResendRequest connection state, where we have to process the connection's state change to Closed // than we have to resend the request. // If we would send the Resend request here, than a few lines below the Closed connection state change, // request events are processed before connection events (just switching the EnqueueRequestEvent and EnqueueConnectionEvent wouldn't work // see order of ProcessQueues in HTTPManager.OnUpdate!) and it would pick this very same closing/closed connection! if (proposedConnectionState == HTTPConnectionStates.Closed) { ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, this.conn.CurrentRequest)); } else { RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.conn.CurrentRequest, RequestEvents.Resend)); } } else if (this.conn.CurrentRequest.Response != null && this.conn.CurrentRequest.Response.IsUpgraded) { proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown; } else if (this.conn.CurrentRequest.State == HTTPRequestStates.Processing) { if (this.conn.CurrentRequest.Response != null) { this.conn.CurrentRequest.State = HTTPRequestStates.Finished; } else { this.conn.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}", this.ToString(), this.conn.CurrentRequest.State.ToString(), this.conn.State.ToString())); this.conn.CurrentRequest.State = HTTPRequestStates.Error; proposedConnectionState = HTTPConnectionStates.Closed; } } this.conn.CurrentRequest = null; if (proposedConnectionState == HTTPConnectionStates.Processing) { proposedConnectionState = HTTPConnectionStates.Recycle; } if (proposedConnectionState != HTTPConnectionStates.ClosedResendRequest) { ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, proposedConnectionState)); } } } }
public void RunHandler() { HTTPManager.Logger.Information("HTTP1Handler", string.Format("[{0}] started processing request '{1}'", this, this.conn.CurrentRequest.CurrentUri.ToString()), this.Context, this.conn.CurrentRequest.Context); HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing; bool resendRequest = false; try { if (this.conn.CurrentRequest.IsCancellationRequested) { return; } #if !BESTHTTP_DISABLE_CACHING // Setup cache control headers before we send out the request if (!this.conn.CurrentRequest.DisableCache) { HTTPCacheService.SetHeaders(this.conn.CurrentRequest); } #endif // Write the request to the stream this.conn.CurrentRequest.SendOutTo(this.conn.connector.Stream); if (this.conn.CurrentRequest.IsCancellationRequested) { return; } this.conn.CurrentRequest.OnCancellationRequested += OnCancellationRequested; // Receive response from the server bool received = Receive(this.conn.CurrentRequest); if (this.conn.CurrentRequest.IsCancellationRequested) { return; } if (!received && this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries) { proposedConnectionState = HTTPConnectionStates.Closed; this.conn.CurrentRequest.Retries++; resendRequest = true; return; } ConnectionHelper.HandleResponse(this.conn.ToString(), this.conn.CurrentRequest, out resendRequest, out proposedConnectionState, ref this._keepAlive, this.conn.Context, this.conn.CurrentRequest.Context); } catch (TimeoutException e) { this.conn.CurrentRequest.Response = null; // We will try again only once if (this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries) { this.conn.CurrentRequest.Retries++; resendRequest = true; } else { this.conn.CurrentRequest.Exception = e; this.conn.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; } proposedConnectionState = HTTPConnectionStates.Closed; } catch (Exception e) { if (this.ShutdownType == ShutdownTypes.Immediate) { return; } string exceptionMessage = string.Empty; if (e == null) { exceptionMessage = "null"; } else { System.Text.StringBuilder sb = new System.Text.StringBuilder(); Exception exception = e; int counter = 1; while (exception != null) { sb.AppendFormat("{0}: {1} {2}", counter++.ToString(), exception.Message, exception.StackTrace); exception = exception.InnerException; if (exception != null) { sb.AppendLine(); } } exceptionMessage = sb.ToString(); } HTTPManager.Logger.Verbose("HTTP1Handler", exceptionMessage, this.Context, this.conn.CurrentRequest.Context); #if !BESTHTTP_DISABLE_CACHING if (this.conn.CurrentRequest.UseStreaming) { HTTPCacheService.DeleteEntity(this.conn.CurrentRequest.CurrentUri); } #endif // Something gone bad, Response must be null! this.conn.CurrentRequest.Response = null; if (!this.conn.CurrentRequest.IsCancellationRequested) { this.conn.CurrentRequest.Exception = e; this.conn.CurrentRequest.State = HTTPRequestStates.Error; } proposedConnectionState = HTTPConnectionStates.Closed; } finally { this.conn.CurrentRequest.OnCancellationRequested -= OnCancellationRequested; // Exit ASAP if (this.ShutdownType != ShutdownTypes.Immediate) { if (this.conn.CurrentRequest.IsCancellationRequested) { // we don't know what stage the request is canceled, we can't safely reuse the tcp channel. proposedConnectionState = HTTPConnectionStates.Closed; this.conn.CurrentRequest.Response = null; this.conn.CurrentRequest.State = this.conn.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted; } else if (resendRequest) { RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.conn.CurrentRequest, RequestEvents.Resend)); } else if (this.conn.CurrentRequest.Response != null && this.conn.CurrentRequest.Response.IsUpgraded) { proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown; } else if (this.conn.CurrentRequest.State == HTTPRequestStates.Processing) { if (this.conn.CurrentRequest.Response != null) { this.conn.CurrentRequest.State = HTTPRequestStates.Finished; } else { this.conn.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}", this.ToString(), this.conn.CurrentRequest.State.ToString(), this.conn.State.ToString())); this.conn.CurrentRequest.State = HTTPRequestStates.Error; proposedConnectionState = HTTPConnectionStates.Closed; } } this.conn.CurrentRequest = null; if (proposedConnectionState == HTTPConnectionStates.Processing) { proposedConnectionState = HTTPConnectionStates.Recycle; } ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, proposedConnectionState)); } } }
public void RunHandler() { HTTPManager.Logger.Information("HTTP2Handler", "Processing thread up and running!"); Thread.CurrentThread.Name = "HTTP2 Process"; PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ReadThread); try { bool atLeastOneStreamHasAFrameToSend = true; this.HPACKEncoder = new HPACKEncoder(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.SendChanges(this.outgoingFrames); // The default window size for the whole connection is 65535 bytes, // but we want to set it to the maximum possible value. Int64 diff = HTTPManager.HTTP2Settings.InitialConnectionWindowSize - 65535; if (diff > 0) { this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)diff)); } this.pingFrequency = HTTPManager.HTTP2Settings.PingFrequency; while (this.isRunning) { 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 //-----|--------|----|------------ int wait = (int)((this.lastPingSent + this.pingFrequency) - DateTime.UtcNow).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.newFrameSignal.WaitOne(wait); } } DateTime 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 //HTTPManager.Logger.Warning("HTTP2Handler", string.Format("No stream found for id: {0}! Can't deliver frame: {1}", header.StreamId, header)); } } else { switch (header.Type) { case HTTP2FrameTypes.SETTINGS: this.settings.Process(header, this.outgoingFrames); 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()); 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); } // 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; } } } 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.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.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.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.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()); } // 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())); 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()); } 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.Logger.Exception("HTTP2Handler", "Sender thread", ex); } } 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", "Sender thread closing - cleaning up remaining request..."); 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())); 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"); } try { // 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 { } }
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"); } }
protected override void ThreadFunc() { if (this.CurrentRequest.IsRedirected) { this.CurrentRequest.Timing.Add(TimingEventNames.Queued_For_Redirection); } else { this.CurrentRequest.Timing.Add(TimingEventNames.Queued); } if (this.connector != null && !this.connector.IsConnected) { // this will send the request back to the queue RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend)); ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); return; } if (this.connector == null) { this.connector = new Connections.TCPConnector(); try { this.connector.Connect(this.CurrentRequest); } catch (Exception ex) { if (HTTPManager.Logger.Level == Logger.Loglevels.All) { HTTPManager.Logger.Exception("HTTPConnection", "Connector.Connect", ex, this.Context, this.CurrentRequest.Context); } if (ex is TimeoutException) { this.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; } else if (!this.CurrentRequest.IsTimedOut) // Do nothing here if Abort() got called on the request, its State is already set. { this.CurrentRequest.Exception = ex; this.CurrentRequest.State = HTTPRequestStates.Error; } ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); return; } #if !NETFX_CORE // data sending is buffered for all protocols, so when we put data into the socket we want to send them asap this.connector.Client.NoDelay = true; #endif StartTime = DateTime.UtcNow; HTTPManager.Logger.Information("HTTPConnection", "Negotiated protocol through ALPN: '" + this.connector.NegotiatedProtocol + "'", this.Context, this.CurrentRequest.Context); switch (this.connector.NegotiatedProtocol) { case HTTPProtocolFactory.W3C_HTTP1: this.requestHandler = new Connections.HTTP1Handler(this); ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP1)); break; #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2 case HTTPProtocolFactory.W3C_HTTP2: this.requestHandler = new Connections.HTTP2.HTTP2Handler(this); this.CurrentRequest = null; ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP2)); break; #endif default: HTTPManager.Logger.Error("HTTPConnection", "Unknown negotiated protocol: " + this.connector.NegotiatedProtocol, this.Context, this.CurrentRequest.Context); RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend)); ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); return; } } this.requestHandler.Context.Add("Connection", this.GetHashCode()); this.Context.Add("RequestHandler", this.requestHandler.GetHashCode()); this.requestHandler.RunHandler(); LastProcessTime = DateTime.Now; }
public void RunHandler() { HTTPManager.Logger.Information("HTTP1Handler", string.Format("[{0}] started processing request '{1}'", this, this.conn.CurrentRequest.CurrentUri.ToString())); HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing; bool resendRequest = false; try { if (this.conn.CurrentRequest.IsCancellationRequested) { return; } #if !BESTHTTP_DISABLE_CACHING // Try load the full response from an already saved cache entity. // If the response could be loaded completely, we can skip connecting (if not already) and a full round-trip time to the server. if (HTTPCacheService.IsCachedEntityExpiresInTheFuture(this.conn.CurrentRequest) && ConnectionHelper.TryLoadAllFromCache(this.ToString(), this.conn.CurrentRequest)) { HTTPManager.Logger.Information("HTTP1Handler", string.Format("[{0}] Request could be fully loaded from cache! '{1}'", this, this.conn.CurrentRequest.CurrentUri.ToString())); return; } #endif if (this.conn.CurrentRequest.IsCancellationRequested) { return; } #if !BESTHTTP_DISABLE_CACHING // Setup cache control headers before we send out the request if (!this.conn.CurrentRequest.DisableCache) { HTTPCacheService.SetHeaders(this.conn.CurrentRequest); } #endif // Write the request to the stream this.conn.CurrentRequest.SendOutTo(this.conn.connector.Stream); if (this.conn.CurrentRequest.IsCancellationRequested) { return; } // Receive response from the server bool received = Receive(this.conn.CurrentRequest); if (this.conn.CurrentRequest.IsCancellationRequested) { return; } if (!received && this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries) { proposedConnectionState = HTTPConnectionStates.Closed; this.conn.CurrentRequest.Retries++; resendRequest = true; return; } ConnectionHelper.HandleResponse(this.conn.ToString(), this.conn.CurrentRequest, out resendRequest, out proposedConnectionState, ref this._keepAlive); } catch (TimeoutException e) { this.conn.CurrentRequest.Response = null; // We will try again only once if (this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries) { this.conn.CurrentRequest.Retries++; resendRequest = true; } else { this.conn.CurrentRequest.Exception = e; this.conn.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; } proposedConnectionState = HTTPConnectionStates.Closed; } catch (Exception e) { if (this.ShutdownType == ShutdownTypes.Immediate) { return; } #if !BESTHTTP_DISABLE_CACHING if (this.conn.CurrentRequest.UseStreaming) { HTTPCacheService.DeleteEntity(this.conn.CurrentRequest.CurrentUri); } #endif // Something gone bad, Response must be null! this.conn.CurrentRequest.Response = null; if (!this.conn.CurrentRequest.IsCancellationRequested) { this.conn.CurrentRequest.Exception = e; this.conn.CurrentRequest.State = HTTPRequestStates.Error; } proposedConnectionState = HTTPConnectionStates.Closed; } finally { // Exit ASAP if (this.ShutdownType != ShutdownTypes.Immediate) { if (this.conn.CurrentRequest.IsCancellationRequested) { // we don't know what stage the request is cancelled, we can't safely reuse the tcp channel. proposedConnectionState = HTTPConnectionStates.Closed; this.conn.CurrentRequest.Response = null; this.conn.CurrentRequest.State = this.conn.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted; } else if (resendRequest) { RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.conn.CurrentRequest, RequestEvents.Resend)); } else if (this.conn.CurrentRequest.Response != null && this.conn.CurrentRequest.Response.IsUpgraded) { proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown; } else if (this.conn.CurrentRequest.State == HTTPRequestStates.Processing) { if (this.conn.CurrentRequest.Response != null) { this.conn.CurrentRequest.State = HTTPRequestStates.Finished; } else { this.conn.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}", this.ToString(), this.conn.CurrentRequest.State.ToString(), this.conn.State.ToString())); this.conn.CurrentRequest.State = HTTPRequestStates.Error; proposedConnectionState = HTTPConnectionStates.Closed; } } this.conn.CurrentRequest = null; if (proposedConnectionState == HTTPConnectionStates.Processing) { proposedConnectionState = HTTPConnectionStates.Recycle; } ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, proposedConnectionState)); } } }
void OnResponse(int httpStatus, byte[] buffer, int bufferLength) { HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing; bool resendRequest = false; try { if (this.CurrentRequest.IsCancellationRequested) { return; } using (var ms = new BufferPoolMemoryStream()) { Stream = ms; XHR_GetStatusLine(NativeId, OnBufferCallback); XHR_GetResponseHeaders(NativeId, OnBufferCallback); if (buffer != null && bufferLength > 0) { ms.Write(buffer, 0, bufferLength); } ms.Seek(0L, SeekOrigin.Begin); var internalBuffer = ms.GetBuffer(); string tmp = System.Text.Encoding.UTF8.GetString(internalBuffer); HTTPManager.Logger.Information(this.NativeId + " OnResponse - full response ", tmp, this.Context); SupportedProtocols protocol = CurrentRequest.ProtocolHandler == SupportedProtocols.Unknown ? HTTPProtocolFactory.GetProtocolFromUri(CurrentRequest.CurrentUri) : CurrentRequest.ProtocolHandler; CurrentRequest.Response = HTTPProtocolFactory.Get(protocol, CurrentRequest, ms, CurrentRequest.UseStreaming, false); CurrentRequest.Response.Receive(buffer != null && bufferLength > 0 ? (int)bufferLength : -1, true); KeepAliveHeader keepAlive = null; ConnectionHelper.HandleResponse(this.ToString(), this.CurrentRequest, out resendRequest, out proposedConnectionState, ref keepAlive); } } catch (Exception e) { HTTPManager.Logger.Exception(this.NativeId + " WebGLConnection", "OnResponse", e, this.Context); if (this.ShutdownType == ShutdownTypes.Immediate) { return; } #if !BESTHTTP_DISABLE_CACHING if (this.CurrentRequest.UseStreaming) { HTTPCacheService.DeleteEntity(this.CurrentRequest.CurrentUri); } #endif // Something gone bad, Response must be null! this.CurrentRequest.Response = null; if (!this.CurrentRequest.IsCancellationRequested) { this.CurrentRequest.Exception = e; this.CurrentRequest.State = HTTPRequestStates.Error; } proposedConnectionState = HTTPConnectionStates.Closed; } finally { // Exit ASAP if (this.ShutdownType != ShutdownTypes.Immediate) { if (this.CurrentRequest.IsCancellationRequested) { // we don't know what stage the request is cancelled, we can't safely reuse the tcp channel. proposedConnectionState = HTTPConnectionStates.Closed; this.CurrentRequest.Response = null; this.CurrentRequest.State = this.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted; } else if (resendRequest) { RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.Resend)); } else if (this.CurrentRequest.Response != null && this.CurrentRequest.Response.IsUpgraded) { proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown; } else if (this.CurrentRequest.State == HTTPRequestStates.Processing) { if (this.CurrentRequest.Response != null) { this.CurrentRequest.State = HTTPRequestStates.Finished; } else { this.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}", this.ToString(), this.CurrentRequest.State.ToString(), this.State.ToString())); this.CurrentRequest.State = HTTPRequestStates.Error; proposedConnectionState = HTTPConnectionStates.Closed; } } this.CurrentRequest = null; if (proposedConnectionState == HTTPConnectionStates.Processing) { proposedConnectionState = HTTPConnectionStates.Closed; } ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, proposedConnectionState)); } } }
protected override void ThreadFunc() { if (this.connector != null && !this.connector.IsConnected) { // this will semd the request back to the queue RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend)); ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); return; } if (this.connector == null) { this.connector = new Connections.TCPConnector(); try { this.connector.Connect(this.CurrentRequest); } catch (Exception ex) { HTTPManager.Logger.Exception("HTTPConnection", "Connector.Connect", ex); this.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut; ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); return; } #if !NETFX_CORE // data sending is buffered for all protocols, so when we put data into the socket we want to send them asap this.connector.Client.NoDelay = true; #endif StartTime = DateTime.UtcNow; HTTPManager.Logger.Information("HTTPConnection", "Negotiated protocol through ALPN: '" + this.connector.NegotiatedProtocol + "'"); switch (this.connector.NegotiatedProtocol) { case HTTPProtocolFactory.W3C_HTTP1: this.requestHandler = new Connections.HTTP1Handler(this); ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP1)); break; #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2 case HTTPProtocolFactory.W3C_HTTP2: this.requestHandler = new Connections.HTTP2.HTTP2Handler(this); this.CurrentRequest = null; ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP2)); break; #endif default: HTTPManager.Logger.Error("HTTPConnection", "Unknown negotiated protocol: " + this.connector.NegotiatedProtocol); RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend)); ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed)); return; } } this.requestHandler.RunHandler(); LastProcessTime = DateTime.Now; }