Example #1
0
        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);
        }
Example #2
0
        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));
                    }
                }
            }
        }
Example #3
0
        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();
            }
        }