changeset 6607:c70b7f4537e1

Stream: variables and script. This is a port of corresponding http code with unrelated features excluded.
author Vladimir Homutov <vl@nginx.com>
date Mon, 04 Jul 2016 16:37:36 +0300
parents 2f41d383c9c7
children eb4293155e87
files auto/modules src/stream/ngx_stream.c src/stream/ngx_stream.h src/stream/ngx_stream_core_module.c src/stream/ngx_stream_handler.c src/stream/ngx_stream_script.c src/stream/ngx_stream_script.h src/stream/ngx_stream_variables.c src/stream/ngx_stream_variables.h
diffstat 9 files changed, 1849 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/auto/modules
+++ b/auto/modules
@@ -976,9 +976,13 @@ if [ $STREAM != NO ]; then
                      ngx_stream_upstream_module"
     ngx_module_incs="src/stream"
     ngx_module_deps="src/stream/ngx_stream.h \
+                     src/stream/ngx_stream_variables.h \
+                     src/stream/ngx_stream_script.h \
                      src/stream/ngx_stream_upstream.h \
                      src/stream/ngx_stream_upstream_round_robin.h"
     ngx_module_srcs="src/stream/ngx_stream.c \
+                     src/stream/ngx_stream_variables.c \
+                     src/stream/ngx_stream_script.c \
                      src/stream/ngx_stream_handler.c \
                      src/stream/ngx_stream_core_module.c \
                      src/stream/ngx_stream_proxy_module.c \
--- a/src/stream/ngx_stream.c
+++ b/src/stream/ngx_stream.c
@@ -230,6 +230,10 @@ ngx_stream_block(ngx_conf_t *cf, ngx_com
         }
     }
 
+    if (ngx_stream_variables_init_vars(cf) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
     *cf = pcf;
 
 
--- a/src/stream/ngx_stream.h
+++ b/src/stream/ngx_stream.h
@@ -20,64 +20,66 @@
 typedef struct ngx_stream_session_s  ngx_stream_session_t;
 
 
+#include <ngx_stream_variables.h>
+#include <ngx_stream_script.h>
 #include <ngx_stream_upstream.h>
 #include <ngx_stream_upstream_round_robin.h>
 
 
 typedef struct {
-    void                  **main_conf;
-    void                  **srv_conf;
+    void                         **main_conf;
+    void                         **srv_conf;
 } ngx_stream_conf_ctx_t;
 
 
 typedef struct {
-    ngx_sockaddr_t          sockaddr;
-    socklen_t               socklen;
+    ngx_sockaddr_t                 sockaddr;
+    socklen_t                      socklen;
 
     /* server ctx */
-    ngx_stream_conf_ctx_t  *ctx;
+    ngx_stream_conf_ctx_t         *ctx;
 
-    unsigned                bind:1;
-    unsigned                wildcard:1;
+    unsigned                       bind:1;
+    unsigned                       wildcard:1;
 #if (NGX_STREAM_SSL)
-    unsigned                ssl:1;
+    unsigned                       ssl:1;
 #endif
 #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
-    unsigned                ipv6only:1;
+    unsigned                       ipv6only:1;
 #endif
 #if (NGX_HAVE_REUSEPORT)
-    unsigned                reuseport:1;
+    unsigned                       reuseport:1;
 #endif
-    unsigned                so_keepalive:2;
+    unsigned                       so_keepalive:2;
 #if (NGX_HAVE_KEEPALIVE_TUNABLE)
-    int                     tcp_keepidle;
-    int                     tcp_keepintvl;
-    int                     tcp_keepcnt;
+    int                            tcp_keepidle;
+    int                            tcp_keepintvl;
+    int                            tcp_keepcnt;
 #endif
-    int                     backlog;
-    int                     type;
+    int                            backlog;
+    int                            type;
 } ngx_stream_listen_t;
 
 
 typedef struct {
-    ngx_stream_conf_ctx_t  *ctx;
-    ngx_str_t               addr_text;
+    ngx_stream_conf_ctx_t         *ctx;
+    ngx_str_t                      addr_text;
 #if (NGX_STREAM_SSL)
-    ngx_uint_t              ssl;    /* unsigned   ssl:1; */
+    ngx_uint_t                     ssl;    /* unsigned   ssl:1; */
 #endif
 } ngx_stream_addr_conf_t;
 
 typedef struct {
-    in_addr_t               addr;
-    ngx_stream_addr_conf_t  conf;
+    in_addr_t                      addr;
+    ngx_stream_addr_conf_t         conf;
 } ngx_stream_in_addr_t;
 
 
 #if (NGX_HAVE_INET6)
 
 typedef struct {
-    struct in6_addr         addr6;
-    ngx_stream_addr_conf_t  conf;
+    struct in6_addr                addr6;
+    ngx_stream_addr_conf_t         conf;
 } ngx_stream_in6_addr_t;
 
 #endif
@@ -85,21 +87,21 @@ typedef struct {
 
 typedef struct {
     /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */
-    void                   *addrs;
-    ngx_uint_t              naddrs;
+    void                          *addrs;
+    ngx_uint_t                     naddrs;
 } ngx_stream_port_t;
 
 
 typedef struct {
-    int                     family;
-    int                     type;
-    in_port_t               port;
-    ngx_array_t             addrs;       /* array of ngx_stream_conf_addr_t */
+    int                            family;
+    int                            type;
+    in_port_t                      port;
+    ngx_array_t                    addrs; /* array of ngx_stream_conf_addr_t */
 } ngx_stream_conf_port_t;
 
 
 typedef struct {
-    ngx_stream_listen_t     opt;
+    ngx_stream_listen_t            opt;
 } ngx_stream_conf_addr_t;
 
 
@@ -107,10 +109,21 @@ typedef ngx_int_t (*ngx_stream_access_pt
 
 
 typedef struct {
-    ngx_array_t             servers;     /* ngx_stream_core_srv_conf_t */
-    ngx_array_t             listen;      /* ngx_stream_listen_t */
-    ngx_stream_access_pt    limit_conn_handler;
-    ngx_stream_access_pt    access_handler;
+    ngx_array_t                    servers;     /* ngx_stream_core_srv_conf_t */
+    ngx_array_t                    listen;      /* ngx_stream_listen_t */
+
+    ngx_stream_access_pt           limit_conn_handler;
+    ngx_stream_access_pt           access_handler;
+
+    ngx_hash_t                     variables_hash;
+
+    ngx_array_t                    variables;   /* ngx_stream_variable_t */
+    ngx_uint_t                     ncaptures;
+
+    ngx_uint_t                     variables_hash_max_size;
+    ngx_uint_t                     variables_hash_bucket_size;
+
+    ngx_hash_keys_arrays_t        *variables_keys;
 } ngx_stream_core_main_conf_t;
 
 
@@ -118,42 +131,54 @@ typedef void (*ngx_stream_handler_pt)(ng
 
 
 typedef struct {
-    ngx_stream_handler_pt   handler;
-    ngx_stream_conf_ctx_t  *ctx;
-    u_char                 *file_name;
-    ngx_int_t               line;
-    ngx_log_t              *error_log;
-    ngx_flag_t              tcp_nodelay;
+    ngx_stream_handler_pt          handler;
+
+    ngx_stream_conf_ctx_t         *ctx;
+
+    u_char                        *file_name;
+    ngx_int_t                      line;
+
+    ngx_flag_t                     tcp_nodelay;
+
+    ngx_log_t                     *error_log;
 } ngx_stream_core_srv_conf_t;
 
 
 struct ngx_stream_session_s {
-    uint32_t                signature;         /* "STRM" */
+    uint32_t                       signature;         /* "STRM" */
+
+    ngx_connection_t              *connection;
 
-    ngx_connection_t       *connection;
+    off_t                          received;
 
-    off_t                   received;
+    ngx_log_handler_pt             log_handler;
 
-    ngx_log_handler_pt      log_handler;
+    void                         **ctx;
+    void                         **main_conf;
+    void                         **srv_conf;
+
+    ngx_stream_upstream_t         *upstream;
 
-    void                  **ctx;
-    void                  **main_conf;
-    void                  **srv_conf;
+    ngx_stream_variable_value_t   *variables;
 
-    ngx_stream_upstream_t  *upstream;
+#if (NGX_PCRE)
+    ngx_uint_t                     ncaptures;
+    int                           *captures;
+    u_char                        *captures_data;
+#endif
 };
 
 
 typedef struct {
-    ngx_int_t             (*preconfiguration)(ngx_conf_t *cf);
-    ngx_int_t             (*postconfiguration)(ngx_conf_t *cf);
+    ngx_int_t                    (*preconfiguration)(ngx_conf_t *cf);
+    ngx_int_t                    (*postconfiguration)(ngx_conf_t *cf);
 
-    void                 *(*create_main_conf)(ngx_conf_t *cf);
-    char                 *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+    void                        *(*create_main_conf)(ngx_conf_t *cf);
+    char                        *(*init_main_conf)(ngx_conf_t *cf, void *conf);
 
-    void                 *(*create_srv_conf)(ngx_conf_t *cf);
-    char                 *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
-                                            void *conf);
+    void                        *(*create_srv_conf)(ngx_conf_t *cf);
+    char                        *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
+                                                   void *conf);
 } ngx_stream_module_t;
 
 
--- a/src/stream/ngx_stream_core_module.c
+++ b/src/stream/ngx_stream_core_module.c
@@ -10,7 +10,9 @@
 #include <ngx_stream.h>
 
 
+static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf);
 static void *ngx_stream_core_create_main_conf(ngx_conf_t *cf);
+static char *ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf);
 static void *ngx_stream_core_create_srv_conf(ngx_conf_t *cf);
 static char *ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent,
     void *child);
@@ -24,6 +26,20 @@ static char *ngx_stream_core_listen(ngx_
 
 static ngx_command_t  ngx_stream_core_commands[] = {
 
+    { ngx_string("variables_hash_max_size"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      offsetof(ngx_stream_core_main_conf_t, variables_hash_max_size),
+      NULL },
+
+    { ngx_string("variables_hash_bucket_size"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_STREAM_MAIN_CONF_OFFSET,
+      offsetof(ngx_stream_core_main_conf_t, variables_hash_bucket_size),
+      NULL },
+
     { ngx_string("server"),
       NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
       ngx_stream_core_server,
@@ -57,11 +73,11 @@ static ngx_command_t  ngx_stream_core_co
 
 
 static ngx_stream_module_t  ngx_stream_core_module_ctx = {
-    NULL,                                  /* preconfiguration */
+    ngx_stream_core_preconfiguration,      /* preconfiguration */
     NULL,                                  /* postconfiguration */
 
     ngx_stream_core_create_main_conf,      /* create main configuration */
-    NULL,                                  /* init main configuration */
+    ngx_stream_core_init_main_conf,        /* init main configuration */
 
     ngx_stream_core_create_srv_conf,       /* create server configuration */
     ngx_stream_core_merge_srv_conf         /* merge server configuration */
@@ -84,6 +100,13 @@ ngx_module_t  ngx_stream_core_module = {
 };
 
 
+static ngx_int_t
+ngx_stream_core_preconfiguration(ngx_conf_t *cf)
+{
+    return ngx_stream_variables_add_core_vars(cf);
+}
+
+
 static void *
 ngx_stream_core_create_main_conf(ngx_conf_t *cf)
 {
@@ -107,10 +130,32 @@ ngx_stream_core_create_main_conf(ngx_con
         return NULL;
     }
 
+    cmcf->variables_hash_max_size = NGX_CONF_UNSET_UINT;
+    cmcf->variables_hash_bucket_size = NGX_CONF_UNSET_UINT;
+
     return cmcf;
 }
 
 
+static char *
+ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_stream_core_main_conf_t *cmcf = conf;
+
+    ngx_conf_init_uint_value(cmcf->variables_hash_max_size, 1024);
+    ngx_conf_init_uint_value(cmcf->variables_hash_bucket_size, 64);
+
+    cmcf->variables_hash_bucket_size =
+               ngx_align(cmcf->variables_hash_bucket_size, ngx_cacheline_size);
+
+    if (cmcf->ncaptures) {
+        cmcf->ncaptures = (cmcf->ncaptures + 1) * 3;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
 static void *
 ngx_stream_core_create_srv_conf(ngx_conf_t *cf)
 {
--- a/src/stream/ngx_stream_handler.c
+++ b/src/stream/ngx_stream_handler.c
@@ -149,6 +149,15 @@ ngx_stream_init_connection(ngx_connectio
 
     cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
 
+    s->variables = ngx_pcalloc(s->connection->pool,
+                               cmcf->variables.nelts
+                               * sizeof(ngx_stream_variable_value_t));
+
+    if (s->variables == NULL) {
+        ngx_stream_close_connection(c);
+        return;
+    }
+
     if (cmcf->limit_conn_handler) {
         rc = cmcf->limit_conn_handler(s);
 
new file mode 100644
--- /dev/null
+++ b/src/stream/ngx_stream_script.c
@@ -0,0 +1,852 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+static ngx_int_t ngx_stream_script_init_arrays(
+    ngx_stream_script_compile_t *sc);
+static ngx_int_t ngx_stream_script_done(ngx_stream_script_compile_t *sc);
+static ngx_int_t ngx_stream_script_add_copy_code(
+    ngx_stream_script_compile_t *sc, ngx_str_t *value, ngx_uint_t last);
+static ngx_int_t ngx_stream_script_add_var_code(
+    ngx_stream_script_compile_t *sc, ngx_str_t *name);
+#if (NGX_PCRE)
+static ngx_int_t ngx_stream_script_add_capture_code(
+    ngx_stream_script_compile_t *sc, ngx_uint_t n);
+#endif
+static ngx_int_t ngx_stream_script_add_full_name_code(
+    ngx_stream_script_compile_t *sc);
+static size_t ngx_stream_script_full_name_len_code(
+    ngx_stream_script_engine_t *e);
+static void ngx_stream_script_full_name_code(ngx_stream_script_engine_t *e);
+
+
+#define ngx_stream_script_exit  (u_char *) &ngx_stream_script_exit_code
+
+static uintptr_t ngx_stream_script_exit_code = (uintptr_t) NULL;
+
+
+void
+ngx_stream_script_flush_complex_value(ngx_stream_session_t *s,
+    ngx_stream_complex_value_t *val)
+{
+    ngx_uint_t *index;
+
+    index = val->flushes;
+
+    if (index) {
+        while (*index != (ngx_uint_t) -1) {
+
+            if (s->variables[*index].no_cacheable) {
+                s->variables[*index].valid = 0;
+                s->variables[*index].not_found = 0;
+            }
+
+            index++;
+        }
+    }
+}
+
+
+ngx_int_t
+ngx_stream_complex_value(ngx_stream_session_t *s,
+    ngx_stream_complex_value_t *val, ngx_str_t *value)
+{
+    size_t                         len;
+    ngx_stream_script_code_pt      code;
+    ngx_stream_script_engine_t     e;
+    ngx_stream_script_len_code_pt  lcode;
+
+    if (val->lengths == NULL) {
+        *value = val->value;
+        return NGX_OK;
+    }
+
+    ngx_stream_script_flush_complex_value(s, val);
+
+    ngx_memzero(&e, sizeof(ngx_stream_script_engine_t));
+
+    e.ip = val->lengths;
+    e.session = s;
+    e.flushed = 1;
+
+    len = 0;
+
+    while (*(uintptr_t *) e.ip) {
+        lcode = *(ngx_stream_script_len_code_pt *) e.ip;
+        len += lcode(&e);
+    }
+
+    value->len = len;
+    value->data = ngx_pnalloc(s->connection->pool, len);
+    if (value->data == NULL) {
+        return NGX_ERROR;
+    }
+
+    e.ip = val->values;
+    e.pos = value->data;
+    e.buf = *value;
+
+    while (*(uintptr_t *) e.ip) {
+        code = *(ngx_stream_script_code_pt *) e.ip;
+        code((ngx_stream_script_engine_t *) &e);
+    }
+
+    *value = e.buf;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_compile_complex_value(ngx_stream_compile_complex_value_t *ccv)
+{
+    ngx_str_t                    *v;
+    ngx_uint_t                    i, n, nv, nc;
+    ngx_array_t                   flushes, lengths, values, *pf, *pl, *pv;
+    ngx_stream_script_compile_t   sc;
+
+    v = ccv->value;
+
+    nv = 0;
+    nc = 0;
+
+    for (i = 0; i < v->len; i++) {
+        if (v->data[i] == '$') {
+            if (v->data[i + 1] >= '1' && v->data[i + 1] <= '9') {
+                nc++;
+
+            } else {
+                nv++;
+            }
+        }
+    }
+
+    if ((v->len == 0 || v->data[0] != '$')
+        && (ccv->conf_prefix || ccv->root_prefix))
+    {
+        if (ngx_conf_full_name(ccv->cf->cycle, v, ccv->conf_prefix) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        ccv->conf_prefix = 0;
+        ccv->root_prefix = 0;
+    }
+
+    ccv->complex_value->value = *v;
+    ccv->complex_value->flushes = NULL;
+    ccv->complex_value->lengths = NULL;
+    ccv->complex_value->values = NULL;
+
+    if (nv == 0 && nc == 0) {
+        return NGX_OK;
+    }
+
+    n = nv + 1;
+
+    if (ngx_array_init(&flushes, ccv->cf->pool, n, sizeof(ngx_uint_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    n = nv * (2 * sizeof(ngx_stream_script_copy_code_t)
+                  + sizeof(ngx_stream_script_var_code_t))
+        + sizeof(uintptr_t);
+
+    if (ngx_array_init(&lengths, ccv->cf->pool, n, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    n = (nv * (2 * sizeof(ngx_stream_script_copy_code_t)
+                   + sizeof(ngx_stream_script_var_code_t))
+                + sizeof(uintptr_t)
+                + v->len
+                + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+    if (ngx_array_init(&values, ccv->cf->pool, n, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    pf = &flushes;
+    pl = &lengths;
+    pv = &values;
+
+    ngx_memzero(&sc, sizeof(ngx_stream_script_compile_t));
+
+    sc.cf = ccv->cf;
+    sc.source = v;
+    sc.flushes = &pf;
+    sc.lengths = &pl;
+    sc.values = &pv;
+    sc.complete_lengths = 1;
+    sc.complete_values = 1;
+    sc.zero = ccv->zero;
+    sc.conf_prefix = ccv->conf_prefix;
+    sc.root_prefix = ccv->root_prefix;
+
+    if (ngx_stream_script_compile(&sc) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (flushes.nelts) {
+        ccv->complex_value->flushes = flushes.elts;
+        ccv->complex_value->flushes[flushes.nelts] = (ngx_uint_t) -1;
+    }
+
+    ccv->complex_value->lengths = lengths.elts;
+    ccv->complex_value->values = values.elts;
+
+    return NGX_OK;
+}
+
+
+char *
+ngx_stream_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t                            *value;
+    ngx_stream_complex_value_t          **cv;
+    ngx_stream_compile_complex_value_t    ccv;
+
+    cv = (ngx_stream_complex_value_t **) (p + cmd->offset);
+
+    if (*cv != NULL) {
+        return "duplicate";
+    }
+
+    *cv = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t));
+    if (*cv == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+    ccv.cf = cf;
+    ccv.value = &value[1];
+    ccv.complex_value = *cv;
+
+    if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+ngx_uint_t
+ngx_stream_script_variables_count(ngx_str_t *value)
+{
+    ngx_uint_t  i, n;
+
+    for (n = 0, i = 0; i < value->len; i++) {
+        if (value->data[i] == '$') {
+            n++;
+        }
+    }
+
+    return n;
+}
+
+
+ngx_int_t
+ngx_stream_script_compile(ngx_stream_script_compile_t *sc)
+{
+    u_char       ch;
+    ngx_str_t    name;
+    ngx_uint_t   i, bracket;
+
+    if (ngx_stream_script_init_arrays(sc) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < sc->source->len; /* void */ ) {
+
+        name.len = 0;
+
+        if (sc->source->data[i] == '$') {
+
+            if (++i == sc->source->len) {
+                goto invalid_variable;
+            }
+
+#if (NGX_PCRE)
+            {
+            ngx_uint_t  n;
+
+            if (sc->source->data[i] >= '1' && sc->source->data[i] <= '9') {
+
+                n = sc->source->data[i] - '0';
+
+                if (ngx_stream_script_add_capture_code(sc, n) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                i++;
+
+                continue;
+            }
+            }
+#endif
+
+            if (sc->source->data[i] == '{') {
+                bracket = 1;
+
+                if (++i == sc->source->len) {
+                    goto invalid_variable;
+                }
+
+                name.data = &sc->source->data[i];
+
+            } else {
+                bracket = 0;
+                name.data = &sc->source->data[i];
+            }
+
+            for ( /* void */ ; i < sc->source->len; i++, name.len++) {
+                ch = sc->source->data[i];
+
+                if (ch == '}' && bracket) {
+                    i++;
+                    bracket = 0;
+                    break;
+                }
+
+                if ((ch >= 'A' && ch <= 'Z')
+                    || (ch >= 'a' && ch <= 'z')
+                    || (ch >= '0' && ch <= '9')
+                    || ch == '_')
+                {
+                    continue;
+                }
+
+                break;
+            }
+
+            if (bracket) {
+                ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
+                                   "the closing bracket in \"%V\" "
+                                   "variable is missing", &name);
+                return NGX_ERROR;
+            }
+
+            if (name.len == 0) {
+                goto invalid_variable;
+            }
+
+            sc->variables++;
+
+            if (ngx_stream_script_add_var_code(sc, &name) != NGX_OK) {
+                return NGX_ERROR;
+            }
+
+            continue;
+        }
+
+        name.data = &sc->source->data[i];
+
+        while (i < sc->source->len) {
+
+            if (sc->source->data[i] == '$') {
+                break;
+            }
+
+            i++;
+            name.len++;
+        }
+
+        sc->size += name.len;
+
+        if (ngx_stream_script_add_copy_code(sc, &name, (i == sc->source->len))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return ngx_stream_script_done(sc);
+
+invalid_variable:
+
+    ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name");
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_stream_script_init_arrays(ngx_stream_script_compile_t *sc)
+{
+    ngx_uint_t   n;
+
+    if (sc->flushes && *sc->flushes == NULL) {
+        n = sc->variables ? sc->variables : 1;
+        *sc->flushes = ngx_array_create(sc->cf->pool, n, sizeof(ngx_uint_t));
+        if (*sc->flushes == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (*sc->lengths == NULL) {
+        n = sc->variables * (2 * sizeof(ngx_stream_script_copy_code_t)
+                             + sizeof(ngx_stream_script_var_code_t))
+            + sizeof(uintptr_t);
+
+        *sc->lengths = ngx_array_create(sc->cf->pool, n, 1);
+        if (*sc->lengths == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (*sc->values == NULL) {
+        n = (sc->variables * (2 * sizeof(ngx_stream_script_copy_code_t)
+                              + sizeof(ngx_stream_script_var_code_t))
+                + sizeof(uintptr_t)
+                + sc->source->len
+                + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+        *sc->values = ngx_array_create(sc->cf->pool, n, 1);
+        if (*sc->values == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    sc->variables = 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_stream_script_done(ngx_stream_script_compile_t *sc)
+{
+    ngx_str_t    zero;
+    uintptr_t   *code;
+
+    if (sc->zero) {
+
+        zero.len = 1;
+        zero.data = (u_char *) "\0";
+
+        if (ngx_stream_script_add_copy_code(sc, &zero, 0) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (sc->conf_prefix || sc->root_prefix) {
+        if (ngx_stream_script_add_full_name_code(sc) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (sc->complete_lengths) {
+        code = ngx_stream_script_add_code(*sc->lengths, sizeof(uintptr_t),
+                                          NULL);
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    if (sc->complete_values) {
+        code = ngx_stream_script_add_code(*sc->values, sizeof(uintptr_t),
+                                          &sc->main);
+        if (code == NULL) {
+            return NGX_ERROR;
+        }
+
+        *code = (uintptr_t) NULL;
+    }
+
+    return NGX_OK;
+}
+
+
+void *
+ngx_stream_script_add_code(ngx_array_t *codes, size_t size, void *code)
+{
+    u_char  *elts, **p;
+    void    *new;
+
+    elts = codes->elts;
+
+    new = ngx_array_push_n(codes, size);
+    if (new == NULL) {
+        return NULL;
+    }
+
+    if (code) {
+        if (elts != codes->elts) {
+            p = code;
+            *p += (u_char *) codes->elts - elts;
+        }
+    }
+
+    return new;
+}
+
+
+static ngx_int_t
+ngx_stream_script_add_copy_code(ngx_stream_script_compile_t *sc,
+    ngx_str_t *value, ngx_uint_t last)
+{
+    u_char                         *p;
+    size_t                          size, len, zero;
+    ngx_stream_script_copy_code_t  *code;
+
+    zero = (sc->zero && last);
+    len = value->len + zero;
+
+    code = ngx_stream_script_add_code(*sc->lengths,
+                                      sizeof(ngx_stream_script_copy_code_t),
+                                      NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_stream_script_code_pt) ngx_stream_script_copy_len_code;
+    code->len = len;
+
+    size = (sizeof(ngx_stream_script_copy_code_t) + len + sizeof(uintptr_t) - 1)
+            & ~(sizeof(uintptr_t) - 1);
+
+    code = ngx_stream_script_add_code(*sc->values, size, &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_stream_script_copy_code;
+    code->len = len;
+
+    p = ngx_cpymem((u_char *) code + sizeof(ngx_stream_script_copy_code_t),
+                   value->data, value->len);
+
+    if (zero) {
+        *p = '\0';
+        sc->zero = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_stream_script_copy_len_code(ngx_stream_script_engine_t *e)
+{
+    ngx_stream_script_copy_code_t  *code;
+
+    code = (ngx_stream_script_copy_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_copy_code_t);
+
+    return code->len;
+}
+
+
+void
+ngx_stream_script_copy_code(ngx_stream_script_engine_t *e)
+{
+    u_char                         *p;
+    ngx_stream_script_copy_code_t  *code;
+
+    code = (ngx_stream_script_copy_code_t *) e->ip;
+
+    p = e->pos;
+
+    if (!e->skip) {
+        e->pos = ngx_copy(p, e->ip + sizeof(ngx_stream_script_copy_code_t),
+                          code->len);
+    }
+
+    e->ip += sizeof(ngx_stream_script_copy_code_t)
+          + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1));
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0,
+                   "stream script copy: \"%*s\"", e->pos - p, p);
+}
+
+
+static ngx_int_t
+ngx_stream_script_add_var_code(ngx_stream_script_compile_t *sc, ngx_str_t *name)
+{
+    ngx_int_t                      index, *p;
+    ngx_stream_script_var_code_t  *code;
+
+    index = ngx_stream_get_variable_index(sc->cf, name);
+
+    if (index == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (sc->flushes) {
+        p = ngx_array_push(*sc->flushes);
+        if (p == NULL) {
+            return NGX_ERROR;
+        }
+
+        *p = index;
+    }
+
+    code = ngx_stream_script_add_code(*sc->lengths,
+                                      sizeof(ngx_stream_script_var_code_t),
+                                      NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_stream_script_code_pt)
+                      ngx_stream_script_copy_var_len_code;
+    code->index = (uintptr_t) index;
+
+    code = ngx_stream_script_add_code(*sc->values,
+                                      sizeof(ngx_stream_script_var_code_t),
+                                      &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_stream_script_copy_var_code;
+    code->index = (uintptr_t) index;
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_stream_script_copy_var_len_code(ngx_stream_script_engine_t *e)
+{
+    ngx_stream_variable_value_t   *value;
+    ngx_stream_script_var_code_t  *code;
+
+    code = (ngx_stream_script_var_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_var_code_t);
+
+    if (e->flushed) {
+        value = ngx_stream_get_indexed_variable(e->session, code->index);
+
+    } else {
+        value = ngx_stream_get_flushed_variable(e->session, code->index);
+    }
+
+    if (value && !value->not_found) {
+        return value->len;
+    }
+
+    return 0;
+}
+
+
+void
+ngx_stream_script_copy_var_code(ngx_stream_script_engine_t *e)
+{
+    u_char                        *p;
+    ngx_stream_variable_value_t   *value;
+    ngx_stream_script_var_code_t  *code;
+
+    code = (ngx_stream_script_var_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_var_code_t);
+
+    if (!e->skip) {
+
+        if (e->flushed) {
+            value = ngx_stream_get_indexed_variable(e->session, code->index);
+
+        } else {
+            value = ngx_stream_get_flushed_variable(e->session, code->index);
+        }
+
+        if (value && !value->not_found) {
+            p = e->pos;
+            e->pos = ngx_copy(p, value->data, value->len);
+
+            ngx_log_debug2(NGX_LOG_DEBUG_STREAM,
+                           e->session->connection->log, 0,
+                           "stream script var: \"%*s\"", e->pos - p, p);
+        }
+    }
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_stream_script_add_capture_code(ngx_stream_script_compile_t *sc,
+    ngx_uint_t n)
+{
+    ngx_stream_script_copy_capture_code_t  *code;
+
+    code = ngx_stream_script_add_code(*sc->lengths,
+                                  sizeof(ngx_stream_script_copy_capture_code_t),
+                                  NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_stream_script_code_pt)
+                      ngx_stream_script_copy_capture_len_code;
+    code->n = 2 * n;
+
+
+    code = ngx_stream_script_add_code(*sc->values,
+                                  sizeof(ngx_stream_script_copy_capture_code_t),
+                                  &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_stream_script_copy_capture_code;
+    code->n = 2 * n;
+
+    if (sc->ncaptures < n) {
+        sc->ncaptures = n;
+    }
+
+    return NGX_OK;
+}
+
+
+size_t
+ngx_stream_script_copy_capture_len_code(ngx_stream_script_engine_t *e)
+{
+    int                                    *cap;
+    ngx_uint_t                              n;
+    ngx_stream_session_t                   *s;
+    ngx_stream_script_copy_capture_code_t  *code;
+
+    s = e->session;
+
+    code = (ngx_stream_script_copy_capture_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_copy_capture_code_t);
+
+    n = code->n;
+
+    if (n < s->ncaptures) {
+        cap = s->captures;
+        return cap[n + 1] - cap[n];
+    }
+
+    return 0;
+}
+
+
+void
+ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e)
+{
+    int                                    *cap;
+    u_char                                 *p, *pos;
+    ngx_uint_t                              n;
+    ngx_stream_session_t                   *s;
+    ngx_stream_script_copy_capture_code_t  *code;
+
+    s = e->session;
+
+    code = (ngx_stream_script_copy_capture_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_copy_capture_code_t);
+
+    n = code->n;
+
+    pos = e->pos;
+
+    if (n < s->ncaptures) {
+        cap = s->captures;
+        p = s->captures_data;
+        e->pos = ngx_copy(pos, &p[cap[n]], cap[n + 1] - cap[n]);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0,
+                   "stream script capture: \"%*s\"", e->pos - pos, pos);
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_stream_script_add_full_name_code(ngx_stream_script_compile_t *sc)
+{
+    ngx_stream_script_full_name_code_t  *code;
+
+    code = ngx_stream_script_add_code(*sc->lengths,
+                                    sizeof(ngx_stream_script_full_name_code_t),
+                                    NULL);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = (ngx_stream_script_code_pt)
+                                          ngx_stream_script_full_name_len_code;
+    code->conf_prefix = sc->conf_prefix;
+
+    code = ngx_stream_script_add_code(*sc->values,
+                        sizeof(ngx_stream_script_full_name_code_t), &sc->main);
+    if (code == NULL) {
+        return NGX_ERROR;
+    }
+
+    code->code = ngx_stream_script_full_name_code;
+    code->conf_prefix = sc->conf_prefix;
+
+    return NGX_OK;
+}
+
+
+static size_t
+ngx_stream_script_full_name_len_code(ngx_stream_script_engine_t *e)
+{
+    ngx_stream_script_full_name_code_t  *code;
+
+    code = (ngx_stream_script_full_name_code_t *) e->ip;
+
+    e->ip += sizeof(ngx_stream_script_full_name_code_t);
+
+    return code->conf_prefix ? ngx_cycle->conf_prefix.len:
+                               ngx_cycle->prefix.len;
+}
+
+
+static void
+ngx_stream_script_full_name_code(ngx_stream_script_engine_t *e)
+{
+    ngx_stream_script_full_name_code_t  *code;
+
+    ngx_str_t  value, *prefix;
+
+    code = (ngx_stream_script_full_name_code_t *) e->ip;
+
+    value.data = e->buf.data;
+    value.len = e->pos - e->buf.data;
+
+    prefix = code->conf_prefix ? (ngx_str_t *) &ngx_cycle->conf_prefix:
+                                 (ngx_str_t *) &ngx_cycle->prefix;
+
+    if (ngx_get_full_name(e->session->connection->pool, prefix, &value)
+        != NGX_OK)
+    {
+        e->ip = ngx_stream_script_exit;
+        return;
+    }
+
+    e->buf = value;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, e->session->connection->log, 0,
+                   "stream script fullname: \"%V\"", &value);
+
+    e->ip += sizeof(ngx_stream_script_full_name_code_t);
+}
new file mode 100644
--- /dev/null
+++ b/src/stream/ngx_stream_script.h
@@ -0,0 +1,123 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_SCRIPT_H_INCLUDED_
+#define _NGX_STREAM_SCRIPT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef struct {
+    u_char                       *ip;
+    u_char                       *pos;
+    ngx_stream_variable_value_t  *sp;
+
+    ngx_str_t                     buf;
+    ngx_str_t                     line;
+
+    unsigned                      flushed:1;
+    unsigned                      skip:1;
+
+    ngx_stream_session_t         *session;
+} ngx_stream_script_engine_t;
+
+
+typedef struct {
+    ngx_conf_t                   *cf;
+    ngx_str_t                    *source;
+
+    ngx_array_t                 **flushes;
+    ngx_array_t                 **lengths;
+    ngx_array_t                 **values;
+
+    ngx_uint_t                    variables;
+    ngx_uint_t                    ncaptures;
+    ngx_uint_t                    size;
+
+    void                         *main;
+
+    unsigned                      complete_lengths:1;
+    unsigned                      complete_values:1;
+    unsigned                      zero:1;
+    unsigned                      conf_prefix:1;
+    unsigned                      root_prefix:1;
+} ngx_stream_script_compile_t;
+
+
+typedef struct {
+    ngx_str_t                     value;
+    ngx_uint_t                   *flushes;
+    void                         *lengths;
+    void                         *values;
+} ngx_stream_complex_value_t;
+
+
+typedef struct {
+    ngx_conf_t                   *cf;
+    ngx_str_t                    *value;
+    ngx_stream_complex_value_t   *complex_value;
+
+    unsigned                      zero:1;
+    unsigned                      conf_prefix:1;
+    unsigned                      root_prefix:1;
+} ngx_stream_compile_complex_value_t;
+
+
+typedef void (*ngx_stream_script_code_pt) (ngx_stream_script_engine_t *e);
+typedef size_t (*ngx_stream_script_len_code_pt) (ngx_stream_script_engine_t *e);
+
+
+typedef struct {
+    ngx_stream_script_code_pt     code;
+    uintptr_t                     len;
+} ngx_stream_script_copy_code_t;
+
+
+typedef struct {
+    ngx_stream_script_code_pt     code;
+    uintptr_t                     index;
+} ngx_stream_script_var_code_t;
+
+
+typedef struct {
+    ngx_stream_script_code_pt     code;
+    uintptr_t                     n;
+} ngx_stream_script_copy_capture_code_t;
+
+
+typedef struct {
+    ngx_stream_script_code_pt     code;
+    uintptr_t                     conf_prefix;
+} ngx_stream_script_full_name_code_t;
+
+
+void ngx_stream_script_flush_complex_value(ngx_stream_session_t *s,
+    ngx_stream_complex_value_t *val);
+ngx_int_t ngx_stream_complex_value(ngx_stream_session_t *s,
+    ngx_stream_complex_value_t *val, ngx_str_t *value);
+ngx_int_t ngx_stream_compile_complex_value(
+    ngx_stream_compile_complex_value_t *ccv);
+char *ngx_stream_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+
+
+ngx_uint_t ngx_stream_script_variables_count(ngx_str_t *value);
+ngx_int_t ngx_stream_script_compile(ngx_stream_script_compile_t *sc);
+
+void *ngx_stream_script_add_code(ngx_array_t *codes, size_t size, void *code);
+
+size_t ngx_stream_script_copy_len_code(ngx_stream_script_engine_t *e);
+void ngx_stream_script_copy_code(ngx_stream_script_engine_t *e);
+size_t ngx_stream_script_copy_var_len_code(ngx_stream_script_engine_t *e);
+void ngx_stream_script_copy_var_code(ngx_stream_script_engine_t *e);
+size_t ngx_stream_script_copy_capture_len_code(ngx_stream_script_engine_t *e);
+void ngx_stream_script_copy_capture_code(ngx_stream_script_engine_t *e);
+
+#endif /* _NGX_STREAM_SCRIPT_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/stream/ngx_stream_variables.c
@@ -0,0 +1,621 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+#include <nginx.h>
+
+
+static ngx_int_t ngx_stream_variable_nginx_version(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+
+
+static ngx_stream_variable_t  ngx_stream_core_variables[] = {
+
+    { ngx_string("nginx_version"), NULL, ngx_stream_variable_nginx_version,
+      0, 0, 0 },
+
+    { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+ngx_stream_variable_value_t  ngx_stream_variable_null_value =
+    ngx_stream_variable("");
+ngx_stream_variable_value_t  ngx_stream_variable_true_value =
+    ngx_stream_variable("1");
+
+
+ngx_stream_variable_t *
+ngx_stream_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
+{
+    ngx_int_t                     rc;
+    ngx_uint_t                    i;
+    ngx_hash_key_t               *key;
+    ngx_stream_variable_t        *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    if (name->len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"$\"");
+        return NULL;
+    }
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    key = cmcf->variables_keys->keys.elts;
+    for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) {
+        if (name->len != key[i].key.len
+            || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0)
+        {
+            continue;
+        }
+
+        v = key[i].value;
+
+        if (!(v->flags & NGX_STREAM_VAR_CHANGEABLE)) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the duplicate \"%V\" variable", name);
+            return NULL;
+        }
+
+        return v;
+    }
+
+    v = ngx_palloc(cf->pool, sizeof(ngx_stream_variable_t));
+    if (v == NULL) {
+        return NULL;
+    }
+
+    v->name.len = name->len;
+    v->name.data = ngx_pnalloc(cf->pool, name->len);
+    if (v->name.data == NULL) {
+        return NULL;
+    }
+
+    ngx_strlow(v->name.data, name->data, name->len);
+
+    v->set_handler = NULL;
+    v->get_handler = NULL;
+    v->data = 0;
+    v->flags = flags;
+    v->index = 0;
+
+    rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);
+
+    if (rc == NGX_ERROR) {
+        return NULL;
+    }
+
+    if (rc == NGX_BUSY) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "conflicting variable name \"%V\"", name);
+        return NULL;
+    }
+
+    return v;
+}
+
+
+ngx_int_t
+ngx_stream_get_variable_index(ngx_conf_t *cf, ngx_str_t *name)
+{
+    ngx_uint_t                    i;
+    ngx_stream_variable_t        *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    if (name->len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"$\"");
+        return NGX_ERROR;
+    }
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    v = cmcf->variables.elts;
+
+    if (v == NULL) {
+        if (ngx_array_init(&cmcf->variables, cf->pool, 4,
+                           sizeof(ngx_stream_variable_t))
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+
+    } else {
+        for (i = 0; i < cmcf->variables.nelts; i++) {
+            if (name->len != v[i].name.len
+                || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0)
+            {
+                continue;
+            }
+
+            return i;
+        }
+    }
+
+    v = ngx_array_push(&cmcf->variables);
+    if (v == NULL) {
+        return NGX_ERROR;
+    }
+
+    v->name.len = name->len;
+    v->name.data = ngx_pnalloc(cf->pool, name->len);
+    if (v->name.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_strlow(v->name.data, name->data, name->len);
+
+    v->set_handler = NULL;
+    v->get_handler = NULL;
+    v->data = 0;
+    v->flags = 0;
+    v->index = cmcf->variables.nelts - 1;
+
+    return v->index;
+}
+
+
+ngx_stream_variable_value_t *
+ngx_stream_get_indexed_variable(ngx_stream_session_t *s, ngx_uint_t index)
+{
+    ngx_stream_variable_t        *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    if (cmcf->variables.nelts <= index) {
+        ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0,
+                      "unknown variable index: %ui", index);
+        return NULL;
+    }
+
+    if (s->variables[index].not_found || s->variables[index].valid) {
+        return &s->variables[index];
+    }
+
+    v = cmcf->variables.elts;
+
+    if (v[index].get_handler(s, &s->variables[index], v[index].data)
+        == NGX_OK)
+    {
+        if (v[index].flags & NGX_STREAM_VAR_NOCACHEABLE) {
+            s->variables[index].no_cacheable = 1;
+        }
+
+        return &s->variables[index];
+    }
+
+    s->variables[index].valid = 0;
+    s->variables[index].not_found = 1;
+
+    return NULL;
+}
+
+
+ngx_stream_variable_value_t *
+ngx_stream_get_flushed_variable(ngx_stream_session_t *s, ngx_uint_t index)
+{
+    ngx_stream_variable_value_t  *v;
+
+    v = &s->variables[index];
+
+    if (v->valid || v->not_found) {
+        if (!v->no_cacheable) {
+            return v;
+        }
+
+        v->valid = 0;
+        v->not_found = 0;
+    }
+
+    return ngx_stream_get_indexed_variable(s, index);
+}
+
+
+ngx_stream_variable_value_t *
+ngx_stream_get_variable(ngx_stream_session_t *s, ngx_str_t *name,
+    ngx_uint_t key)
+{
+    ngx_stream_variable_t        *v;
+    ngx_stream_variable_value_t  *vv;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len);
+
+    if (v) {
+        if (v->flags & NGX_STREAM_VAR_INDEXED) {
+            return ngx_stream_get_flushed_variable(s, v->index);
+
+        } else {
+
+            vv = ngx_palloc(s->connection->pool,
+                            sizeof(ngx_stream_variable_value_t));
+
+            if (vv && v->get_handler(s, vv, v->data) == NGX_OK) {
+                return vv;
+            }
+
+            return NULL;
+        }
+    }
+
+    vv = ngx_palloc(s->connection->pool, sizeof(ngx_stream_variable_value_t));
+    if (vv == NULL) {
+        return NULL;
+    }
+
+    vv->not_found = 1;
+
+    return vv;
+}
+
+
+static ngx_int_t
+ngx_stream_variable_nginx_version(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->len = sizeof(NGINX_VERSION) - 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = (u_char *) NGINX_VERSION;
+
+    return NGX_OK;
+}
+
+
+void *
+ngx_stream_map_find(ngx_stream_session_t *s, ngx_stream_map_t *map,
+    ngx_str_t *match)
+{
+    void        *value;
+    u_char      *low;
+    size_t       len;
+    ngx_uint_t   key;
+
+    len = match->len;
+
+    if (len) {
+        low = ngx_pnalloc(s->connection->pool, len);
+        if (low == NULL) {
+            return NULL;
+        }
+
+    } else {
+        low = NULL;
+    }
+
+    key = ngx_hash_strlow(low, match->data, len);
+
+    value = ngx_hash_find_combined(&map->hash, key, low, len);
+    if (value) {
+        return value;
+    }
+
+#if (NGX_PCRE)
+
+    if (len && map->nregex) {
+        ngx_int_t                n;
+        ngx_uint_t               i;
+        ngx_stream_map_regex_t  *reg;
+
+        reg = map->regex;
+
+        for (i = 0; i < map->nregex; i++) {
+
+            n = ngx_stream_regex_exec(s, reg[i].regex, match);
+
+            if (n == NGX_OK) {
+                return reg[i].value;
+            }
+
+            if (n == NGX_DECLINED) {
+                continue;
+            }
+
+            /* NGX_ERROR */
+
+            return NULL;
+        }
+    }
+
+#endif
+
+    return NULL;
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_stream_variable_not_found(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    v->not_found = 1;
+    return NGX_OK;
+}
+
+
+ngx_stream_regex_t *
+ngx_stream_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc)
+{
+    u_char                       *p;
+    size_t                        size;
+    ngx_str_t                     name;
+    ngx_uint_t                    i, n;
+    ngx_stream_variable_t        *v;
+    ngx_stream_regex_t           *re;
+    ngx_stream_regex_variable_t  *rv;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    rc->pool = cf->pool;
+
+    if (ngx_regex_compile(rc) != NGX_OK) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err);
+        return NULL;
+    }
+
+    re = ngx_pcalloc(cf->pool, sizeof(ngx_stream_regex_t));
+    if (re == NULL) {
+        return NULL;
+    }
+
+    re->regex = rc->regex;
+    re->ncaptures = rc->captures;
+    re->name = rc->pattern;
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+    cmcf->ncaptures = ngx_max(cmcf->ncaptures, re->ncaptures);
+
+    n = (ngx_uint_t) rc->named_captures;
+
+    if (n == 0) {
+        return re;
+    }
+
+    rv = ngx_palloc(rc->pool, n * sizeof(ngx_stream_regex_variable_t));
+    if (rv == NULL) {
+        return NULL;
+    }
+
+    re->variables = rv;
+    re->nvariables = n;
+
+    size = rc->name_size;
+    p = rc->names;
+
+    for (i = 0; i < n; i++) {
+        rv[i].capture = 2 * ((p[0] << 8) + p[1]);
+
+        name.data = &p[2];
+        name.len = ngx_strlen(name.data);
+
+        v = ngx_stream_add_variable(cf, &name, NGX_STREAM_VAR_CHANGEABLE);
+        if (v == NULL) {
+            return NULL;
+        }
+
+        rv[i].index = ngx_stream_get_variable_index(cf, &name);
+        if (rv[i].index == NGX_ERROR) {
+            return NULL;
+        }
+
+        v->get_handler = ngx_stream_variable_not_found;
+
+        p += size;
+    }
+
+    return re;
+}
+
+
+ngx_int_t
+ngx_stream_regex_exec(ngx_stream_session_t *s, ngx_stream_regex_t *re,
+    ngx_str_t *str)
+{
+    ngx_int_t                     rc, index;
+    ngx_uint_t                    i, n, len;
+    ngx_stream_variable_value_t  *vv;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
+
+    if (re->ncaptures) {
+        len = cmcf->ncaptures;
+
+        if (s->captures == NULL) {
+            s->captures = ngx_palloc(s->connection->pool, len * sizeof(int));
+            if (s->captures == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+    } else {
+        len = 0;
+    }
+
+    rc = ngx_regex_exec(re->regex, str, s->captures, len);
+
+    if (rc == NGX_REGEX_NO_MATCHED) {
+        return NGX_DECLINED;
+    }
+
+    if (rc < 0) {
+        ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0,
+                      ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"",
+                      rc, str, &re->name);
+        return NGX_ERROR;
+    }
+
+    for (i = 0; i < re->nvariables; i++) {
+
+        n = re->variables[i].capture;
+        index = re->variables[i].index;
+        vv = &s->variables[index];
+
+        vv->len = s->captures[n + 1] - s->captures[n];
+        vv->valid = 1;
+        vv->no_cacheable = 0;
+        vv->not_found = 0;
+        vv->data = &str->data[s->captures[n]];
+
+#if (NGX_DEBUG)
+        {
+        ngx_stream_variable_t  *v;
+
+        v = cmcf->variables.elts;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
+                       "stream regex set $%V to \"%v\"", &v[index].name, vv);
+        }
+#endif
+    }
+
+    s->ncaptures = rc * 2;
+    s->captures_data = str->data;
+
+    return NGX_OK;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_stream_variables_add_core_vars(ngx_conf_t *cf)
+{
+    ngx_int_t                     rc;
+    ngx_stream_variable_t        *cv, *v;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,
+                                       sizeof(ngx_hash_keys_arrays_t));
+    if (cmcf->variables_keys == NULL) {
+        return NGX_ERROR;
+    }
+
+    cmcf->variables_keys->pool = cf->pool;
+    cmcf->variables_keys->temp_pool = cf->pool;
+
+    if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    for (cv = ngx_stream_core_variables; cv->name.len; cv++) {
+        v = ngx_palloc(cf->pool, sizeof(ngx_stream_variable_t));
+        if (v == NULL) {
+            return NGX_ERROR;
+        }
+
+        *v = *cv;
+
+        rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v,
+                              NGX_HASH_READONLY_KEY);
+
+        if (rc == NGX_OK) {
+            continue;
+        }
+
+        if (rc == NGX_BUSY) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "conflicting variable name \"%V\"", &v->name);
+        }
+
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_stream_variables_init_vars(ngx_conf_t *cf)
+{
+    ngx_uint_t                    i, n;
+    ngx_hash_key_t               *key;
+    ngx_hash_init_t               hash;
+    ngx_stream_variable_t        *v, *av;
+    ngx_stream_core_main_conf_t  *cmcf;
+
+    /* set the handlers for the indexed stream variables */
+
+    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);
+
+    v = cmcf->variables.elts;
+    key = cmcf->variables_keys->keys.elts;
+
+    for (i = 0; i < cmcf->variables.nelts; i++) {
+
+        for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
+
+            av = key[n].value;
+
+            if (v[i].name.len == key[n].key.len
+                && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len)
+                   == 0)
+            {
+                v[i].get_handler = av->get_handler;
+                v[i].data = av->data;
+
+                av->flags |= NGX_STREAM_VAR_INDEXED;
+                v[i].flags = av->flags;
+
+                av->index = i;
+
+                if (av->get_handler == NULL) {
+                    break;
+                }
+
+                goto next;
+            }
+        }
+
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "unknown \"%V\" variable", &v[i].name);
+
+        return NGX_ERROR;
+
+    next:
+        continue;
+    }
+
+
+    for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
+        av = key[n].value;
+
+        if (av->flags & NGX_STREAM_VAR_NOHASH) {
+            key[n].key.data = NULL;
+        }
+    }
+
+
+    hash.hash = &cmcf->variables_hash;
+    hash.key = ngx_hash_key;
+    hash.max_size = cmcf->variables_hash_max_size;
+    hash.bucket_size = cmcf->variables_hash_bucket_size;
+    hash.name = "variables_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = NULL;
+
+    if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts,
+                      cmcf->variables_keys->keys.nelts)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    cmcf->variables_keys = NULL;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/stream/ngx_stream_variables.h
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_STREAM_VARIABLES_H_INCLUDED_
+#define _NGX_STREAM_VARIABLES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_stream.h>
+
+
+typedef ngx_variable_value_t  ngx_stream_variable_value_t;
+
+#define ngx_stream_variable(v)     { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v }
+
+typedef struct ngx_stream_variable_s  ngx_stream_variable_t;
+
+typedef void (*ngx_stream_set_variable_pt) (ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+typedef ngx_int_t (*ngx_stream_get_variable_pt) (ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data);
+
+
+#define NGX_STREAM_VAR_CHANGEABLE   1
+#define NGX_STREAM_VAR_NOCACHEABLE  2
+#define NGX_STREAM_VAR_INDEXED      4
+#define NGX_STREAM_VAR_NOHASH       8
+
+
+struct ngx_stream_variable_s {
+    ngx_str_t                     name;   /* must be first to build the hash */
+    ngx_stream_set_variable_pt    set_handler;
+    ngx_stream_get_variable_pt    get_handler;
+    uintptr_t                     data;
+    ngx_uint_t                    flags;
+    ngx_uint_t                    index;
+};
+
+
+ngx_stream_variable_t *ngx_stream_add_variable(ngx_conf_t *cf, ngx_str_t *name,
+    ngx_uint_t flags);
+ngx_int_t ngx_stream_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);
+ngx_stream_variable_value_t *ngx_stream_get_indexed_variable(
+    ngx_stream_session_t *s, ngx_uint_t index);
+ngx_stream_variable_value_t *ngx_stream_get_flushed_variable(
+    ngx_stream_session_t *s, ngx_uint_t index);
+
+ngx_stream_variable_value_t *ngx_stream_get_variable(ngx_stream_session_t *s,
+    ngx_str_t *name, ngx_uint_t key);
+
+
+#if (NGX_PCRE)
+
+typedef struct {
+    ngx_uint_t                    capture;
+    ngx_int_t                     index;
+} ngx_stream_regex_variable_t;
+
+
+typedef struct {
+    ngx_regex_t                  *regex;
+    ngx_uint_t                    ncaptures;
+    ngx_stream_regex_variable_t  *variables;
+    ngx_uint_t                    nvariables;
+    ngx_str_t                     name;
+} ngx_stream_regex_t;
+
+
+typedef struct {
+    ngx_stream_regex_t           *regex;
+    void                         *value;
+} ngx_stream_map_regex_t;
+
+
+ngx_stream_regex_t *ngx_stream_regex_compile(ngx_conf_t *cf,
+    ngx_regex_compile_t *rc);
+ngx_int_t ngx_stream_regex_exec(ngx_stream_session_t *s, ngx_stream_regex_t *re,
+    ngx_str_t *str);
+
+#endif
+
+
+typedef struct {
+    ngx_hash_combined_t           hash;
+#if (NGX_PCRE)
+    ngx_stream_map_regex_t       *regex;
+    ngx_uint_t                    nregex;
+#endif
+} ngx_stream_map_t;
+
+
+void *ngx_stream_map_find(ngx_stream_session_t *s, ngx_stream_map_t *map,
+    ngx_str_t *match);
+
+
+ngx_int_t ngx_stream_variables_add_core_vars(ngx_conf_t *cf);
+ngx_int_t ngx_stream_variables_init_vars(ngx_conf_t *cf);
+
+
+extern ngx_stream_variable_value_t  ngx_stream_variable_null_value;
+extern ngx_stream_variable_value_t  ngx_stream_variable_true_value;
+
+
+#endif /* _NGX_STREAM_VARIABLES_H_INCLUDED_ */