ThreeMammals / Ocelot

.NET API Gateway
https://www.nuget.org/packages/Ocelot
MIT License
8.4k stars 1.64k forks source link

API Administration - 404 not found #645

Closed gfelot closed 6 years ago

gfelot commented 6 years ago

Expected Behavior

Be able to get access to all the API endpoints

Actual Behavior / Motivation for New Feature

I got a 404 on each route.

Steps to Reproduce the Problem

  1. Build a simple API Gw with Ocelot
  2. Test your Ocelot sample app with a working service. Check !
  3. Read the doc : https://ocelot.readthedocs.io/en/latest/features/administration.html
  4. Install : Ocelot.Administration 0.1.0
  5. Add this to your Startup.cs
    public void ConfigureServices(IServiceCollection services)
    {
    services
        .AddOcelot()
        .AddCacheManager(x => { x.WithDictionaryHandle(); })
        .AddAdministration("/administration", "secret");
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
  6. Try to get a JWT like the doc shows you : https://ocelot.readthedocs.io/en/latest/features/administration.html#administration-api with something like with Postman:
    curl -X POST \
    http://localhost:5000/administration/connect/token \
    -H 'Cache-Control: no-cache' \
    -H 'Content-Type: application/json' \
    -H 'Postman-Token: bdd45799-bba3-4933-99db-eb3ba263187d' \
    -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
    -F client_id=admin \
    -F client_secret=secret \
    -F scope=admin \
    -F grant_type=client_credentials

=> Get a 404 :(

Here my ocelot.jsonfile

{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/service/stats/collected",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5100
        }
      ],
      "UpstreamPathTemplate": "/api/stats/collected"
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:5000"
  }
}

Specifications

TomPallister commented 6 years ago

@gfelot I will take a look at this ASAP!

TomPallister commented 6 years ago

@gfelot can you set your logger level to trace and paste the Ocelot log here when you make the request?

gfelot commented 6 years ago

I followed this tutorial : https://www.c-sharpcorner.com/article/building-api-gateway-using-ocelot-in-asp-net-core-part-three-logging2/

I'm stuck at part 2 because I don't have any logging on the console. Do I have to do something else ? (New to .Net Core).

EDIT: I make it work adding to Program.cs

.ConfigureLogging((hostingContext, logging) =>
{
    //add your logging
    logging.AddConsole();
})

Here the log when I try to hit http://localhost:5000/administration/connect/token

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 POST http://localhost:5000/administration/connect/token  0
warn: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0]
      requestId: 0HLH7FCI7DMPL:00000002, previousRequestId: no previous request id, message: DownstreamRouteFinderMiddleware setting pipeline errors. IDownstreamRouteFinder returned Error Code: UnableToFindDownstreamRouteError Message: Unable to find downstream route for path: /administration/connect/token, verb: POST
warn: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0]
      requestId: 0HLH7FCI7DMPL:00000002, previousRequestId: no previous request id, message: Unable to find downstream route for path: /administration/connect/token, verb: POST
warn: Ocelot.Responder.Middleware.ResponderMiddleware[0]
      requestId: 0HLH7FCI7DMPL:00000002, previousRequestId: no previous request id, message: Error Code: UnableToFindDownstreamRouteError Message: Unable to find downstream route for path: /administration/connect/token, verb: POST errors found in ResponderMiddleware. Setting error response for request path:/administration/connect/token, request method: POST
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 0.4863ms 404 

I also see that my caching system doesn't work for my ReRoute part. Maybe I'm doing somehting wrong here :

public void ConfigureServices(IServiceCollection services)
        {
            services
                .AddOcelot()
                .AddCacheManager(x => { x.WithDictionaryHandle(); })
                .AddAdministration("/administration", "secret");
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }
TomPallister commented 6 years ago

@gfelot the log suggests that Ocelot is trying to route your request to http://localhost:5000/administration/connect/token into the normal Ocelot middleware not the administration middleware :/

Could you share your csproj file to make sure the dependencies are correct?

gfelot commented 6 years ago

Sure.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Ocelot">
      <Version>12.0.0</Version>
    </PackageReference>
    <PackageReference Include="Ocelot.Administration">
      <Version>0.1.0</Version>
    </PackageReference>
    <PackageReference Include="Ocelot.Cache.CacheManager">
      <Version>0.1.0</Version>
    </PackageReference>
  </ItemGroup>

</Project>
TomPallister commented 6 years ago

@gfelot how does your configure method look? The part with app.UseOcelot.Wait();

gfelot commented 6 years ago

Startup.cs

[...]

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            //console logging  
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }

Program.cs

public static void Main(string[] args)
        {
            new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    config
                        .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
                        .AddJsonFile("appsettings.json", false, true)
                        .AddJsonFile(
                            $"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json",
                            true,
                            true
                        )
                        .AddJsonFile("ocelot.json", false, true)
                        .AddJsonFile(
                            $"ocelot.{hostingContext.HostingEnvironment.EnvironmentName}.json",
                            true,
                            true
                        )
                        .AddEnvironmentVariables();
                })
                .ConfigureServices(s => { s.AddOcelot(); })
                .ConfigureLogging((hostingContext, logging) =>
                {
                    //add your logging
                    logging.AddConsole();
                })
                .UseIISIntegration()
                .Configure(app => { app.UseOcelot().Wait(); })
                .Build()
                .Run();
        }

That help @TomPallister ?

TomPallister commented 6 years ago

@gfelot sorry I have been really busy and haven't had a chance to look at this yet. I cannot really see anything wrong with your setup. I will try and take a look asap.

TomPallister commented 6 years ago

@gfelot I have pushed a sample administration project to https://github.com/ThreeMammals/Ocelot/tree/develop/samples/AdministrationApi.

Please try and use this and see if it works for you! I tested it locally and it was working fine. Please note I am not using IIS, nginx or anything. Just Kestrel and using the postman scripts in the folder to test it.

gfelot commented 6 years ago

I make it work thanks to your sample and I figure it out how to reproduce my errors, but it's maybe my lack of knowledge in this stack that make me write something wrong. (hope my English will be good enough to explain)

In your doc for administration (or how to add cache), the sample code we have to write is sayed to be inside the function ConfigureServices in Startup.csfile. So I follow like this.

 // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services
                .AddOcelot()
                .AddCacheManager(c => { c.WithDictionaryHandle(); })
                .AddAdministration("/administration", "secret");
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

I just use the template's project build by dotnet new webapi and added Ocelot and its options.

But in the sample you add for administration I saw that you use Program.cs file only to setup the project and if we follow the getting started at the beginning of your documentation you replace this file in a way that Program.cs never call Startup.cs. So if we strictly follow everything that make you have the same issue as me.

I mix everything up and make it work by simplifying Program.cs with the use of WebHost.CreateDefaultBuilder() and used Startup.cs to be as close as the webapi template produce be doing like this :

Program.cs

public class Program
    {

        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost
                .CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((hostingContext, config) =>
                {
                    config
                        .AddJsonFile("ocelot.json", false, true)
                        .AddJsonFile(
                            $"ocelot.{hostingContext.HostingEnvironment.EnvironmentName}.json",
                            true,
                            true
                        );
                })
                .ConfigureLogging((hostingContext, logging) =>
                {
                    //add your logging
                    logging.AddConsole();
                })
                .UseIISIntegration()
                .UseStartup<Startup>();
    }

Startup.cs

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services
                .AddOcelot()
                .AddCacheManager(c => { c.WithDictionaryHandle(); })
                .AddAdministration("/administration", "secret");
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            //console logging  
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseOcelot().Wait();
            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }

I think it would be nice to update the getting started with those files in that way. Like this the other example are available in Startup.cs