美文网首页
nginx - variable

nginx - variable

作者: 老杜振熙 | 来源:发表于2021-04-21 18:05 被阅读0次

    WHY Nginx Variable

    其实完全可以把Nginx configuration file视为一种微型的编程语言,其语言风格深受shell和perl的影响。而编程语言的核心之一就是变量。

    Nginx的变量类型只能是string(除开一些扩展module)。变量在nginx.conf中的声明和定义很简单:set $my_variable "value"(当然,一些其他命令也能声明定义变量),之后通过$符号转义使用即可。

    • module:Nginx的世界其实就是由module组成,所有的命令都来自于不同的module(包括上面的set,其来自于标准modulengx_rewrite)。module可以分为标准模块和第三方模块;

    那么如何打印$符号呢?很遗憾,在nginx中,没有直接可用于转义$符号的方法。比较折中的方法是使用geo命令将一个变量的值设置为$,随后再对其进行引用(因为geo不支持转义),如下:

    geo $dollar {
        default "$";
    }
    

    Variable Declaration

    另外一个问题:变量的声明创建都是在什么时候?
    在Nginx中,变量唯一能够被合法声明创建的时期就是在导入配置文件的配置期(configure time);因此,如果在变量未声明之前就进行使用,Nginx是会直接报错的;

    configure time带来的一个特性是:只要一个变量被声明了,那么它在所有block中都是可见的。
    但是请注意:不同的block中,变量名可能相同,但相互之间绝对不会影响,可以视为局部变量

    server {
        listen 8080;
        location /bar {
            echo "XXX $foo";
        }
        location /foo {
            set $foo "hello";
            echo "XXX $foo";
        }
    }
    
    ### 访问bar服务时,虽然foo变量可用,但只是一个局部变量
    $ curl "http://localhost:8080/bar"
    $ XXX
    $
    $ curl "http://localhost:8080/foo"
    $ XXX hello
    $
    $ curl "http://localhost:8080/bar" #即使在/foo中变量已经被初始化了,但在/bar依旧是未初始化状态
    $ XXX
    

    Variable Lifetime

    configure time已经进行了阐述,接下来就是关于变量生命期的一些需要注意的问题。
    一言以蔽之:nginx变量的生命期只和发来的request的处理期一致,而和location block毫无关系。
    因为nginx中存在可以调用其他location 的命令(比如echo_exec),因此,如果nginx变量和location block相关的话,则当执行其他的location的时候,之前的变量就不存在了,这显然是错误的。

    built-in Variable

    上面小节中,用set等类似的指令创建的变量称为用户自定义变量(user-defined variable);

    其实Nginx也为用户创建了一些原生的,built-in的变量,比较常见的如下:

    • $uri:request的对应服务(不包含query string);比如curl "http://localhost:8080/bar?a=1%20&b=2",则url的值是/bar;
    • $request_urirequest除去IP-port的全部(包含query string);还是上面的例子,则request_uri的值是/bar?a=1%20&b=2
    • $arg_XXX:request的query string中XXX对应的值;还是上面的例子,则arg_b的值是2;(注意,这是一个infinite variables,所以只能由nginx core创建)

    注意:大部分built-in variable都是只读的,所以最好不要自己对这些变量赋值。

    但也存在一些可写的built-in variable,常见的如下:

    • $args:request的所有query_string;很有意思的是,只要修改了$args,那么诸如$arg_XXX等需要从args中进行读取的变量,它们的值都会改变;

    Container VS. Get/Set handler

    所谓容器(Container),其意思就是,这里有一片空间,专门用来存储对应的变量。一个变量如果存储于某个容器中,则称它是被索引的(indexed);

    但对于大多数built-in variable以及其他一些变量,它们是未被索引的,因此,对它们进行读写则需要执行特制的get handler和set handler函数;

    这两者的区别在哪里?核心就在于:set handler和get handler都是实时的,不经存储的。比如,当读取$arg_XXX变量时,系统会实时的扫描request的url,寻找XXX对应的值,而不是提前扫描之后将其存储起来;

    Container for Cache

    很有意思的是,对于一些module来说,它们会提前将变量的值保存起来,从而达到缓存的作用,这样可以避免上一节中多次调用get handler函数带来的额外开销。

    但这也同时造成了一些困惑,比较经典的就是ngx_map模块的map函数。map $args $my_var的作用是将args映射到my_var,但这个函数的特点是它只执行一次get handler,就把my_var的值缓存下来了。如下所示的例子,

    map $args $my_var{
        default "0";
        special "1";
    }
    
    server {
        listen 8080;
        location /foo{
            echo "original var : $my_var";
            set $args "special";
            echo "new var : $my_var";
        }
        location /bar{
            echo "hello there";
        }
    }
    
    $ curl "http://localhost/foo"
    $ original var : 0
    new var : 0 ### 因为my_var在request刚发送过来的时候就已经缓存好了,所以后续即使改变了$args,也不会对my_var造成影响
    $
    $ curl "http://localhost/foo?special"
    $ original var : 1
    new var : 1 ### 因为my_var在request刚发送过来的时候就已经缓存好了,所以后续即使改变了$args,也不会对my_var造成影响
    
    • Context:上面的例子中,map规则是写在server的外部的,也就是在http block中,这是因为每个函数都有它自己的context规则。所以如果有不清楚的地方,应该及时查阅文档。
    • 惰性计算:因为map直接定义在http block的原因,所以有人可能会认为所有的location都会对map内部执行一次计算,即使它们根本没有使用对应的变量,从而加大了计算开销。但其实不是这样的,对于ngx_map模块来说,它使用的是惰性计算策略,即,只有当变量真正被读写时,才会进行计算,所以在上面的例子中,/bar是不会对map进行计算的。

    Main Request VS. Subrequest

    在Nginx的世界中,其实存在着两类request,即main request和subrequest。

    main request指的就是从HTTP客户端发来的一个完整的request。而subrequest的定义则相对抽象一些,它存在的意义只是为了将main request进行解耦,从而转化为多个子请求以更方便的执行。对应的conf file中,就是某个location中调用多个其他的location。(比较常见的是通过ngx_echo的echo_location进行subrequest调用)

    但需要注意的是,main request和subrequest对应的location必须位于同一个server中(也就是配置文件所定义的同一个虚拟服务器中)。这样的限制,或者说特性,背后的原理就是:main request调用subrequest,其实就是调用另一个C函数罢了。因此main request调用subrequest是不涉及服务器的网络通信的,所以会很高效。

    Shared Variable Container

    前面提到过“局部变量”这个概念,意思是不同location中,即使变量名相同,但其也是不同的变量,不会相互影响。但在Nginx中,也存在一些module,它们使用的是共享机制,即,main request和subrequest之间,所用变量都会共享,相互影响。比较常见的就是ngx_auth_request模块,当main request使用该模块调用subrequest的时候,subrequest完全可以改变main request中变量的值。

    shared variable container一定程度上减少了空间开销,但也因为location之间可以相互影响,所以会带来一些潜在的,不可预料的危险,因此大部分module采用的还是局部变量的分离机制。

    # 关于shared variable container造成的困惑的一个经典的例子
    map $args $my_var{
        default "0"
        /foo "1"
        /bar "2"
    }
    server{
        listen 8080;
        location /foo{
            auto_request /bar
            echo "my_var in foo : $my_var"
        }
        location /bar{
            echo "my_var in bar : $my_var"
        }
    }
    
    $ curl "http://localhost:8080/foo"
    $ my_var in foo : 2
    # 1. auth_request会忽略subrequest的response body,只关注它的返回码,所以echo "my_var in bar : $my_var"没有显示
    # 2. 最先读取my_var是在/bar中,此时my_var被映射为2,并且被缓存了!所以后续在/foo中读取时,my_var还是2
    

    Confusion of Built-in Variable

    built-in variable比较容易造成困惑的一点在于,不同的built-in variables在main request和subrequest中的表现可能是完全不一样的,所谓的表现,主要分为两类:对subrequest context是否敏感:

    敏感:也就是说,当该变量在sub request进行读取时,其展现的是sub request中的上下文,包括$uri, $args等;

    不敏感:也就是说,当该变量在sub request进行读取时,其展现的是main request中的上下文,包括$request_uri, $request_mothod等;以$request_uri为例,其意思是未经加工的,原始的request中的query string,因此,不敏感是更符合其语义的,因为subrequest url毕竟已经进行了加工。

    • 到底如何区别一个built-in variable是否敏感,一是观察其语义,二是...查文档吧

    Empty Arg VS. Invalid Arg

    对于request中未曾出现的arg,其在nginx中并非对应一个空字符串,相反,其实际上是一个特制的字串"invalid"或者"not found"。后续,当读取时,get handler再将这些特制的字串转换为空。

    • invalid:对于使用set等命令进行了声明,但并没有初始化的变量,其值就是"invalid"
    • not found:对于query string中不存在的key,引用$arg_XXX等变量时,其值就是"not found"

    相关文章

      网友评论

          本文标题:nginx - variable

          本文链接:https://www.haomeiwen.com/subject/ngxzlltx.html