我有 iOS 和 Android 移动应用程序,可以将数据提交到 PHP Web 应用程序。我正在将Web应用程序从运行CentOS 7、Apache 2.4.6和PHP 7.3.33 (mod_php)的服务器移动到运行Alma Linux 9、Apache 2.4.57和PHP 8.0.30 (PHP-FPM)的服务器。使用新服务器,仅当从 Android 提交时,PHP 无法识别大于 7800 字节左右的 post 数据;
$_POST
数组为空,并且 php://input
为空。较小的提交即可,从 iOS 提交的任何长度都可以。
在 Android 端,我使用
HttpURLConnection
和 outputStream
。我认为数据已正确编码和提交,因为它可以很好地提交到旧服务器,而少量数据可以很好地提交到新服务器。 Android 应用程序没有改变,只是网络服务器改变了。
在 Web 服务器端,我将
upload_max_filesize
和 post_max_size
设置得比 /etc/php.ini 中所需的大得多。我知道这些是有效的,因为如果我将它们设置得太低,我会在 Apache 错误日志中收到 PHP 错误(“POST 内容长度超出限制...”)。有趣的是,当从 Android 提交太多数据时,不会触发此操作,因此显然在 PHP 进行检查之前,发布数据已丢失或丢弃。我尝试使用 tcpdump,看起来所有数据都已到达服务器。
同样,我检查了 Apache 中的
LimitRequestBody
指令。我没有设置它,所以它应该默认为 1 GB,如果出现问题,应该记录错误。
我测试了从Android发送各种大小的数据; 7800 字节或更少成功,7900 字节或更多失败。 Apache 访问日志显示状态为 200 的单个 POST 请求,因此它不会被重定向,并且 Android 应用程序会从 Web 服务器接收该状态代码和响应文本,无论成功还是失败。失败时,Apache 错误日志中不会添加任何内容。我看到的唯一区别是我的脚本没有可用的发布数据。
我已经使用运行 Android 7、Android 12 和 Android 14 的设备进行了测试。
我希望这是一个服务器问题,因为那部分已更改,但我不知道任何取决于客户端操作系统的服务器设置。
也许 Android 应用程序正在以旧服务器允许但新服务器不允许的某种非标准方式对数据进行编码。但是当我将发布数据更改为非常简单的“test=aaaaaa”和 7900 个 a 时,它仍然失败。 7800 a 就可以正常工作了。
我通常通过 SSL 提交,但我尝试在端口 80 上以纯文本形式提交,但这没有什么区别。
也许这是处理发布数据所需的时间而不是数据长度的问题,但似乎没有发生任何超时,因为应用程序收到了服务器的响应。而且看起来确实一致,仅取决于数据的长度。
我之前一直将 PHP 作为 Apache 模块运行,所以也许我需要对 PHP 设置进行一些更改。我没有更改 /etc/php-fpm.d/www.conf 中的任何内容。另外,我所做的大部分网络搜索都会带来有关 Nginx 的帖子。我在我的服务器上看到一些 Nginx 配置文件,但我不知道它是什么,也不知道如何使用它。我知道我正在使用 Apache,因为这是我设置虚拟主机、日志记录等的地方。
有人对我有更多想法吗?
更新:这是提交数据的Android代码。我使用的是 Android SDK 版本 33。提交到旧的 Web 服务器时效果很好。
val data = "test=" + "a".repeat(7900)
var request: HttpURLConnection? = null
int responseCode = 0;
try {
request = url.openConnection() as HttpURLConnection
responseCode = request.getResponseCode();
} catch (e: IOException) {
e.printStackTrace()
this.error = "bad_request"
return false
}
request.connectTimeout = (this.timeoutLength * 1000).roundToInt()
if ((method == "POST")&&(data != null)) {
try {
request.doOutput = true
request.setChunkedStreamingMode(data.length)
val outputStream = request.outputStream
val outputStreamWriter = OutputStreamWriter(outputStream)
val outputWriter = BufferedWriter(outputStreamWriter)
var next = 0
var chunkLength = 512
val totalLength = data.length
this.publishProgress(0, totalLength)
while (next < totalLength) {
if (next + chunkLength > totalLength) {
chunkLength = totalLength - next
}
outputWriter.write(data, next, chunkLength)
this.publishProgress(next, 0)
next += chunkLength
}
outputWriter.close()
outputStreamWriter.close()
} catch (e: IOException) {
e.printStackTrace()
this.error = "bad_post_data"
return false
}
}
try {
this.publishProgress(0, request.contentLength)
val inputStream = request.inputStream
val chunk = ByteArray(4096)
var chunkSize = -1
while (inputStream.read(chunk).also { chunkSize = it } != -1) {
try {
this.response.write(chunk, 0, chunkSize)
publishProgress(this.response.size(), 0)
} catch (e: OutOfMemoryError) {
this.error = "out_of_memory"
inputStream.close()
return false
}
}
inputStream.close()
if (this.response.size() == 0) {
this.error = "bad_response"
return false
}
} catch (e: Exception) {
e.printStackTrace()
if (e.toString().contains("UnknownHostException")) {
this.error = "offline"
} else if (e.toString().contains("SocketTimeoutException")) {
this.error = "timeout"
} else {
this.error = "bad_response"
}
return false
} finally {
request.disconnect()
}
在 PHP 脚本的顶部,该脚本提交到:
$rawdata = file_get_contents("php://input");
error_log($rawdata); // [empty]
error_log(count($_POST)); // 0
我专注于服务器端,但根据 Robert 的建议专注于 Android 端,我发现了问题,用这一行:
request.setChunkedStreamingMode(data.length)
Android 文档说“并非所有 HTTP 服务器都支持此模式”,因此当更改 Web 服务器时问题就会开始,这是有道理的。只有当数据超过一定大小时,“分块”功能才会失败,这是有道理的。我认为它没有做任何事情,因为参数应该是块大小,并且我将其设置为完整大小。
无论如何,我用此替换了该行,现在一切都很好:
request.setRequestProperty("Content-Length", data.length.toString())