0

私が持っている 1 つの Web サイトは、もともと PHP で作成されていました。ユーザーが Web サイトで特定のクエリを実行するたびに、別の Web サイトに対して Web POST 要求を実行します。

function post_request($url, $data, $referer='') {
$data = http_build_query($data);
$url = parse_url($url);

if ($url['scheme'] != 'http') { 
    die('Error: Only HTTP request are supported !');
}

// extract host and path:
$host = $url['host'];
$path = $url['path'];

// open a socket connection on port 80 - timeout: 7 sec
$fp = fsockopen($host, 80, $errno, $errstr, 7);

if ($fp){
    // Set non-blocking mode 
    stream_set_blocking($fp, 0);

    // send the request headers:
    fputs($fp, "POST $path HTTP/1.1\r\n");
    fputs($fp, "Host: $host\r\n");

    if ($referer != '')
        fputs($fp, "Referer: $referer\r\n");

    fputs($fp, "User-Agent: Mozilla/5.0 Firefox/3.6.12\r\n");
    fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
    fputs($fp, "Content-length: ". strlen($data) ."\r\n");
    fputs($fp, "Connection: close\r\n\r\n");
    fputs($fp, $data);

    $result = ''; 
    while(!feof($fp)) {
        // receive the results of the request
        $result .= fgets($fp, 128);
    }

   // close the socket connection:
   fclose($fp);
}
else { 
    return array(
        'status' => 'err', 
        'error' => "$errstr ($errno)"
    );
}

// split the result header from the content
$result = explode("\r\n\r\n", $result, 2);

$header = isset($result[0]) ? $result[0] : '';
$content = isset($result[1]) ? $result[1] : '';

// return as structured array:
return array(
    'status' => 'ok',
    'header' => $header,
    'content' => $content
);
}

このアプローチは問題なく機能します。唯一の問題は、上記のコードで 100 人の同時ユーザーをサポートするのにほぼ 3 つの CPU が必要なことです。

これを行うには Node.js が適していると考えて (Web 要求は非同期になります)、次のようにしました。CPU 要件に関しては、明確な改善がありました (ほとんどの場合、1 つの CPU で動作し、多くても 2 つです)。

function postPage(postPath, postData, postReferal, onReply, out) {
    var post_options = {
          host: 'www.somehost.com',
          port: '80',
          path: postPath,
          method: 'POST',
          headers: {
              'Referer': postReferal,
              'Content-Type': 'application/x-www-form-urlencoded',
              'Content-Length': postData.length,
              'User-Agent': 'Mozilla/5.0 Firefox/3.6.12',
              'Connection': 'close'
          }
      };

    // create request
    var post_req = http.request(post_options, function (res) {
        var reply = '';
        res.setEncoding('utf8');
        res.on('data', function (chunk) {
            reply += chunk;
        });

        res.on('end', function () {
            onReply(reply, out);
        });

        res.on('error', function (err) {
            out.writeHead(500, { 'Content-Type': 'text/html' });
            out.end('Error');
        });
    });

    // post the data
    post_req.write(postData);
    post_req.end();
}

この場合の問題は、非常に壊れやすく、Web 要求の約 20% が失敗することです。ユーザーがクエリを再試行すると機能しますが、良いエクスペリエンスではありません。

Windows Azure Web サイトを使用して、上記の両方のソリューションをホストしています。

さて、質問

  1. これにPHPを使用すると、それだけ多くのリソースが必要になると予想されますか、それとも私のコードが最適ではないためですか?
  2. 非常に多くのリクエストが失敗するのは、私のノード コード (または Azure) の何が問題なのですか?
4

1 に答える 1

1

リクエスト ライブラリを使用する

応答全体のバッファリング

最も基本的な方法は、リクエストを作成し、リモート サービス (indianrail.gov.in) からのレスポンス全体をメモリにバッファリングしてから、それをクライアントに送り返すことです。ただし、以下のストリーミングの例を見る価値があります

必要な依存関係をインストールする npm install request eyespect

var request = require('request');
var inspect = require('eyespect').inspector({maxLength: 99999999});  // nicer console logging
var url = 'http://www.indianrail.gov.in';

var postData = {
  fooKey: 'foo value'
};
var postDataString = JSON.stringify(postData);
var opts = {
  method: 'post',
  body: postDataString // postData must be a string here..request can handle encoding key-value pairs, see documentation for details
};

inspect(postDataString, 'post data body as a string');
inspect(url, 'posting to url');
request(url, function (err, res, body) {
  if (err) {
    inspect('error posting request');
    console.log(err);
    return;
  }
  var statusCode = res.statusCode;
  inspect(statusCode, 'statusCode from remote service');
  inspect(body,'body from remote service');
});

ストリーミング

操作するレスポンス ストリームがある場合は、最初にすべてをメモリにバッファリングしなくても、ポスト データをストリーミングできます。あなたの例では、これがoutパラメータであると推測しています。

エラー処理を追加するには、async モジュールを使用して、正常に完了するか、最大試行回数に達するまで、post 要求を繰り返し試行します。

npm install request filed temp eyespect async

var request = require('request');
var inspect = require('eyespect').inspector({maxLength: 99999999});  // nicer console logging
var filed = require('filed');
var temp = require('temp');
var rk = require('required-keys');
var async = require('async');

function postToService(data, cb) {

  // make sure the required key-value pairs were passed in the data parameter
  var keys = ['url', 'postData'];
  var err = rk.truthySync(data, keys);
  if (err) { return cb(err); }

  var url = data.url;
  var postData = data.postData;
  var postDataString = JSON.stringify(postData);
  var opts = {
    method: 'post',
    body: postDataString // postData must be a string here..request can handle encoding key-value pairs, see documentation for details
  };

  var filePath = temp.path({suffix: '.html'});
  // open a writable stream to a file on disk. You could however replace this with any writeable stream such as "out" in your example
  var file = filed(filePath);
  // stream the response to disk just as an example
  var r = request(url).pipe(file);
  r.on('error', function (err) {
    inspect(err, 'error streaming response to file on disk');
    cb(err);
  });

  r.on('end', function (err) {
    cb();
  });
}

function keepPostingUntilSuccess(callback) {
  var url = 'http://www.google.com';
  var postData = {
    fooKey: 'foo value'
  };
  var data = {
    url: url,
    postData: postData
  };
  var complete = false;
  var maxAttemps = 50;
  var attempt = 0;
  async.until(
    function () {
      if (complete) {
        return true;
      }
      if (attempt >= maxAttemps) {
        return true;
      }
      return false;
    },

    function (cb) {
      attempt++;
      inspect(attempt, 'posting to remote service, attempt number');
      postToService(data, function (err) {

        // simulate the request failing 3 times, then completing correctly
        if (attempt < 3) {
          err = 'desired number of attempts not yet reached';
        }
        if (!err) {
          complete = true;
        }
        cb();
      });
    },
    function (err) {
      inspect(complete, 'done with posting, did we complete successfully?');
      if (complete) {
        return callback();
      }
      callback('failed to post data, maximum number of attempts reached');
    });
}


keepPostingUntilSuccess(function (err) {
  if (err) {
    inspect(err, 'error posting data');
    return;
  }
  inspect('posted data successfully');
});
于 2013-02-06T07:23:15.353 に答える