Contact Us

Working with Variables in Angie

Variables in the Angie server are used for advanced control over request handling, more flexible configurations, and for gathering detailed information about clients and requests. This article covers the full range of what variables in Angie can do.

Video version

A video version of this article is available on YouTube.

How variables work

Variables in Angie are much like variables in scripting languages. With them you can build configuration that contains elements of program code and if-then constructs. Variables are declared at configuration time, hold strings, and are set for each request. Their scope is global within a request. All variables fall into three types:

  • built-in variables — defined by Angie modules, described in the reference of directives;
  • user-defined variables — defined in the configuration through the directives map, set, geo, split_clients.
  • positional variables — defined when a regular expression matches ($1, $2, etc.)

Variable values are computed only at the moment of use, so the mere fact of defining user variables with map or geo adds no overhead to request processing if they are not used.

There is also a separate class of variables where part of the name is fixed and the rest is determined by request properties, for example: $arg_*, $http_*, and so on.

Variables are always referenced with a dollar sign: $uri, $host, and so on.

Escaping the dollar sign inside a variable value is not supported. To use a dollar sign in strings with variables, use the geo directive to get a variable with the value you need:

http {
  geo $doll {
        default "$";
  }
}

Variables in directives

By default, variables cannot be used as directive values. Every case where variables are allowed is described in the documentation of the directive. Take the HTTP add_header directive, for example. To debug work with variables, you can output the value of a string containing a variable:

server {
add_header x-ruri "request_uri: $request_uri";
add_header x-uri "uri: $uri";
}

curl --head http://locahost?ddd

...
x-ruri: request_uri: /?ddd
x-uri: uri: /
...

As the example shows, mixed strings made up of variables and plain characters are allowed. If you need to separate a variable name from the plain string, use curly braces:

${uri}_test

One of the most powerful tools for working with variables is the Map module, which we turn to next.

Defining variables with map

The built-in Map module lets you create user variables from other variables and strings. For example, suppose you need a variable with a "bot" flag depending on the HTTP User-agent header:

http {
    map $http_user_agent $limit_search_bots {
        default 0;
        ~*(google|wget|bing|yandex|msnbot|apachebench|curl) 1;
        "bot" 2;
    }
}

Note that map is allowed only in the http or stream contexts. The map directive takes two parameters: a string with the source variables ($http_user_agent) and the resulting variable ($limit_search_bots). The block defines the various values of the resulting variable. The value default (0) is set if there is not a single match with the source parameters in the other options.

The second line of the block is a case-insensitive regular expression (the ~* operator) that the $http_user_agent string is matched against. If the regular expression matches, the $limit_search_bots variable takes the value 1.

The third line is a plain case-insensitive string comparison of bot against the value of $http_user_agent. On a match, $limit_search_bots takes the value 2.

The map directive only defines the variable. The resulting variable can be used when writing to the log (specified in the log format or used to implement conditional logging), in the values of other directives, and in an if block of the Rewrite module. Consider the last option:

server {
  if ($limit_search_bots = 1) {
    return 421;
  }
}

Here the if block compares the $limit_search_bots variable to one. If the comparison succeeds, the configuration in the block applies, that is, returning response code 421. Inside the block, use only Rewrite module directives to keep the predictable behavior of the server configuration. For example, you can replace return with a redirect via rewrite, or send processing to a named location via goto.

The map directive supports attaching value lists from external files with the include parameter, which improves the readability of configuration with large lists:

map $uri $redir {
    default     "";
    include     /etc/angie/redir.map;
}

# cat /etc/angie/redir.map

...
/old_content                   /new-page;
/legacy/api            /new/api;
"~^/old_blog/([0-9]+)/(.*)$"   /blog/$1/$2;
...

server {
  if ($redir) {
    return 301 $redir;
  }
}

In this example we defined a list of addresses to redirect from and the target addresses for them. If there is no match for $uri in the list, the variable gets an empty string value and no redirect happens.

Another option of map is the volatile property. With this option enabled, the variable is not cached for the duration of the request; instead it is computed every time it is referenced. Such behavior can be useful if you change the variable during request processing and do not want the value cached. For example, some built-in variables allow modification, and the following construction will make sense:

http {
  map $uri $is_test {
    volatile;
    default 0;
    "/test/" 1;
    "/ddd" 2;
  }

  server {
    location /test/ {
      rewrite ^ /ddd?$is_test last;
      add_header x-test "$is_test";
      proxy_pass http://localhost:8080;
    }

    location / {
      add_header x-test-root "$is_test";
      proxy_pass http://localhost:8080;
    }
  }
}

First we defined the $is_test variable with map and the volatile attribute. It depends on the processed page address ($uri). A request landing in location /test/ is redirected to location /. The value of the $is_test variable in the first case (in the rewrite directive) is "1", while in the second case, when the header is output, it is already "2". So the variable's value changes while a single request is being processed.

Setting the address of the proxied server with variables

With proxying, you can define the address or a group of servers through a variable. To do this, create a variable:

http {
    map $uri $host_dest {
        default back_def;
        "~^/admin/" back_adm;
        "~^/api/" back_api;
    }
}

Now we have a $host_dest variable that holds the name of the server group to proxy to. Applying this variable is straightforward:

http {
  upstream back_def {
    server 192.168.0.100;
  }
  upstream back_adm {
    server 192.168.0.110;
  }
  upstream back_api {
    server 192.168.0.120;
  }

  server {
    location / {
        proxy_pass $host_dest;
    }
  }
}

Here is an example for the stream context with the SSL_preread module, where the ssl_preread directive reads the TLS protocol version and lets you proxy several protocols over a single TCP port, for example like this:

stream {
  map $ssl_preread_protocol $upstream {
    ""        192.168.122.142:22;
    "TLSv1.2" 192.168.122.142:443;
    "TLSv1.3" 192.168.122.142:443;
    default   192.168.122.142:443;
  }

  server {
    listen 8443;
    ssl_preread on;
    proxy_pass $upstream;
  }
}

When there is a valid TLS protocol version value, proxying goes to 192.168.122.142:443, and for an empty version value the address is 192.168.122.142:22 (SSH).

Using the geo module and directive

The geo module solves a simple task: set the value of a variable depending on the client's IP address. For example, with geo you can implement simple access rules for a web application.

http {
  geo $block_access {
      default        0;
      1.2.3.4        1;
      196.186.1.0/24 1;
      200.1.0.0/16   1;
      2001:0db8::/32 1;
  }

  server {
    if ($block_access) {
        return 403;
    }
  }
}

By default the client address is taken from the $remote_addr variable, but you can set an arbitrary variable.

http {
  geo $http_x_forwarded_for $block_access {
      default        0;
      1.2.3.4        1;
      196.186.1.0/24 1;
      200.1.0.0/16   1;
      2001:0db8::/32 1;
  }
}

Summary

We have covered many ways to apply variables in Angie. They let you build flexible configurations and partially customize server behavior. Used well, variables solve a wide range of tasks for a web server and load balancer.