Configure URLs with prefix in Mojolicious behind Reverse Proxy (ProxyPass)

后端 未结 3 1629
攒了一身酷
攒了一身酷 2021-01-05 21:21

I\'m looking for a reliable way to configure Mojolicious running behind an Apache reverse proxy under /app, so that url_for(\'/foo\') actually returns /ap

相关标签:
3条回答
  • 2021-01-05 21:50

    You need to set base path for each request url in before_dispatch hook

    $app->hook(before_dispatch => sub {
      my $c = shift;
      $c->req->url->base->path('/app/');
    });
    

    Example:

    use Mojolicious::Lite;
    
    app->hook(before_dispatch => sub {
      shift->req->url->base->path('/app/');
    });
    
    
    get '/' => sub {
      my $c = shift;
      $c->render(text => $c->url_for('test'));
    };
    
    get '/test/url' => sub { ... } => 'test';
    
    app->start;
    

    And result:

    $ curl 127.0.0.1:3000
    /app/test/url
    
    0 讨论(0)
  • 2021-01-05 21:55

    You should mount your application under required path.

    In your startup you should do:

    $r =  $app->routes;
    $r =  $r->any( '/api' )->partial( 1 );
    
    $r->get( '/test' );
    

    You should not configure specially your apache. When GET /api/test will come your app will get /api/test route. This route partially matched to /api and the rest route /test will be assigned into ->stash->{ path }.

    So rest routes will be checked against /test (source)

    0 讨论(0)
  • 2021-01-05 22:17

    I'm now answering my own question as I'm getting more suggestions (not just here) from people who would put a hard-coded prefix in their application code. It should be obvious that prefixing all generated urls manually isn't a solution. Just imagine two instances of the same application deployed on the same server, one under /app1 and the other under /app2. The whole point of the suggested code in my question is that the application works and produces correct urls if accessed through a reverse proxy without breaking requests going directly to the application server. A developer would run Morbo, but a hard-coded prefix would break that. Also, I made at least one mistake in my question, but nobody seems to have noticed.

    Flaws in code

    I had too many slashes in my example in the question. The way the Location block is defined, requests to /app without trailing slash would fail. It might be better to write it like that:

    <Location "/app">
    ...
    

    Next, I wrote that I check for the X-Forwarded-For header but I actually checked for X-Forwarded-Host. That wouldn't be a problem if I were also clearing that header but I cleared X-Forwarded-For instead. With that awkward mistake, the safety mechanism wouldn't work, so if you'd set this header while working with your application server at localhost:3000, the app would try to repair the manipulated url even though it's not supposed to do that.

    It should've been:

    RequestHeader unset X-Forwarded-Host
    

    Example:

    ProxyPreserveHost On
    <Location /app>
        ProxyPass http://localhost:3000/app
        RequestHeader unset X-Forwarded-Host
    </Location>
    

    The ProxyPreserveHost directive isn't required as long as the application uses relative urls everywhere. If the application wants to generate an absolute url, for example url_for('/page')->to_abs, ProxyPreserveHost should be enabled, otherwise external clients would get http://localhost:3000/app/page.

    Reverse proxy detection

    When I wrote that question, I saw the before_dispatch hook in the Mojolicious documentation and, as pointed out in the question, I wanted to use it for an application running under /app. However, I didn't want to break Morbo. The example assumes that the application is in production mode ($app->mode) while running behind a reverse proxy but not when access directly through Morbo, but I didn't want to change the mode for every other request.

    That's why I added a condition to check if the request came through a reverse proxy. As this header is only set by Apache (by the mod_proxy_http module) and not by Mojo::Server::Morbo, it can be used as reverse proxy detection. Together with the right directive to clear the X-Forwarded-Host, I believe the answer to my question would be that yes, that should work reliably.

    (Although that last part isn't strictly necessary as long as direct access to the app server is limited to the developer.)

    Manipulated url

    To show why I've added the /app prefix to the ProxyPass line in the Apache configuration, I'd like to point out that this approach manipulates the url to allow the application to work under the given prefix. There's another question of someone who forgot to add the prefix in the Apache configuration and I wrote an answer explaining what the hook does.

    Morbo: localhost:3000
    Apache reverse proxy: host.com/app or localhost/app
    
    # Browser > Apache:
    http://host.com/app
    # Apache (ProxyPass http://localhost:3000/) > Mojolicious sees:
    GET /
    url_for '/test' = /test 
    (or even //test if the hook pushes undef, see the other answer linked above)
    # Apache (configured as described above) > Mojolicious sees:
    GET /app
    # Hook:
    base = /app
    request = /
    url_for '/test' = /app/test 
    

    Normally, the local target argument in the ProxyPass directive would not have the prefix, it would just be something like ProxyPass http://...:3000/. In that case, the application doesn't know about the prefix, which is why all generated urls and links are incomplete.

    This approach requires you to let Apache pass the prefix through to the application server. The application doesn't know about the prefix, so it wouldn't know what to do with a request to /app/page. This is where the hook comes in. It assumes that the first level of the path is always the prefix, so it turns /app/page into /page and it conveniently appends the /app prefix to the url base, which is used when generating urls, making sure that a link to /test actually points to /app/test.

    Obviously, this modification should not be done for any request sent directly to Morbo.

    Alternative

    Alternatively, a custom request header could be set by the reverse proxy and then used by the hook to produce working urls. The Mojolicious::Plugin::RequestBase module works that way. It expects you to define the prefix in the X-Request-Base header, not in the url:

    RequestHeader set X-Request-Base "/app"
    

    In that case, the application should only receive requests for urls relative to that prefix:

    ProxyPass http://localhost:3000/
    

    All that module really does is pick up the header and use it as url base:

    $c->req->url->base($url); # url = X-Request-Base = /app
    

    Example:

    <Location /app>
        ProxyPass http://localhost:3000
        RequestHeader set X-Request-Base "/app"
    </Location>
    

    This is a nice and simply solution. Note that the /app prefix appears twice in that case. And of course, the hook implemented by that module only does its work if the X-Request-Base header is set, just like the hook shown above does nothing if the X-Forwarded-Host header is not set.

    0 讨论(0)
提交回复
热议问题