Open Simonl9l opened 4 years ago
Did you try to override the "default" implementation to your own and see if that fixes your issue?
@jeremyVignelles thanks - how does one recommend going about that?
Work around recommendations appreciated!
Are you looking for this kind of code ? https://github.com/RicoSuter/NSwag/blob/b63faa8bb9f1dea7df985ea79cd3089cad3fa44c/samples/WithMiddleware/Sample.AspNetCore21.Nginx/Startup.cs#L57-L80
@jeremyVignelles - well if you mean uncomment the commented code (and make the X-External-Path
lowercase, then I guess yes...I assume I don't need the
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
parts etc.
UseForwardedHeaders seems to be asp.net core thing. not sure what it does and whether you should keep it
@jeremyVignelles - yes not sure why it's in the sample?
So I have modified the code as suggested above, however from an Envoy perspective it does not use the X-External-Path
header, but does make the x-envoy-original-path
header available as documented here. This is the full path prior to any rewrite rules.
Given my routing per the original post (as /service1/swagger
) I'd need to split the path and pull the first part to use as the external path (per the code sample), such as not to have a repeating swagger
route element. I've no idea is there is a more NSwag elegant solution?
app.UseSwaggerUi3(config =>
{
config.TransformToExternalPath = (internalUiRoute, request) =>
{
var externalPath = !request.Headers.ContainsKey("x-envoy-original-path") ? "" :
request.Headers["x-envoy-original-path"].First().Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
return externalPath + internalUiRoute;
};
});
However this still does not work, and there must be more needed, as it ends up at the default route (/
) swagger endpoint/service with the envoy logging showing the requests:
GET /service1/swagger/index.html -> "127.0.0.1:5012 <-- correct service
GET /swagger/v1/swagger.json HTTP/1.1" -> 127.0.0.1:5014 <-- incorrect (default) service also note swagger path is also incorrect
GET /service1/swagger/swagger-ui-standalone-preset.js.map -> "127.0.0.1:5012 <-- correct service
GET /service1/swagger/swagger-ui.css.map -> 127.0.0.1:5012 <-- correct service
GET /service1/swagger/swagger-ui-bundle.js.map -> 127.0.0.1:5012 <-- correct service
Of note: something is obviously missing to have the swagger.json
not be routed to the correct service, and the other files are generic just rendering the swagger.json
?
Any suggestion given deeper NSwag knowledge how this might be fixed ?
@RicoSuter is thee a need to consider if the default implementation based on this documentation (and referenced here) perhaps with some configurability of both the header name used, and how the route is redefined?
To confirm using the implementation per the documentation we end up per login with these endpoints being hit
GET /service1/swagger -> 127.0.0.1:5012
GET /swagger/index.html -> 127.0.0.1:5014
GET /swagger/v1/swagger.json -> 127.0.0.1:5014
GET /swagger/swagger-ui.css.map -> 127.0.0.1:5014
GET /swagger/swagger-ui-bundle.js.map -> 127.0.0.1:5014
GET /swagger/swagger-ui-standalone-preset.js.map -> 127.0.0.1:5014
So not working at all.
All help greatly appreciated.
Did you change the code in the document generation settings too?
I don't see the point of changing the default handling if :
@jeremyVignelles Thanks for the quick turn around...
I assume you mean this:
app.UseOpenApi(config => config.PostProcess = (document, request) =>
{
if (request.Headers.ContainsKey("X-External-Host"))
{
// Change document server settings to public
document.Host = request.Headers["X-External-Host"].First();
document.BasePath = request.Headers["X-External-Path"].First();
}
});
Just seeing how I'd make this work in my case given above.
Well I'd say that NginX/IIS is just a part of the revers proxy universe. Envoy is now part of the AWS AppMesh implementation, so it's hardly "not standard"!.
Why have users of NSwag jump though hoops decoding the documentation (and missing elements of swashbuckle). Why do the X-External-Host
default implementation ?
Swagger UI is not the problem. You need to configure whole app to be aware of being behind proxy to have proper host and scheme in http context. Read this first https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-6.0
Now sample code
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.AllowedHosts.Add("*.some.domain.com"); //this will allow from any subdoamin like appX.some.domain.com
//you can set many and pull this directly from env like builder.Configuration.GetValue<string>("PROXY_DOMAIN")
if (builder.Environment.IsLocalDevelopment()) //this is our extention to tell us we are at localhost via "ASPNETCORE_ENVIRONMENT": "Local"
{
options.AllowedHosts.Add("localhost"); //don't want this in production
}
//Host is a must to handle the domain, Proto is needed to properly handle SSL termination at proxy [FD Https] -> [Origin Http]
options.ForwardedHeaders = ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto;
});
...
var app = builder.Build();
//!! just after builder.Build();
app.UseForwardedHeaders(); //this will adjust your HTTP context host and scheme in case you use SSL termination at proxy Proxy Https -> http origin, see options.ForwardedHeaders
//app.UsePathBase(new PathString("/yourRoute")); //this doesn't work!!!
//for a case you host https://appX.domain.com and use reverse proxy ex. Azure Front door to expose as https://appB.domain/yourRoute/ you need to add PathBase
var proxyPath = "/yourRoute"; pull this from env like builder.Configuration.GetValue<string>("PROXY_ROUTE") //or whatever other config
if (!string.IsNullOrEmpty(proxyPath))
{
app.Use((context, next) =>
{
//for FD this is static, in localhost via ngnix must add this header in the ngnix conf.
//You can use config here any other header I call proxy marker, so the app knows is behind proxy,
//because you don't want to add to PathBase when loading from origin location of https://appX.domain.com
if (context.Request.Headers.Any(x => x.Key == "X-Azure-FDID"))
{
context.Request.PathBase = new PathString(proxyPath);
}
return next(context);
});
}
//CONTINUE THE USUAL PIPELINE REGISTRATION
//app.UseXYZ
Having proper values in HttpContext.Request
will make SwaggerUI work correctly without additional hacking. Also all kinds of OAuth redirects, etc. will relay HttpContext.Request.Host
so this setup is a must for the whole app to route properly.
I hope this helps.
Sample 1
Sample 2
Hi (@jeremyVignelles - pre other thread) - Per this documentation
We're hosting behind an Envoy Mesh Gateway, in a micro services environment. We have public/internet accessing hosts and more importantly internal hosts, that also support the swagger routes such they they are not exposed externally - such that we can use them for diagnostics and testing.
Most of our micro services have a route prefix behind the reverse-proxy, with rewrite rules. we host one of the micro services on the default route without a route prefix (
/
), and all our calls to get to swagger from the other micro services (say/service1/swagger
) are falling back to that (/swagger
).Per the Envoy logging (abbreviated) we get this sequence of calls:
"GET /service1/swagger" -> 127.0.0.1:5012
this correctly routes to the correct micro service"GET /swagger/index.html HTTP/1.1" -> "127.0.0.1:5014"
looses the route...so directs a a different micro service (Note different Port)"GET /swagger/v1/swagger.json -> "127.0.0.1:5014"
as above.Of note that being behind Envoy, and differing from IIS/NginX, due to some other testing it's know that we see an
x-forwarded-for
header. Again note the lower case. Perhaps the default implementation needs to be case insensitive ?