从 Java 到 PHP 文件执行许多(顺序)HTTP POST 命令时出现服务器异常


Server exceptions while doing many (sequential) HTTP POST commands from Java to a PHP file

位于

我的 Godaddy 服务器上的 PHP 文件的六十个 HTTP POST 系列反复导致 SocketException(使用 Java JRE6/JRE7 时)、NoHttpResponseException(在 Android 中)或偶尔出现 SocketTimeoutException(Java 和 Android)在第 61 次尝试中,服务器变得无响应大约 60 秒,此时我可以在相同的异常发生之前偷偷浏览更多请求。

java.net.SocketException: Unexpected end of file from server at sun.net.www.http.HttpClient.parseHTTPHeader(Unknown Source) at sun.net.www.http.HttpClient.parseHTTP(Unknown Source) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source) at Tester.postRequest(Tester.java:47) at Tester.main(Tester.java:20)

在花了一周时间尝试将原因与我的 Android 项目隔离开来之后,我最终将其简化为如下所示的独立独立 Java 测试用例。 在这种情况下,循环正确执行 60 次(我解析响应并看到 PHP 文件看到 POST),然后在第 61 次尝试时失败。 这是非常可重复的,尽管我可以通过在 PHP 文件中添加代码(例如记录到文件等)来向下调整数字。 请注意,执行HTTP GET不会导致问题,只会导致POST。

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class Tester
{
    private static final String SERVER_PHP_URI="http://allucanapp.com/phptest.php";
    private static final String CHARSET="UTF-8";
    public static void main(String[] args)
    {
        try
        {
            for (int i=0; i < 200; i++)
                postRequest(i);
        } catch (IOException e) { e.printStackTrace(); }
    }
    private static void postRequest(int count) throws IOException
    {
        String query="command="+count;
        HttpURLConnection conn=(HttpURLConnection) (new URL(SERVER_PHP_URI).openConnection());
        try
        {
            conn.setReadTimeout(10000);
            conn.setConnectTimeout(15000);
            conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            conn.setFixedLengthStreamingMode(query.length());
            BufferedOutputStream output = null;
            try
            {
                output = new BufferedOutputStream(conn.getOutputStream());
                output.write(query.getBytes(CHARSET));
                output.flush();
            } finally { if (output != null) try { output.close(); } catch (IOException e) { e.printStackTrace(); } }
            InputStream response = conn.getInputStream();
            if (conn.getResponseCode() != HttpURLConnection.HTTP_OK)
                System.out.println("Error "+conn.getResponseCode()+": "+conn.getResponseMessage());
            else
            {
                BufferedReader reader = null;
                try
                {
                    reader = new BufferedReader(new InputStreamReader(response,CHARSET));
                    for (String line; (line = reader.readLine()) != null;)
                        System.out.println(count+":"+line+"'n");
                }
                finally { if (reader != null) try { reader.close(); } catch (IOException e) { e.printStackTrace(); } }
                response.close();
            }
        }
        finally { conn.disconnect(); }
    }
}

作为参考,这里是 PHP 文件。 服务器支持 PHP 版本 5.3。

<?php
echo "hi there " . $_POST['command'];
?>

我花了几天时间研究这个问题,发现许多其他人发布了几年前的类似问题,通常没有得到解决。 我尝试了以下方法,都表现出相同的失败。

  1. 我尝试了Apache HttpClient(使用DefaultHttpClient)和HttpURLConnection。
  2. 我调整了请求的节奏,每个请求大约一秒钟,只有很小的改进(例如,它在第 65 次迭代而不是第 61 次迭代中失败)
  3. 出现错误时,如果我等待稍微超过 60 秒(巧合?),我可以在另一个错误之前再通过几个 POST。
  4. 我使用了 HttpUrlConnection 的所有参数。
  5. 我玩过所有各种HTTP标头设置。
  6. 我注释掉了上面与 BufferedOutputStream 相关的代码,即。我做了一个空的开机而复机自检。
  7. 我修改了 PHP 文件以记录传入的请求。它看到了 60 个成功的请求,但从未收到第 61 个请求(失败的请求)。
  8. 把最好的留到最后。我在台式机(有线互联网)和笔记本电脑(连接到同一路由器的wifi)上运行了此代码。
      a) 如果我在一台机器上运行,
    • 它在 60 次迭代后失败,那么如果我在另一台机器上运行,它会立即失败。
    • b) 如果我同时运行两个,两个都运行 ~30 次迭代,当一个失败时,另一个立即失败。
    • c) 如果我将我的笔记本电脑切换为在 3G 上运行,这样它就不会共享路由器,而 (a) 仍然发生,(b) 不会,它们由服务器独立处理。

使用我的本地 (10.0.2.2) Zend 服务器时,此问题不会出现。 如果我不知道更好,我会认为 Godaddy 服务器正在节流,也许是某种反拒绝服务策略。 我会联系 Godaddy,但我找不到任何控制这种行为的 php.ini 设置,除了限制并发请求。 困扰我的是,这个问题已经潜伏在我的代码库中 2.5 个月了,只是因为我正在进行性能测试才表现出来。

为了抵御关于为什么我应该重复 POST 等的评论,请记住以上是一个测试用例,原始应用程序使用 POST 将图像文件上传到服务器上,虽然我可以合并这些以减少请求数量,但这只是一个笨拙并产生新问题(文件上传大小的限制, 等)

事实证明,我的测试用例被他们的服务器标记为 DDoS 事件,这会暂时将我客户的 IP 地址列入黑名单。他们声称一分钟内的6次尝试被视为威胁。 我想我很"幸运",我可以在一分钟内完成 60 个请求。他们的解决方案? 升级到他们的虚拟专用服务器,每月花费大约 6 倍,他们认为不存在这种限制,因为我负责保护服务器。 不完全是最有帮助的回应。我想他们的安全解决方案迎合的是点击网络表单的人,而不是使用PHP连接到MySQL数据库的Android应用程序。 更烦人的是缺乏文档,因为我浪费了一个星期来调试上述问题。

无论如何,相反,我将尝试将所有HTTP POST请求分组到一个非常小的数字中,例如,一个用于上传一百张图像的超长多部分请求。 然后弄清楚如何处理跟踪中间上传/下载进度,避免缓冲区溢出,固有PHP限制等,并希望我不要遇到其他一些Godaddy服务器限制(例如PHP脚本超时!

在我看来,这听起来好像他们在一定数量的请求/时间后限制您的请求,这可能是您提到的他们防止针对其服务器的潜在 DDoS 攻击的情况,或者他们可能没有为服务请求分配非常高的客户端线程数。

如果可用于处理您的请求的线程数量很少,并且它们通过您的努力不断连接到它们,那么它们将积压并最终超时,从而导致您缺乏响应。

精彩的帖子,有很多细节,很高兴看到你尝试过什么,我建议联系 GoDaddy,因为我很确定这是他们的限制。

让我知道它是如何实现的,祝你好运