public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied() { var mockConnectionHandler = new MockConnectionHandler(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext() { ConnectionHandler = mockConnectionHandler }; var transport = new LibuvTransport(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); mockConnectionHandler.InputOptions = pool => new PipeOptions( bufferPool: pool, maximumSizeHigh: 3); // We don't set the output writer scheduler here since we want to run the callback inline mockConnectionHandler.OutputOptions = pool => new PipeOptions(bufferPool: pool, readerScheduler: thread); Task connectionTask = null; try { await thread.StartAsync(); // Write enough to make sure back pressure will be applied await thread.PostAsync <object>(_ => { var listenerContext = new ListenerContext(transportContext) { Thread = thread }; var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); var connection = new LibuvConnection(listenerContext, socket); connectionTask = connection.Start(); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); }, null); // Now assert that we removed the callback from libuv to stop reading Assert.Null(mockLibuv.AllocCallback); Assert.Null(mockLibuv.ReadCallback); // Now complete the output writer so that the connection closes mockConnectionHandler.Output.Writer.Complete(); await connectionTask.TimeoutAfter(TimeSpan.FromSeconds(10)); // Assert that we don't try to start reading Assert.Null(mockLibuv.AllocCallback); Assert.Null(mockLibuv.ReadCallback); } finally { await thread.StopAsync(TimeSpan.FromSeconds(1)); } }
/// <inheritdoc/> protected override void BeginAccept(ListenerContext listener) { SocketAsyncEventArgs acceptEventArg = (SocketAsyncEventArgs)listener.Tag; if (acceptEventArg == null) { acceptEventArg = new SocketAsyncEventArgs(); acceptEventArg.UserToken = listener; acceptEventArg.Completed += new EventHandler <SocketAsyncEventArgs>(AcceptEventArg_Completed); } else { // socket must be cleared since the context object is being reused acceptEventArg.AcceptSocket = null; } Boolean willRaiseEvent; try { willRaiseEvent = listener.Socket.AcceptAsync(acceptEventArg); } catch (ObjectDisposedException) { return; } if (!willRaiseEvent) { ProcessAccept(acceptEventArg); } }
public async Task DoesNotEndConnectionOnZeroRead() { var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext(); var thread = new LibuvThread(mockLibuv, transportContext); var listenerContext = new ListenerContext(transportContext) { Thread = thread }; try { await thread.StartAsync(); await thread.PostAsync(_ => { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); listenerContext.HandleConnection(socket); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored); }, (object)null); await using var connection = await listenerContext.AcceptAsync(); var readAwaitable = connection.Transport.Input.ReadAsync(); Assert.False(readAwaitable.IsCompleted); } finally { await thread.StopAsync(TimeSpan.FromSeconds(5)); } }
private IEnumerator <object> KeepAliveTask(ListenerContext context, Socket socket) { EndPoint localEp = socket.LocalEndPoint, remoteEp = socket.RemoteEndPoint; var evtArgs = new ConnectionEventArgs(localEp, remoteEp); if (SocketOpened != null) { SocketOpened(this, evtArgs); } try { using (var adapter = new SocketDataAdapter(socket, true)) { while (!adapter.IsDisposed && adapter.Socket.Connected) { var fTask = Scheduler.Start(RequestTask(context, adapter)); yield return(fTask); if (fTask.Failed) { adapter.Dispose(); yield break; } } } } finally { if (SocketClosed != null) { SocketClosed(this, evtArgs); } } }
public void DoesNotEndConnectionOnZeroRead() { var mockLibuv = new MockLibuv(); using (var memory = new MemoryPool()) using (var engine = new KestrelEngine(mockLibuv, new TestServiceContext())) { engine.Start(count: 1); var trace = new TestKestrelTrace(); var context = new ListenerContext(new TestServiceContext()) { FrameFactory = connectionContext => new Frame <HttpContext>( new DummyApplication(httpContext => TaskUtilities.CompletedTask), connectionContext), Memory = memory, ServerAddress = ServerAddress.FromUrl($"http://localhost:{TestServer.GetNextPort()}"), Thread = engine.Threads[0] }; var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, trace); var connection = new Connection(context, socket); connection.Start(); Libuv.uv_buf_t ignored; mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 0, ref ignored); Assert.False(connection.SocketInput.RemoteIntakeFin); connection.ConnectionControl.End(ProduceEndType.SocketDisconnect); } }
public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled() { var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext(); var thread = new LibuvThread(mockLibuv, transportContext); var listenerContext = new ListenerContext(transportContext) { Thread = thread }; try { await thread.StartAsync(); await thread.PostAsync(_ => { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); listenerContext.HandleConnection(socket); var ignored = new LibuvFunctions.uv_buf_t(); mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); }, (object)null); await using var connection = await listenerContext.AcceptAsync(); var readAwaitable = await connection.Transport.Input.ReadAsync(); Assert.True(readAwaitable.IsCompleted); } finally { await thread.StopAsync(TimeSpan.FromSeconds(5)); } }
/// <summary> /// On request /// </summary> /// <param name="context">Command context</param> public void OnRequest(ListenerContext context) { context.StatusCode = 200; context.ResponseEncoding = Encoding.UTF8; context.ContentType = "text/html"; context.Response = "It works!"; }
Ex2Listener() { // Subject를 등록합니다. ListenerContext.AddSubject(new Ex2Subject()); // Listener를 등록합니다. ListenerContext.BindListener <Ex2Subject>(this); }
public void Dispose() { // 등록된 Listener 해제 합니다. ListenerContext.UnbindListener(this); // 등록된 Subject를 제거합니다. ListenerContext.RemoveSubject <Ex1Subject>(); }
Ex1Listener() { // Subject를 등록합니다. ListenerContext.AddSubject(new Ex1Subject()); // Listener를 등록합니다. ListenerContext.BindListener(this); }
public async Task ConnectionDoesNotResumeAfterSocketCloseIfBackpressureIsApplied() { var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext(); var thread = new LibuvThread(mockLibuv, transportContext); var listenerContext = new ListenerContext(transportContext) { Thread = thread, InputOptions = new PipeOptions( pool: thread.MemoryPool, pauseWriterThreshold: 3, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false), // We don't set the output writer scheduler here since we want to run the callback inline OutputOptions = new PipeOptions( pool: thread.MemoryPool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false) }; try { await thread.StartAsync(); // Write enough to make sure back pressure will be applied await thread.PostAsync <object>(_ => { var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); listenerContext.HandleConnection(socket); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); }, null); var connection = await listenerContext.AcceptAsync(); // Now assert that we removed the callback from libuv to stop reading Assert.Null(mockLibuv.AllocCallback); Assert.Null(mockLibuv.ReadCallback); // Now complete the output writer so that the connection closes await connection.DisposeAsync(); // Assert that we don't try to start reading Assert.Null(mockLibuv.AllocCallback); Assert.Null(mockLibuv.ReadCallback); } finally { await thread.StopAsync(TimeSpan.FromSeconds(5)); } }
private IEnumerator <object> KeepAliveTask(ListenerContext context, IncomingConnection incomingConnection) { var socket = incomingConnection.Socket; EndPoint localEp = socket.LocalEndPoint, remoteEp = socket.RemoteEndPoint; var evtArgs = new ConnectionEventArgs(localEp, remoteEp); var keepAliveStarted = DateTime.UtcNow; if (SocketOpened != null) { SocketOpened(this, evtArgs); } int requestCount = 0; try { using (var adapter = new SocketDataAdapter(socket, true)) { while (!adapter.IsDisposed && adapter.Socket.Connected) { var fTask = Scheduler.Start(RequestTask(context, adapter)); yield return(fTask); requestCount += 1; if (fTask.Failed) { adapter.Dispose(); yield break; } } } } finally { var keepAliveEnded = DateTime.UtcNow; if (SocketClosed != null) { SocketClosed(this, evtArgs); } if (Trace != null) { Trace( String.Format( "KA_START {0:0000.0}ms KA_LENGTH {1:00000.0}ms {2} REQ(S)", (keepAliveStarted - incomingConnection.AcceptedWhenUTC).TotalMilliseconds, (keepAliveEnded - keepAliveStarted).TotalMilliseconds, requestCount ) ); } } }
/// <summary> /// Creates a new connection from the listener context. /// </summary> /// <param name="context">The listener context to use.</param> /// <param name="socket">The socket handle.</param> internal Connection(ListenerContext context, UvStreamHandle socket) : base(context) { _socket = socket; socket.Connection = this; ConnectionId = ConnectionId.NewConnectionId(); _rawSocketInput = new SocketInput(Memory, ThreadPool); _rawSocketOutput = new SocketOutput(Thread, _socket, Memory, this, ConnectionId, ThreadPool, WriteReqPool); this.Expires = Timer.Now + TimeSpan.FromMinutes(1); this.ConnectionManager.Register(this); }
protected override void BeginAccept(ListenerContext listener) { try { listener.Socket.BeginAccept(AcceptCallback, listener); } catch (ObjectDisposedException) { // do nothing } catch (System.Net.Sockets.SocketException ex) { ExceptionMonitor.Instance.ExceptionCaught(ex); } }
private void StartAccept(ListenerContext listener) { if (_connectionPool == null) { BeginAccept(listener); } else { #if NET20 System.Threading.ThreadPool.QueueUserWorkItem(_startAccept, listener); #else System.Threading.Tasks.Task.Factory.StartNew(_startAccept, listener); #endif } }
public async Task DoesNotThrowIfOnReadCallbackCalledWithEOFButAllocCallbackNotCalled() { var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext() { ConnectionDispatcher = mockConnectionDispatcher }; var transport = new LibuvTransport(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); Task connectionTask = null; try { await thread.StartAsync(); await thread.PostAsync(_ => { var listenerContext = new ListenerContext(transportContext) { Thread = thread }; var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); connectionTask = connection.Start(); var ignored = new LibuvFunctions.uv_buf_t(); mockLibuv.ReadCallback(socket.InternalGetHandle(), TestConstants.EOF, ref ignored); }, (object)null); var readAwaitable = await mockConnectionDispatcher.Input.Reader.ReadAsync(); Assert.True(readAwaitable.IsCompleted); } finally { mockConnectionDispatcher.Input.Reader.Complete(); mockConnectionDispatcher.Output.Writer.Complete(); await connectionTask; await thread.StopAsync(TimeSpan.FromSeconds(5)); } }
/// <summary> /// Ends an accept operation. /// </summary> /// <param name="socket">the accepted client socket</param> /// <param name="listener">the <see cref="ListenerContext"/></param> protected void EndAccept(System.Net.Sockets.Socket socket, ListenerContext listener) { if (socket != null) { IoSession session = NewSession(_processor, socket); try { InitSession <IoFuture>(session, null, null); session.Processor.Add(session); } catch (Exception ex) { ExceptionMonitor.Instance.ExceptionCaught(ex); } } // Accept the next connection request StartAccept(listener); }
public SignalFuture StartListening() { if (IsListening) { throw new InvalidOperationException("Already listening"); } var context = new ListenerContext(this); ActiveListener = Scheduler.Start(ListenerTask(context)); ActiveListener.RegisterOnComplete((_) => { if (_.Failed) { OnListenerError(_.Error); } }); return(context.Started); }
private IEnumerator <object> ListenerTask(ListenerContext context) { using (context) { yield return(Future.RunInThread(context.Start)); var wfns = new WaitForNextStep(); var acceptedConnections = new List <IncomingConnection>(); const int connectionsToAcceptPerStep = 4; Future <IncomingConnection> acceptedConnection = null; while (true) { if (acceptedConnection != null) { if (acceptedConnection.Failed) { OnListenerError(acceptedConnection.Error); } else { acceptedConnections.Add(acceptedConnection.Result); } } context.IncomingConnections.DequeueMultiple( acceptedConnections, connectionsToAcceptPerStep ); foreach (var ac in acceptedConnections) { var fKeepAlive = Scheduler.Start(KeepAliveTask(context, ac)); fKeepAlive.RegisterOnComplete(RequestOnComplete); } acceptedConnections.Clear(); acceptedConnection = context.IncomingConnections.Dequeue(); yield return(acceptedConnection); } } }
private void AcceptCallback(IAsyncResult ar) { ListenerContext listener = (ListenerContext)ar.AsyncState; System.Net.Sockets.Socket socket; try { socket = listener.Socket.EndAccept(ar); } catch (ObjectDisposedException) { return; } catch (Exception ex) { ExceptionMonitor.Instance.ExceptionCaught(ex); socket = null; } EndAccept(socket, listener); }
/// <summary> /// Creates a connection for a remote endpoint. /// </summary> /// <param name="context">The listener context to use.</param> /// <param name="remote">The endpoint to connect to.</param> public static Task <Connection> ConnectAsync(ListenerContext context, IPEndPoint remote) { var tcs = new TaskCompletionSource <Connection>(); context.Thread.PostAsync((state) => { var socket = new UvTcpHandle(); try { socket.Init(context.Thread.Loop, context.Thread.QueueCloseHandle); socket.NoDelay(true); socket.Connect(context.Thread.Loop, ServiceAddress.FromIPEndPoint(remote), (request, status, ex, sender) => { request.Dispose(); if (ex != null) { // Error has occured, set the exception socket.Dispose(); tcs.SetException(ex); return; } // Create the connection and notify var connection = new Connection(context, socket); tcs.SetResult(connection.Start() ? connection : null); }, tcs); } catch (UvException ex) { //Service.Logger.Log(ex); socket.Dispose(); tcs.SetException(ex); } }, tcs); return(tcs.Task); }
/// <summary> /// Context recieved callback /// </summary> /// <param name="asyncResult">Asynchronous result</param> private void ContextReceivedCallback(IAsyncResult asyncResult) { try { if (IsListening) { HttpListenerContext context = httpListener.EndGetContext(asyncResult); httpListener.BeginGetContext(new AsyncCallback(ContextReceivedCallback), null); using (Stream stream = context.Response.OutputStream) { foreach (IPlugin plugin in plugins) { ListenerContext listener_context = new ListenerContext(context, StandardOutput, ErrorOutput); plugin.OnRequest(listener_context); stream.Write(listener_context.ResponseBytes, 0, listener_context.ResponseBytes.Length); } } } } catch (Exception e) { ErrorOutput.WriteLine(e); } }
public ContinuationAction MessageReceived(ListenerContext e) { // You can inherit from MessageExtendedInformation if you wish, // the likely scenario for this is if you support plugins yourself. var extended = e.ReceivedInformation.Message.GetExtendedInformation<MessageExtendedInformation>(); var replyExtended = extended.Invert(null); string workflowName; if (extended.Message.Title.StartsWith("START: ", StringComparison.OrdinalIgnoreCase)) { workflowName = extended.Message.Title.Substring(7).Trim(); } else { return ContinuationAction.Continue; // Allow other plugins to execute. } // Get the message body. string body; try { using (var bodyStream = e.ReceivedInformation.Message.OpenView(new ContentType("text/plain"))) { if (bodyStream != null) { // Another plugin may have moved the position within the stream. bodyStream.Seek(0, System.IO.SeekOrigin.Begin); using (var sr = new StreamReader(bodyStream)) { body = sr.ReadToEnd().Trim(); } } else { body = string.Empty; } } } catch { // TODO: Logging etc. return ContinuationAction.Continue; // Allow other plugins to execute. } // TODO: Get data from the body somehow. // You can also look into attachments for e.g. InfoPath forms: // e.ReceivedInformation.Message.Attachments try { var con = new Connection(); var cs = new ConnectionSetup(); cs.ParseConnectionString(_k2ServerConnectionString); try { Exception lastException = null; con.Open(cs); // AlternateIdentities are identities with the same email // address, most likely due badly configured claims. for (var i = 0; i < e.AlternateIdentities.Length; i++) { var alt = e.AlternateIdentities[i]; string fqn; // Search for a FQN in the identity. if (alt.TryGetValue("Fqn", out fqn)) { try { con.RevertUser(); con.ImpersonateUser(fqn); var pi = con.CreateProcessInstance(workflowName); // TODO: Set data in the workflow. con.StartProcessInstance(pi); // Tell the user the workflow was started. _destination.ReplyTo(e.ReceivedInformation.Message, new MessageBodyReader("text/plain", "Workflow started"), replyExtended); e.ReceivedInformation.Commit(); // Indicate we were able to handle the message. return ContinuationAction.Halt; // Stop other plugins from executing. } catch (Exception ex) { // TODO: Logging etc. // This isn't nessecarily a complete failure, // one of the other alternate identities may be // able to action this. lastException = ex; } } } string message; if (lastException != null) { // Identities exist, but the user likely doesn't have rights. message = lastException.ToString(); } else { // No identities exist. message = "Could not find a K2 user for your email address."; } message = "The workflow could not be started: " + message; // Respond with the error. _destination.ReplyTo(e.ReceivedInformation.Message, new MessageBodyReader("text/plain", message), replyExtended); e.ReceivedInformation.Commit(); // Indicate we were able to handle the message. return ContinuationAction.Halt; // Stop other plugins from executing. } finally { if (con != null) { con.Close(); con.Dispose(); } } } catch { // TODO: Logging etc. return ContinuationAction.Continue; // Allow other plugins to execute. } }
private static async Task <Task <LibuvConnection> > WaitForSecondaryListener(Uri address, ListenerContext listenerPrimary, ListenerContext listenerSecondary) { int maxRetries = 100; int retryDelay = 100; Task <LibuvConnection> primary = null; Task <LibuvConnection> secondary = null; for (var i = 0; i < maxRetries; i++) { primary ??= listenerPrimary.AcceptAsync().AsTask(); secondary ??= listenerSecondary.AcceptAsync().AsTask(); using var _ = await HttpClientSlim.GetSocket(address); var task = await Task.WhenAny(primary, secondary); if (task == secondary) { // Dispose this connection now that we know the seconary listener is working await(await secondary).DisposeAsync(); // Return the primary task (it should be incomplete), we do this so that we can return(primary); } else { // Dispose the connection await(await primary).DisposeAsync(); primary = null; } await Task.Delay(retryDelay); } Assert.True(false, $"'{address}' failed to get queued connection in secondary listener in {maxRetries} retries."); return(null); }
private static async Task AssertRoundRobin(Uri address, ListenerPrimary listenerPrimary, ListenerSecondary listenerSecondary, ListenerContext currentListener, Task <LibuvConnection> expected = null, int connections = 4) { for (int i = 0; i < connections; i++) { if (currentListener == listenerPrimary) { expected ??= listenerSecondary.AcceptAsync().AsTask(); currentListener = listenerSecondary; } else { expected ??= listenerPrimary.AcceptAsync().AsTask(); currentListener = listenerPrimary; } using var socket = await HttpClientSlim.GetSocket(address); await using var connection = await expected.DefaultTimeout(); expected = null; } }
/// <summary> /// Begins an accept operation. /// </summary> /// <param name="listener"></param> protected abstract void BeginAccept(ListenerContext listener);
ContinuationAction IMessageListener.MessageReceived(ListenerContext e) { string body; using (Stream bodyStream = e.ReceivedInformation.Message.OpenView(new ContentType("text/plain"))) { if (bodyStream != null) { // Another plugin may have moved the position within the stream. bodyStream.Seek(0, System.IO.SeekOrigin.Begin); using (StreamReader sr = new StreamReader(bodyStream)) { body = sr.ReadToEnd().Trim(); } } else { body = string.Empty; } } foreach (ConnectionInformation con in _connections) { string path = con.ConnectionString; string filename = string.Format(@"{0}\received\MessageReceived_{1}.txt", path, e.ReceivedInformation.Message.Title.Replace(':', '_')); using (StreamWriter writer = new StreamWriter(filename)) { writer.Write(body); writer.Close(); } } // This method should fire when a message is received by the MessageBus. // Processing can be done by yourself. e.ReceivedInformation.Commit(); return ContinuationAction.Halt; }
private IEnumerator<object> RequestTask(ListenerContext context, SocketDataAdapter adapter) { var startedWhen = DateTime.UtcNow; bool successful = false; try { const int headerBufferSize = 1024 * 32; const int bodyBufferSize = 1024 * 128; const double requestLineTimeout = 5; // RFC2616: // Words of *TEXT MAY contain characters from character sets other than ISO-8859-1 [22] // only when encoded according to the rules of RFC 2047 [14]. Encoding headerEncoding; try { headerEncoding = Encoding.GetEncoding("ISO-8859-1"); } catch { headerEncoding = Encoding.ASCII; } Request request; RequestBody body; HeaderCollection headers; long bodyBytesRead = 0; long? expectedBodyLength = null; var reader = new AsyncTextReader(adapter, headerEncoding, headerBufferSize, false); string requestLineText; while (true) { var fRequestLine = reader.ReadLine(); var fRequestOrTimeout = Scheduler.Start(new WaitWithTimeout(fRequestLine, requestLineTimeout)); yield return fRequestOrTimeout; if (fRequestOrTimeout.Failed) { if (!(fRequestOrTimeout.Error is TimeoutException)) OnRequestError(fRequestOrTimeout.Error); yield break; } if (fRequestLine.Failed) { if (!(fRequestLine.Error is SocketDisconnectedException)) OnRequestError(fRequestLine.Error); yield break; } requestLineText = fRequestLine.Result; // RFC2616: // In the interest of robustness, servers SHOULD ignore any empty line(s) received where a // Request-Line is expected. In other words, if the server is reading the protocol stream // at the beginning of a message and receives a CRLF first, it should ignore the CRLF. if ((requestLineText != null) && (requestLineText.Trim().Length == 0)) continue; break; } var requestLineParsed = DateTime.UtcNow; headers = new HeaderCollection(); while (true) { var fHeaderLine = reader.ReadLine(); yield return fHeaderLine; if (String.IsNullOrWhiteSpace(fHeaderLine.Result)) break; headers.Add(new Header(fHeaderLine.Result)); } var headersParsed = DateTime.UtcNow; var expectHeader = (headers.GetValue("Expect") ?? "").ToLowerInvariant(); var expectsContinue = expectHeader.Contains("100-continue"); string hostName; if (headers.Contains("Host")) { hostName = String.Format("http://{0}", headers["Host"].Value); } else { var lep = (IPEndPoint)adapter.Socket.LocalEndPoint; hostName = String.Format("http://{0}:{1}", lep.Address, lep.Port); } var requestLine = new RequestLine(hostName, requestLineText); var remainingBytes = reader.DisposeAndGetRemainingBytes(); bodyBytesRead += remainingBytes.Count; var connectionHeader = (headers.GetValue("Connection") ?? "").ToLowerInvariant(); var shouldKeepAlive = ((requestLine.Version == "1.1") || connectionHeader.Contains("keep-alive")) && !connectionHeader.Contains("close"); if (headers.Contains("Content-Length")) expectedBodyLength = long.Parse(headers["Content-Length"].Value); body = new RequestBody(remainingBytes, expectedBodyLength); if (expectsContinue) yield return adapter.Write(Continue100, 0, Continue100.Length); request = new Request( this, adapter, shouldKeepAlive, requestLine, headers, body ); IncomingRequests.Enqueue(request); var requestEnqueued = DateTime.UtcNow; DateTime? requestBodyRead = null; // FIXME: I think it's technically accepted to send a body without a content-length, but // it seems to be impossible to make that work right. if (expectedBodyLength.HasValue) { using (var bodyBuffer = BufferPool<byte>.Allocate(bodyBufferSize)) while (bodyBytesRead < expectedBodyLength.Value) { long bytesToRead = Math.Min(expectedBodyLength.Value - bodyBytesRead, bodyBufferSize); if (bytesToRead <= 0) break; var fBytesRead = adapter.Read(bodyBuffer.Data, 0, (int)bytesToRead); yield return fBytesRead; if (fBytesRead.Failed) { if (fBytesRead.Error is SocketDisconnectedException) break; body.Failed(fBytesRead.Error); OnRequestError(fBytesRead.Error); yield break; } var bytesRead = fBytesRead.Result; bodyBytesRead += bytesRead; body.Append(bodyBuffer.Data, 0, bytesRead); } requestBodyRead = DateTime.UtcNow; } body.Finish(); successful = true; request.Timing = new Request.TimingData { Line = (requestLineParsed - startedWhen), Headers = (headersParsed - requestLineParsed), Queue = (requestEnqueued - headersParsed), Body = (requestBodyRead - requestEnqueued) }; } finally { if (!successful) adapter.Dispose(); } }
public async Task ConnectionDoesNotResumeAfterReadCallbackScheduledAndSocketCloseIfBackpressureIsApplied() { var mockConnectionDispatcher = new MockConnectionDispatcher(); var mockLibuv = new MockLibuv(); var transportContext = new TestLibuvTransportContext() { ConnectionDispatcher = mockConnectionDispatcher }; var transport = new LibuvTransport(mockLibuv, transportContext, null); var thread = new LibuvThread(transport); var mockScheduler = new Mock <PipeScheduler>(); Action backPressure = null; mockScheduler.Setup(m => m.Schedule(It.IsAny <Action <object> >(), It.IsAny <object>())).Callback <Action <object>, object>((a, o) => { backPressure = () => a(o); }); mockConnectionDispatcher.InputOptions = pool => new PipeOptions( pool: pool, pauseWriterThreshold: 3, resumeWriterThreshold: 3, writerScheduler: mockScheduler.Object, readerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); mockConnectionDispatcher.OutputOptions = pool => new PipeOptions(pool: pool, readerScheduler: thread, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); Task connectionTask = null; try { await thread.StartAsync(); // Write enough to make sure back pressure will be applied await thread.PostAsync <object>(_ => { var listenerContext = new ListenerContext(transportContext) { Thread = thread }; var socket = new MockSocket(mockLibuv, Thread.CurrentThread.ManagedThreadId, transportContext.Log); var connection = new LibuvConnection(socket, listenerContext.TransportContext.Log, thread, null, null); listenerContext.TransportContext.ConnectionDispatcher.OnConnection(connection); connectionTask = connection.Start(); mockLibuv.AllocCallback(socket.InternalGetHandle(), 2048, out var ignored); mockLibuv.ReadCallback(socket.InternalGetHandle(), 5, ref ignored); }, null); // Now assert that we removed the callback from libuv to stop reading Assert.Null(mockLibuv.AllocCallback); Assert.Null(mockLibuv.ReadCallback); // Now release backpressure by reading the input var result = await mockConnectionDispatcher.Input.Reader.ReadAsync(); // Calling advance will call into our custom scheduler that captures the back pressure // callback mockConnectionDispatcher.Input.Reader.AdvanceTo(result.Buffer.End); // Cancel the current pending flush mockConnectionDispatcher.Input.Writer.CancelPendingFlush(); // Now release the back pressure await thread.PostAsync(a => a(), backPressure); // Assert that we don't try to start reading since the write was cancelled Assert.Null(mockLibuv.AllocCallback); Assert.Null(mockLibuv.ReadCallback); // Now complete the output writer and wait for the connection to close mockConnectionDispatcher.Output.Writer.Complete(); await connectionTask.DefaultTimeout(); // Assert that we don't try to start reading Assert.Null(mockLibuv.AllocCallback); Assert.Null(mockLibuv.ReadCallback); } finally { await thread.StopAsync(TimeSpan.FromSeconds(5)); } }
public ContinuationAction MessageReceived(ListenerContext e) { // You can inherit from MessageExtendedInformation if you wish, // the likely scenario for this is if you support plugins yourself. var extended = e.ReceivedInformation.Message.GetExtendedInformation <MessageExtendedInformation>(); var replyExtended = extended.Invert(null); string workflowName; if (extended.Message.Title.StartsWith("START: ", StringComparison.OrdinalIgnoreCase)) { workflowName = extended.Message.Title.Substring(7).Trim(); } else { return(ContinuationAction.Continue); // Allow other plugins to execute. } // Get the message body. string body; try { using (var bodyStream = e.ReceivedInformation.Message.OpenView(new ContentType("text/plain"))) { if (bodyStream != null) { // Another plugin may have moved the position within the stream. bodyStream.Seek(0, System.IO.SeekOrigin.Begin); using (var sr = new StreamReader(bodyStream)) { body = sr.ReadToEnd().Trim(); } } else { body = string.Empty; } } } catch { // TODO: Logging etc. return(ContinuationAction.Continue); // Allow other plugins to execute. } // TODO: Get data from the body somehow. // You can also look into attachments for e.g. InfoPath forms: // e.ReceivedInformation.Message.Attachments try { var con = new Connection(); var cs = new ConnectionSetup(); cs.ParseConnectionString(_k2ServerConnectionString); try { Exception lastException = null; con.Open(cs); // AlternateIdentities are identities with the same email // address, most likely due badly configured claims. for (var i = 0; i < e.AlternateIdentities.Length; i++) { var alt = e.AlternateIdentities[i]; string fqn; // Search for a FQN in the identity. if (alt.TryGetValue("Fqn", out fqn)) { try { con.RevertUser(); con.ImpersonateUser(fqn); var pi = con.CreateProcessInstance(workflowName); // TODO: Set data in the workflow. con.StartProcessInstance(pi); // Tell the user the workflow was started. _destination.ReplyTo(e.ReceivedInformation.Message, new MessageBodyReader("text/plain", "Workflow started"), replyExtended); e.ReceivedInformation.Commit(); // Indicate we were able to handle the message. return(ContinuationAction.Halt); // Stop other plugins from executing. } catch (Exception ex) { // TODO: Logging etc. // This isn't nessecarily a complete failure, // one of the other alternate identities may be // able to action this. lastException = ex; } } } string message; if (lastException != null) { // Identities exist, but the user likely doesn't have rights. message = lastException.ToString(); } else { // No identities exist. message = "Could not find a K2 user for your email address."; } message = "The workflow could not be started: " + message; // Respond with the error. _destination.ReplyTo(e.ReceivedInformation.Message, new MessageBodyReader("text/plain", message), replyExtended); e.ReceivedInformation.Commit(); // Indicate we were able to handle the message. return(ContinuationAction.Halt); // Stop other plugins from executing. } finally { if (con != null) { con.Close(); con.Dispose(); } } } catch { // TODO: Logging etc. return(ContinuationAction.Continue); // Allow other plugins to execute. } }
private IEnumerator<object> ListenerTask(ListenerContext context) { using (context) { yield return Future.RunInThread(context.Start); var wfns = new WaitForNextStep(); var acceptedConnections = new List<IncomingConnection>(); const int connectionsToAcceptPerStep = 4; Future<IncomingConnection> acceptedConnection = null; while (true) { if (acceptedConnection != null) { if (acceptedConnection.Failed) OnListenerError(acceptedConnection.Error); else acceptedConnections.Add(acceptedConnection.Result); } context.IncomingConnections.DequeueMultiple( acceptedConnections, connectionsToAcceptPerStep ); foreach (var ac in acceptedConnections) { var fKeepAlive = Scheduler.Start(KeepAliveTask(context, ac)); fKeepAlive.RegisterOnComplete(RequestOnComplete); } acceptedConnections.Clear(); acceptedConnection = context.IncomingConnections.Dequeue(); yield return acceptedConnection; } } }
public ConnectionContext(ListenerContext context) : base(context) { }
public async Task ConnectionsGetRoundRobinedToSecondaryListeners() { var libuv = new LibuvFunctions(); var endpoint = new IPEndPoint(IPAddress.Loopback, 0); var transportContextPrimary = new TestLibuvTransportContext(); var transportContextSecondary = new TestLibuvTransportContext(); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeMessage = Guid.NewGuid().ToByteArray(); // Start primary listener var libuvThreadPrimary = new LibuvThread(libuv, transportContextPrimary); await libuvThreadPrimary.StartAsync(); var listenerPrimary = new ListenerPrimary(transportContextPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadPrimary); var address = GetUri(listenerPrimary.EndPoint); var acceptTask = listenerPrimary.AcceptAsync().AsTask(); using (var socket = await HttpClientSlim.GetSocket(address)) { await(await acceptTask.DefaultTimeout()).DisposeAsync(); } acceptTask = listenerPrimary.AcceptAsync().AsTask(); using (var socket = await HttpClientSlim.GetSocket(address)) { await(await acceptTask.DefaultTimeout()).DisposeAsync(); } var listenerCount = listenerPrimary.UvPipeCount; // Add secondary listener var libuvThreadSecondary = new LibuvThread(libuv, transportContextSecondary); await libuvThreadSecondary.StartAsync(); var listenerSecondary = new ListenerSecondary(transportContextSecondary); await listenerSecondary.StartAsync(pipeName, pipeMessage, endpoint, libuvThreadSecondary); var maxWait = Task.Delay(TestConstants.DefaultTimeout); // wait for ListenerPrimary.ReadCallback to add the secondary pipe while (listenerPrimary.UvPipeCount == listenerCount) { var completed = await Task.WhenAny(maxWait, Task.Delay(100)); if (ReferenceEquals(completed, maxWait)) { throw new TimeoutException("Timed out waiting for secondary listener to become available"); } } // Once a secondary listener is added, TCP connections start getting dispatched to it // This returns the incomplete primary task after the secondary listener got the last // connection var primary = await WaitForSecondaryListener(address, listenerPrimary, listenerSecondary); // TCP connections will still get round-robined to the primary listener ListenerContext currentListener = listenerSecondary; Task <LibuvConnection> expected = primary; await AssertRoundRobin(address, listenerPrimary, listenerSecondary, currentListener, expected); await listenerSecondary.DisposeAsync(); await libuvThreadSecondary.StopAsync(TimeSpan.FromSeconds(5)); await listenerPrimary.DisposeAsync(); await libuvThreadPrimary.StopAsync(TimeSpan.FromSeconds(5)); }
protected void EndAccept(System.Net.Sockets.Socket socket, ListenerContext listener) { if (socket != null) { IoSession session = NewSession(_processor, socket); try { InitSession<IoFuture>(session, null, null); session.Processor.Add(session); } catch (Exception ex) { ExceptionMonitor.Instance.ExceptionCaught(ex); } } // Accept the next connection request StartAccept(listener); }
private IEnumerator <object> RequestTask(ListenerContext context, SocketDataAdapter adapter) { var startedWhen = DateTime.UtcNow; bool successful = false; try { const int headerBufferSize = 1024 * 32; const int bodyBufferSize = 1024 * 128; const double requestLineTimeout = 5; // RFC2616: // Words of *TEXT MAY contain characters from character sets other than ISO-8859-1 [22] // only when encoded according to the rules of RFC 2047 [14]. Encoding headerEncoding; try { headerEncoding = Encoding.GetEncoding("ISO-8859-1"); } catch { headerEncoding = Encoding.ASCII; } Request request; RequestBody body; HeaderCollection headers; long bodyBytesRead = 0; long? expectedBodyLength = null; var reader = new AsyncTextReader(adapter, headerEncoding, headerBufferSize, false); string requestLineText; while (true) { var fRequestLine = reader.ReadLine(); var fRequestOrTimeout = Scheduler.Start(new WaitWithTimeout(fRequestLine, requestLineTimeout)); yield return(fRequestOrTimeout); if (fRequestOrTimeout.Failed) { if (!(fRequestOrTimeout.Error is TimeoutException)) { OnRequestError(fRequestOrTimeout.Error); } yield break; } if (fRequestLine.Failed) { if (!(fRequestLine.Error is SocketDisconnectedException)) { OnRequestError(fRequestLine.Error); } yield break; } requestLineText = fRequestLine.Result; // RFC2616: // In the interest of robustness, servers SHOULD ignore any empty line(s) received where a // Request-Line is expected. In other words, if the server is reading the protocol stream // at the beginning of a message and receives a CRLF first, it should ignore the CRLF. if ((requestLineText != null) && (requestLineText.Trim().Length == 0)) { continue; } break; } var requestLineParsed = DateTime.UtcNow; headers = new HeaderCollection(); while (true) { var fHeaderLine = reader.ReadLine(); yield return(fHeaderLine); if (String.IsNullOrWhiteSpace(fHeaderLine.Result)) { break; } headers.Add(new Header(fHeaderLine.Result)); } var headersParsed = DateTime.UtcNow; var expectHeader = (headers.GetValue("Expect") ?? "").ToLowerInvariant(); var expectsContinue = expectHeader.Contains("100-continue"); string hostName; if (headers.Contains("Host")) { hostName = String.Format("http://{0}", headers["Host"].Value); } else { var lep = (IPEndPoint)adapter.Socket.LocalEndPoint; hostName = String.Format("http://{0}:{1}", lep.Address, lep.Port); } var requestLine = new RequestLine(hostName, requestLineText); var remainingBytes = reader.DisposeAndGetRemainingBytes(); bodyBytesRead += remainingBytes.Count; var connectionHeader = (headers.GetValue("Connection") ?? "").ToLowerInvariant(); var shouldKeepAlive = ((requestLine.Version == "1.1") || connectionHeader.Contains("keep-alive")) && !connectionHeader.Contains("close"); if (headers.Contains("Content-Length")) { expectedBodyLength = long.Parse(headers["Content-Length"].Value); } body = new RequestBody(remainingBytes, expectedBodyLength); if (expectsContinue) { yield return(adapter.Write(Continue100, 0, Continue100.Length)); } request = new Request( this, adapter, shouldKeepAlive, requestLine, headers, body ); IncomingRequests.Enqueue(request); var requestEnqueued = DateTime.UtcNow; DateTime?requestBodyRead = null; // FIXME: I think it's technically accepted to send a body without a content-length, but // it seems to be impossible to make that work right. if (expectedBodyLength.HasValue) { using (var bodyBuffer = BufferPool <byte> .Allocate(bodyBufferSize)) while (bodyBytesRead < expectedBodyLength.Value) { long bytesToRead = Math.Min(expectedBodyLength.Value - bodyBytesRead, bodyBufferSize); if (bytesToRead <= 0) { break; } var fBytesRead = adapter.Read(bodyBuffer.Data, 0, (int)bytesToRead); yield return(fBytesRead); if (fBytesRead.Failed) { if (fBytesRead.Error is SocketDisconnectedException) { break; } body.Failed(fBytesRead.Error); OnRequestError(fBytesRead.Error); yield break; } var bytesRead = fBytesRead.Result; bodyBytesRead += bytesRead; body.Append(bodyBuffer.Data, 0, bytesRead); } requestBodyRead = DateTime.UtcNow; } body.Finish(); successful = true; request.Timing = new Request.TimingData { Line = (requestLineParsed - startedWhen), Headers = (headersParsed - requestLineParsed), Queue = (requestEnqueued - headersParsed), Body = (requestBodyRead - requestEnqueued) }; } finally { if (!successful) { adapter.Dispose(); } } }
public SignalFuture StartListening() { if (IsListening) throw new InvalidOperationException("Already listening"); var context = new ListenerContext(this); ActiveListener = Scheduler.Start(ListenerTask(context)); ActiveListener.RegisterOnComplete((_) => { if (_.Failed) OnListenerError(_.Error); }); return context.Started; }
private IEnumerator<object> KeepAliveTask(ListenerContext context, IncomingConnection incomingConnection) { var socket = incomingConnection.Socket; EndPoint localEp = socket.LocalEndPoint, remoteEp = socket.RemoteEndPoint; var evtArgs = new ConnectionEventArgs(localEp, remoteEp); var keepAliveStarted = DateTime.UtcNow; if (SocketOpened != null) SocketOpened(this, evtArgs); int requestCount = 0; try { using (var adapter = new SocketDataAdapter(socket, true)) { while (!adapter.IsDisposed && adapter.Socket.Connected) { var fTask = Scheduler.Start(RequestTask(context, adapter)); yield return fTask; requestCount += 1; if (fTask.Failed) { adapter.Dispose(); yield break; } } } } finally { var keepAliveEnded = DateTime.UtcNow; if (SocketClosed != null) SocketClosed(this, evtArgs); if (Trace != null) Trace( String.Format( "KA_START {0:0000.0}ms KA_LENGTH {1:00000.0}ms {2} REQ(S)", (keepAliveStarted - incomingConnection.AcceptedWhenUTC).TotalMilliseconds, (keepAliveEnded - keepAliveStarted).TotalMilliseconds, requestCount ) ); } }
private void StartAccept(ListenerContext listener) { if (_connectionPool == null) { BeginAccept(listener); } else { System.Threading.Tasks.Task.Factory.StartNew(_startAccept, listener); } }
/// <inheritdoc/> protected override void BeginAccept(ListenerContext listener) { SocketAsyncEventArgs acceptEventArg = (SocketAsyncEventArgs)listener.Tag; if (acceptEventArg == null) { acceptEventArg = new SocketAsyncEventArgs(); acceptEventArg.UserToken = listener; acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed); } else { // socket must be cleared since the context object is being reused acceptEventArg.AcceptSocket = null; } Boolean willRaiseEvent; try { willRaiseEvent = listener.Socket.AcceptAsync(acceptEventArg); } catch (ObjectDisposedException) { return; } if (!willRaiseEvent) { ProcessAccept(acceptEventArg); } }
protected abstract void BeginAccept(ListenerContext listener);