Skip to content
This repository has been archived by the owner on Dec 19, 2018. It is now read-only.

Question: best practice Linux + nginx + subfolder in url #815

Closed
Weboholics opened this issue Jul 13, 2016 · 32 comments
Closed

Question: best practice Linux + nginx + subfolder in url #815

Weboholics opened this issue Jul 13, 2016 · 32 comments

Comments

@Weboholics
Copy link

Hi,
We are having problem using subfolder in url, example:
http://domainName.com/weboholics
We have tried two approaches:

(What we think is the right way) Rewrite url in nginx
(nginx configuration)
rewrite ^(/weboholics)(.*)$ $2 break;
With this approach razor get confused and @Url.Action("Index","Home") returns:
/Home/Index when the correct url should be
/weboholics/Home/Index

We have asked others, and they have suggested to use WebHostBuilder().UseUrls("http://domainName.com/weboholics"), but seems not feasible due to our usage of Unix Socket
(our UseUrl)
.UseUrls("http://unix:/var/disk2/sparfiler/www/weboholics.sock")

Second approach - change routing

With this approach Razor Url.Action() works but this doesn't work in Razor:
(code)
<script type="application/ecmascript" src="~/ckeditor/ckeditor.js"></script>
Razor will render this to:
<script type="application/ecmascript" src="/ckeditor/ckeditor.js" ></script>
(How it should be)
<script type="application/ecmascript" src="/weboholics/ckeditor/ckeditor.js" ></script>

Our startup (both approaches )

public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseUrls("http://unix:/var/disk2/sparfiler/www/weboholics.sock")
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }

What is the correct approach for using subfolders? especially in an Linux + nginx environment?

Best Regards
Jesse / Weboholics

@Tratcher
Copy link
Member

UseUrls("http://domainName.com/weboholics") is exactly how we handle this for IIS.
@halter73, is there a way to make this work for unix sockets? Maybe we should come up with one.

@davidfowl
Copy link
Member

davidfowl commented Jul 13, 2016

Can't there just be a middleware in the pipeline that sets the path base on http context? (that's effectively) what happens when you put the path in there anyways.

@Tratcher
Copy link
Member

There could be, but this is a deployment parameter so it should be easily configurable. That's why we included it in the Server.

@davidfowl
Copy link
Member

@Weboholics As a workaround add this code to your application's Startup.cs

public class Startup
{
   public void Configure(IApplicationBuilder app)
   {
      app.Map("/weboholics", ConfigureCore);
   }

   private void ConfigureCore(IApplicationBuilder app)
   {
        // Configure your pipeline here
   }
}

This should unblock you for now.

@halter73
Copy link
Member

UseUrls("http://domainName.com/weboholics") is exactly how we handle this for IIS.

@Tratcher You sure about this? It looks to me that both UseUrls and UseIISIntegration clobber the WebHostDefaults.ServerUrlsKey with no regard for what was there previously. My underastanding is that if UseUrls is called after IISIntegration, then the proxying is completely broken. If IISIntegration is called second, UseUrls is ignored.

I see that aspnet/IISIntegration#14 is closed with a comment saying this has been fixed by the ASP.NET Core Module. I would like to know how. I don't see anything in IISIntegration that updates the PathBase which I think would be required.

@Tratcher
Copy link
Member

It's here:
https://github.com/aspnet/IISIntegration/blob/dev/src/Microsoft.AspNetCore.Server.IISIntegration/WebHostBuilderIISExtensions.cs#L38-L39

It gets the base path from IIS/ANCM via environment variable and gives it to Kestrel to manage. (Note UseSetting and UseUrl do the same thing here).

Can we come up with a syntax for the user to specify a base path in UseUrls for the unix sockets scenario?

@davidfowl
Copy link
Member

Feels like a hack. We should be using different fields for different data even in the default case.

@Weboholics
Copy link
Author

@davidfowl Thanks. Your workaround seems to work. Looking forward for a final solution.

@muratg muratg added this to the 1.1.0 milestone Jul 18, 2016
@muratg muratg removed this from the 1.1.0 milestone Aug 10, 2016
@muratg
Copy link

muratg commented Aug 10, 2016

@DamianEdwards @shirhatti

Do we want this as a feature?

@DamianEdwards
Copy link
Member

Yes pls

@muratg
Copy link

muratg commented Aug 10, 2016

@shirhatti Can you come up with the requirements for 1.1.0?

@muratg muratg added this to the 1.1.0 milestone Aug 10, 2016
@muratg muratg modified the milestones: 1.1.0-preview1, 1.1.0 Oct 11, 2016
@muratg muratg modified the milestones: 1.2.0, 1.1.0 Oct 31, 2016
@muratg muratg modified the milestones: 1.2.0, 2.0.0 Jan 12, 2017
@muratg muratg removed this from the 2.0.0-preview2 milestone May 25, 2017
@muratg muratg modified the milestones: 2.0.0-preview3, 2.0.0 Jun 12, 2017
@muratg
Copy link

muratg commented Jun 26, 2017

@shirhatti Are we doing anything on this in the next few weeks?

@muratg muratg modified the milestones: 2.1.0, 2.0.0 Jun 27, 2017
@Weboholics
Copy link
Author

Sorry to see that the milestone has been moved - davidfowl hack is working for us and we appreciate the help received from you. But for people new to .net core this will create problems - the hack IS NOT intuitive. With a major version change to 2.0 it would be natural to fix the API.

@halter73
Copy link
Member

halter73 commented Jun 28, 2017

@Weboholics In 2.0 you will be able to do something like the following by using KestrelServerOptions.ListenUnixSocket and app.UsePathBase. It's conceptually pretty similar to @davidfowl's hack, but hopefully more intuitive.

Program.Main()

var host = new WebHostBuilder()
    .UseKestrel(options =>
    {
        options.ListenUnixSocket("/var/disk2/sparfiler/www/weboholics.sock");
    })
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseStartup<Startup>()
    .Build();

host.Run();

Startup.Configure(IApplicationBuilder app)

app.UsePathBase("/weboholics");
// Configure your pipeline here

@ygoe
Copy link

ygoe commented Jun 28, 2017

Wait, are we talking about the situation where the Kestrel webserver thinks it's called in the root path of the domain but the public web server is rewriting this into a subdirectory? This can only work with the URL rewriting in place, not without, right? So it's impossible to use and test this with Kestrel alone, you absolutely need the proxy web server for the URL rewriting.

If I'm correct until here, how could this possibly be configured in the source code? This isn't a configuration feature of the application itself, it's only a thing of the hosting environment. The application itself just doesn't care or know about the URL remapping outside of it. So this should not be configurable through an API call but only with an environment variable or some other means accessible through the command line or such.

At least that's what I need for my hosting environment. I can configure an ASP.NET Core app to run in a public subdirectory of the domain – it just won't work because it thinks it's in the root directory and generates wrong URLs to resources and other pages.

@halter73
Copy link
Member

halter73 commented Jun 28, 2017

@ygoe If requests are already being routed to the correct handlers, and you just wan to prefix all your generated URLs with "/weboholics", you can do so by adding the following to the start of your Configure(IApplicationBuilder) method.

app.Use((context, next) =>
{
    context.Request.PathBase = "/weboholics";
    return next();
});

Of course, if you change your rewrite rule, you will also need to change the PathBase to match, but there's nothing stopping you from reading the PathBase from config yourself.

@ygoe
Copy link

ygoe commented Jun 29, 2017

I'm thinking of an application that's not written by me, some CMS for example. I, as the admin, want to be able to install that app in whatever subdirectory I like. Obviously I don't want to recompile that app (written by somebody else, possibly not even available as source code) to fit my subdirectory. And if the app doesn't provide such configuration, it just won't work anywhere else than the root directory. To me this is clearly a hosting environment option, not an application option. And as such it doesn't belong in the code. (Or at least not exclusively. The server TCP port is another such option and it works perfectly from the environment. I can't run multiple apps all on port 5000.)

@davidfowl
Copy link
Member

The application is self hosted so they are one and the same. You can always write code that reads from configuration and sets up the correct base path as a result.

@ygoe
Copy link

ygoe commented Jun 29, 2017

I can set the TCP port from the outside, without any specific code in the application itself. I'm just thinking the same should be possible for the URL path.

ASPNETCORE_URLS=http://127.0.0.1:$PORT dotnet MyApp.dll

This might look something like the following:

ASPNETCORE_URLS=http://127.0.0.1:$PORT/$APPPATH dotnet MyApp.dll

But that doesn't work last time I checked (ASP.NET Core 2.0 preview 1).

@davidfowl
Copy link
Member

Yea, that doesn't work. You can't bind to a path (like @halter73 said) but that really has nothing to do with being able to set the url via an environment variable or not. That's because Kestrel itself doesn't support "binding to a path".

We should do a few things here:

  • We should just add support for specifying the path base as a hosting option. We'd call UsePathBase on your behalf so that it would "just work" if you had that setting.
  • As a result ASPNETCORE_PATHBASE would just work

We're shutting down for 2.0 so this would be a 2.1 thing.

/cc @Tratcher

@ygoe
Copy link

ygoe commented Jun 29, 2017

That sounds good!

@Weboholics
Copy link
Author

Thanks @halter73 and @davidfowl with the feedback that the most important parts of the API cleanup regarding Subfolders has been resolved. As I understand you can now use app.UsePathBase instead of app.Map. @davidfowl ASPNETCORE_PATHBASE environment variable seems a future (2.1) nice to have feature, but doesn't sound critical - you can use configuration - as we do today to make subfolders configurable without recompilation

@rubberduck203
Copy link

Wait a sec. what problem are we trying to solve here? Because this worked just fine for me.

#815 (comment)

@Weboholics
Copy link
Author

@rubberduck203 - your solution doesn't work on Linux using unix sockets - a limitation of method .UseUrls. The original question did spawn further questions about API and how to enable this functionality during deployment / administration - you don't want to recompile an application because changes in hosting.

@rubberduck203
Copy link

Okay. That's what I was missing. The environment variable works for some cases, but not for unix sockets. Got it.

@Tratcher
Copy link
Member

Kestrel used to allow PathBase in UseUrls, but it dropped it due to incomplete support. That's when we introduced UsePathBase.

@BartusZak
Copy link

How can I handle it with Apache2? (Ubuntu 16.04)
I have spent 2 days already and nobody didn't come up with any idea :(

@Tratcher
Copy link
Member

Comments on closed issues are not tracked, please open a new issue with the details for your scenario.

Did you try UsePathBase?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests