Nginx request line 换行导致的 400 异常分析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx request line 换行导致的 400 异常分析相关的知识,希望对你有一定的参考价值。
参考技术A 最近在做一个需求时,nginx 出现了 400 异常。具体细节如下:有两台独立的 NGINX,每一台 NGINX 都能正常的处理请求。但是当我用 lua 脚本,将一台 NGINX 接收到的请求筛选后,转发给另外一台 NGINX 处理时,收到如下 400 响应:而另外一台 NGINX 的 access 日志中,只显示请求响应 400,error 日志中无其他报错。当我用 postman 或者 curl 命令直接请求这台 NGINX 时,响应一切正常。 curl 命令内容如下:
在将 NGINX 的 error 日志调成 debug 级别后,重新复现问题时,发现 NGINX 在响应400 时,会有一行 info 级别的报错:
根据报错信息显示 “NGINX 在读取请求行时,发现客户端发起的是一个无效请求”,而又结合直接请求这台 NGINX 时响应正常的现象分析,第一台 NGINX 在转发请求时,发起的请求可能有问题。在第二台 NGINX 上使用 tcpdump 命令抓包分析正常请求和异常请求的内容时,发现被转发的异常请求的 request line 中有换行,除此外无其他任何区别:
既然只发现这一处差异,那是不是 request line 中有换行导致 NGINX 在处理请求时出现 400 异常呢?
首先看一下 RFC 2616 定义的标准 HTTP 请求的格式:
第一行就是 Request-Line,Request-Line 显示了请求的方式、请求资源的位置及使用的 HTTP 协议版本。根据 RFC 2616 文档定义,Request-Line 是由三部分组成:请求方法+Request-URI+HTTP-Version,而且这三部分之间,只能用空格(Space)分开,Request-Line 的最后部分,必须是一个换行符(CRLF)。根据这个定义,异常请求的 request line 中有个换行一定是不合法的。
NGINX 接收到请求后,会对请求进行解析。在 NGINX 实现中,会根据 HTTP 协议规范实现一个有限状态机,NGINX 在解析请求时,会经过这个状态机。在读取 request line 时,NGINX 会记录请求方法、URI 信息及 HTTP 协议信息,以及其他有用的信息放在缓存中,以便后面流程使用。如果请求行处理正常,接着会按照状态机流程处理请求头。但是在处理请求行时,如果客户端提前关闭连接、或者请求行不符合协议规范,读取数据时发生了错误,NGINX 就会立刻终止请求解析,同时给客户端返回一个400错误。当然,如果是客户端提前关闭了连接,400 错误就无法正常到达客户端。因此,被 NGINX 转发的请求,由于 request line 多了一个换行,所以 NGINX 返回了 400 BAD REQUEST 异常。
那么,这个换行是怎么来的呢?
在我们实现的逻辑中,我们会把需要被 NGINX 转发的接口放到一个配置文件中,当需要增加或减少接口时,直接修改配置文件即可:
配置文件中的每一行都有两部分组成:原 URI 和目的 URI,中间用“#”号隔开,且原 URI 和目的 URI 不一样。我们的需求就是如果 NGINX 接收的请求在这个配置文件中,则将请求的URI 替换成目的 URI,然后转发给另外一台 NGINX 处理。
我们用 lua 脚本一行行读取这个配置文件,然后解析成原 URI 和目的 URI,存放在共享内存中。刚开始的时候,功能一直正常,请求转发和请求处理都可以正常处理。几天之后,NGINX 在处理转发请求时,开始报 400 异常。同时,我们从日志中也观察到,客户端 NGINX 在进行 URI 替换时, 日志中输出的替换后的 URI会多一个 ^M 符号。为什么之前日志中没有这个符号,现在突然多个这个符号呢?这个符号又是什么意思呢?
其实 Linux/Unix 系统、Mac 系统和 DOS/Windows 系统在处理文本文件时,对换行处理是不一样的。Windows 中的换行符是 \r\n ,Linux/Unix 下的换行符是 \n , Mac 系统是 \r 。其中 \r 表示的是回车, \n 是换行。因为对换行处理的不一致,导致 Windows 系统的文本文件在 Linux/Unix
打开时,每行的结尾会多一个 ^M 符号;而 Linux/Unix 系统的文本文件在 Windows 中打开时,所有行都会变成一行。
而导致我们这次问题的原因,就是因为最开始大家在编辑这个配置文件的时候,使用的都是 Mac 电脑,即 Unix 系统。后来,其他同事使用 Windows 电脑编辑这个配置文件,并上传到服务器。lua 脚本在读取配置文件的时候,每一行后面都多个了一个 \r ,即换行符,因此解析得到的目的 URI,也多了一个换行符。所以,客户端 NGINX 在转发请求的时候,request line 中多了一个换行符,导致目的 NGINX 在解析的时候报错。
所以,lua 在读取配置文件时,增加对换行的兼容,问题得到解决。
当在 Linux/Unix 系统打开 Windows 的文本文件出现 ^M 符号时,可以很简单通过下面方式解决:
或者:
Nginx giving 400 error
nginx的请求处理阶段 (90%)
nginx中http request处理的流程
How to Remove ^M in Linux & Unix
lua文件读取注意事项
VIM 小技巧 - replace '^M'
Gunicorn启动时 nginx 400 request line is too large (4360 4094)
设置gunicorn 参数
--limit-request-line 8188 (默认是4094)
参考
limit_request_line --limit-request-line INT 4094 The maximum size of HTTP request line in bytes. This parameter is used to limit the allowed size of a client’s HTTP request-line. Since the request-line consists of the HTTP method, URI, and protocol version, this directive places a restriction on the length of a request-URI allowed for a request on the server. A server needs this value to be large enough to hold any of its resource names, including any information that might be passed in the query part of a GET request. Value is a number from 0 (unlimited) to 8190. This parameter can be used to prevent any DDOS attack.
以上是关于Nginx request line 换行导致的 400 异常分析的主要内容,如果未能解决你的问题,请参考以下文章
fastdfs整合nginx,上传的图片访问报400 Bad Request,求大神指教