Johni
Johni

Reputation: 2961

Running Symfony 5 with reverse proxy in subdirectory

I like to run a Symfony 5 application behind a reverse proxy which supplies the following endpoint:

https://my.domain/service1/

The proxy config basically is this:

ProxyPass /marketsy/ http://internal.service1/

On the server the reverse proxy is connecting to, I use the following apache rule for serving my Symfony application:

<VirtualHost *:80>
  ServerName internal.service1
  DocumentRoot /webroot/service1/public

 <FilesMatch \.php$>
     SetHandler proxy:unix:/run/php/php7.2-fpm-ui.sock|fcgi://localhost
     SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
     SetEnv HTTP_X_FORWARDED_PROTO "https"
 </FilesMatch>

 <Directory  /webroot/service1/public>
     AllowOverride None
     Require all granted
     FallbackResource /index.php
 </Directory>

 <Directory  /webroot/service1/public/bundles>
     FallbackResource disabled
 </Directory>
</VirtualHost>

The application itself is reqachable but Symfony can't hanle the "service1" path prefix.

For example it tries to access the profiler under https://my.domain/_wdt/8e3926 instead of https://my.domain/service1/_wdt/8e3926 and beside the root route all the routing isn't working:

For example: When I try to access https://my.domain/service1/my/page i will get redirected to https://my.domain/my/page

Now my question is, how can I configure Symfony to know about the "service1" path prefix when generation urls ets.

Upvotes: 7

Views: 3456

Answers (3)

Jan B&#246;hmer
Jan B&#246;hmer

Reputation: 1

Since Symfony 5.3 there is no need to modify the requests paths manually by an event subscriber or by changing route prefixes, as symfony now supports the X-Forwarded-Prefix HTTP header. When the Reverse proxy pass this header to the symfony application, then the base URL of the Request object is correctly set and all generated paths get automatically prefixed.

In apache, you can use mod_header to add the header, by adding a RequestHeader set X-Forwarded-Prefix "/marketsys" (or like) directive to vhost configuration.

Be sure that X-Forwared-Prefix is configured as trusted header, if you want to use this feature.

Upvotes: 0

Johni
Johni

Reputation: 2961

In addition to the solution @Jimmix provided, I had to write a subscriber to add a prefix to my request path:

<?php namespace My\Bundle\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class AppPrefixSubscriber implements EventSubscriberInterface {

    /** @var string */
    private $appPrefix;

    public function __construct(?string $appPrefix) {
        $this->appPrefix = $appPrefix;
    }

    /**
     * Returns events to subscribe to
     *
     * @return array
     */
    public static function getSubscribedEvents() {
        return [
            KernelEvents::REQUEST => [
                ['onKernelRequest', 3000]
            ]
        ];
    }

    /**
     * Adds base url to request based on environment var
     *
     * @param RequestEvent $event
     */
    public function onKernelRequest(RequestEvent $event) {
        if (!$event->isMasterRequest()) {
            return;
        }

        if ($this->appPrefix) {
            $request = $event->getRequest();

            $newUri =
                $this->appPrefix .
                $request->server->get('REQUEST_URI');

            $event->getRequest()->server->set('REQUEST_URI', $newUri);
            $request->initialize(
                $request->query->all(),
                $request->request->all(),
                $request->attributes->all(),
                $request->cookies->all(),
                $request->files->all(),
                $request->server->all(),
                $request->getContent()
            );
        }
    }

}

Upvotes: 1

Jimmix
Jimmix

Reputation: 6506

Proper way of doing it (example):

Create src/Controller/BarController.php

<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;

class BarController
{
    public function index()
    {
        return new Response('<p>Bar controler response</p>');
    }
}

and src/Controller/FooController.php

<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;

class FooController
{
    public function index()
    {
        return new Response('<p>Foo controler response</p>');
    }
}

Create config/routes/prefix-routes.yaml

index:
    path: /
    controller: App\Controller\DefaultController::index

bar:
    path: /bar
    controller: App\Controller\BarController::index
 
foo:
    path: /foo
    controller: App\Controller\FooController::index
 

and edit routing config/routes.yaml - delete its contents and just put:

prefixed:
   resource: "routes/prefix-routes.yaml"
   prefix: service1

all of controllers are now available at urls:

http://localhost/service1/ for DefaultController.php
http://localhost/service1/bar for BarController.php
http://localhost/service1/foo for FooController.php

If you want your profiler to work with service1 prefix as well then edit config/routes/dev/web_profiler.yaml this way:

web_profiler_wdt:
    resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
    prefix: service1/_wdt

web_profiler_profiler:
    resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
    prefix: service1/_profiler

now they should be available at:

http://localhost/service1/_wdt... for wdt
http://localhost/service1/_profiler for profiler

Adding prefix for annotations:

Create controller src/Controller/AnnoController.php:

<?php

namespace App\Controller;

use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class AnnoController extends AbstractController
{
    /**
     * @Route("/anno", name="anno")
     */
    public function index()
    {
        return new Response('<p>Anno controler response</p>');
    }
}

edit config/routes/annotations.yaml and add prefix: service1:

controllers:
    resource: ../../src/Controller/
    type: annotation
    prefix: service1

kernel:
    resource: ../../src/Kernel.php
    type: annotation

Now prefix is added to routes done via annotation:

http://localhost/service1/anno for AnnoController.php

Some references:

Symfony Routing Prefix
Symfony Routing Configuration Keys

Adding prefix quick and dirty workaround to add prefix service1 to all of routing (not recommended).

Instead of changing routing as above just Edit src/Kernel.php protected function configureRoutes

and change every $routes->import line by adding ->prefix('service1') at the end so it looks this way:

protected function configureRoutes(RoutingConfigurator $routes): void
{
    $routes->import('../config/{routes}/'.$this->environment.'/*.yaml')->prefix('service1');
    $routes->import('../config/{routes}/*.yaml')->prefix('service1');

    if (is_file(\dirname(__DIR__).'/config/routes.yaml')) {

        $routes->import('../config/{routes}.yaml')->prefix('service1');

    } elseif (is_file($path = \dirname(__DIR__).'/config/routes.php')) {
        (require $path)($routes->withPath($path), $this);
    }
}

all of controllers are now available at urls:

http://localhost/service1/ for DefaultController.php
http://localhost/service1/bar for BarController.php
http://localhost/service1/foo for FooController.php

as well as the profiler:

http://localhost/service1/_wdt... for wdt
http://localhost/service1/_profiler for profiler

Upvotes: 8

Related Questions