public Task Invoke(HttpContext context) { var backend = context.Features.Get <BackendInfo>() ?? throw new InvalidOperationException("Backend unspecified."); var endpointsFeature = context.Features.Get <IAvailableBackendEndpointsFeature>(); var endpoints = endpointsFeature?.Endpoints ?? throw new InvalidOperationException("The AvailableBackendEndpoints collection was not set."); // TODO: Set defaults properly var loadBalancingOptions = backend.Config.Value?.LoadBalancingOptions ?? default; var endpoint = _operationLogger.Execute( "ReverseProxy.PickEndpoint", () => _loadBalancer.PickEndpoint(endpoints, in loadBalancingOptions)); if (endpoint == null) { _logger.LogWarning($"No available endpoints after load balancing."); context.Response.StatusCode = 503; return(Task.CompletedTask); } endpointsFeature.Endpoints = new[] { endpoint }; return(_next(context)); }
/// <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(); } } }