asyncapi / saunter

Saunter is a code-first AsyncAPI documentation generator for dotnet.
https://www.asyncapi.com/
MIT License
195 stars 55 forks source link

Issues caused by PathString when running saunter ui behind reverse proxy #161

Open hatzhang opened 1 year ago

hatzhang commented 1 year ago

The following test case will pass:

    [Fact]
    public void PathStringTest()
    {
        var first = new PathString("/m-module");
        var second = new PathString("/{document}/asynciapi/asyncapi.json");
        var final = first.Add(second);
        final.Value.Should().Be("/m-module/{document}/asynciapi/asyncapi.json");
        final.ToString().Should().Be("/m-module/%7Bdocument%7D/asynciapi/asyncapi.json");
    }

So in the method GetDocumentFullRoute of AsyncApiUiMiddleware, PathString's value should be returned. Or the api endpoint won't be hit as expected.

        private string GetDocumentFullRoute(HttpRequest request)
        {
            if (request.PathBase != null)
            {
                return request.PathBase.Add(_options.Middleware.Route).Value;
            }
            return _options.Middleware.Route;
        }
hatzhang commented 1 year ago
image
devlux commented 1 year ago

hi @hatzhang, this route is only used for setting up the routing internally and that placeholder ({document}) is actually replaced by the corresponding document names when using the feature "named documents". therefore that URL is not expected to hit the browser.

could you please show how your setup looks like?

hatzhang commented 1 year ago

The request behaves that way, because of wrong url had been feeded to front end. In the following method of AsyncApiUiMiddleware, asyncApiDocumentUrl is given the value of "/m-module/%7Bdocument%7D/asynciapi/asyncapi.json", thus frontend will request the wrong url, thus leads to the result that the AsyncApiMiddleware can not find the pattern of {document}.

  private async Task RespondWithAsyncApiHtml(HttpResponse response, string route)
        {
            using (var stream = GetType().Assembly.GetManifestResourceStream($"{GetType().Namespace}.index.html"))
            using (var reader = new StreamReader(stream))
            {
                var indexHtml = new StringBuilder(await reader.ReadToEndAsync());

                // Replace dynamic content such as the AsyncAPI document url
                foreach (var replacement in new Dictionary<string, string>
                {
                    ["{{title}}"] = _options.Middleware.UiTitle,
                    ["{{asyncApiDocumentUrl}}"] = route,
                })
                {
                    indexHtml.Replace(replacement.Key, replacement.Value);
                }

                response.StatusCode = (int)HttpStatusCode.OK;
                response.ContentType = MediaTypeNames.Text.Html;
                await response.WriteAsync(indexHtml.ToString(), Encoding.UTF8);
            }
        }

The setup is simple, multi-documents are leveraged:

services.AddAsyncApiSchemaGeneration(options =>
{
    options.Middleware.Route = "/{document}/asyncapi/asyncapi.json";
    options.Middleware.UiBaseRoute = "/{document}/asyncapi/ui/";
});
services.ConfigureNamedAsyncApi("module1", asyncApi =>
{
    asyncApi.Info = new Info("module1", "0.0.1");
});
services.ConfigureNamedAsyncApi("module2", asyncApi =>
{
    asyncApi.Info = new Info("module2", "0.0.1");
});

And UsePathBase before UseRouting should be able to re-create this.

app.UsePathBase("/m-module");
app.UseRouting();
 app.UseEndpoints(endpoints =>
            {
                 endpoints.MapAsyncApiDocuments();
                 endpoints.MapAsyncApiUi();
                endpoints.MapControllers();
            });

And the following method should also return value of PathString, or the /ui/* won't work.

        private string GetUiIndexFullRoute(HttpRequest request)
        {
            if (request.PathBase != null)
            {
                return request.PathBase.Add(UiIndexRoute).Value;
            }

            return UiIndexRoute;
        }
devlux commented 1 year ago

what .NET version are you using?

can you please include also the classes that represent those two named documents?

did you also try to tell saunter where to search for those classes in AddAsyncApiSchemaGeneration?

options.AssemblyMarkerTypes = new[] { typeof(Startup) };
hatzhang commented 1 year ago

I am using net6. And all settings should be good. If i fix those two methods, it will work. Or it shows that error.