一 return和rewrite重定向再探
① 前言
本为'不涉及'讨论如下的'绝对'重定向
1) return 301 http://www.wzj.com:6443/url?name=wzj
2) rewrite ... http://www.wzj.com:6443/url
2) rewrite ... http://www.wzj.com:6443/url permanent
② 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/'导致
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'非期望原因之一
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'的日志
++++++++++++++ "涉及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'不一样
测试说明: 由于'proxy_redirect'只剩下'Location: /wzj/kafka',经过'$scheme'等作用
案例3: '上游'Location传递错误的'$scheme',需要'proxy_redirect'对应修改 --> "省略"
遗留: 对于'请求[流]链路'复杂的,需要'多服务协作',或者'抓包定边界'
举例: 如果'上游服务器是nginx',其配置'absolute_redirect off';作为'proxy'的nginx处理?
error_page 497 https://$host:$server_port$request_uri;
场景: nginx在'SSL阶段'对自定义的'49x'的特殊处理
三 if和content handler的探究
题外话: 之所以要'看原版英文'解读
1) '一'是'原汁原味',避免层层传播产生'歧义'
2) '二'是'掌握'单词',学习计算机'术语'描述
① if工作原理
关注点:'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 阶段指令
③ set和proxy_pass杂谈
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配置文件格式化 变量漫谈
⑧ 扩展:了解即可
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'