热门标签 | 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可能更麻烦。不过这个不那么容易做,但也不是不能做。
 


推荐阅读
  • 本文详细介绍了如何正确设置Shadowsocks公共代理,包括调整超时设置、检查系统限制、防止滥用及遵守DMCA法规等关键步骤。 ... [详细]
  • LNMP流程图nginxPHPMysqlNginxFastcgi_pass<-FastCGI->fastcgi-(php-fpm)<->wrapperPhp ... [详细]
  • centos 7.0 lnmp成功安装过程(很乱)
    下载nginx[rootlocalhostsrc]#wgethttp:nginx.orgdownloadnginx-1.7.9.tar.gz--2015-01-2412:55:2 ... [详细]
  • 用阿里云的免费 SSL 证书让网站从 HTTP 换成 HTTPS
    HTTP协议是不加密传输数据的,也就是用户跟你的网站之间传递数据有可能在途中被截获,破解传递的真实内容,所以使用不加密的HTTP的网站是不 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 大家好,我是李白。本文将分享一个从零开始的全栈项目,涵盖了设计、前端、后端和服务端的全面学习过程。通过这个项目,我希望能够帮助初学者更好地理解和掌握全栈开发的技术栈。 ... [详细]
  • 两个条件,组合控制#if($query_string~*modviewthread&t(&extra(.*)))?$)#{#set$itid$1;#rewrite^ ... [详细]
  • 本指南详细介绍了如何利用华为云对象存储服务构建视频点播(VoD)平台。通过结合开源技术如Ceph、WordPress、PHP和Nginx,用户可以高效地实现数据存储、内容管理和网站搭建。主要内容涵盖华为云对象存储系统的配置步骤、性能优化及安全设置,为开发者提供全面的技术支持。 ... [详细]
  • 服务器部署中的安全策略实践与优化
    服务器部署中的安全策略实践与优化 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 在配置Nginx的SSL证书后,虽然HTTPS访问能够正常工作,但HTTP请求却会遇到400错误。本文详细解析了这一问题,并提供了Nginx配置的具体示例。此外,还深入探讨了DNS服务器证书、SSL证书的申请与安装流程,以及域名注册、查询方法和CDN加速技术的应用,帮助读者全面了解相关技术细节。 ... [详细]
  • 在优化Nginx与PHP的高效配置过程中,许多教程提供的配置方法存在诸多问题或不良实践。本文将深入探讨这些常见错误,并详细介绍如何正确配置Nginx和PHP,以实现更高的性能和稳定性。我们将从Nginx配置文件的基本指令入手,逐步解析每个关键参数的最优设置,帮助读者理解其背后的原理和实际应用效果。 ... [详细]
  • Nginx作为前端服务器时,Tomcat与Apache作为后端,War包应部署在何处? ... [详细]
  • Nginx 反向代理配置与应用指南
    本文详细介绍了 Nginx 反向代理的配置与应用方法。首先,用户可以从官方下载页面(http://nginx.org/en/download.html)获取最新稳定版 Nginx,推荐使用 1.14.2 版本。下载并解压后,通过双击 `nginx.exe` 文件启动 Nginx 服务。文章进一步探讨了反向代理的基本原理及其在实际应用场景中的配置技巧,包括负载均衡、缓存管理和安全设置等,为用户提供了一套全面的实践指南。 ... [详细]
  • 如何在Ubuntu系统中彻底卸载Nginx及其相关配置文件 ... [详细]
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社区 版权所有