nginx(七十九)rewrite模块指令再探之(一)变量漫谈

一  set与变量

 ①  知识回顾

rewrite模块

1) 关注一些'易错'点、'难'点的案例场景

2) 本文内容很'杂',建议读者'选取感兴趣的阅读'

3) 重点关注: nginx.conf中的'脚本类'指令、本节关注'if和set'

rewrite功能

②  带着问题学习

1) 变量的'作用域'

2) 变量的'声明周期'

3) nginx变量值的'类型'

4) 变量与'请求'的关系

③  nginx变量漫谈

(1) nginx 变量的'创建'和'赋值'操作发生在全然'不同'的'时间'阶段

 [1]、nginx '变量的创建'只能发生在'nginx 配置加载'的时候,也即'reload|restart|start'时

 [2]、而'赋值'操作则只会发生在'请求实际处理'的时候;

 [3]、这意味着'不创建而直接使用变量'会导致启动'失败';

 [4]、同时也意味着我们'无法在请求处理时'dynamic'动态地'创建新的 nginx 变量

结论1: 

 [1]、'变量名'的'可见'范围虽然是'整个'配置,但'每个请求'都有所有变量的'独立'副本

 [2]、或者说'都有各变量'用来存放'值'的容器的独立副本,彼此'互不干扰'

结论2:

 [1]、nginx 变量的生命期是'不'可能跨越'请求边界'的

 遗留: 请求导致的'internal'是在请求边界的,但是'subrequest'各个模块的处理手段'不一样'

 [2]、nginx 变量理解成某种在'请求之间'全局'共享'的东西

(3) 常见的内建'非只读变量',这里的只读是指无法通过'set'显示修改

  [1]、常见非只读变量:'$args'、'$limit_rate'、'$uri'

    1) $args可以通过'set、rewrite形式修改'

    备注: '$args_xxx'是只读变量,无法修改

    附加: $arg_xxx 变量在请求url中有多个'同名xx'参数时,只会返回最先出现那个'xxx'参数的值

    补充: 可以'通过'ngx.req.get_uri_args 函数获取'所有同名的'

    2) $limit_rate 只能通过'set'形式修改

  [2]、常见的'只读'变量:'$uri',但是可以通过'特殊手段'变相修改

    1) $uri可以通过'internal'形式修改,无法通过'$uri'形式修改

    2) 常见:try_file、index、rewrite、error_page、'proxy_pass+location'

    备注:这里也'隐含'修改传递给后端的'$request_uri'

  [3]、set 修改只读变量'报错'

   1) [emerg] the duplicate "uri" variable ...  --> "尝试 set $uri ..."

   2) 修改'$arg_xxx'导致'进程崩溃'

  [4]、并非所有的内建变量都作用于当前请求,少数内建变量只作用于"主请求",比如$request_method

(4) 变量与'请求'

 1) 变量值容器的生命期是'与请求绑定'的

 2) 请求'分类'

  [1]、客户端发起的 'HTTP' 请求     --> "原始请求或主请求(main request)"

  [2]、internal的内部'重定向'请求   --> 也是'主请求'

  常见:try_file、index、rewrite、error_page、echo_exec、internal指令发起"内部跳转"

  [3]、subrequest'子请求'

    子请求: 是由 nginx 正在处理的请求'在 nginx 内部'发起的一种'级联'请求

    常见子请求'指令': auth_request、echo_location、mirror

    1) echo_location /sub  --> 发起的是'GET'请求

    特点: 由 echo_location 发起的"子请求",其执行是按照配置书写的'顺序串行'处理的

    说明: 子请求在处理过程中对'变量 $var 各自所做的修改'都丝毫'没有'影响到"主请求"

    2) auth_request /sub

    特点:ngx_auth_request 模块发起的"子请求"却会自动'共享'其"父请求"的变量值容器

    细节: echo 指令作了一些'输出',所以隐式地返回了指示正常的' 200 '状态码

    3) ngx_lua、ngx_srcache 在内的许多'第三方模块'都选择了'禁用'父子请求间的'变量共享'

(5)  map: '惰性'求值,只有在该'变量被实际读取'时才会执行

 nginx调试必备

(6) nginx 变量的值的'类型'

  [1] 变量的值'只有'一种类型,那就是'字符串(string)'

  [2] 但是变量也有可能压根就'不存在有意义'的值,'没有值的变量'也有两种'特殊'的值

    1) 一种是"不合法[invalid]"
 
    备注: 当 nginx 用户变量 $wzj '创建了却未被赋值时',$foo 的值便是"不合法"
 
    2) 另一种是"没找到[not found]"

    备注: 当前请求url参数串中并没有提及'xxx'这个参数,则"$arg_XXX"内建变量值便是'没找到'

  [3] 无论是'不合法'还是'没找到',这两种nginx变量所拥有'特殊值、空字符串'这种取值是完全不同

    1) 'js语言'中也有专门的 'undefined' 和 'null' 这两种'特殊'值

    2) 而 lua 语言中也有'专门的 nil 值':

    特点: 它们既'不等同于空字符串',也不等同于'数字0',更不是'布尔值' false

  [4]、由 set 指令创建的变量未初始化就用在"变量插值"中时,效果等同于'空字符串'

    解读:set 指令为它创建的变量自动注册了一个"取处理程序",将'不合法'变量值转换为'空字符串'

++++++++++++++  "上面输出的解读"  ++++++++++++++ 

1) nginx 会首先'检查'其值容器里的取值,结果它看到了"不合法"这个特殊值

2) 于是它这才决定继续调用 $name 变量的"取处理程序",于是 $foo 变量的'取处理程序'开始运行

3) 它向 nginx 的错误日志打印出上面那条'警告'消息

4) 然后返回一个'空字符串'作为 $name 的值,并从此缓存在 $name 的值容器中

+++++++++  nginx'原生'无法区分'undefined [nil]'和'空串'  +++++++++

备注: 通过'第三方模块 ngx_lua'可以轻松'区分'

补充: 

  1) nginx 变量 $arg_language 为特殊值'没找到'、'不合法'时  

  2) ngx.var.arg_name 在 lua 世界中的值就是 'nil',即 lua 语言里的'空'

  判断方式: if ngx.var.arg_language == nil 

  nginx.conf中的脚本类指令

①  带着问题学习

②  为什么需要set外部变量?

考虑方面: set 修改有什么'用处'?

原因1:set $var value,完成两件事情

 [1]、如果是'第一次出现'就创建'新new'变量;变量存在则'修改'这个变量值

 [2]、通过修改变量'与模块dynamic动态交互',见下面的'两个场景',常见'限速'和设置'memcached'

 备注: 可以通过'请求相关信息','动态'交互

 注意: 不要与'内建变量或模块预定义变量'重复

ngx_http_memcached_module

③   nginx.conf中二次编程能力

1) nginx.conf'原生支持'脚本类指令,有'条件'分支,有设置变量的'归属'关系,甚至'循环'等等? 

 [1]、这种二次开发,不需要'lua、js'就可以实现

 [2]、原来的'rewrite模块'就是来做这些事情,'set指令'也是rewrite提供的

 [3]、set主要是为'给if用'的

+++++++++++++++ nginx.conf的'二次编程'能力  [后续"重点"] +++++++++++++++

1) 早期引入web语言,就会引入很多'不确定'性,现在'通用的lua和js',这些语言很'完备'

2) 后续通过' 三个维度来理解nginx的二次开发' ngx-perl、ngx-lua、ngx-js  

   二次开发的'产品': openresty、kong、ingress、nginx plus、nginx unit、APISIX

3) nginx原生通过simple virtual stack machine的形式进行二次编程能力,实现简单动态脚本功能
​
++++++++++++  '脚本语言'涉及'编译器'前端技术  ++++++++++++

例如: if、set、rewrite、return,涉及脚本语言,'脚本语言'涉及四个部分,'见上';

③  为什么需要set外部变量?

原因2: '脚本类指令'需要完成很多'复杂'的功能,需要依托'set'
​
1) 但是rewrite模块提供的'脚本类指令是有很多坑'的

  [1]、因为'普通的nginx指令'是Directive,是定义式和声明式的指令

  [2]、但是'脚本类指令不是',我们一般称之为'动态脚本、动态语言、解释性语言'

  [3]、二者完全是'两码'事,但是却'并存'在nginx.conf中

  [4]、而且'没有明显'的差别,不像嵌入'lua代码或js代码'这样'明显'

2) 官方的nginx博客说'if是邪恶'的

3) 为什么要讲解'if'指令,这也是回答之前提出的为什么要'set 外部变量',下面先看'if指令'的用法

+++++++++++++  注意事项

1) 为什么'需要set 外部变量'?了解'if指令'的用法就明白了

2) 'condition'条件的形式

 备注:跟'正常的编程语言'的条件'不一样','不'支持计算表达式,类似: '($a > 100)' 等等

3) 只支持'以下'的几种方式,导致我们必须'加入set变量'才能实现一些'复杂'的功能

  [1]、直接形式,形如:'($var)' ,不区分'数字'还是'字符串',统一作为'字符串'

  [2]、加不加引号: 带有‘空格'、() {}'等'特殊'字符必须加"双"引号或者'单'引号

  [3]、注意判断'相等',不要写成'==',nginx压根'没有用=赋值'的形式,所以'不会引起歧义'

  [4]、前'三个'与'set'强相关,因为'使用到了变量'

  [5]、后面'四'个:跟文件、做'静态资源映射',做静态服务器这个时候才会用到,常见'CDN'使用

   备注: 做'反向代理'负载均衡'很少'用到

  [6]、上传php的文件,因为php有执行能力,可以通过-x进行判断,检查是否是可执行文件,避免被误执行

4) 如果字符串包含'某种特性'匹配,则提取'局部'变量,也算是'多条件'的一种

  例如: if ($uri ~ /kk(?<name>.*) {...},通过if 结合正则捕获,然后引用

5) 为什么需要set? 

说明: if实现条件判断,一般需要'逻辑and、or、else(非)',通过set来实现

④  if指令是邪恶的

脚本指令: 'rewrite'模块提供的,'set'、'rewrite'、'break'、'return'、'if'

普通指令: 'add_header'、'proxy_pass'之类的

原因1: 配置块'间'的'指令继承'问题

1)location'天然有作用域'的概念,由于'{}'

[1]、不管是哪种location,同一层级location间'(除非脚本类指令)'是互不影响,互不'干扰'的

   举例:多个并列的if就是多个'同级的匿名 no named location'

原因2: 脚本类指令和11个HTTP阶段的执行顺序是'完全不一样'的

[1]、'普通指令'的执行顺序和'脚本类'的执行顺序不一样

[2]、'直观上(感性认识)'好像要么是从'上到下'执行,要么是'按照阶段'执行  --> "错误认知"

[3]、其实不是,'脚本类'指令是按照'相应阶段'从上到下执行,'普通'指令是按照HTTP 11个阶段执行

+++++++++++++++ "分割线"  +++++++++++++++

对'指令的继承'和'if指令的作用域'的探讨

  1)普通指令有一个'向下继承'关系 --> 父子配置块

  2)兄弟配置块,'同一层级';if 之间的'普通指令互相不继承'的  --> 见'下面的add_header'指令

解读: 兄弟之间是无关的'(两个if形成的匿名loation)',上面的if中的普通指令是无法'影响'下面的

细节点: 如果if里面只有'脚本类'指令,没有普通指令,则'不用管',一旦'掺入'普通指令,就要'注意'了

常见: 脚本类指令'(rewrite模块提供)'和普通指令(proxy_pass)'混合使用'

原因2: '脚本类指令'的执行

1) 不存在向下'继承'关系

上面'案例'解读: 也即'不是'location中的rewrite'覆盖'server中的rewrite

              而是'按照出现的顺序'执行,两次rewrite'都执行'了

2) '下面'图谱:脚本式指令的执行流程   --> '米红色的阶段执行(rewrite模块的脚本集指令)'

注意: 

 [1]、server_rewrite中'指令'只会执行一遍

 [2]、但是location_rewrite'由于find_location',可能会执行'多次',最多10次

补充: 并不是每次请求'完全经过'11个阶段

 [1]、return 直接调用filter  

 [2]、access'拒绝' 直接调用filter

 [3]、 限速,直接调用filter

1) if中放置普通指令,一定要注意'先后'关系,'语义'要自己把握

set的功能主要是'满足if',满足if的'与或非'的功能,就需要'把set放进来',会导致'几个'问题

  [1]、if对普通类指令的'继承'

  [2]、多个if是匿名location,是同级,是'兄弟关系'location块,是没有继承关系,是排他性的,

  [3]、脚本类指令是'顺序'执行的,后续讲解'指令寄存器和栈寄存器'

  [4]、普通类指令是按照'11个阶段'执行的

⑤   nginx中利用if 等价&&多条件

需求背景: 
 
 1) nginx'不'支持'&&、||、and、or'等逻辑操作符

 场景: 一个变量'多个值'的场景

 案例: if ($http_name ~ ^(jane|tony)$) { }  <==> 任意条件'满足'即可,相当'||'

 2) 思考:如何'实现' 类似 if ($arg_a == "" && $arg_b == "") 形式

 3) 本文针对'不同'场景,通过'4种'方式来'讲解'

 4) 不建议if'进行多条件'判断

  注意: 首先'测试案例'要全面,避免有'遗漏',其次测试方式多样化:"浏览器和curl"多种方式

 5) 以下只是提供了'一种解决问题'的思路,要根据'场景'合理使用

正则表达式中的if then else  正则表达式if语句

方式1: '多个条件'刚好在一个'变量'中,可以用'if 正则'进行匹配

常见: $request、$request_uri

方式2: 可以通过'map'进行变量的'组合嵌套',然后通过 if ($new_var ~)进行匹配

备注:  这里演示只是'简单'进行变量判断

map优点: 'text'文本可以使用'多个变量',最终'解析的字符串'作为最终的text文本;if'不行'

方式3:变量初始化,然后多个if层层判断   其它参考

原理: 首先通过'if'设置'$flag'标志位,然后在if判断过程中改变'$flag'达到效果

说明: 下面的'案例'演示了'&、|、!'的场景

方式4:PCRE正则表达式if语句

方式4: PCRE正则'零宽度断言',来'模拟if'语句的行为,在匹配模式时根据'条件'选择'不同'的模式

(?(condition)true-pattern|false-pattern) --> "遗留"

备注1: 一般'condition'使用'?=regex、?!regex、?<=regex、?<!regex'或'?=regex'形式

备注2:'condition'也可以使用其它'任何'形式

应用场景: 变量之间有'依赖关系',这里的依赖指的是'值'有关联 --> '某种正则模式'、'包含'关系

补充: 

  1) 关于正则'被匹配的地方':不能'引用已存在的变量($解读正则字符)'、但可'通过补获产生新变量'

  备注: 不管是'map ~',还是'if 中 ~',变量表示的'pattern'都不会进行'解析'

  补充: '非'正则场景,if ($http_name = ${arg_name}) {...} --> "变量解析字面字符串"

  2) 匹配'地方'可以预定义变量:$(http|arg|cookie)_xx --> 产生'独一无二的变量',避免报错

  3) 重点: nginx正则不支持'$var'变量内插,不会进行'变量解析',而是直接作为'正则字符'

⑥  if进行域名跳转

背景: 'A域名'要下线,在'彻底下线'过渡阶段,'A'域名跳转到'B'域名,只进行'域名替换'

细节点: 在server块配置,在'location_config寻址前',直接处理

扩展点: '某个变量'和'uri' 结合&&判断

举例:'$host和$uri'判断

if ($host='www.wzj.com') {

  rewrite ^/wzj $request_uri? break;
  rewrite ^/hello $equest_uri break;
  reqrite ^ https://nginx.wzj.com permanent;
}

## 更通俗的'理解' --> $host和$uri'同时'满足,可以有'多种'符号'='、'~'等

if ($host='www.wzj.com') {

  set $var 1

} 

if ($uri='/wzj') {

   action

}

⑦  if指令再探

1) 从'源码'分析'unexpect if'案例

2) 从'案例'加以'原理'辅助理解

3) if指令是'邪恶'的:不了解'nginx的执行阶段,在'location内'滥用,'server context'不存在

4) 目的: 如何'随心所欲'的使用if,写出'符合要求'的配置

if指令是邪恶的

+++++++++ "if的一些非预期的案例场景" +++++++++

"重点结论":

1) 如果location上下文'if'指令的结果是'match' 的,那么 if 会创建一个'内嵌的 location 块'

2) 只有这里面的 content 处理指令'NGX_HTTP_CONTENT_PHASE 阶段'会执行

3) if 条件为'真(true)'时,nginx 使用这个'无名 location 作用域的配置'处理当前请求

备注: 

  1、在解析if指令时,会把'if'当做'anonymous["匿名"] location'来处理;

  2、并且把这个'if对应的location'的type被设置成了'noname',在内部创建一个'无名'location

  3、所以在进行'url匹配'时并'不会查找'到此location;

  

1) 过滤模块是过滤'响应头response_header'和'内容content'的模块

   备注: 'add_header'指令就属于'filter'模块

2) 它工作在'获取响应内容'之'后',向用户发送响应之'前'

3) 处理过程分为'两个阶段':过滤HTTP响应的头部和主体,在这两个阶段可以分别对头部和主体进行修改

nginx的filter过滤模块   nginx的11个阶段  详解nginx的11个阶段   if is evil解读

  

1) proxy_pass 是一个 'action directive',动作指令

2) 按照定义和其它嵌套location 的实现来看,这个指令'不应该被子作用域'继承

3) 但是 location 'if(true) {}' 当作子作用域的话,会被'继承'

NGINX脚本语言原理及源码分析(一)

NGINX脚本语言原理及源码分析(二)

NGINX脚本语言原理及源码分析(三)

NGINX脚本语言原理及源码分析(四)

陶辉大神的三篇变量使用脚本指令

思考: break 指令 和 rewrite 'flag' 为break的'区别'?

1) break'指令'终止所有的'rewrite模块的指令',不仅仅包括'rewrite指令',还有其它'set、if等'

2) rewrite 的'falg'为break也禁止执行后续的'rewrite'模块指令执行

3) 不管哪种'break',都只是停止对应'(SERVER|LOCATION)_REWRITE'阶段的'rewrite'模块指令

4) 然后进入nginx的'下一个'执行阶段

eg: server 块中if指令中使用rewrite ... break,还是会进行'location级别'find_location

 ⑧  避坑if方案

+++++++++  "绝对安全[官方推荐]" +++++++++

location / {
  if (condition) {
    return ...;
  }
  
  if (condition) {
    rewrite ... last;
  }
}

++++++++++ "分割线"

location / {

  if ($uri = '/wzj12') {
      proxy_pass http://127.0.0.1:11111;
      break; # 这里的 break 将阻止下面 if 的执行,跳过其它rewrite模块的阶段
  }   

  if ($uri ~ '/wzj1') {
      proxy_pass http://127.0.0.1:11112;
  } 

}

++++++++++ "分割线"

备注: 两个条件'互斥',不用break

location / {

  if ($uri = '/wzj1') {
      proxy_pass http://127.0.0.1:11111;
  }   

  if ($uri = '/wzj2') {
      proxy_pass http://127.0.0.1:11112;
  } 

}

⑨  关于预定义变量,set可以操作的

1) 只读'变量': $request_uri、$uri、$query_string这两个变量也'不能 set'修改;

2) 只有$args 、$limit_rate等极少数可以'直接 set'修改强行修改只读变量会导致程序运行错误;

if ($args ~ (^|.*&)databases=(.*)) {
    set $args $1database=$2;
}

验证: 修改'$args'是否影响'proxy_paas'?  --> "会影响"

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值