热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

Nginxrewriteif指令详细分析

nginx的if功能确实是弱得可以,严重影响了生产效率。故此,先提出严正抗议!对于这个功能奇弱的if指令,nginx实现得还特别复杂。下面将对其实现进行剖析。1.1.指令解析if指令由ngx_http_rewrite_if函数负责解析。这个函数的主要工作是543:ctxngx_pc
nginx的if功能确实是弱得可以,严重影响了生产效率。故此,先提出严正抗议!

对于这个功能奇弱的if指令,nginx实现得还特别复杂。下面将对其实现进行剖析。

1.1.       指令解析
if 指令由ngx_http_rewrite_if函数负责解析。这个函数的主要工作是
543:  ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
552:  ctx->loc_cOnf= ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
582:  ngx_http_add_location(cf, &pclcf->locations, clcf);
586:  ngx_http_rewrite_if_condition(cf, lcf);
590:  if_code = ngx_array_push_n(lcf->codes, sizeof(ngx_http_script_if_code_t));
618:  ngx_conf_parse(cf, NULL);
632:  if_code->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
633:                                                 - (u_char *) if_code;
以上是基于nginx 1.2.0版本摘录出来的提纲语句。我们现在细细来讲:
i.              进入ngx_http_rewrite_if函数后,首先进行的是创建nginx http配置数据结构的过程,对应于上面的line 543和line 552两个标记行引导的程序段。为什么需要这样呢?因为nginx对于块指令的指令解析回调函数仅支持两种模式。第一种方式,块内指令解析和配置存储(数据结构和存储方式)全部由用户定义,这种模式见于ngx_http_geo_block()。而第二种方式,块内指令解析和配置存储(数据结构和存储方式)则全部由nginx定义,ngx_http_rewrite_if()就属于这种方式。这种方式要求模块配置存储在ngx_http_conf_ctx_t中。
ii.              产生了nginx要求的配置存储结构以后,流程执行到line 582为终结的阶段——创建匿名location配置。之所以要创建一个匿名location,是为了实现在if匹配成功后,使用if块中的location配置替换请求的location配置。注意,这个location配置不参与nginx匹配请求uri的过程,因为有这条语句的存在clcf->nOname= 1;。另外,细心的读者可能可以意识到为什么可以写在if中的配置指令无一例外都是保存在location配置中的,因为if指令在匹配上以后只会替换请求的location配置。
iii.              接着,nginx开始解析if指令后面的条件。对应line 586。如果解析成功,nginx创建一个if_code的内部指令,对应line 590开始的程序段。这一部分可以这么理解,nginx将if指令解释成一个单目指令,产生类似于
mov [bp], val
test [bp], 0
这样的内部指令。其中test对应着if指令,mov则是if指令括号中条件的抽象。
iv.              处理完if指令本身,nginx开始处理if指令后面的块指令。这一步对应于line 618前后的程序段。注意这里,nginx根据location配置中pclcf->name.len == 0的条件,也就是location的uri,将后续的解析指令的类型设置成为NGX_HTTP_SIF_CONF和NGX_HTTP_LIF_CONF两种,也就是对应server中的if和location中的if了。
v.              全部解析完成以后,nginx设置if_code->next。这个指针的用途是记录这个if块中最后一条内部指令的后续地址,一旦请求处理时if条件不满足,nginx就直接跳到这个地址执行if块后面的指令。

1.2.       条件解析
条件解析在ngx_http_rewrite_if_condition中完成。因为条件中可能含有空格,所以这个函数里面也有单独的词法分析,有点不雅,不过也没有别的办法。
659:  if (value[1].len <1 || value[1].data[0] != '(') {
691:  if (len > 1 && p[0] == '$') {
712:     if (len == 1 && p[0] == '=') {
729:     if (len == 2 && p[0] == '!' && p[1] == '=') {
745:     if ((len == 1 && p[0] == '~')
746:               || (len == 2 && p[0] == '~' && p[1] == '*')
747:               || (len == 2 && p[0] == '!' && p[1] == '~')
748:               || (len == 3 && p[0] == '!' && p[1] == '~' && p[2] == '*'))
785:  } else if ((len == 2 && p[0] == '-')
786:                  || (len == 3 && p[0] == '!' && p[1] == '-'))
上面列举了这个条件解析过程的核心条件。
i.              从line 659开始,到line 691结束,nginx进行了两项预处理过程。第一是剥离括号,第二是条件正文的参数下标和在该参数字符串中的字符下标。
ii.              line 691是开始一个重要分支,处理形如$var op $val这样的变量条件表达式。与之相对的是line 785开始的分支,处理文件检测类的条件。
iii.              line 691和line 712之间,nginx解析变量,负责这件工作的是ngx_http_rewrite_variable()函数。解析完变量后,nginx再分析变量条件表达式。分三种情况,line 712开始的变量等式,line 729开始的变量不等式和line 745开始的变量正则匹配。
 
举例:
i.              if ($var = ‘chen’) 这个条件解析完成以后生成的内部指令序列是:
ngx_http_script_var_code
ngx_http_script_value_code
ngx_http_script_equal_code
ngx_http_script_if_code
ii.              if ($var ~ ‘test’) 这个条件解析完成以后生成的内部指令序列是:
ngx_http_script_var_code
ngx_http_script_regex_start_code
ngx_http_script_if_code
 
 
iii.              if ( -f ‘test$i.sh’) 这个条件解析完成以后生成的内部指令序列是:
ngx_http_script_complex_value_code
ngx_http_script_file_code
ngx_http_script_if_code

 

   表一:nginx条件总表

条件类型
内部指令集
备注
变量等式
ngx_http_script_var_code
值拷贝根据是否含变量分为两类
值拷贝
ngx_http_script_value_code
ngx_http_script_complex_value_code
ngx_http_script_equal_code
变量不等式
ngx_http_script_var_code
值拷贝根据是否含变量分为两类
值拷贝
ngx_http_script_value_code
ngx_http_script_complex_value_code
ngx_http_script_not_equal_code
变量正则表达式
ngx_http_script_var_code
 
ngx_http_script_regex_start_code
文件检测
值拷贝
ngx_http_script_value_code
值拷贝根据是否含变量分为两类
ngx_http_script_complex_value_code
ngx_http_script_file_code

1.3.       块解析
if条件后面是一个块。我们前面说了,这个块中的指令类型可能是NGX_HTTP_SIF_CONF或者NGX_HTTP_LIF_CONF。我们现在来看一下,究竟有哪些指令属于这个范畴。我们不具体列出这些指令,因为nginx发展过程中,这些指令肯定会发生变量,我们这里只看他们的特点。
所有指令分为两类:
rewrite模块指令:rewrite模块中的所有指令都可以在if产生的这两种块中被解析。解析产生的内部指令序列接在if条件的内部指令序列后面,并不断向后延伸。直到块解析完成,if_code->next指向内部指令序列的尾部。当if条件满足时,顺序指令后面的指令。当条件不满足时,通过if_code->next指针跳过块中所有指令。
普通指令(其他模块指令):都是将配置保存在location配置中的指令。块中的指令配置保存在刚刚建立的匿名location配置中。当if条件满足时,使用此location配置替换处理请求的location配置。
if指令和其他rewrite模块指令一样,都是在处理请求的时候,在REWRITE_PHASE时被处理。处理函数是ngx_http_rewrite_handler。其核心
 
    while (*(uintptr_t *) e->ip) {
        code = *(ngx_http_script_code_pt *) e->ip;
        code(e);
    }
由此可见,nginx的指令执行的核心其实是各个内部指令的实现本身。接下来,我们就一起来分析if涉及到的这些内部指令。

2.1.       nginx内部指令
nginx内部指令和CPU指令有点类似:op和操作数。我们刚刚看到的ngx_http_script_var_code等等就是op,其实就是个函数指针。每个op都有不同数量和类型的操作数。这些操作数和op一起放在各个不同的数据结构中,比如ngx_http_script_var_code对应的数据结构就是ngx_http_script_var_code_t。每个内部指令的整个数据结构都完整的放在nginx的内部指令序列中,就和C代码段中既有op,又有直接操作数一样。那么nginx如何识别指令呢?那就是所有数据结构的第一个属性都必须是op回调函数指针。这样一来,nginx通过ip指针指向内部指令序列的某一个地址,那个地址一定是op回调函数指针。在op回调函数,ip指针被修改,移到下一条指令的开始处,那么此地址也是下一条指令的op回调函数指针。

2.2.       ngx_http_script_var_code
取得变量,*e->sp = *value; e->sp++; 这里有个问题,sp是什么?nginx内部指令处理过程中,ip指向内部指令序列,sp指向结果序列。看到sp,大家其实很容易联想到堆栈,nginx的sp实现确实像个堆栈,有压栈也有出栈。

2.3.       ngx_http_script_value_code
核心代码是
1660:  e->sp->len = code->text_len;
1661:  e->sp->data = (u_char *) code->text_data;
1666:  e->sp++;
 
这个很简单,就是字符串赋值。

2.4.       ngx_http_script_complex_value_code
这个过程稍微复杂一点,但是原理还是一样的,可以看这段代码
1645:  e->sp->len = e->buf.len;
1646:  e->sp->data = e->buf.data;
1647:  e->sp++;
甭管值是如何得到的,过程是一样的。在这里,值是通过执行另一段内部指令序列得到的,这里不作展开。

2.5.       ngx_http_script_equal_code
1426:  e->sp--;
1427:  val = e->sp;
1428:  res = e->sp - 1;
1432:  if (val->len == res->len
1433:      && ngx_strncmp(val->data, res->data, res->len) == 0)
1434:  {
1435:      *res = ngx_http_variable_true_value;
1436:      return;
1437:  }
1439:  *res = ngx_http_variable_null_value;
因为存值的时候sp都加了1,所以判断相等时sp先减1,。接着取出比较的两个值val和res。注意比较的结果存放在res中,这个过程中sp的示意图是:
before:
after:

2.6.       ngx_http_script_not_equal_code
它和ngx_http_script_equal_code流程完全相同,逻辑完全相反,不做赘述。

2.7.       ngx_http_script_regex_start_code
ngx_http_script_regex_start_code在多种条件下使用,所以逻辑很杂。和if相关的流程如下:
931:  e->sp--;
932:  e->line.len = e->sp->len;
933:  e->line.data = e->sp->data;
936:  rc = ngx_http_regex_exec(r, code->regex, &e->line);
938:  if (rc == NGX_DECLINED) {
947:      if (code->test) {

965:      }
978:  if (code->test) {

992:  }
正则表达式的处理和前面的等式以及不等式有些区别,不知道大家发现了没有?操作数的处理不同。后两者的操作数都是通过别的内部指令提前存放在结果序列中的,而正则表达式只有一个参数在结果序列中,另外一个参数在指令数据结构中保存引用。为什么需要这样呢?因为“~”后面的字符串只有在前面是正则运算符的时候才有意义,所以这个字符串就在处理正则运算符的时候连带处理掉了。处理过程前面已经遇到过,大家可以再回顾一下。

2.8.       ngx_http_script_file_code
内部指令ngx_http_script_file_code从sp取得文件名,接着调用ngx_open_cached_file()函数测试文件,最后根据测试结果将真或假存回sp。

2.9.       ngx_http_script_if_code
内部指令ngx_http_script_if_code很显著的特点是一个消费者,它从sp中取出数据,判断值是否为真,但是不产生新的值。我们前面提到过if条件为真时的处理逻辑。看到
1401:  if (e->sp->len && (e->sp->len !=1 || e->sp->data[0] != '0')) {
1402:      if (code->loc_conf) {
1403:          e->request->loc_cOnf= code->loc_conf;
1404:          ngx_http_update_location_config(e->request);
1405:      }
1407:      e->ip += sizeof(ngx_http_script_if_code_t);
1408:      return;
1409:  }
1415:  e->ip += code->next;

分析这段代码可以得出if条件为真时的工作过程是使用if中的loc_conf替换请求loc_conf e->request->loc_cOnf= code->loc_conf;,更新请求参数,然后执行后面的内部指令e->ip += sizeof(ngx_http_script_if_code_t);。如果条件非真,则直接跳过解析if块中的指令得到的内部指令序列e->ip += code->next;。

代码中明显更新的是e->request,为什么说更新的是r->request呢?因为在ngx_http_rewrite_handler()中有e->request = r;。
至于nginx是如何通过location配置更新请求参数的,这个问题不在本文讨论范围内。
本文分析了nginx处理if条件的流程,见识了nginx内部指令的。虽然说并不是完全了解了nginx的脚本机制,但也不仅仅只是豹窥一斑的肤浅程度。
了解了这些有什么用呢?
稍微复杂点的条件写成nginx配置,怎得一个蛋疼可以形容。了解了这块的逻辑,搞个and\or\not难道是一个怎么复杂的事情?
有if没有else,没有这个比没有and\or可能更麻烦。不过这个不那么容易做,但也不是不能做。
 


推荐阅读
  • 深入解析 Spring Security 用户认证机制
    本文将详细介绍 Spring Security 中用户登录认证的核心流程,重点分析 AbstractAuthenticationProcessingFilter 和 AuthenticationManager 的工作原理。通过理解这些组件的实现,读者可以更好地掌握 Spring Security 的认证机制。 ... [详细]
  • 本文将深入探讨PHP编程语言的基本概念,并解释PHP概念股的含义。通过详细解析,帮助读者理解PHP在Web开发和股票市场中的重要性。 ... [详细]
  • 使用Nginx反向代理实现多域名端口映射
    本文介绍如何通过配置本地hosts文件和Nginx反向代理,实现多个虚拟域名的端口映射,使用户可以通过标准HTTP端口80访问不同后端服务。 ... [详细]
  • 本文详细介绍了如何使用Docker运行最简单的镜像,并创建第一个容器。通过具体的操作步骤和命令解释,帮助初学者快速上手Docker。完整课程请点击:Docker入门教程。 ... [详细]
  • 本文详细介绍了在 Windows 7 系统中配置 Nginx 1.10.3 和 PHP 7.1.1 NTS 的步骤,包括修改 PHP 配置文件、处理依赖项以及创建批处理脚本启动和停止服务。重点解释了如何解决常见的运行时错误。 ... [详细]
  • 探讨在开发、学习和实验过程中,使用 VMware 和 Docker 的优劣,帮助用户根据具体需求做出最佳选择。 ... [详细]
  • 本文详细介绍了如何在云服务器上配置Nginx、Tomcat、JDK和MySQL。涵盖从下载、安装到配置的完整步骤,帮助读者快速搭建Java Web开发环境。 ... [详细]
  • 优化Flask应用的并发处理:解决Mysql连接过多问题
    本文探讨了在Flask应用中通过优化后端架构来应对高并发请求,特别是针对Mysql 'too many connections' 错误的解决方案。我们将介绍如何利用Redis缓存、Gunicorn多进程和Celery异步任务队列来提升系统的性能和稳定性。 ... [详细]
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • 本文档汇总了Python编程的基础与高级面试题目,涵盖语言特性、数据结构、算法以及Web开发等多个方面,旨在帮助开发者全面掌握Python核心知识。 ... [详细]
  • 本文旨在回顾并总结近期学习的.NET Core基础知识,通过具体的操作指南加深理解,并为初学者提供实用建议,避免常见的错误和陷阱。内容涵盖CentOS的安装配置、.NET Core环境搭建及网站部署等。 ... [详细]
  • 三菱PLC SLMP协议报文详解
    本文详细解析了三菱PLC中使用的SLMP协议报文结构,包括其工作原理、通信流程及报文格式,旨在帮助工程师和技术人员更好地理解和运用这一协议。 ... [详细]
  • 本文详细介绍了 Kubernetes 集群管理工具 kubectl 的基本使用方法,涵盖了一系列常用的命令及其应用场景,旨在帮助初学者快速掌握 kubectl 的基本操作。 ... [详细]
  • 本文将详细介绍如何在ThinkPHP6框架中实现多数据库的部署,包括读写分离的策略,以及如何通过负载均衡和MySQL同步技术优化数据库性能。 ... [详细]
  • 全能终端工具推荐:高效、免费、易用
    介绍一款备受好评的全能型终端工具——MobaXterm,它不仅功能强大,而且完全免费,适合各类用户使用。 ... [详细]
author-avatar
手机用户2502898335
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有