1

对位于我 Godaddy 服务器上的 PHP 文件的一系列 60 个 HTTP POST 在第 61 次尝试时反复导致 SocketException(使用 Java JRE6/JRE7 时)、NoHttpResponseException(在 Android 中)或偶尔出现 SocketTimeoutException(Java 和 Android),并且服务器变得无响应大约 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 相关的代码,即。我做了一个空的POST。
  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 将图像文件上传到服务器,虽然我可以合并这些以减少请求的数量,这只是一个杂项并产生新的问题(文件上传大小的限制等)

4

2 回答 2

1

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

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

于 2013-09-29T11:26:59.720 回答
0

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

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

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

让我知道结果如何,祝你好运

于 2013-09-19T14:13:54.460 回答