Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to handle non-root gateway? #194

Closed
leonluc-dev opened this issue Oct 12, 2021 · 10 comments
Closed

How to handle non-root gateway? #194

leonluc-dev opened this issue Oct 12, 2021 · 10 comments
Labels
waiting-8-days Closing after 8 days of waiting for the additional info requested.

Comments

@leonluc-dev
Copy link

I have a question regarding the handling of swagger generation when the Ocelot gateway endpoint is not on root.
Using the following ocelot.json:

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/v1/settings/{everything}",
      "DownstreamScheme": "http",
      "ServiceName": "backendpreferences",
      "UpstreamPathTemplate": "/api/v1/settings/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": []
      },
      "SwaggerKey": "settings"
    }
  ],
  "SwaggerEndPoints": [
    {
      "Key": "settings",
      "TransformByOcelotConfig": true,
      "Config": [
        {
          "Name": "My Settings API",
          "Version": "v1",
          "Service": {
            "Name": "backendpreferences",
            "Path": "/swagger/v1/swagger.json"
          }
        }
      ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://myapi.com/mygateway",
    "RequestIdKey": "OcRequestId",
    "AdministrationPath": "/administration",
    "UseServiceDiscovery": true,
    "ServiceDiscoveryProvider": {
      "Host": "consul",
      "Port": 8500,
      "Type": "Consul"
    }
  }

In code the SwaggerForOcelot setup is very basic:

app.UseSwaggerForOcelotUI(opt =>
{
    opt.PathToSwaggerGenerator = "/swagger/docs";
});

Ocelot itself works fine with this setup.

However, whenever we open the swagger documentation at https://myapi.com/mygateway/swagger/index.html the page starts loading but it tries to load the subdocuments at https://myapi.com/swagger/docs rather than the expected https://myapi.com/mygateway/swagger/docs. This causes the page to not load fully and show a fetch error.

Is there a way to set the base url which SwaggerForOcelot should use for the gateway (or how to make it use the Ocelot baseUrl)?

We've tried something like opt.DownstreamSwaggerEndPointBasePath = "../swagger/docs"; as suggested in issue #113, and while this solved the loading of documents, it doesn't fix the urls in the generated document itself. Those still go for the root and therefore the calls fail with a 404:
afbeelding

@Burgyn
Copy link
Owner

Burgyn commented Oct 19, 2021

Hi,

Do you have a base url set in Configure method? app.UsePathBase("/mygateway");

Minimal working example:

Ocelot.json

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/v1/settings/{everything}",
      "UpstreamPathTemplate": "/api/v1/settings/{everything}",
      "SwaggerKey": "settings"
    }
  ],
  "SwaggerEndPoints": [
    {
      "Key": "settings",
      "Config": [
        {
          "Name": "My Settings API",
          "Version": "v1",
          "Url": "http://localhost:5100/swagger/v1/swagger.json"
        }
      ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:5000/mygateway"
  }

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddOcelot();

    services.AddSwaggerForOcelot(Configuration);
    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UsePathBase("/mygateway");
    app.UseSwaggerForOcelotUI()
        .UseOcelot()
        .Wait();
}

@Burgyn Burgyn added the waiting-8-days Closing after 8 days of waiting for the additional info requested. label Oct 19, 2021
@leonluc-dev
Copy link
Author

We had set the base path in ocelot.json. But adding it in code using UsePathBase seems to have solved the issue.

@alex-tselikovsky
Copy link

We had set the base path in ocelot.json. But adding it in code using UsePathBase seems to have solved the issue.

Hello. Did it solve your problem?
I have the same issue. But if I set app.UsePathBase("/basepath"); it doesn't help me.
I get request without changes
image

{
"Routes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 10002
}
],
"UpstreamPathTemplate": "/api/v1/location/{everything}",
"UpstreamHttpMethod": [ "Get" ],
"SwaggerKey": "location",
"VirtualDirectory":"/some"
}
],
"SwaggerEndPoints": [
{
"Key": "location",
"Config": [
{
"Name": "Location API",
"Version": "v1",
"Url": "http://localhost:10002/swagger/v1/swagger.json"
}
]
}],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000/basepath"
}
}
Whan could be the problem? Thanks

@BernhardJ
Copy link

Hi
If I'm not completly wrong, this could be the same issue, like I had. But my solution was different.
See #243
Both, the ocelot gateway and the downstream service are not in the root directory.
I hope this could help.

@alex-tselikovsky
Copy link

Hi If I'm not completly wrong, this could be the same issue, like I had. But my solution was different. See #243 Both, the ocelot gateway and the downstream service are not in the root directory. I hope this could help.

Thanks for your answer. Do you have correct urls in your swagger.json for downstream service? My urls don't have prefix "basepath".
For example I need "basepath/api/v1/location/Areas", but it get me only "/api/v1/location/Areas".

@BernhardJ
Copy link

Hi If I'm not completly wrong, this could be the same issue, like I had. But my solution was different. See #243 Both, the ocelot gateway and the downstream service are not in the root directory. I hope this could help.

Thanks for your answer. Do you have correct urls in your swagger.json for downstream service? My urls don't have prefix "basepath". For example I need "basepath/api/v1/location/Areas", but it get me only "/api/v1/location/Areas".

The 'basepath' is called 'testpath' in my sample for the downstream service.
The virtual directory / application directory of the downstream api is testpath.
I do not set the app.UsePathBase("/testpath") in my code

The virtual directory / application directory of the ocelot gateway is 'ocelotpath' in my sample.

    <PackageReference Include="MMLib.SwaggerForOcelot" Version="6.1.0" />
    <PackageReference Include="Ocelot" Version="18.0.0" />

@alex-tselikovsky
Copy link

@Burgyn Hi, I tried your minimal example, but I didn't have correct urls for downstream services. For example for instead of "mygateway/api/Books", I get just "/api/Books" without mygateway.

ocelot.js

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/{everything}",
      "UpstreamPathTemplate": "/api/{everything}",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 7237
        }
      ],
      "SwaggerKey": "settings"
    }
  ],
  "SwaggerEndPoints": [
    {
      "Key": "settings",
      "Config": [
        {
          "Name": "My Settings API",
          "Version": "v1",
          "Url": "https://localhost:7237/swagger/v1/swagger.json"
        }
      ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:7173/mygateway/"
  }
}

Program.cs

using Ocelot.DependencyInjection;
using Ocelot.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOcelot();
builder.Configuration.SetBasePath(builder.Environment.ContentRootPath)
  .AddJsonFile("ocelot.json", optional: false)
  .AddEnvironmentVariables();
builder.Services.AddSwaggerForOcelot(builder.Configuration);
builder.Services.AddControllers();
var app = builder.Build();

app.UsePathBase("/mygateway");
app.UseSwaggerForOcelotUI(opt =>
  {
    opt.DownstreamSwaggerEndPointBasePath = "/mygateway/swagger/docs";
  })
  .UseOcelot()
  .Wait();

app.Run();

@alex-tselikovsky
Copy link

@leonluc-dev did you solve your problem?

@liamharper2453
Copy link

@alex-tselikovsky None of the above worked for me.

Had to do something like:

    private string AlterUpstreamSwaggerJson(HttpContext context, string swaggerJson)
    {
        swaggerJson = swaggerJson.Replace("\"/v", "\"/api-gateway/v");
        var swagger = JObject.Parse(swaggerJson);
        return swagger.ToString(Formatting.Indented);
    }

    app.UseSwaggerForOcelotUI(opt =>
    {
      opt.ReConfigureUpstreamSwaggerJson = env.IsDevelopment() ? default : AlterUpstreamSwaggerJson;
      opt.DownstreamSwaggerEndPointBasePath = "../swagger/docs";
    });

@MatheusXavier
Copy link

I have the following scenario:

My API Gateways run, within a path of my subdomain:

My configuration file is very simple:

{
  "Routes": [
    {
      "SwaggerKey": "deviceapi",
      "UpstreamPathTemplate": "/api/{version}/rooms",
      "UpstreamHttpMethod": [
        "POST",
        "OPTIONS"
      ],
      "DownstreamPathTemplate": "/api/{version}/rooms",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "device-api",
          "Port": 80
        }
      ]
    }
  ],
  "GlobalConfiguration": {
    "RequestIdKey": "OcRequestId",
    "AdministrationPath": "/administration"
  },
  "SwaggerEndPoints": [
    {
      "Key": "deviceapi",
      "Config": [
        {
          "Name": "Device API",
          "Version": "v1",
          "Url": "http://device-api:80/swagger/v1/swagger.json"
        }
      ]
    }
  ]
}

And I was facing the same problem reported in this issue, I tried the solutions that were reported:

  1. Using app.UsePathBase("/mygateway");
  2. Solution reported on Configure SwaggerForOcelot running in a subpath (IIS application) #243

I wasn't successful with any of them and I ended up following the solution that @liamharper2453 commented on:

public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder UseSwaggerForOcelot(this IApplicationBuilder app, IConfiguration configuration)
    {
        const string swaggerDocsPath = "/swagger/docs";
        bool hasPathBase = !string.IsNullOrEmpty(configuration["PATH_BASE"]);
        string pathBase = hasPathBase ? configuration["PATH_BASE"] : string.Empty;

        app.UseSwaggerForOcelotUI(
            options =>
            {
                options.PathToSwaggerGenerator = swaggerDocsPath;
                options.DownstreamSwaggerEndPointBasePath = pathBase + swaggerDocsPath;
                options.ReConfigureUpstreamSwaggerJson = (HttpContext _, string swaggerJson) =>
                {
                    if (!hasPathBase)
                    {
                        return swaggerJson;
                    }

                    return JObject.Parse(swaggerJson.Replace("\"/api/v", $"\"{pathBase}/api/v")).ToString(Formatting.Indented);
                };
            },
            options => options.OAuthClientId("apigwswagger"));

        return app;
    }
}

I understand that it's not the best solution, but it's the one I managed to make work, if anyone has a better solution I accept suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waiting-8-days Closing after 8 days of waiting for the additional info requested.
Projects
None yet
Development

No branches or pull requests

6 participants