azure.net-coreasp.net-web-apiazure-storage-accountocelot

Dynamically replace downstream Host in Ocelot gateway


I have an ocelot gateway that is routing API requests to multiple microservices that are supporting multitenancy.

However, I wanted to use the azure storage APIs to simplify the solution instead of writing code to handle the blobs. I have one storage account for each Tenant, and the issue is I want to switch to the correct storage account based on the tenant-id I have in request as a parameter.

I tried to rewrite the url and switch the downstream host using a middleware.

in program.cs

  var configuration = new OcelotPipelineConfiguration
        {
            PreQueryStringBuilderMiddleware = async (ctx, next) =>
            {
                await preQueryStringBuilderMiddleware.InvokeAsync(ctx, options, next);
            }
        };

Inside the middleware

  {
           //getting the tenantid from the request url parameter 
           ......

            var downstreamRoute = httpContext.Items.DownstreamRoute();

            
            List<DownstreamHostAndPort> downstreamHostAndPorts = new List<DownstreamHostAndPort>()
            { 
               //setting the tenantid as the host
                new DownstreamHostAndPort($"{tenantId}{storageAccountOptions.StorageAccountSuffix}",storageAccountOptions.StorageAccountDefaultPort)
            };

            httpContext.Items.UpsertDownstreamRoute(
                new DownstreamRoute(
                    key: downstreamRoute.Key,
                    upstreamPathTemplate: downstreamRoute.UpstreamPathTemplate,
                    upstreamHeadersFindAndReplace: downstreamRoute.UpstreamHeadersFindAndReplace,
                    downstreamHeadersFindAndReplace: downstreamRoute.DownstreamHeadersFindAndReplace,
                    downstreamAddresses: downstreamHostAndPorts,
                    serviceName: downstreamRoute.ServiceName,
                    serviceNamespace: downstreamRoute.ServiceNamespace,
                    httpHandlerOptions: downstreamRoute.HttpHandlerOptions,
                    useServiceDiscovery: downstreamRoute.UseServiceDiscovery,
                    enableEndpointEndpointRateLimiting: downstreamRoute.EnableEndpointEndpointRateLimiting,
                    qosOptions: downstreamRoute.QosOptions,
                    downstreamScheme: downstreamRoute.DownstreamScheme,
                    requestIdKey: downstreamRoute.RequestIdKey,
                    isCached: downstreamRoute.IsCached,
                    cacheOptions: downstreamRoute.CacheOptions,
                    loadBalancerOptions: downstreamRoute.LoadBalancerOptions,
                    rateLimitOptions: downstreamRoute.RateLimitOptions,
                    routeClaimsRequirement: downstreamRoute.RouteClaimsRequirement,
                    claimsToQueries: downstreamRoute.ClaimsToQueries,
                    claimsToHeaders: downstreamRoute.ClaimsToHeaders,
                    claimsToClaims: downstreamRoute.ClaimsToClaims,
                    claimsToPath: downstreamRoute.ClaimsToPath,
                    isAuthenticated: downstreamRoute.IsAuthenticated,
                    isAuthorized: downstreamRoute.IsAuthorized,
                    authenticationOptions: downstreamRoute.AuthenticationOptions,
                    downstreamPathTemplate: downstreamRoute.DownstreamPathTemplate,
                    loadBalancerKey: downstreamRoute.LoadBalancerKey,
                    delegatingHandlers: downstreamRoute.DelegatingHandlers,
                    addHeadersToDownstream: downstreamRoute.AddHeadersToDownstream,
                    addHeadersToUpstream: downstreamRoute.AddHeadersToUpstream,
                    dangerousAcceptAnyServerCertificateValidator: downstreamRoute.DangerousAcceptAnyServerCertificateValidator,
                    securityOptions: downstreamRoute.SecurityOptions,
                    downstreamHttpMethod: downstreamRoute.DownstreamHttpMethod,
                    downstreamHttpVersion: downstreamRoute.DownstreamHttpVersion
            ));
        }
        await next.Invoke();
 }

The config file looks like below

{
  "DownstreamPathTemplate": "/{sasToken}",
  "DownstreamScheme": "https",
  "DownstreamHostAndPorts": [
    {
      "Host": "ReplacedInRuntime",
      "Port": 443
    }
  ],
  "UpstreamPathTemplate": "/api/{version}/Media/{tenantId}/{sasToken}",

  "UpstreamHttpMethod": [ "POST", "PUT", "GET", "DELETE" ],
  "SwaggerKey": "backgroundService"
},

This works the first time and not afterwards.

I can't use dynamic routing as I have other routes in my config.

Thanks in advance.


Solution

  • I figured this could be solved using Delegte Handlers

     protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
     {
         if (request.RequestUri != null && request.RequestUri.ToString().Contains("the path to override"))
         {
            //do your thing
         }
        // then forward the request
         return await base.SendAsync(request, cancellationToken);
     }