About using regexp in map nginx

I haven't written anything for a long time, so let's dilute the end of Friday with simple, but not always obvious searches in Nginx .





This web server has a wonderful map directive that allows you to greatly simplify and shorten configs. The essence of the directive is that it allows you to create a new variable, the value of which depends on the values โ€‹โ€‹of one or more of the original variables. The directive becomes even more powerful when using regular expressions, but at the same time it is forgotten about one important point. Excerpt from the manual:





Since the variables are only evaluated at the time of use, even a large number of map variable declarations in themselves do not incur any additional query processing overhead.





And here it is important not only that "map does not entail any additional costs for processing requests", but also that "variables are calculated only at the time of use".





As you know, the Nginx config is mostly declarative. This also applies to the map directive , and, despite the fact that it is located in the http context , it is not evaluated until the request is processed. That is, when using the resulting variable in the contexts server, location, if , etc. we "substitute" not the finished result of the calculation, but only the "formula" by which this result will be calculated at the right time. There are no problems in this configuration casuistry until we use regular expressions. Namely regular expressions with selections. More precisely, regular expressions with unnamed selections. It is easier to show with an example.





Let's say we have a domain example.com with many 3rd level subdomains, like ru.example.com, en.example.com, de.example.com , etc., and we want to redirect them to new subdomains ru.example.org, en.example.org, de.example.org , etc. Instead of describing hundreds of lines of redirects, we will do this:





map $host $redirect_host {
  default "example.org";
  "~^(\S+)\.example\.com$"  $1.example.org;
}
server {
    listen       *:80;
    server_name  .example.com;
  location / {
        rewrite ^(.*)$ https://$redirect_host$1 permanent;
    
}
      
      



Here we mistakenly expected that when requesting ru.example.com, the regex will be calculated in the map and, accordingly, when it gets to location , the $ redirect_host variable will contain the value of ru.example.org , but in reality this is not the case:





$ GET -Sd ru.example.com
GET http://ru.example.com
301 Moved Permanently
GET https://ru.example.orgru
      
      



, ru.example.orgru. - , " " rewrite .





- regexp map , , :





map $host $redirect_host {
  default "example.org";
  "~^(\S+)\.example\.com$"  $1.example.org;
}
server {
    listen       *:80;
    server_name  .example.com;
    location / {
        return 301 https://$redirect_host$request_uri;
    }
}
      
      



, ( ).

map:





map $host $redirect_host {
  default "example.org";
  "~^(?<domainlevel3>\S+)\.example\.com$"  $domainlevel3.example.org;
}
server {
    listen       *:80;
    server_name  .example.com;
    location / {
        rewrite ^(.*)$ https://$redirect_host$1 permanent;
    }
}
      
      



:





$ GET -Sd ru.example.com
GET http://ru.example.com
301 Moved Permanently
GET https://ru.example.orgru
      
      



since our unnamed allocation $ 1 will get the result of named $ domainlevel3 . That is, you need to use named selections in both regexes:





map $host $redirect_host {
  default "example.org";
  "~^(?<domainlevel3>\S+)\.example\.com$"  $domainlevel3.example.org;
}
server {
    listen       *:80;
    server_name  .example.com;
    location / {
        rewrite ^(?<requri>.*)$ https://$redirect_host$requri permanent;
    }
}
      
      



And now everything works as expected:





$ GET -Sd ru.example.com
GET http://ru.example.com
301 Moved Permanently
GET https://ru.example.org/
      
      






All Articles