您现在的位置: 365建站网 > 365学习 > 解决Nginx 400 Bad Request错误问题的方法

解决Nginx 400 Bad Request错误问题的方法

文章来源:365jz.com     点击数:67    更新时间:2018-09-12 23:41   参与评论

400 Bad Request是一种HTTP错误状态码。HTTP/1.1对400 Bad Request的定义主要是:1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。2、请求参数有误。 
在这段时间笔者遇到了好几次生产问题Nginx报400异常,且原因细究下来各不相同,有些甚至在网上没有搜到类似案例。遂产生了兴趣,做了本次梳理,希望会对大家有一定帮助!


  • 1. 一般导致400异常的场景

    • 1.1 请求头过大

    • 1.1 空请求

  • 2. 特殊问题场景一:URLConnection发起HTTPS请求经过代理400异常

  • 3. 特殊问题场景二:网络传输丢包导致的400异常

  • 4. 参考文献


1. 一般导致400异常的场景

一般使用Nginx在以下场景会报400 Bad Request:

1.1 请求头过大

nginx 400 Bad request是request header过大所引起,request过大,通常是由于cookie中写入了较大的值所引起。在nginx.conf中,调整client_header_buffer_size和large_client_header_buffer参数大小可以解决问题(ps,网上很多博客只片面强调调大两个参数的值,并未研究参数区别和用法,真正大规模应用明显是不合适的)。 
那么这两个参数是如何定义的呢?

Syntax: client_header_buffer_size size;Default: client_header_buffer_size 1k;Context: http, server123

设置用于读取客户端请求头的缓冲区大小。对于多数请求缓冲1K字节是足够的。然而,如果一个请求包括Cookie,或来自一个WAP客户端,它可能远不止1K。如果request line或者request header超过1K,则由large_client_header_buffers指令配置分配。

Syntax: large_client_header_buffers number size;Default: large_client_header_buffers 4 8k;Context: http, server123

设置用于读取大型客户端请求头的缓冲区的最大数量和大小。request line不能超过一个缓冲区的大小,否则将返回414(请求URI太大)错误给客户端。request header不能超过一个缓冲区的大小,否则将返回400(错误请求)错误给客户端。缓冲区只能按需分配。默认情况下,缓冲区的大小为8K字节。如果一个连接请求处理结束后转变为保持状态,这些缓冲区被释放。 
所以nginx处理header的方法是:

  1. 先处理请求的request_line,之后才是request_header。

  2. 这两者的buffer分配策略相同。

  3. 先根据client_header_buffer_size配置的值分配一个buffer,如果分配的buffer无法容纳 request_line/request_header,那么就会再次根据large_client_header_buffers配置的参数分配large_buffer,如果large_buffer还是无法容纳,那么就会返回414(处理request_line)/400(处理request_header)错误。

综上所述,网上的很多调整是不合适的。要按具体业务需求调整参数大小。

  1. 如果你的请求中的header都很大,那么应该使用client_header_buffer_size,这样能减少一次内存分配。

  2. 如果你的请求中只有少量请求header很大,那么应该使用large_client_header_buffers,因为这样就仅需在处理大header时才会分配更多的空间,从而减少无谓的内存空间浪费。

1.1 空请求

0.7.12以前版本的nginx收到一个空请求,nginx不会去与任何虚拟主机匹配,直接返回400错误,之后的新版本nginx可以用server_name _;匹配空请求头来处理。

server {
  listen 80 default_server;
  server_name _;  return 404;
  access_log off;
}123456

当然以上情况在网上都很普遍,下面是本文重点想说的两种特殊场景。

2. 特殊问题场景一:URLConnection发起HTTPS请求经过代理400异常

我们有某个系统A,在和系统B之间通信时使用的是HTTPS协议,在系统B的nginx代理层有大量400错误抛出。经过我们排查,不存在上述请求头部过大或者空请求的情况。那么400问题因何而起呢?我们根据问题症状浏览了大量国外技术网站,终于定位到这是JDK的一个bug:

JDK-6687282 : URLConnection for HTTPS connection through Proxy w/ Digest Authentication gives 400 Bad Request

A系统的JDK版本比较老java version "1.6.0_05",同时使用的是JDK原生的 URLConnection,在通信的过程中我们是这样做的:

1.- Create an HTTPS URL. 
2.- Obtain an URLConnection with url.openConnection() providing a Proxy 
3.- Setup the SSLSocketFactory. 
4.- Setup the default Authenticator. 
5.- Read result from connection

问题出现在身份验证上,身份验证在生成响应hash时使用request-URI作为其算法的一部分。request-URI通常取的是uri的绝对路径(abs_path )。但也不全是,比如,在建立隧道请求(tunneling)时,需要用主机+端口号作为request-URI,例如。

"CONNECT verisign.com:443 HTTP/1.1"1

sun.net.www.protocol.http.digestauthentication 只考虑了通过绝对路径作为request-URI的场景(abs_path )。这显然是有问题的,在建立隧道时,需要使用主机+端口号方式。所以导致在身份验证时产生400 Bad Request:

1.- Send CONNECT HTTP Request to the Proxy 
2.- Receive a 407 Proxy Authentication Required 
3.- Send new CONNECT with authentication credentials. 
4.- Receive 400 Bad Request

解决问题的方案有多种: 
1.升级JDK版本,如下图 
11.png 
2.使用Apache HttpClient代替URLConnection

3. 特殊问题场景二:网络传输丢包导致的400异常

这个场景困扰我们时间比较长,某个和交易相关的核心系统每天有0.01~0.03%的400错误,全部都是用户端过来的post请求。 
22.png

如上图,nginx有零星的400错误,然而tcp dump抓包发现并没有对应的400包。潜意识里我们认为所有的400 Bad Request不可能不被tcp dump所记录,所以我们纠结了很久。最终发现,是因为客户端Post请求Packet在网络传输过程中部分丢失导致到服务端无法正常响应,客户端30s超时断开连接,这时候nginx记录了400。这种情况下,nginx实际未反馈400的response,只是在连接断开时记录了400的日志。 
如下图所示, 
33.png44.png 
将上述报文转码后可以更直观发现,POST请求报文不全: 
55.png

在access.log中有大量400错误,并以每天几百M的速度增加,占用大量空间.
tail -f /opt/nginx/logs/access.log

    116.236.228.180 - - [15/Dec/2010:11:00:15 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:15 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:15 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:15 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:15 +0800] "-" 400 0 "-" "-"
    119.97.196.7 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    119.97.196.7 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    219.243.95.197 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"
    116.236.228.180 - - [15/Dec/2010:11:00:16 +0800] "-" 400 0 "-" "-"

网上大把的文章说是HTTP头/Cookie过大引起的,可以修改nginx.conf中两参数来修正.

    client_header_buffer_size 16k;
          large_client_header_buffers 4 32k;

修改后

    client_header_buffer_size 64k;
         large_client_header_buffers 4 64k;

没有效果,就算我把nginx0.7.62升到最新的0.8.54也没能解决.
在官方论坛中nginx作者提到空主机头不会返回自定义的状态码,是返回400错误.
http://forum.nginx.org/read.PHP?2,9695,11560

最后修正如下
改为原先的值

    client_header_buffer_size 16k;
         large_client_header_buffers 4 32k;

关闭默认主机的日志记录就可以解决问题

    server {
    listen *:80 default;
    server_name _;
    return 444;
    access_log   off;
         }

如对本文有疑问,请提交到交流论坛,广大热心网友会为你解答!! 点击进入论坛


发表评论 (67人查看0条评论)
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
用户名: 验证码: 点击我更换图片
最新评论
------分隔线----------------------------