nginx的mirror模块请求返回400状态

问题背景

突然被抓来协助大数据的上报网关(OpenResty)

client -> OpenResty -> Kafka -> ClickHouse

这里的 OpenResty 负责数据的兼容, 过滤, 路由

因为需要经常的修改 OpenResty 的逻辑配合数据分析这边的需求
导致改动容易出现问题, 需要搞个测试环境.
通过测试环境的验证后, 在更新到正式环境

实施方案是使用 nginx 的 mirror 模块将请求镜像到测试站点.
同样的代码和配置, 通过环境变量来区分生产和测试环境

大概是这样的.

1
2
                                (不返回client)
client <-------------> nginx <-------------> mirror

那么问题来了
在实施中, 请求主站点时, 镜像站点也能收到请求
但一直显示status_code 400, 从表现上来看像是 POST 的数据丢失了.
在 nginx 的 log_format 把$request_body参数加上, 发现确实 POST 数据没有.

1
2
3
172.19.0.1 - [08/May/2020:06:35:09] "POST /user_login HTTP/1.0" 400 419 "-"
172.19.0.1 - [08/May/2020:07:01:09] "POST /user_login HTTP/1.0" 400 419 "-"
172.19.0.1 - [08/May/2020:07:02:43] "POST /user_login HTTP/1.0" 400 419 "-"

思路验证

nginx 的配置问题?

难道是的配置有问题?

1
2
3
4
5
6
7
8
9
10
11
...

mirror /mirror;

location /mirror {
internal;
proxy_pass http://127.0.0.1:8080$request_uri;
proxy_set_header X-Original-URI $request_uri;
}

...

主站点上日志的 POST 数据是没问题的, 所以很容易怀疑是 mirror 配置的问题

查看 nginx 的官网发现mirror_request_body是用于控制镜像请求时是否附带 body 数据

1
2
3
4
Syntax:	mirror_request_body on | off;
Default:
mirror_request_body on;
Context: http, server, location

但这个参数默认是打开的, 所以不是这个问题.

OpenResty 的版本问题?

因为使用的 docker 来搭建的开发环境
并且长时间没有更新, 所以怀疑是不是版本兼容问题.
使用docker pull OpenResty/OpenResty更新后发现…
也不是这个问题

业务配置的问题?

现在无法确定是主站点上的配置问题, 还是镜像站点上配置的问题.
所以使用 nginx 镜像配置了个干净的环境来进行测试.

1
2
3
4
5
6
7
8
9
...
server {
listen 8080 ssl http2

location / {
return 200;
}
}
...

结果还是一样, 那么镜像站点的配置问题排除.

同样的使用 nginx 配置一个干净的主站点环境, 测试…
结果还是一样…

抓包确认

当时就纳闷了, 既然请求通过 mirror 模块发起请求都会出现问题.
那就确定了一下, 到底发出来的请求数据有没有问题.

先将 mirror 的地址改成本机(本地开发环境), 然后开启Wireshark抓包

0fa3315fc3faac41c1ed40363b1ea14d.png

这么看来 mirror 模块的请求的 POST 数据是没问题的.

问题定位

现在我们知道了, 请求的数据是没问题的
但是通过 mirror 模块请求的 status_code 有问题
那就直接请求测试站点看看…

结果真相来了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>

<head>
<title>400 The plain HTTP request was sent to HTTPS port</title>
</head>

<body>
<center>
<h1>400 Bad Request</h1>
</center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr>
<center>OpenResty/1.15.8.3</center>
</body>

</html>

镜像站点的配置和主站点是一致的, 都是基于 ssl 的.
而主站点里的 mirror 模块使用的是proxy_pass http://127.0.0.1:8080$request_uri;

至此问题定位完毕, 修复起来也就很简单了.

复盘总结

细节问题

配置文件是直接复制的, 所以没有注意到细节.

对 http status_code 理解不深刻

之前工作中一直碰到的 400 都是缺少参数导致服务器处理不了

其实真正的意思是说 client 的请求错误, 不仅仅是参数, 经验主义啊.

1
2
3
4
5
6
6.5.1.  400 Bad Request

The 400 (Bad Request) status code indicates that the server cannot or
will not process the request due to something that is perceived to be
a client error (e.g., malformed request syntax, invalid request
message framing, or deceptive request routing).

从设计上来看

为什么设计上不将 http 请求 https 的错误作为一个独立的 status_code
猜测可能是因为将错误信息输出在了 respone text 里
所以就没有必要将单独作为一个 status_code, 毕竟这种情况也不常见.