一 rewrite模块再探
① 知识回顾
1) 结合自己'遇到过'的案例场景
2) 关注一些'易错'点、'难'点
3) 本文内容很'杂',建议读者'选取感兴趣的阅读'
rewrite模块
rewrite功能
② nginx中利用if 等价&&多条件
需求背景:
1) nginx'不'支持'&&、||、and、or'等逻辑操作符
场景: 一个变量'多个值'的场景
案例: if ($http_name ~ ^(jane|tony)$) { } <==> 任意条件'满足'即可,相当'||'
2) 思考:如何'实现' 类似 if ($arg_a == "" && $arg_b == "") 形式
3) 本文针对不同场景,通过'3种'方式来'实现&&'
4) 不建议if'进行多条件'判断
注意: 首先'测试案例'要全面,避免有'遗漏',其次测试方式多样化:"浏览器和curl"多种方式
5) 以下只是提供了'一种解决问题'的思路,要根据'场景'合理使用
正则表达式中的if then else 正则表达式if语句
方式1: '多个条件'刚好在一个'变量'中,可以用'if 正则'进行匹配
方式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寻址前',直接处理
⑤ 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'当做'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'? --> "会影响"
⑧ return和rewrite再探
1) return 比 rewrite 效率更'高',但是'rewrite'可以利用'正则'完成更复杂功能
补充: 'rewrite'会隐式的带查询参数,可以通过'?'进行调整;但是'return'必须显示指定$args
2) 重点: 只探究'相对'路径时,哪些因素影响重定向'Location'形式?
思考方向:'$scheme、$host、$port、$args'
涉及手段:'return code'、'rewrite ... ...["不涉及协议"] permanent|redirect'
强调:建议显示指定'全路经'的重定向
3) 补充: 暂时'不考虑'以下场景
[1]、proxy模块涉及'proxy_redirect',以及后端'30(1|2|7|8)',及自定义Location响应头
[2]、'目录资源'导致301重定向: 默认'目录资源'nginx'301'重定向;'try_files $uri/'导致
前期铺垫:nginx与Location响应头细节探讨
port_in_redirect off的两个应用场景 nginx 的port_in_redirect 问题解决
1) Location'响应头'常见的组成:
[1]、$scheme://$host:$port$request_uri --> "全路径",最'常见'的形式
[2]、$request_uri --> "带查询参数,相对路径"
[3]、$uri --> "纯uri"
备注1: 实际可以加上'#'锚点数据,但是一般'不指定,最终返回的Location响应头是相对的还是绝对'
备注2: '$status'和'Location'共同作用'重定向'
2) absolute_redirect['总开关'] on
备注: relative url没有'协议'、'主机名'、'端口号'
3) server_name_in_redirect['host来源'] off
需求:可以为被代理服务器发出的'相对重定'向增加'主机名'
存在的'问题'1: 原始是通过'ip正向代理访问'的,可能'域名'无法解析、防火墙'不通'
存在的'问题'2: 获取的'$server_name'是一个带正则的'字符串',客户端'无法'解析'所谓'域名
4) port_in_redirect['port来源'] on --> "是否携带端口"、"携带谁的端口"
1、默认的情况下,是会在重定向的url后'自动添加nginx 处理server块当前站点监听'的端口
2、会对服务器造成'潜在'的威胁,'后端端口暴露'出来,可能会被人直接对这个端口进行诸如CC类攻击
5) 补充'说明'
1、这几种'配置'方式不能显示随心所欲的'指定'
2、通过改变'port'、'host',可能导致'重定向后'匹配一个新的'server'块
6) 本次关注'$schme',经常会出现'如下报错':
400 Bad Request The plain 'HTTP' request was sent to 'HTTPS' port
根因: 'nginx'对应的'端口'监听的是'https',但是却用'http'访问
场景1、nginx正常'配置 https 443',但是用户错误'http://www.wzj.com:443'访问
场景2、nginx的'proxy_pass'是'https',但是后端只支持'http',导致转发'报错'
$scheme是'http',但是'listen port'为443
7) 浏览器如何处理'多个'Loation响应头 --> "待验证"
案例1: 'rewrite ^ kafka redirect;' 和 'rewrite ^ /kafka redirect;' 等价
备注: 客户端['浏览器'、'curl']根据'上次'请求的'scheme、host、port'+'相对url'发起请求
案例2: 多个重复'Location'响应头,浏览器和'curl'命令行'处理行为'不一样
++++++++++++++ "分割线" ++++++++++++++
⑨ The palin HTTP request was sent to HTTPS port
目的: 必须明确'原理',也即'为什么'重定向后'https'变成了'http'?
openssl非交互的生成自签名证书 利用openssl -config生成自签名证书
需求:openssl'非交互'生成'私钥'和'证书'
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout server.key -out server.crt -subj \
"/C=CN/ST=HeNan/L=XinYang/O=devops/OU=unicorn/CN=ssl.wzj.com"
说明: 对于'自签名'证书,'curl'访问必须使用'(-k|--secure)'声明
补充: https非'443[6443]'端口的时候,必须指定'port_in_redirect on','不要修改'默认行为
报错原因:'HTTP请求'被发送到'HTTPS端口'
排错手段: 看'配置文件'、看'请求方式'是否错误
#版本1.15.0及以下
listen 443;
ssl on;
#版本1.15.0以上
listen 443 ssl;
listen 80; #这种可以'不同端口'监听同一服务
问题点: redirect涉及https跳转到http,这是一个'$scheme'非期望原因之一
Kong X-Forwarded-Port 8443 问题
base标签导致The palin HTTP request was sent to HTTPS port
nginx不同版本ssl配置导致The plain HTTP request was sent to HTTPS port
proxy_paas协议错误导致The plain HTTP request was sent to HTTPS port
++++++++++ "不谈网站架构和需求都是流氓" ++++++++++
问题现象: 请求'proxy1.wzj.com/kafka'
报错: 'Client sent an HTTP request to an HTTPS server.'
网站架构:用户 --> 'http' --> '反向代理' --> 'proxy_paas:http' --> 'nginx'
案例'模拟'场景: proxy_paas http://ssl.wzj.com:6443 -->但是'后端'只支持'https'协议
补充现象: 被'代理'服务'报错'502,注意观察'error.log'的日志
nginx访问https跳转到http的解决方法
++++++++++++++ "涉及proxy模块解决$scheme的问题" ++++++++++++++
解决: 通过'nginx'访问https'跳转到http'的解决
方式1:需要'同时修改'nginx配置和程序 --> '不推荐'
1) 在nginx代理中增加一个'proxy_set_header X-Forwarded-Proto $scheme'
说明: 标志'用户请求'是http还是https
2) 后端获取header决定'跳转'到http/https页面
方式2: nginx代理中配置'proxy_redirect'
proxy_redirect http:// $scheme://; 或 proxy_redirect http:// https://;
场景: nginx的'proxy_pass'是'https',但是后端只支持'http',导致转发'报错'
+++++++++ '题外话' +++++++++
1) Jenkins根据请求头'proxy_ser_header X-Forwarded-Port $server_port'确定重定向端口
2) mesher优先根据'proxy_ser_header X-Forwarded-Port $server_port'来进行'端口转发'
案例2: 不同的'url'访问,'proxy_redirect default'行为导致重定向的'协议$scheme'不一样
测试1说明: 由于'proxy_redirect'只剩下'Location: /wzj/kafka',经过'$scheme'等作用
案例3: '上游'Location传递错误的'$scheme',需要'proxy_redirect'对应修改 --> "省略"
遗留: 对于'请求[流]链路'复杂的,需要'多服务协作',或者'抓包定边界'
举例: 如果'上游服务器是nginx',其配置'absolute_redirect off';作为'proxy'的nginx处理?
⑩ 你真的理解rewrite吗
1) 'rewrite ... last;',在location中,且location和rewrite的'匹配规则'能匹配到相同的URL
2) nginx'最多'将进行'10次'循环匹配,并最终返回'500状态码'报错
++++++++++++ rewrite flag'无'值与'有'值区别 ++++++++++++
1) 当flag'有值'时,rewrite值'会中断',last会引发location重匹配;
2) 当flag'无值'时,rewrite会继续往下走,最后一个rewrite值覆盖前面,在引发location重新匹配
3) 未指定 flag 的情况与 flag=last 类似
唯一'区别':是在同一层级中未指定 flag 的 rewrite 语句'不会终止后续'的 rewrite 匹配
二 章神关于if和set解读
题外话: 之所以要'看原版英文'解读
1) '一'是'原汁原味',避免层层传播产生'歧义'
2) '二'是'掌握'单词',学习计算机'术语'描述
① if工作原理
② 知识铺垫:CONTENT阶段模块的执行顺序
淘宝对CONTENT阶段的解读 nginx模块执行顺序
关注点:'content handle'、'NGX_HTTP_CONTENT_PHASE'阶段涉及的'模块'和'指令'
1) nginx 'CONTENT'阶段中'默认'服务模块 --> 'static content' --> '兜底'
[1]、当一个 location 中'未使用'任何 content 阶段的指令,没有模块注册'内容处理程序'时
[2]、nginx 一般会在 content 阶段安排'三'个这样的'静态资源服务'模块
[3]、'依次'是 'index' 模块、'autoindex' 模块、以及 'static' 模块
1、index模块涉及'index'指令
2、autoindex模块
3、static模块涉及'root'、'alias'指令
默认'CONTENT'阶段起作用的时机:
1、location 中'没有'使用运行在 CONTENT 阶段的模块指令
2、也即'没有'模块'显示注册'这个 location 的"内容处理程序(content handler)"
3、处理权便'自动'落到了在 CONTENT 阶段'垫底'的那 3 个'静态'资源服务模块
2) 哪些'模快'会显示在CONTENT阶段'注册'内容处理程序? 这些CONTENT阶段模块的执行'顺序'?
[1]、proxy模块、fastcgi模块、uwsgi模块在 CONTENT 阶段执行
备注: 一般关注'proxy_pass'指令
补充: 将客户端过来的请求体'如果有的话'以'相应协议[转换]'完整的'转发'到后端服务进程
[2]、ngx_echo 模块
备注: 涉及'echo'、echo_exec、echo_location
[3]、ngx_lua模块
备注:涉及'content_by_lua'、'block_by_lua'等
3) nginx 模块在'向 content 阶段注册配置指令'时一些'原理细节'
[1]、'本质'上是在当前的'location'配置块中'注册'所谓的"内容处理程序(content handler)"
[2]、每一个 location 只能有'一'个"内容处理程序"
[3]、因此,当在 location 中同时使用'多个模块的 content 阶段指令'时
[4]、只有'其中一个模块'能成功注册"内容处理程序",即只能'解析一条'相同的指令
3) 通过'案例'重点讨论'多'个模块同时注册 content 阶段指令?
注意: echo 、'proxy_paas'、'content_by_lua'同时出现的的'优先级',与'顺序'有关吗?
强调: 应当'避免'在同一个 location 中使用'多个模'块的 content 阶段指令
content和filter
③ set和proxy_paas杂谈
could not be resolved (3: Host not found)
+++++++++++++ "小结" +++++++++++++
1) if block模块'没有'content handler
2) 所以if block模块'继承'了'外部'的`proxy_pass`
3) 最后在'if模块'里执行了这个`proxy_pass`,而不是在'外部'执行的`proxy_pass`
案例来源
④ content handler不同层级
说明: 对比'上面案例'进行理解
1) 此时'if block' 已经有了 'echo [content handler]';
2) 不会'继承'外层的'proxy_paas'这个'content handler';
⑤ if中使用break
说明: '对比'案例理解
++++++++++++++++ "这个结论待验证,暂时遗留" ++++++++++++++++
1) proxy模块在嵌入的'多个location间'的配置的'继承'扮演了关键的角色 --> '上面案例'
2) 但是'其它模块[ngx_echo]'可能不会继承location'内嵌'的content handler --> '???'
备注: 事实上,'大多数'content handler模块,包括upstream都'不支持'继承 --> '???'
⑥ content handler相同层级
'临时'结论:
1) 同一'层级'的'content handler',由于只会'only 有一个 content handler执行'
2) 最后的'content handler 覆盖'前面的
备注: 具体'哪一个模块的指令'会胜出是'不确定'的
3) 常见'content 指令': 'content_by_lua'、'echo'、'proxy_pass'
4) 补充:echo 可以'使用多次','content_by_lua'、'proxy_pass'不行
location /wzj {
echo hello;
echo world;
}
5) 最佳实践: '禁止'在同一个 location 中使用'多个模块的 content 阶段'指令
ngx_header_more模块的more_set_headers指令也会继承if块隐式创建的location
⑦ rewrite last和break标志位
1) last: 停止'当前'这个请求,并根据rewrite匹配的规则'重新'发起一个请求
备注: 跳过原始请求的'location_rewrite'等后续阶段,'新请求'又从'find_location'开始执行
补充: 新请求'保留'了原始请求的'什么'信息?
2) break:相对last,break并'不会'重新发起一个请求
备注:只是'跳过'当前的'rewrite阶段',并执行本请求'后续'的执行'阶段'
1) "内部跳转[internal]"本质上其实就是把当前的请求处理阶段'强行倒退'到 'find-config' 阶段
备注:倒退回find-config阶段动作不是发生在rewrite阶段,而是发生在后面的'post-rewrite'阶段
2) 以便'重新'进行请求 'uri[解码后]' 与 'location 配置块'的配对
3) 如果在'server'配置块中直接使用 rewrite 配置指令对请求uri进行改写,则不会涉及'内部跳转'
原因:因为此时uri改写发生在server-rewrite阶段,'早于'执行location配对的find-config阶段
++++++++++ "404的真正原因" ++++++++++
1) 404的原因'不是'没有定义对应的location,而是没有找到'对应的资源'
2) 404的页面是'谁定义的'?
[1]、nginx'自身'的默认格式还是'nginx 自定义[nginx配置有没有对404报错的处理]'
[2]、如果'$upstream_status'为404,则是'后端服务'的
3种语言对nginx配置文件进行格式化 nginx配置文件格式化 变量漫谈
nginx的access日志打印16进制原因
⑧ 扩展:了解即可
location /test {
echo_before_body "before...";
proxy_pass http://127.0.0.1:8080/foo;
echo_after_body "after...";
}
location /foo {
echo "contents to be proxied";
}
请求: /test'