【问题标题】:How to match a result to a request when sending multiple requests?发送多个请求时如何将结果与请求匹配?
【发布时间】:2019-08-01 13:54:05
【问题描述】:

A.总结

顾名思义,Guzzle 允许一次发送多个请求以节省时间,如 documentation

$responses = $client->send(array(
    $requestObj1,
    $requestObj2,
    ...
));

(given that each request object is an instance of
Guzzle\Http\Message\EntityEnclosingRequestInterface)

当响应返回时,为了识别哪个响应是针对哪个请求,我们可以遍历每个请求并获取响应(仅在执行上述命令后可用):

$response1 = $requestObj1->getResponse();
$response2 = $requestObj2->getResponse();
...

B.问题

如果请求对象包含相同的数据。无法识别原始请求。

假设我们有以下场景,我们需要在远程服务器上创建 2 篇文章:A 和 B:something.com/articles/create.json

每个请求都有相同的 POST 数据:

subject: This is a test article

创建后,返回 2 个位置的 Guzzle 响应:

something.com/articles/223.json
something.com/articles/245.json

使用上面的方法将response-to-its-request链接起来,我们仍然不知道哪个response是针对哪篇文章的,因为request对象完全一样

因此在我的数据库中我无法写下结果:

article A -> Location: 245.json
article B -> Location: 223.json

因为它可以是相反的方式:

article A -> Location: 223.json
article B -> Location: 245.json

一种解决方案是在 POST 请求中添加一些额外的参数,例如

subject: This is a test article
record: A

但是,远程服务器将返回错误并且不会创建文章,因为它不了解关键“记录”。远程服务器是第三方服务器,我无法更改它的工作方式。

另一个适当的解决方案是在请求对象上设置一些特定的 id/tag,以便我们之后可以识别它。但是,我查看了文档,但没有像

这样唯一标识请求的方法
$request->setID("id1")

or

$request->setTag("id1")

这已经困扰我好几个月了,仍然无法解决这个问题。

如果您有解决方案,请告诉我。

非常感谢,你救了我!!!!

感谢您阅读这篇长文。

【问题讨论】:

  • 取决于用例,对于 API,我使用域标识符。

标签: guzzle


【解决方案1】:

我找到了一种合适的方法,Guzzle 允许在请求完成后添加回调。所以我们可以通过在批处理中的每个请求上设置它来实现这一点

每个请求默认都可以这样创建

$request = $client->createRequest('GET', 'http://httpbin.org', [
    'headers' => ['X-Foo' => 'Bar']
]);

所以,要实现我们想要的:

$allRequests = [];
$allResults = [];

for($k=0; $k<=10; $k++){
    $allRequests['key_'.$k] = $client->createRequest('GET', 'http://httpbin.org?id='.$k, [
        'headers' => ['X-Foo' => 'Bar'],
        'events' => [
            'complete' => function ($e) use (&$allResults, $k){
                $response = $e->getResponse();
                $allResults['key_'.$k] = $response->getBody().'';
            }
        ]
    ]);
}

$client->sendAll(array_values($allRequests));

print_r($allResults);

所以现在 $allResults 有每个相应请求的结果。

例如$allResults['key_1'] 是 $allRequests['key_1'] 的结果

【讨论】:

    【解决方案2】:

    我遇到了同样的问题。

    我通过添加一个自定义查询参数来解决这个问题,该参数具有为每个请求生成的唯一 id 并将其添加到请求 url(您需要记住每个请求的这个 id 以便在之后解决它)。

    $responses = $client-&gt;send($requests) 之后,您可以遍历响应并检索有效网址$response-&gt;getEffectiveUrl() 并对其进行解析(请参阅parse_urlparse_str)以获取自定义参数(具有唯一ID)并在您的数组中搜索拥有它的请求数。

    【讨论】:

      【解决方案3】:

      我找到了更好的答案。

      我一次发送 20 个请求的批次,同时发送 4 个请求,并使用了池化技术,我得到了满足,然后被拒绝了,如文档中所述。

      我发现我可以在生成/构建数组时将此代码添加到我的 requestAsync() 函数调用的末尾(我在不同的地方都这样做)。

      $request = $request->then(function (\GuzzleHttp\Psr7\Response $response) use ($source_db_object) {
                  $response->_source_object = $source_db_object;
                  return $response;
      });
      

      然后在池中的clousures中,我可以正常访问响应上的_source_object,效果很好。 我觉得它有点 hacky,但如果你只是确定使用一个从不与 Guzzle 中的任何东西冲突的名称,这应该没问题。

      这是一个完整的例子:

      use GuzzleHttp\Client;
      use GuzzleHttp\Pool;
      use GuzzleHttp\Psr7\Response as GuzzleResponse;
      
      $client = new Client();
      $requests = [];
      
      // Simple set-up here, generate some random async requests
      for ($i = 0; $i < 10; $i++) {
          $request = $client->requestAsync('GET', 'https://jsonplaceholder.typicode.com/todos/1');
          // Here we can attach any identifiable data
          $request->_source_object = $i;
          array_push($requests, $request);
      }
      
      $generator = function () use($requests) {
          while ($request = array_pop($requests)) {
              yield function() use ($request) {
                  return $request->then(function (GuzzleResponse $response) use ($request) {
                      // Attach _source_object from request to the response
                      $response->_source_object = $request->_source_object ?? [];
      
                      return $response;
                  });
              };
          }
      };
      
      $requestPool = new Pool($client, $generator(), [
          'concurrency' => 5,
          'fulfilled' => function ($response) {
              // Then we can properly access the _source_object data once response has arrived here!
              echo $response->_source_object . "\n";
          }
      ]);
      
      $requestPool->promise()->wait();
      

      【讨论】:

        【解决方案4】:

        我是这样做的:

        // create your requests
        $requests[] = $client->createRequest('GET', '/endpoint', ['config' => ['order_id' => 123]]);
        ...
        // in your success callback get 
        $id = $event->getRequest()->getConfig()['order_id']
        

        【讨论】:

          【解决方案5】:

          与新 GuzzleHttp 相关的更新guzzlehttp/guzzle

          并发/并行调用现在通过几种不同的方法运行,包括 Promises..Concurrent Requests

          传递 RequestInterfaces 数组的旧方法将不再适用。

          查看示例这里

              $newClient = new  \GuzzleHttp\Client(['base_uri' => $base]);
              foreach($documents->documents as $doc){
          
                  $params = [
                      'language' =>'eng',
                      'text' => $doc->summary,
                      'apikey' => $key
                  ];
          
                  $requestArr[$doc->reference] = $newClient->getAsync( '/1/api/sync/analyze/v1?' . http_build_query( $params) );
              }
          
              $time_start = microtime(true);
              $responses = \GuzzleHttp\Promise\unwrap($requestArr); //$newClient->send( $requestArr );
              $time_end = microtime(true);
              $this->get('logger')->error(' NewsPerf Dev: took ' . ($time_end - $time_start) );
          

          在此示例中,您将能够使用 $requestArr[$doc->reference] 引用每个响应。简而言之,为您的数组提供一个索引并运行 Promise::unwrap 调用。

          【讨论】:

            【解决方案6】:

            我也遇到过这个问题。这是出现的第一个线程。我知道这是一个已解决的线程,但我最终想出了一个更好的解决方案。这适用于所有可能遇到此问题的人。

            其中一个选项是使用 Guzzle Pool::batch。

            batch 的作用是将池化请求的结果推送到一个数组中并返回该数组。这样可以确保响应和请求的顺序相同。

            $client = new Client();
            
            // Create the requests
            $requests = function ($total) use($client) {
                for ($i = 1; $i <= $total; $i++) {
                    yield new Request('GET', 'http://www.example.com/foo' . $i);
                }
            };
            
            // Use the Pool::batch()
            $pool_batch = Pool::batch($client, $requests(5));
            foreach ($pool_batch as $pool => $res) {
            
                if ($res instanceof RequestException) {
                     // Do sth
                    continue;
                }
            
                // Do sth
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-15
              • 1970-01-01
              • 2013-05-21
              • 1970-01-01
              相关资源
              最近更新 更多