今天在调试上传文件接口的时候发现了一个奇怪的错误,当上传的文件超过一定大小时$_FILES数组会变成空的。仔细研究了一下,发现这还是个挺冷门的错误。原因在于使用Nginx+PHP时,系统中一共有三个上传文件的大小限制:
- php.ini 里的 upload_max_filesize
- php.ini 里的 post_max_size
- nginx.conf 里的 client_max_body_size
假设只上传一个文件,此时会出现三种不同的情况:
a) 文件大于1,小于2
b) 文件大于2,小于3
c) 文件大于3
其中a, c两种情况都很简单(假设该input的name="file"
):
a情况中。超过php.ini中upload_max_filesize
的值,可以在PHP中得到$_FILES['file']['error']==1
。
c情况中,超过nginx.conf中的client_max_body_size
,Nginx会返回413 Request Entity Too Large
错误。
在b情况中,你能看到的就是$_FILES数组为空(即isset($_FILES['file']==FALSE
)。
此时就出现了两种可能,一种是客户端的表单里没有file这个字段,另一种是有超限的附件使表单的大小超过了post_max_size
。
如果客户端是网页,那还比较好办,在HTML表单里,文件类型的input即使没有选择文件,$_FILES[‘file’]也是在的,此时服务器端拿到的应该是$_FILES['file']['error']==4
错误。因此当出现$_FILES为空时,就可以告诉用户上传文件过大了(前提是你确定表单没有问题,尤其是没有忘记加enctype=”multipart/form-data”属性)。
如果客户端是App的话,作为后端程序员难免有点拿不准客户端究竟传没传文件。这时还有一个办法,就是取Header中的Content-Length。也就PHP里的$_SERVER['CONTENT_LENGTH']
,如果是没有传文件的话,纯文本的表单体积很小,通常在几十几百,而如果传了一个超过8M的文件,Content-Length会大于8388608。因此可以用ini_get('upload_max_filesize')
取出upload_max_filesize
的值,换算成字节数与Content-Length比较一下,来确定是哪种情况。
最后要说的是,尽管上面只讨论了上传单文件,但是这种错误更多见于一张表单上传多个文件,上传多个文件时,虽然每个文件的大小都小于upload_max_filesize
,但是若多个文件加起来大于post_max_size
,就会触发这个错误。debug时只看单个文件的大小,没有注意所有文件的总大小,bug就会时隐时现,非常让人头疼。不过话说回来,以现在公网的网速来讲,提交这么大的POST请求体验太差了,还是采取Ajax的方式逐个上传好一点。