Using try_files while forcing a trailing-slash in the URL

Hello,

Brand new to nginx and loving it so far. Thanks to all who contribute to
the project.

The try_files directive is brilliant. The only problem I’m having is
that I would like to be able to force a trailing-slash, a la “rewrite”,
on the fallback URL.

The try_files documentation at
http://wiki.nginx.org/NginxHttpCoreModule#try_files states that
something like this is usually sufficient (and it is):

for Drupal 6 or 7:

try_files $uri $uri/ /index.php?q=$uri&$args;

This works well, but I have a need to eliminate duplicate URLs (for SEO
[“duplicate-content penalty”] reasons) by forcing a trailing slash on
all virtual URLs (that is, URLs that do not point to a real file or
directory).

Am I missing something obvious?

I was able to cook-up a “working solution”, but it will probably bristle
a few peoples’ hair:


location / {
# Because nginx doesn’t support compound logical
# expressions in this context, e.g., “if $a = 1 && $b = 2”.

set $test "";

if (!-d $request_filename) {
    set $test "${test}nd";
}

if (!-f $request_filename) {
    set $test "${test}nf";
}

# The request is NOT for an existing file or directory;
# It must be for a) a file that doesn't exist, b) a legitimate
# clean-URL PHP resource.

# Even if the request is for a legitimate clean-URL PHP resource, we
# always force a trailing slash.

if ($request_filename !~ "/$") {
    set $test "${test}nts";
}

# Not a directory, not a file, and doesn't contain a trailing
# slash; we need to redirect and append the slash.

if ($test = 'ndnfnts') {
    rewrite ^(.*[^/])$ $1/ permanent;
    break;
}

# Not a directory and not a file, but contains a trailing slash;
# we're done, and we can rewrite the URL per our
# Controller Pattern logic.

if ($test = 'ndnf') {
    rewrite  ^(.*)$  /index.php?q=$1  last;
}

}

Ultimately, I’m wondering if there’s a means by which to achieve the
above with try_files, and if not, if there’s a better means than what
I’ve employed on my own.

A related “problem” (more an inconvenience, really) is that using
try_files in the manner described in the cited documentation causes the
trailing ampersand ("&") to be appended to “q” even when there are no
$args. Is this behavior intentional? Maybe there’s an RFC of which I"m
unaware that lead to this decision.

Thanks for any help!

-Ben

Hello,​

On Tue, Jun 18, 2013 at 3:22 PM, Ben J. [email protected]
wrote:

http://wiki.nginx.org/NginxHttpCoreModule#try_files states that
something like this is usually sufficient (and it is):


I would recommend the original documentation:
http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files


If I am correct, the last parameter of the try_files directive handles
error situations. It is used when every rule before failed.
If you use:
try_files​

​ $uri $uri/;
then you have the wanted behavior.

From what I see of your rules and what I understand from what you wish
to
do, you have a conflicting expected behavior :

  • Falling back to index.php?q=$uri&$args
  • Falling back to $uri/
    You need to choose…

You’ll also note that try_files context is either server or location,
thus
you can set up a default rule server-wide and overwrite it for specific
locations.

if (!-d $request_filename) {

if ($test = 'ndnfnts') {
}

$args. Is this behavior intentional? Maybe there’s an RFC of which I"m


B. R.

On 6/18/2013 5:55 PM, Francis D. wrote:

On Tue, Jun 18, 2013 at 03:22:04PM -0400, Ben J. wrote:

Hi there,

Hi, Francis! I really appreciate your help and your time. Thanks for
replying in such detail.

try_files decides which file to serve, with a single fallback option. If
that fallback option is something complicated, it is probably best to
use a named location and put the complications in there.

This makes fine sense.

}
directly.

(Depending on what exactly your php script does, you may also want to
set QUERY_STRING explicitly.)

Your example is a true stroke of genius as compared to the garbage that
I put together. The end-result is the same and your example is far more
elegant.

Ultimately, I’m wondering if there’s a means by which to achieve the
above with try_files, and if not, if there’s a better means than what
I’ve employed on my own.

The above uses try_files, and uses “if” within “location” with only
“return”, which is a safe thing to do. http://wiki.nginx.org/IfIsEvil

Yes, I have read the IfIsEvil article a few times over. I would
certainly prefer to avoid using “if”. =)

A related “problem” (more an inconvenience, really) is that using
try_files in the manner described in the cited documentation causes the
trailing ampersand ("&") to be appended to “q” even when there are no
$args. Is this behavior intentional? Maybe there’s an RFC of which I"m
unaware that lead to this decision.

You asked for $uri, then &, then $args.

You should be surprised if you get anything other than $uri, then &,
then $args.

Haha… fairly-stated. Ask and ye shall receive. And receive I did.

I suspect it was done because the php script it feeds is known to ignore
the unnecessary &.

Ah, I see; the implication is that Drupal 6 ignores the superfluous
ampersand, hence its inclusion in the example that I used.

I’ve made the adjustments necessary for my PHP script to ignore it, too.
I’m happy with the result, as-is, but I appreciate the below suggestions
nonetheless.

nginx does provide the variable $is_args, but that is intended for other
circumstances where it is more useful. You could do more “if” things on
$is_args or on $args to decide whether to add the &, if you want to.

Good luck with it,

f

Thanks again!

-Ben

On Tue, Jun 18, 2013 at 03:22:04PM -0400, Ben J. wrote:

Hi there,

The try_files directive is brilliant. The only problem I’m having is
that I would like to be able to force a trailing-slash, a la “rewrite”,
on the fallback URL.

This works well, but I have a need to eliminate duplicate URLs (for SEO
[“duplicate-content penalty”] reasons) by forcing a trailing slash on
all virtual URLs (that is, URLs that do not point to a real file or
directory).

Am I missing something obvious?

try_files decides which file to serve, with a single fallback option. If
that fallback option is something complicated, it is probably best to
use a named location and put the complications in there.

I was able to cook-up a “working solution”, but it will probably bristle
a few peoples’ hair:

If it works, it works. If you can measure the load on your test server,
you may be interested in trying other things too and comparing.

For example:

location / {
try_files $uri $uri/ @virtual;
}
location @virtual {
if ($uri !~ ‘/$’) {
return 301 $uri/$is_args$args;
}
fastcgi_pass unix:php.sock;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
include fastcgi_params;
}

as well as a location for handling /index.php or any similar things
directly.

(Depending on what exactly your php script does, you may also want to
set QUERY_STRING explicitly.)

Ultimately, I’m wondering if there’s a means by which to achieve the
above with try_files, and if not, if there’s a better means than what
I’ve employed on my own.

The above uses try_files, and uses “if” within “location” with only
“return”, which is a safe thing to do. http://wiki.nginx.org/IfIsEvil

A related “problem” (more an inconvenience, really) is that using
try_files in the manner described in the cited documentation causes the
trailing ampersand ("&") to be appended to “q” even when there are no
$args. Is this behavior intentional? Maybe there’s an RFC of which I"m
unaware that lead to this decision.

You asked for $uri, then &, then $args.

You should be surprised if you get anything other than $uri, then &,
then $args.

I suspect it was done because the php script it feeds is known to ignore
the unnecessary &.

nginx does provide the variable $is_args, but that is intended for other
circumstances where it is more useful. You could do more “if” things on
$is_args or on $args to decide whether to add the &, if you want to.

Good luck with it,

f

Francis D. [email protected]