Response returned only after kernel.terminate event - symfony

My understanding of kernel.terminate is that it triggers after the response has been returned to the client.
In my testing tough, this does not appear to be the case. If I put a sleep(10) in the function that's called on kernel.terminate. the browser also waits for 10 seconds. The processing seems to be happening before the response is sent.
I have the following in config:
calendar:
class: Acme\CalendarBundle\Service\CalendarService
arguments: [ #odm.document_manager, #logger, #security.context, #event_dispatcher ]
tags:
- { name: kernel.event_subscriber }
My subscriber class:
class CalendarService implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
'kernel.terminate' => 'onKernelTerminate'
);
}
public function onKernelTerminate()
{
sleep(10);
echo "hello";
}
}
UPDATE
This appears to be related to Symfony not sending a Content-Length header. If I generate that, the response return properly.
// app_dev.php
...
$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
// --- START EDITS ---
$size = strlen($response->getContent());
$response->headers->set('Content-Length', $size);
$response->headers->set('Connection', 'close');
// ---- END EDITS ----
$response->send();
$kernel->terminate($request, $response);

This issue turned out to be very specific to my setup (Nginx, PHP-FCGI, Symfony).
There were a handful of issues in play that caused the issue:
Symfony does not include a Content-Length nor Connection: close header
PHP-FCGI does not support the fastcgi_finish_request function
Nginx buffers the response from PHP-FCGI because Gzip is on
The solution was to switch from PHP-FCGI to PHP-FPM in order to get support for fastcgi_finish_request. Symfony internally calls this before executing the kernel terminate logic thereby definitively closing the connection.
Another way to solve this would be to turn off Gzip on Nginx, but this wasn't really an option for me.

Related

Symfony: Webhook End Point Problems

I am having trouble with Webhook Endpoints.
#[Route("/web_hook", name: 'web_hook')]
public function webhook(Request $request)
{
$data = json_decode($request->getContent(), true);
if ($data === null){
throw new \Exception('bad bad');
}
$txt = 'samle of some text text text';
$myfile = fopen("test.txt", "w") or die("Unable to open file!");
fwrite($myfile, $txt);
fwrite($myfile, $data->id);
fclose($myfile);
$response = new Response();
$response->setStatusCode(200);
return $response;
}
I am having 0 luck getting it to work, I am sending a POST request to it VIA Postman and it keeps coming back with error 500.
When I visit then URL it gives me this error
"Cannot autowire argument $request of "App\Controller\WebHookController::webhook()": it references class "Symfony\Component\HTTPFoundation\Request" but no such service exists.
"
I based this method off of
https://symfonycasts.com/screencast/stripe-level2/webhook-endpoint-setup.
any ideas on what im doing wrong would be greatly appreciated.
if you get error message like this
Cannot autowire argument $request of
"App\Controller\WebHookController::webhook()": it references class
"Symfony\Component\HTTPFoundation\Request" but no such service exists.
mean this service is not available. please try install http-foundation component first.
composer require symfony/http-foundation
and try to change use Symfony\Component\HTTPFoundation\Request to use Symfony\Component\HttpFoundation\Request

Downloading a file with BrowserKit/Mink

Preface
Using Behat, I can write a scenario like this:
Scenario: I can get a template
When I send a GET request to "/products/template"
Then the response status code should be 200
that links to this Behatch step implemented by vendor/behatch/contexts/src/Context/RestContext.php:
/**
* Sends a HTTP request
*
* #Given I send a :method request to :url
*/
public function iSendARequestTo($method, $url, PyStringNode $body = null, $files = [])
{
return $this->request->send(
$method,
$this->locatePath($url),
[],
$files,
$body !== null ? $body->getRaw() : null
);
}
Request
In order to validate file responses, I would like to write a scenario like:
Scenario: I can get a template
When I send a GET download request to "/products/template" and save it to "/tmp"
Then the response status code should be 200
And the file "/tmp/template.csv" should exist
I would like to write a step that sends a GET request and downloads a file to a provided path, akin to the sink Guzzle functionality, similar to:
/**
* Sends a HTTP request
*
* #Given I send a :method download request to :url and save it to :path
*/
public function iSendADownloadRequestTo($method, $url, $path)
{
return $this->request->send(
$method,
$this->locatePath($url),
["sink" => __DIR__.$path]
);
}
The previous code succesfully sends the request but it doesn't save the file. How could I achieve this?
I would download the file with use of Guzzle or curl. Mink is used and designed to interact with pages, and to my knowledge and quick glance at its docs doesn't support functionality you are after.

Setup periodic taks (using cron.yaml) with Symfony on AWS ElasticBeanstalk

I'm having trouble with periodic tasks on ElasticBeanstalk Worker Tier with Symfony app.
I have deployed the same source code on App Server and Worker Tier.
I have setup my cron.yaml file and it is successfully loaded.
Messages are sent but I get a 406 error :
"POST /worker/reclamation/auto-reply HTTP/1.1" 406 481 "-" "aws-sqsd/2.4"
My cron.yaml file:
version: 1
cron:
- name: "reclamation-reply"
url: "/worker/reclamation/auto-reply"
schedule: "*/10 * * * *"
AWS Documentation says :
The URL is the path to which the POST request is sent to trigger the
job.
From there, I decided to code a FOSRest Route with POST Method in which I trigger the command I need to run.
I don't know if is the right way to do it, so, I suppose my problem may come from here.
/**
* #FOSRest\Route("/worker")
*/
class WorkerController extends AbstractController
{
/**
* #FOSRest\Post("/reclamation/auto-reply")
*/
public function ticketReply(KernelInterface $kernel)
{
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput(array(
'command' => 'app:reclamation:reply',
));
$output = new NullOutput();
$application->run($input, $output);
return new Response("");
}
Thank you in advance for your help !
It finally works !
It seems the error occured because I forget to configure the format_listener of my FOSRest Route.

Symfony 3. Cookie auto deletes immediately after create

I create cookie in EventSubscriber:
function onKernelResponse(FilterResponseEvent $event)
{
$response = new Response();
$cookie_data = 'test';
$cookie = new Cookie('test_cookie', $cookie_data, strtotime('now +1 year'));
$response->headers->setCookie($cookie);
$response->send();
}
then I watch to Application tab in Chrome and press F5.
this cookie appears for 0.5 sec and automatically remove
what i am doing wrong?
I hope this helps somebody.
I found problem: nginx send missed static files like .jpg to application.
When i fix nginx vhost file - cookies starts to set correctly.

Symfony2 Templating without request

I'm trying to send an email from a ContainerAwareCommand in Symfony2. But I get this exception when the email template is render by:
$body = $this->templating->render($template, $data);
Exception:
("You cannot create a service ("templating.helper.assets") of an inactive scope ("request").")
I found in github that this helper need the request object. Anybody knows how can I to instance the Request object?
You need to set the container into the right scope and give it a (fake) request. In most cases this will be enough:
//before you render template add bellow code
$this->getContainer()->enterScope('request');
$this->getContainer()->set('request', new Request(), 'request');
The full story is here. If you want to know the details read this issue on github.
The problem arises because you use asset() function in your template.
By default, asset() relies on Request service to generate urls to your assets (it needs to know what is the base path to you web site or what is the domain name if you use absolute asset urls, for example).
But when you run your application from command line there is no Request.
One way to fix this it to explicitely define base urls to your assets in config.yml like this:
framework:
templating:
assets_base_urls: { http: ["http://yoursite.com"], ssl: ["http://yoursite.com"] }
It is important to define both http and ssl, because if you omit one of them asset() will still depend on Request service.
The (possible) downside is that all urls to assets will now be absolute.
Since you don't have a request, you need to call the templating service directly like this:
$this->container->get('templating')->render($template, $data);
Following BetaRide's answer put me on the right track but that wasn't sufficient. Then it was complaining: "Unable to generate a URL for the named route "" as such route does not exist."
To create a valid request I've modified it to request the root of the project like so:
$request = new Request();
$request->create('/');
$this->container->enterScope('request');
$this->container->set('request', $request, 'request');
You might need to call a different route (secured root?), root worked for me just fine.
Symfony2 Docs
Bonus addition:
I had to do so much templating/routing in cli through Symfony2 commands that I've updated the initializeContainer() method in AppKernel. It creates a route to the root of the site, sets the router context and fakes a user login:
protected function initializeContainer()
{
parent::initializeContainer();
if (PHP_SAPI == 'cli') {
$container = $this->getContainer();
/**
* Fake request to home page for cli router.
* Need to set router base url to request uri because when request object
* is created it perceives the "/portal" part as path info only, not base
* url and thus router will not include it in the generated url's.
*/
$request = Request::create($container->getParameter('domain'));
$container->enterScope('request');
$container->set('request', $request, 'request');
$context = new RequestContext();
$context->fromRequest($request);
$container->get('router')->setContext($context);
$container->get('router')->getContext()->setBaseUrl($request->getRequestUri());
/**
* Fake admin user login for cli. Try database read,
* gracefully print error message if failed and continue.
* Continue mainly for doctrine:fixture:load when db still empty.
*/
try {
$user = $container->get('fos_user.user_manager')->findUserByUsername('admin');
if ($user !== null) {
$token = $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->getContainer()->get('security.token_storage')->setToken($token);
}
} catch (\Exception $e) {
echo "Fake Admin user login failed.\n";
}
}
}
You might not need the last $container->get('router')->getContext()->setBaseUrl($request->getRequestUri()); part, but I had to do it because my site root was at domain.com/siteroot/ and the router was stripping /siteroot/ away for url generation.

Resources