Skip to content

Commit

Permalink
Big refactoring
Browse files Browse the repository at this point in the history
- File scoped namepaces, collection expressions, target type new, top level statements, IHostApplicationBuilder
  • Loading branch information
davidfowl committed Nov 18, 2024
1 parent d101494 commit b36ea05
Show file tree
Hide file tree
Showing 44 changed files with 1,946 additions and 2,281 deletions.
102 changes: 36 additions & 66 deletions samples/ServerApplication/Program.cs
Original file line number Diff line number Diff line change
@@ -1,85 +1,55 @@
using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Bedrock.Framework;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ServerApplication;

namespace ServerApplication
{
public partial class Program
{
public static async Task Main(string[] args)
{
// Manual wire up of the server
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.SetMinimumLevel(LogLevel.Debug);
builder.AddConsole();
});

services.AddSignalR();

var serviceProvider = services.BuildServiceProvider();

var server = new ServerBuilder(serviceProvider)
.UseSockets(sockets =>
{
// Echo server
sockets.ListenLocalhost(5000,
builder => builder.UseConnectionLogging().UseConnectionHandler<EchoServerApplication>());
var builder = Host.CreateApplicationBuilder();

// HTTP/1.1 server
sockets.Listen(IPAddress.Loopback, 5001,
builder => builder.UseConnectionLogging().UseConnectionHandler<HttpApplication>());
builder.Logging.SetMinimumLevel(LogLevel.Debug);

// SignalR Hub
sockets.Listen(IPAddress.Loopback, 5002,
builder => builder.UseConnectionLogging().UseHub<Chat>());
builder.Services.AddSignalR();

// MQTT application
sockets.Listen(IPAddress.Loopback, 5003,
builder => builder.UseConnectionLogging().UseConnectionHandler<MqttApplication>());

// Echo Server with TLS
sockets.Listen(IPAddress.Loopback, 5004,
builder => builder.UseServerTls(options =>
{
options.LocalCertificate = X509CertificateLoader.LoadPkcs12FromFile("testcert.pfx", "testcert");

// NOTE: Do not do this in a production environment
options.AllowAnyRemoteCertificate();
})
.UseConnectionLogging().UseConnectionHandler<EchoServerApplication>());
builder.ConfigureServer(server =>
{
server.UseSockets(sockets =>
{
// Echo server
sockets.ListenLocalhost(5000,
builder => builder.UseConnectionLogging().UseConnectionHandler<EchoServerApplication>());

sockets.Listen(IPAddress.Loopback, 5005,
builder => builder.UseConnectionLogging().UseConnectionHandler<MyCustomProtocol>());
})
.Build();
// HTTP/1.1 server
sockets.Listen(IPAddress.Loopback, 5001,
builder => builder.UseConnectionLogging().UseConnectionHandler<HttpApplication>());

var logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<Program>();
// SignalR Hub
sockets.Listen(IPAddress.Loopback, 5002,
builder => builder.UseConnectionLogging().UseHub<Chat>());

await server.StartAsync();
// MQTT application
sockets.Listen(IPAddress.Loopback, 5003,
builder => builder.UseConnectionLogging().UseConnectionHandler<MqttApplication>());

foreach (var ep in server.EndPoints)
// Echo Server with TLS
sockets.Listen(IPAddress.Loopback, 5004,
builder => builder.UseServerTls(options =>
{
logger.LogInformation("Listening on {EndPoint}", ep);
}
options.LocalCertificate = X509CertificateLoader.LoadPkcs12FromFile("testcert.pfx", "testcert");

var tcs = new TaskCompletionSource<object>();
Console.CancelKeyPress += (sender, e) =>
{
tcs.TrySetResult(null);
e.Cancel = true;
};
// NOTE: Do not do this in a production environment
options.AllowAnyRemoteCertificate();
})
.UseConnectionLogging().UseConnectionHandler<EchoServerApplication>());

sockets.Listen(IPAddress.Loopback, 5005,
builder => builder.UseConnectionLogging().UseConnectionHandler<MyCustomProtocol>());
});
});

await tcs.Task;
var host = builder.Build();

await server.StopAsync();
}
}
}
host.Run();
36 changes: 13 additions & 23 deletions src/Bedrock.Framework/Client/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,23 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;

namespace Bedrock.Framework
namespace Bedrock.Framework;

public class Client(IConnectionFactory connectionFactory, ConnectionDelegate application) : IConnectionFactory
{
public class Client : IConnectionFactory
public async ValueTask<ConnectionContext> ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
{
private readonly IConnectionFactory _connectionFactory;
private readonly ConnectionDelegate _application;

public Client(IConnectionFactory connectionFactory, ConnectionDelegate application)
{
_connectionFactory = connectionFactory;
_application = application;
}

public async ValueTask<ConnectionContext> ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
{
var connection = await _connectionFactory.ConnectAsync(endpoint, cancellationToken).ConfigureAwait(false);
var connection = await connectionFactory.ConnectAsync(endpoint, cancellationToken).ConfigureAwait(false);

// Since nothing is being returned from this middleware, we need to wait for the last middleware to run
// until we yield this call. Stash a tcs in the items bag that allows this code to get notified
// when the middleware ran
var connectionContextWithDelegate = new ConnectionContextWithDelegate(connection, _application);
// Since nothing is being returned from this middleware, we need to wait for the last middleware to run
// until we yield this call. Stash a tcs in the items bag that allows this code to get notified
// when the middleware ran
var connectionContextWithDelegate = new ConnectionContextWithDelegate(connection, application);

// Execute the middleware pipeline
connectionContextWithDelegate.Start();
// Execute the middleware pipeline
connectionContextWithDelegate.Start();

// Wait for it the most inner middleware to run
return await connectionContextWithDelegate.Initialized.Task.ConfigureAwait(false);
}
// Wait for it the most inner middleware to run
return await connectionContextWithDelegate.Initialized.Task.ConfigureAwait(false);
}
}
129 changes: 64 additions & 65 deletions src/Bedrock.Framework/Client/ClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,92 +5,91 @@
using Bedrock.Framework.Infrastructure;
using Microsoft.AspNetCore.Connections;

namespace Bedrock.Framework
namespace Bedrock.Framework;

public partial class ClientBuilder : IConnectionBuilder
{
public partial class ClientBuilder : IConnectionBuilder
{
private readonly ConnectionBuilder _connectionBuilder;
private readonly ConnectionBuilder _connectionBuilder;

public ClientBuilder() : this(EmptyServiceProvider.Instance)
{
public ClientBuilder() : this(EmptyServiceProvider.Instance)
{

}
}

public ClientBuilder(IServiceProvider serviceProvider)
{
_connectionBuilder = new ConnectionBuilder(serviceProvider);
}
public ClientBuilder(IServiceProvider serviceProvider)
{
_connectionBuilder = new ConnectionBuilder(serviceProvider);
}

internal static object Key { get; } = new object();
internal static object Key { get; } = new object();

private IConnectionFactory ConnectionFactory { get; set; } = new ThrowConnectionFactory();
private IConnectionFactory ConnectionFactory { get; set; } = new ThrowConnectionFactory();

public IServiceProvider ApplicationServices => _connectionBuilder.ApplicationServices;
public IServiceProvider ApplicationServices => _connectionBuilder.ApplicationServices;

public Client Build()
public Client Build()
{
// Middleware currently a single linear execution flow without a return value.
// We need to return the connection when it reaches the innermost middleware (D in this case)
// Then we need to wait until dispose is called to unwind that pipeline.

// A ->
// B ->
// C ->
// D
// C <-
// B <-
// A <-

_connectionBuilder.Run(connection =>
{
// Middleware currently a single linear execution flow without a return value.
// We need to return the connection when it reaches the innermost middleware (D in this case)
// Then we need to wait until dispose is called to unwind that pipeline.

// A ->
// B ->
// C ->
// D
// C <-
// B <-
// A <-

_connectionBuilder.Run(connection =>
if (connection is ConnectionContextWithDelegate connectionContextWithDelegate)
{
if (connection is ConnectionContextWithDelegate connectionContextWithDelegate)
{
connectionContextWithDelegate.Initialized.TrySetResult(connectionContextWithDelegate);
connectionContextWithDelegate.Initialized.TrySetResult(connectionContextWithDelegate);


// This task needs to stay around until the connection is disposed
// only then can we unwind the middleware chain
return connectionContextWithDelegate.ExecutionTask;
}
// This task needs to stay around until the connection is disposed
// only then can we unwind the middleware chain
return connectionContextWithDelegate.ExecutionTask;
}

// REVIEW: Do we throw in this case? It's edgy but possible to call next with a differnt
// connection delegate that originally given
return Task.CompletedTask;
});
// REVIEW: Do we throw in this case? It's edgy but possible to call next with a differnt
// connection delegate that originally given
return Task.CompletedTask;
});

var application = _connectionBuilder.Build();
var application = _connectionBuilder.Build();

return new Client(ConnectionFactory, application);
}
return new Client(ConnectionFactory, application);
}

public ClientBuilder UseConnectionFactory(IConnectionFactory connectionFactory)
{
ConnectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
return this;
}
public ClientBuilder UseConnectionFactory(IConnectionFactory connectionFactory)
{
ConnectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
return this;
}

public ClientBuilder Use(Func<IConnectionFactory, IConnectionFactory> middleware)
{
ConnectionFactory = middleware(ConnectionFactory);
return this;
}
public ClientBuilder Use(Func<IConnectionFactory, IConnectionFactory> middleware)
{
ConnectionFactory = middleware(ConnectionFactory);
return this;
}

public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)
{
return _connectionBuilder.Use(middleware);
}
public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)
{
return _connectionBuilder.Use(middleware);
}

ConnectionDelegate IConnectionBuilder.Build()
{
return _connectionBuilder.Build();
}
ConnectionDelegate IConnectionBuilder.Build()
{
return _connectionBuilder.Build();
}

private class ThrowConnectionFactory : IConnectionFactory
private class ThrowConnectionFactory : IConnectionFactory
{
public ValueTask<ConnectionContext> ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
{
public ValueTask<ConnectionContext> ConnectAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
{
throw new InvalidOperationException("No transport configured. Set the ConnectionFactory property.");
}
throw new InvalidOperationException("No transport configured. Set the ConnectionFactory property.");
}
}
}
28 changes: 11 additions & 17 deletions src/Bedrock.Framework/Hosting/ServerHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,19 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

namespace Bedrock.Framework
{
public class ServerHostedService : IHostedService
{
private readonly Server _server;
namespace Bedrock.Framework;

public ServerHostedService(IOptions<ServerHostedServiceOptions> options)
{
_server = options.Value.ServerBuilder.Build();
}
public class ServerHostedService(IOptions<ServerHostedServiceOptions> options) : IHostedService
{
private readonly Server _server = options.Value.ServerBuilder.Build();

public Task StartAsync(CancellationToken cancellationToken)
{
return _server.StartAsync(cancellationToken);
}
public Task StartAsync(CancellationToken cancellationToken)
{
return _server.StartAsync(cancellationToken);
}

public Task StopAsync(CancellationToken cancellationToken)
{
return _server.StopAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _server.StopAsync(cancellationToken);
}
}
11 changes: 3 additions & 8 deletions src/Bedrock.Framework/Hosting/ServerHostedServiceOptions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Bedrock.Framework;

namespace Bedrock.Framework
public class ServerHostedServiceOptions
{
public class ServerHostedServiceOptions
{
public ServerBuilder ServerBuilder { get; set; }
}
public ServerBuilder ServerBuilder { get; set; }
}
Loading

0 comments on commit b36ea05

Please sign in to comment.