问题
In one app, some public image resources are symlinked to alternative names for historic reasons. For example, we might have
a.png
b.png -> a.png
Google's PageSpeed Insight that identical content should be served from a consistent URL applies to assets like this as well as content. Rather than reorganising the assets we have in place, I would like to have Apache perform an external redirect from b.png to a.png.
With mod_rewrite I can make a RewriteCond to narrow in on symbolic links:
'-l' (is symbolic link) Treats the TestString as a pathname and tests whether or not it exists, and is a symbolic link.
But how can I get the expanded path of the symlink? I need it partly to ensure that the target is in web scope, and partly to perform the redirect.
回答1:
(first post here)
I've been looking for a solution to a similar problem. It's possible, but there is no easy answer. I had to mix info from multiple Google searches to make it work.
First of all, a note about symlinks (wiki):
Symbolic links are automatically resolved by the file system. Any software program, upon accessing a symbolic link, will see the target instead, whether the program is aware of symbolic links or not.
So, unless the apache devs add specific options to deal with symlinks (like the -l
you mentionned), we have to rely on another mean to properly resolve them.
mod_rewrite
has the RewriteMap configuration option. With it, you can use different types of external resources to perform the rewriting, such as text files or DB queries. It can also call external programs, such as ... a symlink resolver for example :)
So, here we go. Run all the following commands as root and adapt to your system settings (this is for Debian).
First, if we use the
RewriteMap
option, we also need the RewriteLock option to prevent race conditions. The RewriteLock
option can't be in a <VirtualHost>
or <Directory>
context. It has to be in the global context, so I added it in a new rewrite.conf
and reloaded the module.
Run the following:
lock=/var/lock/apache2/rewrite_lock
touch $lock
chown www-data:www-data $lock
echo "RewriteLock $lock" >> /etc/apache2/mods-available/rewrite.conf
a2dismod rewrite
a2enmod rewrite
Next, create /usr/local/bin/resolve-symlink
and add the following code:
#!/bin/sh
while read line
do
echo `readlink "$line"`
done
Make it executable and test it as apache:
chmod +x /usr/local/bin/resolve-symlink
su - www-data
/usr/local/bin/resolve-symlink
The script should wait for your input, then either return a blank line or the target of the given symlink. Use CTRL+C
to exit. Example (>
is STDIN, <
is STDOUT):
> test
<
> /bin/sh
< bash
> /bin/bash
<
test
doesn't exist, so a blank line is returned. /bin/sh
is a symlink and the script resolved it to bash
. Finally, /bin/bash
is a file and not a link, thus another blank line.
Now, in your <VirtualHost>
configuration, add the following lines:
RewriteEngine On
RewriteMap symlink prg:/usr/local/bin/resolve-symlink
RewriteLog /var/log/apache2/rewrite.log
RewriteLogLevel 9
RewriteMap
can't be in a <Directory>
context, and won't be active unless RewriteEngine
is also set to on
in the <VirtualHost>
context, even if you later set it to on
in a <Directory>
contest!!! Pay attention to all those peculiarities and read your log files carefully or you may lose a few hours banging your head on the wall wondering why it says "map lookup FAILED".
Finally, the rewriting, in whatever context you prefer:
RewriteCond %{REQUEST_FILENAME} -l
RewriteCond ${symlink:%{REQUEST_FILENAME}} ^(.+)$
RewriteRule $ ${REQUEST_SCHEME}://${HTTP_HOST}/%1 [R,L]
First line detects symlinks, second one performs the resolution and checks that the results is not empty, third line rewrites the URL (%1
is the result of the mapping) and sends a 302 redirection to the browser. You may need the END
flag instead of the L
flag.
Now restart apache...
service apache2 restart
... test, check your log files, tweak, rinse and repeat...
When your are done, don't forget to remove the RewriteLog
and RewriteLogLevel
directives from your configuration because they slow apache down a lot.
Important note. In my case, the symlinks all points to files within the same folder, so the URL rewriting is a little easier. If your symlinks point to sub-directories, or even somewhere else on the filesystem, you will have to modify the script and the configuration to account for it, but even then it might not work as expected, as apache won't always be able to figure out the correct URL to send to the browser. For example, if you have a symlink to, let say,
/usr/share/apache2/icons/apache_pb.png
, apache might redirect to http://example.com/usr/share/apache2/icons/apache_pb.png
which, of course, does not exists...
Also, I would have added a few informative links but I'm limited to 2... Anyway, happy debugging!
回答2:
I've got more information about this topic. It's been running for quite some time on my side and I made the following amendments to the file /usr/local/bin/resolve-symlinks
:
#!/bin/bash
BASE=_ALL_YOUR_BASE_ARE_BELONG_TO_US_
BASE_LEN=${#BASE}
while read FILE
do
# $FILE must be in $BASE
if [[ $BASE != ${FILE:0:$BASE_LEN} ]]
then
echo
continue
fi
REL_FILE=${FILE:$BASE_LEN}
# echo $REL_FILE
# $LINK must also be in $BASE
LINK=$(readlink -f $FILE)
if [[ $BASE != ${LINK:0:$BASE_LEN} ]]
then
echo
continue
fi
REL_LINK=${LINK:$BASE_LEN}
# echo $REL_LINK
# $REL_FILE and $REL_LINK must be different
if [[ $(basename $REL_FILE) == $(basename $REL_LINK) ]]
then
echo
continue;
fi
# success!
echo $REL_LINK
done
Of course, replace _ALL_YOUR_BASE_ARE_BELONG_TO_US_
with your favorite path.
Now it should make sure both the requested file and the link target are in the same directory.
来源:https://stackoverflow.com/questions/16351271/apache-redirects-based-on-symlinks