|
1 | 1 | using System.Net; |
2 | 2 | using System.Reflection; |
3 | 3 | using System.Security.Cryptography.X509Certificates; |
| 4 | +using DotNext; |
4 | 5 | using DotNext.Net.Cluster.Consensus.Raft; |
5 | 6 | using DotNext.Net.Cluster.Consensus.Raft.Http; |
6 | 7 | using DotNext.Net.Cluster.Consensus.Raft.Membership; |
| 8 | +using Microsoft.AspNetCore.Connections; |
7 | 9 | using RaftNode; |
| 10 | +using static System.Globalization.CultureInfo; |
8 | 11 | using SslOptions = DotNext.Net.Security.SslOptions; |
9 | 12 |
|
10 | 13 | switch (args.LongLength) |
|
21 | 24 | break; |
22 | 25 | } |
23 | 26 |
|
24 | | -static Task UseAspNetCoreHost(int port, string? persistentStorage = null) |
| 27 | +static async Task UseAspNetCoreHost(int port, string? persistentStorage = null) |
25 | 28 | { |
26 | 29 | var configuration = new Dictionary<string, string?> |
27 | | - { |
28 | | - {"partitioning", "false"}, |
29 | | - {"lowerElectionTimeout", "150" }, |
30 | | - {"upperElectionTimeout", "300" }, |
31 | | - {"requestTimeout", "00:10:00"}, |
32 | | - {"publicEndPoint", $"https://localhost:{port}"}, |
33 | | - {"coldStart", "false"}, |
34 | | - {"requestJournal:memoryLimit", "5" }, |
35 | | - {"requestJournal:expiration", "00:01:00" } |
36 | | - }; |
37 | | - if (!string.IsNullOrEmpty(persistentStorage)) |
38 | | - configuration[SimplePersistentState.LogLocation] = persistentStorage; |
39 | | - return new HostBuilder().ConfigureWebHost(webHost => |
40 | 30 | { |
41 | | - webHost.UseKestrel(options => |
| 31 | + { "partitioning", "false" }, |
| 32 | + { "lowerElectionTimeout", "150" }, |
| 33 | + { "upperElectionTimeout", "300" }, |
| 34 | + { "requestTimeout", "00:10:00" }, |
| 35 | + { "publicEndPoint", $"https://localhost:{port}" }, |
| 36 | + { "coldStart", "false" }, |
| 37 | + { "requestJournal:memoryLimit", "5" }, |
| 38 | + { "requestJournal:expiration", "00:01:00" }, |
| 39 | + { SimplePersistentState.LogLocation, persistentStorage }, |
| 40 | + }; |
| 41 | + |
| 42 | + var builder = WebApplication.CreateSlimBuilder(); |
| 43 | + builder.Configuration.AddInMemoryCollection(configuration); |
| 44 | + builder.WebHost.ConfigureKestrel(options => |
| 45 | + { |
| 46 | + options.ListenLocalhost(port, static listener => listener.UseHttps(LoadCertificate())); |
| 47 | + }); |
| 48 | + |
| 49 | + builder.Services |
| 50 | + .UseInMemoryConfigurationStorage(AddClusterMembers) |
| 51 | + .ConfigureCluster<ClusterConfigurator>() |
| 52 | + .AddSingleton<IHttpMessageHandlerFactory, RaftClientHandlerFactory>() |
| 53 | + .AddOptions() |
| 54 | + .AddRouting(); |
| 55 | + |
| 56 | + if (!string.IsNullOrWhiteSpace(persistentStorage)) |
| 57 | + { |
| 58 | + builder.Services.UsePersistenceEngine<ISupplier<long>, SimplePersistentState>() |
| 59 | + .AddSingleton<IHostedService, DataModifier>(); |
| 60 | + } |
| 61 | + |
| 62 | + ConfigureLogging(builder.Logging); |
| 63 | + builder.JoinCluster(); |
| 64 | + |
| 65 | + await using var app = builder.Build(); |
| 66 | + |
| 67 | + const string leaderResource = "/leader"; |
| 68 | + const string valueResource = "/value"; |
| 69 | + app.UseConsensusProtocolHandler() |
| 70 | + .RedirectToLeader(leaderResource) |
| 71 | + .UseRouting() |
| 72 | + .UseEndpoints(static endpoints => |
42 | 73 | { |
43 | | - options.ListenLocalhost(port, static listener => listener.UseHttps(LoadCertificate())); |
44 | | - }) |
45 | | - .UseStartup<Startup>(); |
46 | | - }) |
47 | | - .ConfigureLogging(ConfigureLogging) |
48 | | - .ConfigureAppConfiguration(builder => builder.AddInMemoryCollection(configuration)) |
49 | | - .JoinCluster() |
50 | | - .Build() |
51 | | - .RunAsync(); |
| 74 | + endpoints.MapGet(leaderResource, RedirectToLeaderAsync); |
| 75 | + endpoints.MapGet(valueResource, GetValueAsync); |
| 76 | + }); |
| 77 | + await app.RunAsync(); |
| 78 | + |
| 79 | + static Task RedirectToLeaderAsync(HttpContext context) |
| 80 | + { |
| 81 | + var cluster = context.RequestServices.GetRequiredService<IRaftCluster>(); |
| 82 | + return context.Response.WriteAsync($"Leader address is {cluster.Leader?.EndPoint}. Current address is {context.Connection.LocalIpAddress}:{context.Connection.LocalPort}", context.RequestAborted); |
| 83 | + } |
| 84 | + |
| 85 | + static async Task GetValueAsync(HttpContext context) |
| 86 | + { |
| 87 | + var cluster = context.RequestServices.GetRequiredService<IRaftCluster>(); |
| 88 | + var provider = context.RequestServices.GetRequiredService<ISupplier<long>>(); |
| 89 | + |
| 90 | + await cluster.ApplyReadBarrierAsync(context.RequestAborted); |
| 91 | + await context.Response.WriteAsync(provider.Invoke().ToString(InvariantCulture), context.RequestAborted); |
| 92 | + } |
| 93 | + |
| 94 | + // NOTE: this way of adding members to the cluster is not recommended in production code |
| 95 | + static void AddClusterMembers(ICollection<UriEndPoint> members) |
| 96 | + { |
| 97 | + members.Add(new UriEndPoint(new("https://localhost:3262", UriKind.Absolute))); |
| 98 | + members.Add(new UriEndPoint(new("https://localhost:3263", UriKind.Absolute))); |
| 99 | + members.Add(new UriEndPoint(new("https://localhost:3264", UriKind.Absolute))); |
| 100 | + } |
52 | 101 | } |
53 | 102 |
|
54 | 103 | static async Task UseConfiguration(RaftCluster.NodeConfiguration config, string? persistentStorage) |
|
0 commit comments