📜 ⬆️ ⬇️

We open possibilities of map in nginx

map is a powerful directive that can make your configs simple and straightforward.
Perhaps this is the most undervalued directive, due to the fact that not everyone knows all its capabilities.
In a compact form, it helps to handle variables, GET parameters, headers, cookies, and backend sets (upstream).
I will try to reveal its capabilities to users.

For simplicity, in the examples, the map directive will be adjacent to the directives from location.
In a real config, the map location is in the http block.
A brief description of the map
This is a directive that sets the value of one variable (right), depending on the other (left).
It looks like this:
map $arg_one $var_two { "one" "two"; } 

Regular expressions can be used on the left, including named selections.
On the right side there can be strings, variables and regexp selections from the left side.
The map directive is described in the http block.
A full description can be found in the documentation .

Replace if with map


If features in nginx
if in nginx is implemented in its own way, it is rather obvious.
In short, it is described on a special page .
As far as I understand, if in nginx is from the category when the world revolves around you, and not vice versa.
For each if in location, nginx generates two config files for itself, with if = true and with if = false.
Plus, some directives behave strangely, or do not work at all next to if in one location.
Therefore, when working with if, there is always a chance of not the behavior you expected.
For guaranteed behavior, if it is better to replace with map.

Consider the examples of substitutions, from simple to complex.

Set variable one value

To do this, you most likely have already written this structure:
 if ($arg_one = "one") { set $var_two "two"; } 

You can do this with the map:
 map $arg_one $var_two { "one" "two"; } 

And in the right place, just use $var_two .
Even in such a simple case, the map has advantages:

Set a variable to one of several values.

I suppose that for this purpose they are already resorting to a map, but sometimes there is a variant with several if:
 if ($arg_one = "one") { set $var_two "two"; } if ($arg_one = "three") { set $var_two "four"; } 

You can do this with the map:
 map $arg_one $var_two { "one" "two"; "three" "four"; } 

Already visible gain in compactness and readability.
If you still need to edit the conditions, you need to correct the line in the map.

Nested if (dependent on several conditions)

I found questions on nginx mailing lists about nested if, when you need to take into account several conditions.
Nested if cannot be done, you can make a crutch from several if.
And you can write one map.
You may not know, but in the source part (where the first variable) you can specify not one variable, but the whole text containing several variables, in quotes.
For example, you need to block users with the “HackYou” user-agent, hacking “POST” with a request to the address "/ admin / some / url".
')
This, in principle, can be done with if:
 if ($http_user_agent ~ "HackYou") { set $block "A"; } if ($method = "POST") { set $block "${block}B"; } if ($uri = "/admin/some/url") { set $block "${block}C"; } if ($block = "ABC") { return 403; } 

You can do this with the map:
 map "$http_user_agent:$method:$uri" $block { "HackYou:POST:/admin/some/url" "1"; } if ($block) { return 403; } 

The colon is just for convenience.
In this example, a “point of no return” occurs in the direction of the map.
If the number of conditions grows (for example, several user-agents), it will not be possible to implement them with an if set, or it will be too cumbersome.

http headers


If you need to add headers depending on some conditions, with if this can be a problem.

For example, this design:
 if ($arg_a = "1") { add_header X-one "one"; } add_header X-two "two"; 

It will give only one header (X-one, if arg_a = true, and X-two, if false).
This is a lack of add_header and developers will not fix it.
And if you have multiple ifs with headers, you may not be able to add several different headers at the same time.

But here comes the map:
 map $arg_a $header_one { "1" "one"; } add_header X-one $header_one; add_header X-two "two"; 

Multiple headers - multiple map.
If the variable is empty, nginx simply does not create a header.
In general, in the case of if, the headers_more module can help; it lacks the add_header lack of if.
The headers_more module is interesting in itself, with its help you can flexibly manage any headers, both of the response and the request (for the backend).
Paired with the map directive, this module can implement many Wishbacks, including the generation of several cookies.

Upstream choice


In directives like proxy_pass (fastcgi_pass and other * _pass), where you can specify upstream as a backend, you can use variables.
Those. This definition works:
 proxy_pass http://$php_backend; 

This is stated in the documentation :
In this case, the server name is searched among the described server groups and, if not found, is determined using the resolver.

Paired with the map, it gives us a field for fantasy.
Here is a rough example - let's say we need this:
There is a cook userid.
If its value is the first number from 0 to 4, then send requests to upstream old_php_backend.
If from 5 to 9 - on new_php_backend.
If there is no cookie, or it is empty, or the first character is not a number, then on default_php_backend.

Usually done with if, rewrite and multiple location:
 location /some/url/ { if ($cookie_userid ~ "~^[0-4]") { rewrite ^(.+)$ /old/$1 last; } if ($cookie_userid ~ "~^[5-9]") { rewrite ^(.+)$ /new/$1 last; } proxy_pass http://default_php_backend; } location /old/some/url/ { internal; rewrite ^/old/(.+)$ $1 break; proxy_pass http://old_php_backend; } location /new/some/url/ { internal; rewrite ^/new/(.+)$ $1 break; proxy_pass http://new_php_backend; } 

Using map makes it all easier:
 map $cookie_userid $php_backend { "~^[0-4]" "old_php_backend"; "~^[5-9]" "new_php_backend"; default "default_php_backend"; } location /some/url/ { proxy_pass http://$php_backend; } 

It really works, it is applied on a loaded service, and there are no problems with it.
The main thing is not to forget about default, so that there is always where to send the request.
This technique can also be used with geo / split_clients.
For example, in split_clients, allocate 1% of requests and send them to a separate backend for tests.

Variables from map can be used in another map


This code works:
 map $arg_a $var_a { "0" "1"; } map $var_a $var_b { "1" "2"; } map $var_b $var_c { "2" "3"; } 

When you refer to $var_c , three map directives will work in sequence.
With the GET parameter a = 0, $var_c will contain “3”.
nginx chews a chain of 12 maps, I haven't tried any further.
You can try, for the sake of interest, find out the maximum length of the chain.
Usually I have a pair of map'ov, which depend on each other.
It is useful for me to form complex headers and cookies using nginx (when you need to add text to the header, send one header to the backend and another to the client).
This can come in handy as an example development with nested if.
In one map, we calculate $var_a , in another map, we calculate $var_b , and the third map depends on "$var_a:var_b ".

The map also works with variables from geo and split_clients.
In geo and split_clients, the resultant variable can be assigned only a simple string; you cannot use variables or regular expressions.
If you, depending on ip, need something more complicated than a simple line, geo + map will help you.
A bunch of split_clients + map will help you, for example, flexibly change the headers for 1% of users.

For example:
 split_clients "${remote_addr}XXX" $test_percent { 1% "1"; * "0"; } map "$test_percent:$http_user_agent" $test_mobile_users { "~*^1:.*iphone.*" "X-tester: iphone"; } more_set_input_headers $test_mobile_users; 

If the user's ip got into the test 1% of users, and there is the word iphone in his user-agent, then the header “X-tester: iphone” is sent along with the request for the backend.
Developers need to respond to this header and give a test version of the site for iPhones.

Conclusion


As you can see, map helps to make complex logic with a small number of commands.
It allows you to get rid of if in most cases.
And together with other directives, creates clever conversions in several lines.
I hope these features will help you, on the one hand, to reduce configs, and on the other - to implement cunning Wishlist.

Source: https://habr.com/ru/post/231277/


All Articles