【问题标题】:Batch requests on SymfonySymfony 上的批量请求
【发布时间】:2016-11-24 20:43:46
【问题描述】:

我正在尝试在他们的图形 api 上重现 facebook batch requests 函数的行为。

所以我认为最简单的解决方案是在控制器上向我的应用程序发出多个请求,例如:

public function batchAction (Request $request)
{
    $requests = $request->all();
    $responses = [];

    foreach ($requests as $req) {
        $response = $this->get('some_http_client')
            ->request($req['method'],$req['relative_url'],$req['options']);

        $responses[] = [
            'method' => $req['method'],
            'url' => $req['url'],
            'code' => $response->getCode(),
            'headers' => $response->getHeaders(),
            'body' => $response->getContent()
        ]
    }

    return new JsonResponse($responses)
}

因此,使用此解决方案,我认为我的功能测试将是绿色的。

但是,我喜欢初始化服务容器 X 次可能会使应用程序变慢。因为对于每个请求,都会构建每个捆绑包,每次都重新构建服务容器等等......

对于我的问题,您还有其他解决方案吗?

换句话说,我是否需要向我的服务器发出完整的新 HTTP 请求才能从我的应用程序中的其他控制器获得响应?

提前感谢您的建议!

【问题讨论】:

  • 我认为使用 Controller 进行批量操作不是一个好习惯。
  • 你应该更喜欢 "Symfony 命令" : symfony.com/doc/current/console.html / "Symfony 应用程序" : symfony.com/doc/current/components/console/… 和 GuzzleClient : docs.guzzlephp.org/en/latest
  • 感谢您的建议。我查看了控制台解决方案,但我认为它不适合我的用例,因为我的端点已经在控制器中定义,来自 routing.yml 文件。因此,为了使用命令,我认为我需要将所有控制器重写为命令,并以某种方式将路由与命令链接起来,我仍然需要用命令输出来响应。有了这个解决方案,我还关心严格的请求信息,如标头、cookie 等......
  • 但是,这与我想要做的非常接近:symfony.com/doc/current/console/calling_commands.html,除了这是用于控制台而不是控制器。这行很有趣:$command = $this->getApplication()->find('demo:greet');,实际上我需要类似的东西:$response = $this->getApplication()->getResponseFrom('/api/some-endpoint');,就像 awesome

标签: symfony


【解决方案1】:

Symfony 在内部使用 http_kernel 组件处理请求。因此,您可以为要执行的每个批处理操作模拟一个请求,然后将其传递给http_kernel 组件,然后详细说明结果。

考虑这个示例控制器:

/**
 * @Route("/batchAction", name="batchAction")
 */
public function batchAction()
{
    // Simulate a batch request of existing route
    $requests = [
        [
            'method' => 'GET',
            'relative_url' => '/b',
            'options' => 'a=b&cd',
        ],
        [
            'method' => 'GET',
            'relative_url' => '/c',
            'options' => 'a=b&cd',
        ],
    ];

    $kernel = $this->get('http_kernel');

    $responses = [];
    foreach($requests as $aRequest){

        // Construct a query params. Is only an example i don't know your input
        $options=[];
        parse_str($aRequest['options'], $options);

        // Construct a new request object for each batch request
        $req = Request::create(
            $aRequest['relative_url'],
            $aRequest['method'],
            $options
        );
        // process the request
        // TODO handle exception
        $response = $kernel->handle($req);

        $responses[] = [
            'method' => $aRequest['method'],
            'url' => $aRequest['relative_url'],
            'code' => $response->getStatusCode(),
            'headers' => $response->headers,
            'body' => $response->getContent()
        ];
    }
    return new JsonResponse($responses);
}

使用以下控制器方法:

/**
 * @Route("/a", name="route_a_")
 */
public function aAction(Request $request)
{
    return new Response('A');
}

/**
 * @Route("/b", name="route_b_")
 */
public function bAction(Request $request)
{
    return new Response('B');
}

/**
 * @Route("/c", name="route_c_")
 */
public function cAction(Request $request)
{
    return new Response('C');
}

请求的输出将是:

[
{"method":"GET","url":"\/b","code":200,"headers":{},"body":"B"},
{"method":"GET","url":"\/c","code":200,"headers":{},"body":"C"}
]

PS:希望我已经正确理解了您的需求。

【讨论】:

  • 我想你明白了!我明天需要测试一下,我会通知你的,谢谢!
  • 嗯,正如预期的那样,这正是我所需要的,我还查看了app.php 文件,它让我更好地理解了请求进入应用程序的过程。
  • 嗨@El_Matella,没错!您是否发现这种方法对性能有一些好处?
  • 我还没有在真正的应用程序上进行非常大的测试,我会在 cmets 中发布它。但我相信,有了这个,性能会更好。
  • 供您参考,以您的示例为例。对/some-endpoint 的单个请求需要170ms,向内核发出多个虚假请求的批处理端点请求需要190ms。所以,对我来说,这要好得多。
【解决方案2】:

有多种方法可以优化测试速度,无论是使用 PHPunit 配置(例如,xdebug 配置,还是使用 phpdbg SAPI 运行测试,而不是将 Xdebug 模块包含到通常的 PHP 实例中)。

由于代码将始终运行 AppKernel 类,因此您还可以针对特定环境在其中进行一些优化 - 包括在测试期间不那么频繁地初始化容器。

我正在使用 Kris Wallsmith 的one such example。这是他的示例代码。

class AppKernel extends Kernel
{
// ... registerBundles() etc
// In dev & test, you can also set the cache/log directories 
// with getCacheDir() & getLogDir() to a ramdrive (/tmpfs).
// particularly useful when running in VirtualBox

protected function initializeContainer()
{
    static $first = true;

    if ('test' !== $this->getEnvironment()) {
        parent::initializeContainer();
        return;
    }

    $debug = $this->debug;

    if (!$first) {
        // disable debug mode on all but the first initialization
        $this->debug = false;
    }

    // will not work with --process-isolation
    $first = false;

    try {
        parent::initializeContainer();
    } catch (\Exception $e) {
        $this->debug = $debug;
        throw $e;
    }

    $this->debug = $debug;
}

【讨论】:

  • 您好,感谢您抽出宝贵时间回答我的问题。我认为我了解您对我的测试的优化。但是,我的问题不是针对任何测试环境。当我说每次都将重建服务容器时,我的意思是在我的控制器发出每个请求后使用$response = $this->get('some_http_client')->request()。我的问题更多是关于“我真的需要发出 http 请求来响应我的问题吗?”
猜你喜欢
  • 1970-01-01
  • 2020-03-04
  • 1970-01-01
  • 1970-01-01
  • 2019-08-25
  • 2017-01-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多