예제 #1
0
        /// <inheritdoc/>
        public async Task InvokeAsync(HttpContext context)
        {
            Contracts.CheckValue(context, nameof(context));

            var aspNetCoreEndpoint = context.GetEndpoint();

            if (aspNetCoreEndpoint == null)
            {
                throw new ReverseProxyException($"ASP .NET Core Endpoint wasn't set for the current request. This is a coding defect.");
            }

            var routeConfig = aspNetCoreEndpoint.Metadata.GetMetadata <RouteConfig>();

            if (routeConfig == null)
            {
                throw new ReverseProxyException($"ASP .NET Core Endpoint is missing {typeof(RouteConfig).FullName} metadata. This is a coding defect.");
            }

            var backend = routeConfig.BackendOrNull;

            if (backend == null)
            {
                throw new ReverseProxyException($"Route has no backend information.");
            }

            var dynamicState = backend.DynamicState.Value;

            if (dynamicState == null)
            {
                throw new ReverseProxyException($"Route has no up to date information on its backend '{backend.BackendId}'. Perhaps the backend hasn't been probed yet? This can happen when a new backend is added but isn't ready to serve traffic yet.");
            }

            // TODO: Set defaults properly
            BackendConfig.BackendLoadBalancingOptions loadBalancingOptions = default;
            var backendConfig = backend.Config.Value;

            if (backendConfig != null)
            {
                loadBalancingOptions = backendConfig.LoadBalancingOptions;
            }

            var endpoint = _operationLogger.Execute(
                "ReverseProxy.PickEndpoint",
                () => _loadBalancer.PickEndpoint(dynamicState.HealthyEndpoints, dynamicState.AllEndpoints, in loadBalancingOptions));

            if (endpoint == null)
            {
                throw new ReverseProxyException($"No available endpoints.");
            }

            var endpointConfig = endpoint.Config.Value;

            if (endpointConfig == null)
            {
                throw new ReverseProxyException($"Chosen endpoint has no configs set: '{endpoint.EndpointId}'");
            }

            // TODO: support StripPrefix and other url transformations
            var targetUrl = BuildOutgoingUrl(context, endpointConfig.Address);

            _logger.LogInformation($"Proxying to {targetUrl}");
            var targetUri = new Uri(targetUrl, UriKind.Absolute);

            using (var shortCts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted))
            {
                // TODO: Configurable timeout, measure from request start, make it unit-testable
                shortCts.CancelAfter(TimeSpan.FromSeconds(30));

                // TODO: Retry against other endpoints
                try
                {
                    // TODO: Apply caps
                    backend.ConcurrencyCounter.Increment();
                    endpoint.ConcurrencyCounter.Increment();

                    // TODO: Duplex channels should not have a timeout (?), but must react to Proxy force-shutdown signals.
                    var longCancellation = context.RequestAborted;

                    var proxyTelemetryContext = new ProxyTelemetryContext(
                        backendId: backend.BackendId,
                        routeId: routeConfig.Route.RouteId,
                        endpointId: endpoint.EndpointId);

                    await _operationLogger.ExecuteAsync(
                        "ReverseProxy.Proxy",
                        () => _httpProxy.ProxyAsync(context, targetUri, backend.ProxyHttpClientFactory, proxyTelemetryContext, shortCancellation: shortCts.Token, longCancellation: longCancellation));
                }
                finally
                {
                    endpoint.ConcurrencyCounter.Decrement();
                    backend.ConcurrencyCounter.Decrement();
                }
            }
        }