清漆C VRT变量/功能

时间:2014-12-30 19:00:37

标签: c varnish varnish-vcl

我开始选择清漆并且在我们的配置中遇到了C代码中的VRT函数(以及网上的示例),我无法找到文档(我明白,我的C知识是不存在的)。这是我能找到的最好的,但它只是原型:http://fossies.org/dox/varnish-4.0.2/vrt__obj_8h.html#a7b48e87e48beb191015eedf37489a290

所以这是我们使用的一个例子(由于我已经发现很多次,它似乎是来自网络的copypasta):

C{
  #include <ctype.h>
  static void strtolower(char *c) {
    for (; *c; c++) {
      if (isupper(*c)) {
        *c = tolower(*c);
      }
    }
  }
}C

sub vcl_recv {
...stuff....
if (req.url ~ "<condition>" && (<another if condition>)) {
  C{
    strtolower((char *)VRT_r_req_url(sp));
  }C
}

所以我的问题是:

  1. 什么是sp?它从何而来?它没有在任何地方定义,也无法找到任何相关内容
  2. VRT_r_req_url做什么?为什么它是VRT_前缀,什么是 r (我看到也有VRT_l_函数)。它从这个结构获取数据是什么?
  3. 所有这些VRT功能是否相同,以获得与C块之外的req.url相同的变量?
  4. 是否有某些文档说明了所有这些内容?例如,我也曾多次见过这个:

    sub detectmobile {
      C{
        VRT_SetHdr(sp, HDR_BEREQ, "\020X-Varnish-TeraWurfl:", "no1", vrt_magic_string_end);
      }C
     }
    

    那么什么是HDR_BEREQ和vrt_magic_string_end?

1 个答案:

答案 0 :(得分:9)

这将是一个很长的答案,因为对你的问题有一点说法。首先,关于你的VCL中的C代码的一些尼特:

  1. 实施strtolower可能是不必要的;标准vmod具有std.tolower功能。如果你正在运行Varnish 3,你应该使用它。 (也就是说,这种存在似乎意味着你可能会使用Varnish 2,所以谁知道呢?)
  2. 您对VRT_SetHdr的来电似乎没必要。我发现它与set bereq.http.X-Varnish-TeraWurfl = "no1";
  3. 之间没有任何区别

    我的一些答案可能不太准确,因为它不清楚您使用的是什么版本的清漆,但我会猜测

    现在,回答你的问题:

      
        
    1. 什么是sp?它从何而来?它没有在任何地方定义,也无法找到任何相关内容
    2.   

    sp在Varnish中是惯用的,意思是会话指针。它的类型为struct sess,包含有关正在进行的请求的一些上下文。根据您使用的Varnish版本,这可能有更多或更少的上下文,因此很难真正定义范围。在Varnish 2中,会话包含从工作空间到请求状态(以及介于两者之间)的所有内容。 Varnish 4大大分解了这一点。

    我猜你正在使用Varnish 2或Varnish 3.在Varnish 4中,你会传递一个名为ctx的东西。

    无论如何,从配置的角度来看,你唯一真正需要了解的是sp,它始终是任何VRT函数的第一个参数。

      
        
    1. VRT_r_req_url做什么?为什么VRT_前缀和r是什么(我也看到VRT_l_函数)。它从这个结构获取数据是什么?
    2.   

    VRT 代表 V CL R un T ime。它是一组在Varnish二进制文件中实现的函数。函数签名和一些不透明结构通过头文件暴露给VCL。 VCL编译器使用此头文件以及它从VCL生成的C代码的输出来创建可加载到Varnish中的共享对象。此外,还有一个TCL脚本(它是Varnish 4中的Python),它将不同的VCL内置函数和变量与VRT函数相关联。

    r l 代表正确,这与变量所在的位置有关用表达式评估。因为VCL不允许任何类型的复杂&#34;表达式(如加法或减法;除非你将max_restarts设置为无界值,否则它确实无法接近图灵完成),实际上只有两个地方可以访问变量:在右侧或左侧 - 手边。例如:

    set req.url = req.url + "/"
    

    将编译为

    VRT_l_req_url(sp, VRT_r_req_url(sp), "/", vrt_magic_string_end);
    

    左侧访问req.url会导致编译器调用VRT_l_req_url,右侧访问会导致它使用VRT_r_req_url。

    考虑它的一种更简单的方法可能是 l 意味着&#34;设置&#34;而 r 意味着&#34;得到&#34; (或&#34;阅读&#34;,如果您愿意的话)。但它确实意味着左右。

    将此与您的代码段绑定:

    strtolower((char *)VRT_r_req_url(sp));
    

    VRT_r_req_url会返回代表const char *值的req.url。此指针将强制转换为char *以删除const限定符。 (此转换是您配置中的错误。)转换指针将发送到strtolower,然后将字符串缩小。

    由于一些原因,这是错误的。 VRT_r_req_url给了你一个const char *,所以你真的不应该修改它。我不认为这会破坏任何东西,但这违反了您给出的API合同。此外,您通过req.url界面写入VRT_l_req_url的方式 - 而不是直接在您的strtolower实施中。因此,正确的方法是使用std.tolower vmod,或者在会话工作区中创建URL的副本,以修改该副本,然后使用VRT_l_req_url将其存储回来。

    另外,strtolower实现不需要if (isupper(*c))检查。此检查仅用于混淆处理器的分支预测器。 tolower(3)基本上每个实现都使用无分支查找表,并且不会转换没有小写等效项的字符(如数字)。

      
        
    1. 所有这些VRT功能是否相同,以获得与C块之外的req.url相同的变量?
    2.   

    是。所有VRT函数都实现函数调用或变量查找。但我认为你的意思是&#34;在C区内&#34;。

      
        
    1. 是否有某些文档说明了所有这些内容?例如,我也曾多次见过这个:
    2.   
    sub detectmobile {
      C{
        VRT_SetHdr(sp, HDR_BEREQ, "\020X-Varnish-TeraWurfl:", "no1", vrt_magic_string_end);
      }C
     }
    
      

    那么什么是HDR_BEREQ和vrt_magic_string_end?

    有一些文档,但相当一部分需要源头潜水。如果您可以说出您正在使用的Varnish版本,我可以为您指出一些可能有助于了解正在发生的事情的文件。

    HDR_BEREQ告诉VRT_SetHdr使用包含将发送到后端的请求的特定工作区。

    vrt_magic_string_end是哨兵。基本上所有可以采用字符串参数的函数也可以将一堆字符串连接在一起。 Varnish通过对这些函数使用varargs解决了这个问题,将多个char *参数传递给函数。通常,如果您的函数具有可变数量的参数,这些参数都是指针,那么您只需使用NULL指针来表示不再有可用的参数。但是,将NULL值传递给许多这些函数是完全有效的。 vrt_magic_string_end是一个常量指针值,不能与任何其他指针混淆,因此是一种安全的方法,用于确定何时不再向该函数传递参数。

    考虑log这样的调用:

    log req.url + " " + req.http.Wookies + "ha!"
    

    此调用将转换为:

    VRT_log(sp, VRT_r_req_url(sp), " ", VRT_GetHdr(sp, HDR_REQ, "\10Wookies:"), "ha!", vrt_magic_string_end);
    

    如果我们没有使用vrt_magic_string_end,而是依赖于NULL,我们永远无法弄清楚&#34;哈!&#34;还需要印刷。

    无论如何,这里有很多回应。我希望它有用;如果您有更多问题,请随时提出问题。

    编辑:后续问题

      
        
    1. C块之外的所有操作实际上只是调用C函数,因此VCL中的所有函数和变量都与VRT函数匹配?
    2.   

    是的,有效。从技术角度来看,VCL确实没有变量(或者可以说是功能)。从严格意义上讲,它并不是一种真正的编程语言。它只是一种调整Varnish HTTP状态机的语言。

      
        
    1. 在VRT_SetHdr中为什么要指定一个工作区但在VRT_r_req_url中你不是吗?像我一样运行VRT_r_bereq_url来获取后端url或者我是否需要使用工作空间调用它来获取它,比如VRT_r_req_url(sp,BEREQ)(或者这不是一个有效的操作,因为你从来没有查找过后端网址)?
    2.   
    3. 我如何知道何时需要传递工作空间以及它们是什么(例如,HDR_BEREQ显然是后端请求标头,但其他工作空间是什么)?
    4.   

    这些问题的答案是相关的,所以我会在一个地方回答它们。

    这是因为解析req.url的位置嵌入在函数名中,这是由于VCL编译器的工作方式有些奇怪。在HTTP中,URL实际上并不是标题的一部分,但是Varnish有点像它一样对待它。同样,beresp.ttlreq.hash_always_miss之类的内容也不是标题。当我们查看的位不是标题时,我们需要特别实现它们。

    确实,找到req.url的实施位置很难,因为一些相当不幸的宏观用法没有任何评论。您对cache_vrt_var.c:64-95感兴趣。

    无论如何,标题是动态的,在您收到请求之前,您根本不知道它们的位置(如果存在的话)。通过各种状态(req.http.*bereq.http.*beresp.http.*resp.http.*)的任何接口访问标头时,您需要针对该特定状态解决它们。为了减少代码重复,通过这些方法读取或设置的任何标题分别通过VRT_GetHdrVRT_SetHdr。由于这些函数是针对所有VCL状态共享的,因此您会向它们传递提示,告诉他们您是在谈论reqbereqberesp还是{{1}标题。您可以想象,您有respHDR_REQHDR_BEREQHDR_BERESP

      
        
    1. 为了学习(忽略有一个vmod)你会介意更新你的帖子以显示实现strtolower函数的最佳方法,避免通过狡猾的强制转换修改const并传递不正确的类型托管功能?
    2.   

    老实说,你可以安全地做到这一点,因为VCL编译器被赋予HDR_RESP的opaque类型。如果没有制作VMOD,您可以做的最好的事情是:

    struct sess

    如果使用C99支持进行编译,则可以执行此操作:

    #include <ctype.h>
    static void 
    strtolower(char *c)
    {
      while (*c != '\0) {
        *c++ = tolower(*c);
      }
    }
    

    老实说,这种实施也不是很好。当你得到一个很长的URL,并且你不想在VCL内部使用malloc时,你可能会冒这个风险。实际的strtolower实现不进行任何边界检查;它只需要你有一个足够大的缓冲区来容纳字符串。这些都是可以解决的问题,但我真的不想花费大量时间,因为这是错误的做法。这就是创建VMOD的确切原因。

    您可以看到standard strtoupper/strtolower implementation明显不同:它从工作区预留空间,复制到工作区缓冲区,然后释放它没有使用的空间。

    (PS我摆脱了未定义的行为注释,因为我意识到我引用的tolower(3)手册页意味着输入必须可以在unsigned char中表示。这是因为tolower(3)采用整数参数;你传递的价值可能会超出范围。所以那是不好的信息,而且我已经撤回了。)