2

こんにちは、スタックオーバーフラワーです!

クライアントからサーバーへの通信に関して、プロジェクトに問題があります。C++ プログラムからサーバーにデータを転送したいと考えています。このため、通信プロトコルとして HTTP を選択しました。同様の PHP スクリプトを介して Web サーバーで処理するのが簡単だからです。C++ プログラムは HTTP POST 経由でデータ (またはコマンド) をサーバーに送信し、サーバーは PHP スクリプト経由でプレーン テキスト応答 (Mime-Type text/plain) を生成します。生成された応答は比較的短く、短い成功または失敗のメッセージと、おそらく小さな「ペイロード」 (すべてプレーン テキスト) が含まれます。

私の開発マシン (ローカル Apache サーバーlampp ) では、すべてがうまく機能しているようです。しかし、今日、ライブWebサーバー(Apache + PHP + MySQLを実行する仮想Webサーバーサービス)でテスト目的でサーバーPHPスクリプトを移動しようとしましたが、何かが機能しなくなりました...

問題

1 つのサーバー側 PHP スクリプトを使用して、C++ アプリケーションからのデータを MySQL データベースに格納します。MySQL データベースに保存したいデータは生の json 文字列です (後で処理される実験データです)。json 文字列は、C++ アプリケーションによって形成されます。これは約 70 kB の大きさで (大きいので!)、POST マルチパート リクエストを介して Web サーバーに送信されます。マルチパート リクエストは、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 文字列を受信しないことです。'data' と呼ばれる 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 の一部です。「データ」フィールドは、サーバーに送信される最初のフィールドです。つまり、上記の curl_formadd ループを介して最初に書き込まれ、wireshark でチェックされた TCP ストリームの最初のフィールドであり、$_POST にある最初のフィールドでもあります。私のローカルサーバー上の配列。

サーバーテスト

この問題を発見した後、私はサーバーをテストし、Firefox を使用して WordPress 経由でファイルをアップロードしようとしました。サーバーが大きな $_POST フィールドを拒否するかどうかをテストしました。ただし、アップロードは機能しているようです(アップロードしたいjsonデータよりも大きい大きなPNGをアップロードするようにテストされています)。

次のテストは、「データ」フィールドを小さくすることでした。〜 900 バイトの文字列の繰り返しをアップロードするようにテストしました

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

これも機能しました(マルチパートポストを使用)。

質問

私の開発マシンのように、長い「データ」フィールドを $_POST 変数の一部として使用できるようにしたいと考えています。何が問題を引き起こしているのかわかりません。私が使用しているマルチパート MIME タイプ (「text/plain」) を使用できますか? Apache/PHP で POST- FIELDのサイズを制限する構成はありますか(全体的な 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 構造内のすべてのデータが必要であり、データ フィールドのシーケンスは変更されません。したがって、データをファイルとして送信するだけでなく、(libcurl を介して) POST に空のフィールドをファイルと同じ名前 ("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;
    }
  }
}

これは機能しますが、POST-フィールドがApacheによってフィルタリングされる理由/方法はまだわかりません。おそらく Apache が行っているわけではありませんが、私のホスティング プロバイダーはある種のディープ パケット フィルタリングを行っています。

まだ混乱していますが、助けようとしたすべての人に感謝します!

于 2013-01-09T12:40:06.820 に答える