berhir / AspNetCore.SpaYarp

An alternative approach to the new ASP.NET Core SPA templates in .NET 6. It uses YARP as proxy to forward requests to the SPA dev server.
MIT License
99 stars 12 forks source link

Make Proxied Request Path Configurable #5

Open DanielStout5 opened 2 years ago

DanielStout5 commented 2 years ago

I haven't been able to fully test this yet (as per my other issue) but from reading the code, I think that the line the line endpoints.Map("/{**catch-all}" in IEndpointRouteBuilderExtensions would make it so the .Net application itself can't have a "catch all" handler - which is I think a relatively common requirement.

I need to be able to have only certain requests proxied - e.g. those starting with /dist/ and other paths specific to the hot reloading features of Webpack. That would let the .Net application handle all other requests, including not matched ones.

I'm thinking that MapSpaYarp should at very least accept the string pattern as a parameter

berhir commented 2 years ago

Thank you for the suggestion. I have not much time currently, but if you create a PR I will merge it.

Not sure if it's better to add a parameter to the MapSpaYarpmethod or if we should add a setting to the SpaDevelopmentServerOptions.

DanielStout5 commented 2 years ago

My opinion would be that it'd be better to add it to MapSpaYarp, since it seems to me that ideally SpaDevelopmentServerOptions could stay in sync with the "real" version that's actually in aspnetcore.

As I mentioned in my other ticket, I've removed SpaYarp and have basically replicated most of what it does - here's how I am now configuring the endpoints:

                if (DebugUtils.IsDebug) // equivalent to #if DEBUG
                {
                    var prov = endpts.ServiceProvider;
                    var forward = prov.GetRequiredService<IHttpForwarder>();
                    var spaOpt = prov.GetRequiredService<IOptions<SpaDevelopmentServerOptions>>().Value;
                    var client = new HttpMessageInvoker(new SocketsHttpHandler()
                    {
                        UseProxy = false,
                        AllowAutoRedirect = false,
                        AutomaticDecompression = System.Net.DecompressionMethods.None,
                        UseCookies = false
                    });

                    var dest = new Uri(spaOpt.ServerUrl).GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);

                    endpts.Map("/dist/{*path}", async ctx =>
                    {
                        var error = await forward.SendAsync(ctx, dest, client);
                        log.LogInformation("Forwarder error: {err}", error);
                    });
                }

Thanks for creating this library - even if I'm not using it directly now, it was how I learned about Yarp and showed me how to solve this issue :)

buchatsky commented 2 years ago

I've made a pull request that introduces SpaDevelopmentServerOptions.PublicPath option which corresponds to devServer.publicPath option of Webpack Dev Server. If .csproj file contains <SpaPublicPath>/dist</SpaPublicPath> only requests with the specified path are forwarded to Dev Server. If .csproj file contains an empty <SpaPublicPath></SpaPublicPath> or this option is omited, forwarding works from the root, as before. I didn't add any specific sample because MVC+SPA pattern is quite cumbersome and its structure is arguable, and it uses Webpack directly. But you can be sure that it works. If 'PublicPath' name is not suitable it can be renamed to something else.

berhir commented 2 years ago

@buchatsky thank you for the PR, I merged it. I will make a few more tests and publish a new version to NuGet within the next few days.

buchatsky commented 2 years ago

There may be some use cases when a single redirect path is not enough. For ex. when several SPAs are launched from different Razor views. So, semicolumn-delimited lists with wildcards would be more suitable

litan1106 commented 2 years ago

@berhir @buchatsky does it make sense to allow custom yarp configuration file for full customization instead of the predefined routes? In the real world applications, we usually have more than one cluster and routes. ex: a signalr route, a spa route, and an api route)

example:

.AddReverseProxy()
      .LoadFromConfig(configuration.GetSection("ReverseProxy"));
muntdan commented 2 years ago

Hello for me using <SpaPublicPath> option doesnt work properly in regards to detecting if ng server is running. If I put <SpaPublicPath>/spa</SpaPublicPath> and <SpaClientUrl>https://localhost:4300</SpaClientUrl> the IsSpaClientRunning doesnt detect succesfull the SPA as it is also lunched with /spa/ option for its routing to work: ng serve --port 4300 --serve-path=/spa/ If I put <SpaPublicPath>/spa</SpaPublicPath> and <SpaClientUrl>https://localhost:4300/spa</SpaClientUrl> IsSpaClientRunning detects it but forwarding is being done with https://localhost:4300/spa/spa/ and doesnt work.

I belive this + _options.PublicPath should be added to IsSpaClientRunning var response = await httpClient.GetAsync(_options.ClientUrl + _options.PublicPath, cancellationTokenSource.Token);

berhir commented 2 years ago

@danmunteanuevo please see #17. a new version that fixes this issue will be published soon