我的 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'];
?>
我花了几天时间研究这个问题,发现许多其他人发布了几年前的类似问题,通常没有得到解决。 我尝试了以下方法,都表现出相同的失败。
- 我尝试了Apache HttpClient(使用DefaultHttpClient)和HttpURLConnection。
- 我调整了请求的节奏,每个请求大约一秒钟,只有很小的改进(例如,它在第 65 次迭代而不是第 61 次迭代中失败)
- 出现错误时,如果我等待稍微超过 60 秒(巧合?),我可以在另一个错误之前再通过几个 POST。
- 我使用了 HttpUrlConnection 的所有参数。
- 我玩过所有各种HTTP标头设置。
- 我注释掉了上面与 BufferedOutputStream 相关的代码,即。我做了一个空的开机而复机自检。
- 我修改了 PHP 文件以记录传入的请求。它看到了 60 个成功的请求,但从未收到第 61 个请求(失败的请求)。
- 把最好的留到最后。我在台式机(有线互联网)和笔记本电脑(连接到同一路由器的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,因为我很确定这是他们的限制。
让我知道它是如何实现的,祝你好运