apache.htaccessmod-rewritereverse-proxyproxypass

RewriteRule overrides ProxyPass


On a centos 7 machine, I'd like to run a python server alongside an apache server. I figured the easiest way would be to configure apache as a reverse proxy. This is my VirtualHost configuration:

<VirtualHost *:443>
        DocumentRoot /home/username/mydomain/src
        ServerName mydomain.com
        ErrorLog logs/mydomain-error_log
        CustomLog logs/mydomain-access_log common
        DirectoryIndex index.php

    <Directory /home/username/mydomain/src>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
        AddOutputFilterByType DEFLATE text/html text/plain text/xml
    </Directory>


    ProxyPreserveHost On
    ProxyPass /mediaproxy http://127.0.0.1:9001/mediaproxy
    ProxyPassReverse /mediaproxy http://127.0.0.1:9001/mediaproxy

    LogLevel alert rewrite:trace6
    RewriteEngine on
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^/api/media/(.*) /data/$1 [L]
    RewriteRule ^/api/v1/* /api/v1/index.php [L]
    RewriteRule ^/assets/(.*) /site/v1/content/assets/$1 [L]
    RewriteRule ^/css/(.*) /site/v1/content/css/$1 [L]
    RewriteRule ^/js/(.*) /site/v1/content/js/$1 [L]
    RewriteRule ^/fonts/(.*) /site/v1/content/fonts/$1 [L]
    RewriteRule ^/* /index.php [L] # problematic rule

    // lets encrypt entries

Now, my problem is that rewrite rules takes precedence over ProxyPass. That ism when I visit mydomain.com/mediaproxy/somepage, it serves the content at /index.php, specified with RewriteRule ^/* /index.php [L] . Reverse proxy works correctly if I remove the problematic rule. Unfortunately I need to keep it.

How do I tell apache to use ProxyPass rule first, and use RewriteRule only if there is no match?


Solution

  • RewriteRule ^/* /index.php [L] # problematic rule
    

    Your rule rewrites everything. You could just make an exception for the URL-path you want to proxy. For example:

    RewriteRule !^/mediaproxy /index.php [L]
    

    The ! prefix on the RewriteRule pattern negates the expression. So it is successful when it does not match.

    This now rewrites everything except URL-paths that start /mediaproxy.

    Note that the trailing * quantifier in the regex ^/* repeats the preceding token 0 or more times. The preceding token in this instance is the slash. You are missing the preceding . (dot). Or omit the .* entirely as it's superfluous (and less efficient).


    Aside:

    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^/api/media/(.*) /data/$1 [L]
    RewriteRule ^/api/v1/* /api/v1/index.php [L]
    RewriteRule ^/assets/(.*) /site/v1/content/assets/$1 [L]
    RewriteRule ^/css/(.*) /site/v1/content/css/$1 [L]
    RewriteRule ^/js/(.*) /site/v1/content/js/$1 [L]
    RewriteRule ^/fonts/(.*) /site/v1/content/fonts/$1 [L]
    RewriteRule ^/* /index.php [L] # problematic rule
    

    The two conditions (RewriteCond directives) are not doing anything here. When used in a virtualhost context, REQUEST_FILENAME is the same as REQUEST_URI, since it is processed early, before the request is mapped to the filesystem. Consequently, both (negated) conditions will always be successful and the following rule is always processed. In a vhost context you need to use a lookahead, ie. LA-U:REQUEST_FILENAME, OR construct the file-path using the DOCUMENT_ROOT server variable, OR move the rules into a directory context.

    However, those two conditions only apply to the first rule that follows. So all the remaining rules (including the last "problematic" rule) are processed unconditionally anyway. This is generally incorrect for a front-controller pattern (the last rule) and should perhaps be written like this instead:

    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
    RewriteRule !^/mediaproxy /index.php [L]
    

    This now rewrites everything except URL-paths that do not start /mediaproxy AND do not map to a directory AND do not map to a file.

    Alternatively, if these condtions should be applied to all rules then create a negated rule instead. For example:

    DirectoryIndex index.php
    
    RewriteEngine on
    
    # Prevent further processing if root directory or "index.php" requested
    RewriteRule ^/(index\.php)?$ - [L]
    
    # Prevent further processing if the request maps to a directory or file
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f
    RewriteRule ^/. - [L]
    
    RewriteRule ^/api/media/(.*) /data/$1 [L]
    
    # This rule is not required since the DirectoryIndex handles this case (the regex is also "incorrect").
    #RewriteRule ^/api/v1/* /api/v1/index.php [L]
    
    RewriteRule ^/assets/(.*) /site/v1/content/assets/$1 [L]
    RewriteRule ^/css/(.*) /site/v1/content/css/$1 [L]
    RewriteRule ^/js/(.*) /site/v1/content/js/$1 [L]
    RewriteRule ^/fonts/(.*) /site/v1/content/fonts/$1 [L]
    RewriteRule !^/mediaproxy /index.php [L]