背景:

业务请求:http://xxx/docs/%e5%a5%bd%e5%a5%bd%e5%ad%a6%e4%b9%a0%e6%83%b3%e5%95%a5%e5%91%a2.pdf响应失败,异常码400,且后端服务未收到该请求。
初步分析链路为: client –> gw1 –> node –> gw2 –> ingress –> service。

排查:

  • 一、经过逐级日志排查,实际是ingress侧抛出了error;此处由于ingress日志输出不完善,浪费部分时间 pic 报错信息为:HPE_INVWALID_URL
  • 二. 经验证,为PATH中包含了未做URLENCODE的中文字符导致该异常!
  • 三. 对整个链路传输过程进行追踪,
    • 1,分别对gw1,node,gw2的输入输出,发现node输出是正确编码的,而gw2收到的是解码后的数据;
    • 2,链路修正:client –>gw1 –> node –> consul-nginx –> gw2 –> ingress –> service;则可以确定问题出现在 consul-nginx 这一环节,经过直接请求测试,也证明了这一点。

根因分析

pic

上图为consul-nginx中,对应请求的location配置,其中PATH部分被$2正则替换!
参考官方文档pic

对官网说明进行分析,请求URL被中转到后端服务的场景如下:

  • 1.当proxy_pass配置的是一个URI(形如:Host+/xxx),则location匹配到的部分URI(_normalized_之后做匹配),会被替换为指令中的/xxx(可以只是根路径:/)。(如官方示例:外部请求为:/name/yyy; –> 后端请求为:/remote/yyy)
  • 2.当proxy_pass指令后配置的仅仅为HOST:1)如果直接处理原始请求:则传递到后端服务的就是外部传入的原始URI(如官方示例:外部请求为:/some/path/yyy; –> 后端请求为:/some/path/yyy);2)如中间修改了URI:则传递到后端服务是被_normalized_的请求URI。
  • 3.当location中指定了正则进行匹配,则proxy_pass中不能只指定URI,否则无法确定替换策略,加载失败;注意:URI之后包含/$2 或 /$query_string等变量,则仍然可以正常替换
  • 4.如果URI被rewrite修改,则proxy_pass中指定的URI被忽略,直接将修改后的完整的URI传递到后端服务;
  • 5.如果proxy_pass中使用了变量,如$request_uri,那么被传入到后端服务的内容使用原始的请求URI内容替换。

备注:

  • 在1.1.12版本之前,proxy_pass中不指定URI时,会直接向后透传原生的URI,而不是修改后的URI;
  • websocket的代理从1.3.13版本开始支持,且需要特殊配置。

补充说明:

URI

参考nginx中uri变量的说明:

  • 仅仅为PATH部分,不包含 ? 及 querystring;
  • // 会被替换为 /;
  • 会做urldecode;
  • 中间过程可以修改该值;

request_uri

就是外部传入的原生URI,没有经过任何转义等操作;

normalized URI

pic

location匹配时使用的URI,实际 是request URI被decoding后的值!
这也是本次问题出现的核心点:$2即为该decoding后的内容,进而导致后端server收到的值,也是被decoding后的内容!

结论

本次问题的引入实际为nginx的proxy_pass对匹配内容进行normalized导致!

优化

其实该操作,从官方角度出发,有助于进行合法性检查和安全性校验,可提升系统的安全性!(如当前场景下,是因为path中出现了中文!)
最终建议用户优化使用方式,并为后续业务URI的定义给出一定的规范!