6

We have a bit of a complicated problem to solve in our nginx configuration. Currently we have a piece of software installed in our document root. This software uses a single entry point (index.php) and query strings in order to show content. Example URLs are:

/index.php?forums/forum-name.1
/index.php?threads/thread-name.1
/index.php?users/user-name.1

Etc...

Now, we are moving this software into a subdirectory of /f/ and installing a new piece of software into the document root. This software ALSO uses index.php (No query strings, though). So we need to come up with a set of rewrite rules to ONLY rewrite the URLs from the old software. At the same time, we are also going to be removing the index.php from the URLs. A set of example mappings is:

/index.php?forums/forum-name.1 --> /f/forums/forum-name.1
/index.php?threads/thread-name.1 --> /f/threads/thread-name.1
/index.php?users/user-name.1 --> /f/users/user-name.1

So basically, I need to redirect a certain subset of index.php requests (Only containing ?forums, ?threads, ?users, etc...), then remove the index.php part and send to the /f/ directory.

I have played with this all morning and just can't get it to work how I need it to.

Kevin
  • 827
  • 3
  • 13
  • 23
  • You could use a `map` directive - see [this answer on SO](https://stackoverflow.com/questions/49191594/nginx-rewrite-a-lot-2000-of-urls-with-parameters/49192527#49192527). – Richard Smith Sep 09 '18 at 18:21
  • I don't think I can use map for this, as I don't have a static listing of original URLs, and even if I did, the numbers would be in the hundreds of thousands...I need some sort of regex matching. – Kevin Sep 09 '18 at 21:38
  • Map supports regular expressions. – Richard Smith Sep 09 '18 at 22:20
  • Right you are. That worked great! Thanks. Do you want to add an answer with this so that I can accept it or do you want me to self-answer it? – Kevin Sep 10 '18 at 01:40

1 Answers1

5

To manage complex redirections, particularly when query strings are involved, the map directive can be used.

You cannot match the query string (anything from the ? onwards) in location and rewrite expressions, as it is not part of the normalized URI, however, the $request_uri contains the original request complete with query string.

Matching the $request_uri may be problematic if the parameters are not sent in a consistent order, also URIs containing strange characters will be percent encoded.

The map directive can match strings and/or regular expressions. See this document for details.

For example:

map $request_uri $redirect {
    default                                               0;
    ~*^/index\.php\?(?<suffix>(forums|threads|users).*)$  /f/$suffix;
    ...
}

server {
    ...
    if ($redirect) {
        return 301 $redirect;
    }
    ...
}

Always use named captures in a map block regular expression. The mapped expression is evaluated at the return statement. As I understand it, every time nginx encounters a statement containing a regular expression (such as rewrite, some location blocks, and some if statements), the numeric captures are reset. Using named captures ensures that they remain in scope at the return statement.

See this caution on the use of if.

Richard Smith
  • 12,834
  • 2
  • 21
  • 29
  • Thank you for the help, Richard. I have the code working on my end with regular (unnamed) captures. Why the warning on always using named captures? – Kevin Sep 10 '18 at 13:03
  • I have expanded the answer. – Richard Smith Sep 10 '18 at 13:51
  • 3
    Query string values are available in the variables `$args` for the complete string, `$arg_name` for a single argument by name and `$is_args` which will be a `?` if there is a query string and empty if not. You can evaluate these within location blocks and add them to return, rewite, proxy_pass etc directives – miknik Sep 12 '18 at 03:17
  • @miknik you should post your solution as an answer. – Richard Smith Sep 12 '18 at 06:33
  • I didn't feel it was any better than your suggestion, particularly as the $arg_name variables are only populated if the query string uses key/value pairs separated by `=` which is not the case in the OPs setup. I just thought it was worth noting query string variables are captured by Nginx. – miknik Sep 12 '18 at 13:48