2

你好亲爱的Stackoverflowers!

我的项目有问题,关于客户端-> 服务器通信。我想将数据从 C++ 程序传输到服务器。为此,我选择了 HTTP 作为通信协议,因为它很容易通过类似的 PHP 脚本在 web 服务器上进行处理。C++ 程序通过 HTTP POST 向服务器发送数据(或命令),服务器通过 PHP 脚本生成纯文本响应(Mime-Type text/plain)。生成的响应相对较短,包含简短的成功或失败消息,也许还有一点“有效负载”(全是纯文本)。

在我的开发机器(本地 Apache 服务器lampp)上,一切似乎都运行良好。但是,今天我尝试在实时网络服务器(运行 Apache + PHP + MySQL 的虚拟网络服务器服务)上移动服务器 PHP 脚本以进行测试,并且,有些东西停止了工作......

问题

一个服务器端 PHP 脚本,用于将来自 C++ 应用程序的数据存储在 MySQL 数据库中。我要存储在 MySQL 数据库中的数据是一个原始的 json 字符串(它是实验数据,稍后处理)。json 字符串由 C++ 应用程序形成。它大约有 70 kB 大(所以它很大!)并通过 POST 多部分请求发送到网络服务器。多部分请求是通过 libcurl 形成的:

    foreach (const HttpKeyValuePair& kv, localServerCommand.httpKeyValuePairs) {
      if (curl_formadd(&httpPostFirst, &httpPostLast, CURLFORM_PTRNAME, kv.key.c_str(), 
                                                      CURLFORM_NAMELENGTH, (long) kv.key.size(),
                                                      CURLFORM_PTRCONTENTS, kv.value.c_str(), 
                                                      CURLFORM_CONTENTSLENGTH, (long) kv.value.size(),
                                                      CURLFORM_CONTENTTYPE, "text/plain",
                                                      CURLFORM_END) != 0) {
        cerr << "Error assembling form data" << endl;
      }
    }

    [...]

    CURL* curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &receiveData);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &receiveBuffer);
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());

    if (useSimplePost) { // Only true if postString.size() < 200 byte 
      curl_easy_setopt(curl, CURLOPT_POST, 1);
      curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postString.c_str());
    } else {
      // Mutlipart
      curl_easy_setopt(curl, CURLOPT_HTTPPOST, httpPostFirst);
    }

    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlErrorBuffer); 
    curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);
    CURLcode errorCode = curl_easy_perform(curl);
    curl_easy_cleanup(curl);

(只是为了解决任何疑问:我检查了wireshark......他正在使用多部分帖子。使用简单帖子的分支仅存在,因为我认为可以通过省略多部分标题来节省网络连接更频繁,小要求。)

但是,现在有趣的部分是:我的服务器端脚本从不接收 json 字符串。应该携带 json 字符串的字段,称为“数据”,不是 PHP 中 $_POST 结构的一部分!更奇怪的是,所有其他字段都在那里。出于测试目的,我将服务器上的 PHP $_GET、$_POST 和 $_FILES 变量转储到一个日志文件中,对于相关请求,它们看起来像这样:

  ------
   GET  
  ------
  array (
  )
  ------
   POST 
  ------
  array (
    'id' => '130',
    'nonceid' => '4656',
    'authentication' => 'fjOynwtBDE/g/llkQlSgrGUx0ttfJMarExF6E3jg0/QeRgzvp+Chr0XqEIzoK6Rm4/19Q6KIA/Lx32Ti1Y+cQhVdF70AS8GaI2i+0FO3Uj7WfFl4FotUzpbyLpD5/AUe0KOiGA==',
  )
  ------
   FILES 
  ------
  array (
  )

使用我的本地服务器时,“数据”字段是 $_POST 的一部分。'data' 字段是发送到服务器的第一个字段,这意味着它首先通过上面的 curl_formadd 循环写入,TCP 流中的第一个字段通过wireshark 检查,也是 $_POST 中的第一个字段我的本地服务器上的数组。

服务器测试

发现这个问题后,我测试了服务器并尝试使用 Firefox 通过 WordPress 上传文件,以测试服务器是否拒绝任何大的 $_POST 字段。然而,上传似乎工作(测试上传一个大的PNG,比我要上传的json数据大)。

下一个测试是使“数据”字段更小。我测试上传〜900字节的重复字符串

"shorter  amount of data with special characters +/=?=?$§+#+\'*>< "

这也有效(使用多部分帖子)。

问题

我希望长的“数据”字段可以作为 $_POST 变量的一部分,就像它在我的开发机器上一样。我不知道是什么导致了这个问题。它可以是我正在使用的多部分 mime 类型(“text/plain”)吗?Apache/PHP 中是否有任何限制 POSTFIELD 大小的配置我只知道整体 POST 大小限制)?

我怀疑这是一个奇特的服务器配置问题。但是,我对冗长且(如果您不花时间在上面)复杂的 httpd.conf 知之甚少。

有谁知道是什么导致了这个问题以及如何在我的本地服务器上重现它?甚至如何解决这个问题?

提前致谢!

4

1 回答 1

0

好吧,我找到了一种解决方法,但不是导致过滤大型 POST 字段的原因。我首先对缺少的 POST 字段进行了一些额外的测试。

正如 Robbie 建议的那样,我尝试欺骗 Firefox 标头(Agent = "Mozilla/5.0", ...),但没有成功。

下一步是划分“允许的数据包大小”,这似乎是 65536(我测试的最接近的间隔是 65000-66000 字节);之后,任何超过此限制的字段大小都消失了。

发现这一点后,我很沮丧,重新打开了 libcurl 的 API 文档,并更改了将数据作为多部分帖子的一部分发送到服务器的方式。大型帖子字段现在打包为文件,而不是字段,这与浏览器进行文件上传的方式相同。

      if (kv.value.size() > 2048) {
        valueString = "";
        if (curl_formadd(&httpPostFirst, &httpPostLast, CURLFORM_PTRNAME, kv.key.c_str(), 
                                                        CURLFORM_NAMELENGTH, (long) kv.key.size(),
                                                        CURLFORM_BUFFERPTR, kv.value.c_str(), 
                                                        CURLFORM_BUFFERLENGTH, (long) kv.value.size(),
                                                        CURLFORM_FILENAME, kv.key.c_str(), 
                                                        CURLFORM_CONTENTTYPE, "text/plain",
                                                        CURLFORM_END) != 0) {
          cerr << "Error assembling form data" << endl;
        }

然而,在 PHP 方面,这意味着我的数据确实作为 $_POST 字段进入脚本,但作为 $_FILES 结构的一部分。但是,我只允许我的程序上传的消息认证机制要求$_POST结构中的所有数据,并且数据字段的顺序没有改变。因此,除了将我的数据作为文件发送之外,我还在我的 POST(通过 libcurl)中添加了一个与文件同名的空字段(“data=”)。

然后在 PHP 中,我将代码添加到我的“配置脚本”中,它总是首先加载,它从发送的数据文件和接收的 $_POST 数据中重新组装原始的 $_POST 消息:

// Reassamble $_POST structure in case parts of it have been sent as file
foreach ($_POST as $pkey => $pvalue) {
  if (strlen($pvalue) == 0) {
    // Perhaps it was sent as file?
    if (isset($_FILES[$pkey])) {
      // Yes, as file! - lets read it in and put it back into $_POST
      $fileData = $_FILES[$pkey];

      if ($fileData["error"] != 0) {
        handleError("File transfer of file '" . $pkey . "' failed");
      }

      $postDataFileHandle = fopen($fileData["tmp_name"], "rb");
      if (!$postDataFileHandle) {
        handleError("Cannot read required \$_POST field that was sent as file");
      }
      $data = fread($postDataFileHandle, $fileData["size"]);
      if ($data === FALSE) {
        handleError("Error on reading required \$_POST field that was sent as file");
      }
      fclose($postDataFileHandle);

      $_POST[$pkey] = $data;
    }
  }
}

这可行,但我仍然不知道Apache 为何/如何过滤POST字段。也许它不是由 Apache 完成的,但我的托管服务提供商会进行某种深度数据包过滤。

仍然很困惑,但它现在可以工作了,感谢所有试图提供帮助的人!

于 2013-01-09T12:40:06.820 回答