private static void Map <TRequest>( this IEndpointRouteBuilder endpoints, string pattern, HttpMethod method, string group = null, CommandEndpointResponse response = null, OpenApiOperation openApi = null) { if (pattern.IsNullOrEmpty()) { throw new ArgumentNullException($"{typeof(TRequest)} cannot be registered with a null or empty route pattern."); } if (!pattern.StartsWith('/')) { pattern = $"/{pattern}"; // ensure leading pattern slash } var mediator = endpoints.ServiceProvider.GetService <IMediator>() ?? throw new InvalidOperationException("IMediator has not been added to IServiceCollection. You can add it with services.AddMediatR(...);"); var configuration = endpoints.ServiceProvider.GetService <ICommandEndpointConfiguration>() // =singleton ?? throw new InvalidOperationException("ICommandEndpointRegistrations has not been added to IServiceCollection. You can add it with services.AddCommandEndpoints(...);"); var registration = configuration.AddRegistration <TRequest>(pattern, method); registration.OpenApi = openApi ?? new OpenApiOperation() { GroupName = group ?? pattern.SliceFromLast("/").SliceTill("?").SliceTill("{").EmptyToNull() ?? "Undefined" }; registration.Response = response; IEndpointConventionBuilder builder = null; if (method == HttpMethod.Get) { builder = endpoints.MapGet(pattern.SliceTill("?"), CommandEndpointRequestHandler.InvokeAsync); } else if (method == HttpMethod.Post) { builder = endpoints.MapPost(pattern.SliceTill("?"), CommandEndpointRequestHandler.InvokeAsync); } else if (method == HttpMethod.Put) { builder = endpoints.MapPut(pattern.SliceTill("?"), CommandEndpointRequestHandler.InvokeAsync); } else if (method == HttpMethod.Delete) { builder = endpoints.MapDelete(pattern.SliceTill("?"), CommandEndpointRequestHandler.InvokeAsync); } //else if (method == HttpMethod.Patch) //{ // builder = endpoints.MapPatch(pattern.SliceTill("?"), CommandEndpointRequestDelegate); //} builder?.WithDisplayName(registration.Name); builder?.WithMetadata(registration); }
public async Task StartAsync(Application application) { var invoker = new HttpMessageInvoker(new ConnectionRetryHandler(new SocketsHttpHandler { AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseProxy = false })); foreach (var service in application.Services.Values) { var serviceDescription = service.Description; if (service.Description.RunInfo is IngressRunInfo runInfo) { var builder = new WebApplicationBuilder(); builder.Services.AddSingleton <MatcherPolicy, IngressHostMatcherPolicy>(); builder.Logging.AddProvider(new ServiceLoggerProvider(service.Logs)); var addresses = new List <string>(); // Bind to the addresses on this resource for (int i = 0; i < serviceDescription.Replicas; i++) { // Fake replicas since it's all running processes var replica = service.Description.Name + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower(); var status = new IngressStatus(service, replica); service.Replicas[replica] = status; var ports = new List <int>(); foreach (var binding in serviceDescription.Bindings) { if (binding.Port == null) { continue; } var port = binding.ReplicaPorts[i]; ports.Add(port); var url = $"{binding.Protocol}://localhost:{port}"; addresses.Add(url); } status.Ports = ports; service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status)); } builder.Server.UseUrls(addresses.ToArray()); var webApp = builder.Build(); _webApplications.Add(webApp); // For each ingress rule, bind to the path and host foreach (var rule in runInfo.Rules) { if (!application.Services.TryGetValue(rule.Service, out var target)) { continue; } _logger.LogInformation("Processing ingress rule: Path:{Path}, Host:{Host}, Service:{Service}", rule.Path, rule.Host, rule.Service); var targetServiceDescription = target.Description; RegisterListener(target); var uris = new List <(int Port, Uri Uri)>(); // HTTP before HTTPS (this might change once we figure out certs...) var targetBinding = targetServiceDescription.Bindings.FirstOrDefault(b => b.Protocol == "http") ?? targetServiceDescription.Bindings.FirstOrDefault(b => b.Protocol == "https"); if (targetBinding == null) { _logger.LogInformation("Service {ServiceName} does not have any HTTP or HTTPs bindings", targetServiceDescription.Name); continue; } // For each of the target service replicas, get the base URL // based on the replica port for (int i = 0; i < targetServiceDescription.Replicas; i++) { var port = targetBinding.ReplicaPorts[i]; var url = $"{targetBinding.Protocol}://localhost:{port}"; uris.Add((port, new Uri(url))); } _logger.LogInformation("Service {ServiceName} is using {Urls}", targetServiceDescription.Name, string.Join(",", uris.Select(u => u.ToString()))); // The only load balancing strategy here is round robin long count = 0; RequestDelegate del = async context => { var next = (int)(Interlocked.Increment(ref count) % uris.Count); // we find the first `Ready` port for (int i = 0; i < uris.Count; i++) { if (_readyPorts.ContainsKey(uris[next].Port)) { break; } next = (int)(Interlocked.Increment(ref count) % uris.Count); } // if we've looped through all the port and didn't find a single one that is `Ready`, we return HTTP BadGateway if (!_readyPorts.ContainsKey(uris[next].Port)) { context.Response.StatusCode = (int)HttpStatusCode.BadGateway; await context.Response.WriteAsync("Bad gateway"); return; } var uri = new UriBuilder(uris[next].Uri) { Path = rule.PreservePath ? $"{context.Request.Path}{context.Request.RouteValues["path"]}" : (string)context.Request.RouteValues["path"] ?? "/", Query = context.Request.QueryString.Value }; await context.ProxyRequest(invoker, uri.Uri); }; IEndpointConventionBuilder conventions = ((IEndpointRouteBuilder)webApp).Map((rule.Path?.TrimEnd('/') ?? "") + "/{**path}", del); if (rule.Host != null) { conventions.WithMetadata(new IngressHostMetadata(rule.Host)); } conventions.WithDisplayName(rule.Service); } await webApp.StartAsync(); foreach (var replica in service.Replicas) { service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Started, replica.Value)); } } } }
public async Task StartAsync(Application application) { var invoker = new HttpMessageInvoker(new ConnectionRetryHandler(new SocketsHttpHandler { AllowAutoRedirect = false, AutomaticDecompression = DecompressionMethods.None, UseProxy = false })); foreach (var service in application.Services.Values) { var serviceDescription = service.Description; if (service.Description.RunInfo is IngressRunInfo runInfo) { var builder = new WebApplicationBuilder(); builder.Services.AddSingleton <MatcherPolicy, IngressHostMatcherPolicy>(); builder.Logging.AddProvider(new ServiceLoggerProvider(service.Logs)); var addresses = new List <string>(); // Bind to the addresses on this resource for (int i = 0; i < serviceDescription.Replicas; i++) { // Fake replicas since it's all running processes var replica = service.Description.Name + "_" + Guid.NewGuid().ToString().Substring(0, 10).ToLower(); var status = new IngressStatus(service, replica); service.Replicas[replica] = status; var ports = new List <int>(); foreach (var binding in serviceDescription.Bindings) { if (binding.Port == null) { continue; } var port = service.PortMap[binding.Port.Value][i]; ports.Add(port); var url = $"{binding.Protocol ?? "http"}://localhost:{port}"; addresses.Add(url); } status.Ports = ports; service.ReplicaEvents.OnNext(new ReplicaEvent(ReplicaState.Added, status)); } builder.Server.UseUrls(addresses.ToArray()); var webApp = builder.Build(); _webApplications.Add(webApp); // For each ingress rule, bind to the path and host foreach (var rule in runInfo.Rules) { if (!application.Services.TryGetValue(rule.Service, out var target)) { continue; } _logger.LogInformation("Processing ingress rule: Path:{Path}, Host:{Host}, Service:{Service}", rule.Path, rule.Host, rule.Service); var targetServiceDescription = target.Description; var uris = new List <Uri>(); // For each of the target service replicas, get the base URL // based on the replica port for (int i = 0; i < targetServiceDescription.Replicas; i++) { foreach (var binding in targetServiceDescription.Bindings) { if (binding.Port == null) { continue; } var port = target.PortMap[binding.Port.Value][i]; var url = $"{binding.Protocol ?? "http"}://localhost:{port}"; uris.Add(new Uri(url)); } } // The only load balancing strategy here is round robin long count = 0; RequestDelegate del = context => { var next = (int)(Interlocked.Increment(ref count) % uris.Count); var uri = new UriBuilder(uris[next]) { Path = (string)context.Request.RouteValues["path"] }; return(context.ProxyRequest(invoker, uri.Uri)); }; IEndpointConventionBuilder conventions = null !; if (rule.Path != null) { conventions = ((IEndpointRouteBuilder)webApp).Map(rule.Path.TrimEnd('/') + "/{**path}", del); } else { conventions = webApp.MapFallback(del); } if (rule.Host != null) { conventions.WithMetadata(new IngressHostMetadata(rule.Host)); } conventions.WithDisplayName(rule.Service); } } } foreach (var app in _webApplications) { await app.StartAsync(); } }