Commits
-------
7c2f11f Merge pull request #1 from pminnieur/post_response
9f4391f [HttpKernel] fixed DocBlocks
2a61714 [HttpKernel] added PostResponseEvent dispatching to HttpKernel
915f440 [HttpKernel] removed BC breaks, introduced new TerminableInterface
7efe4bc [HttpKernel] Add Kernel::terminate() and HttpKernel::terminate() for post-response logic
Discussion
----------
[HttpKernel] Add Kernel::terminate() and HttpKernel::terminate() for post-response logic
This came out of a discussion on IRC about doing stuff post-response, and the fact that right now there is no best practice, and it basically requires adding code after the `->send()` call.
It's an attempt at fixing it in an official way. Of course terminate() would need to be called explicitly, and added to the front controllers, but then it offers a standard way for everyone to listen on that event and do things without slowing down the user response.
---------------------------------------------------------------------------
by stof at 2011/12/06 02:41:26 -0800
We discussed it on IRC and I suggested a way to avoid the BC break of the interface: adding a new interface (``TerminableInterface`` or whatever better name you find) containing this method.
HttpKernel, Kernel and HttpCache can then implement it without breaking the existing apps using the component (Kernel and HttpCache would need an instanceof check to see if the inner kernel implements the method)
For Symfony2 users it will mean they have to change their front controller to benefit from the new event of course, but this is easy to do.
Btw, Silex can then be able to use it without *any* change for the end users as it can be done inside ``Application::run()``
---------------------------------------------------------------------------
by pminnieur at 2011/12/06 11:47:03 -0800
@Seldaek: I opened a pull request so that the discussion on IRC is fulfilled and no BC breaks exist: https://github.com/Seldaek/symfony/pull/1/files
---------------------------------------------------------------------------
by fabpot at 2011/12/07 07:59:49 -0800
Any real-world use case for this?
---------------------------------------------------------------------------
by Seldaek at 2011/12/07 08:10:31 -0800
Doing slow stuff after the user got his response back without having to implement a message queue. I believe @pminnieur wanted to use it to send logs to loggly?
---------------------------------------------------------------------------
by pminnieur at 2011/12/07 09:08:41 -0800
Its a good practice to defer code execution without the introduction of a new software layer (like gearman, amqp, whatever tools people use to defer code execution) which may be way too much just for the goal of having fast responses, whatever my code does.
My real world use case which made me miss this feature the first time:
> I have a calendar with a scheduled Event. For a given period of time, several Event entities will be created, coupled to the scheduled event (the schedule Event just keeps track of `startDate`, `endDate` and the `dateInterval`). Let's say we want this scheduled Event to be on every Monday-Friday, on a weekly basis, for the next 10 years.
This means I have to create `10*52*5` Event entities before I could even think about sending a simple redirect response. If I could defer code execution, I'd only save the scheduled Event, send the redirect response and after that, I create the `10*52*5` entities.
The other use case was loggly, yes. Sending logging data over the wire before the response is send doesn't make sense in my eyes, so it could be deferred after the response is send (this especially sucks if loggly fails and i get a 500 --the frontend/public user is not interested in a working logging facility, he wants his responses).
---------------------------------------------------------------------------
by mvrhov at 2011/12/07 10:07:03 -0800
This would help significantly, but the real problem, that your process is busy and unavailable for the next request, is still there.
---------------------------------------------------------------------------
by fabpot at 2011/12/07 10:15:18 -0800
I think this is the wrong solution for a real problem.
Saying "Its a good practice to defer code execution without the introduction of a new software layer" is just wrong.
It is definitely a good practice to defer code execution, but you should use the right tool for the job.
I'm -1.
---------------------------------------------------------------------------
by pminnieur at 2011/12/07 10:25:44 -0800
It should just give a possibility to put unimportant but heavy lifting code behind the send request with ease. With little effort people could benefit from the usage of `fastcgi_finish_request` without introducing new software, using `register_shutdown_function` or using `__destruct `(which works for simple things, but may act weird with dependencies).
It should not simulate node.js ;-) I agree that the real problem is not solved, but small problems could be solved easily. I personally don't want to setup RabbitMQ or whatever, maintain my crontab or any other software that may allow me to defer code execution.
---------------------------------------------------------------------------
by Seldaek at 2011/12/08 01:08:32 -0800
@fabpot: one could say that on shared hostings it is still useful because they generally don't give you gearman or \*MQs. Anyway I think it'd be nice to really complete the HttpKernel event cycle.
---------------------------------------------------------------------------
by pminnieur at 2011/12/08 01:48:57 -0800
not only on shared hostings, sometimes teams/projects just don't have the resources or knowledge or time to setup such an infrastructure.
---------------------------------------------------------------------------
by videlalvaro at 2011/12/08 01:53:06 -0800
I can say we used `fastcgi_finish_request` quite a lot at poppen with symfony 1.x. It certainly helped us to send data to Graphite, save XHProf runs, send data to RabbitMQ, and so on.
For example we used to connect to RabbitMQ and send the messages _after_ calling `fastcgi_finish_request` so the user never had to wait for stuff like that.
Also keep in mind that if you are using Gearman or RabbitMQ or whatever tool you use to defer code execution… you are not deferring the network connection handling, sending data over the wire and what not. I know this is obvious but is often overlooked.
So it would be nice to have an standard way of doing this.
---------------------------------------------------------------------------
by henrikbjorn at 2011/12/13 01:42:23 -0800
This could have been useful recently while implementing a "Poor mans cronjob" system. The solution was to do a custom Response object and do the stuff after send have been called with a Connection: Close header and ignore_user_abort(); (Yes very ugly)
* 2.0:
[HttpKernel] fixed Content-Length header when using ESI tags (closes#2623)
[HttpFoundation] added an exception to MimeTypeGuesser::guess() when no guesser are available (closes#2636)
[Security] fixed HttpUtils::checkRequestPath() to not catch all exceptions (closes#2637)
[DoctrineBundle] added missing default parameters, needed to setup and use DBAL without ORM
[Transation] Fix grammar.
[TwigBundle] Fix trace to not show 'in at line' when file/line are empty.
That allows projects that only use HttpFoundation and not HttpKernel to be able to
enforce the HTTP specification "rules".
$request = Request::createFromGlobals();
$response = new Response();
// do whatever you want with the Respons
// enforce HTTP spec
$response->prepare($request);
$response->send();
Within Symfony2, the prepare method is automatically called by the ResponseListener.
The onCore* events are fired at some pre-defined points during the
handling of a request. At this is more important than the fact
that you can change things from the event.
The only missing part is ContainerAwareEventManager::addEventSubscriberService(),
because I'm not sure how to find out the class name of a service in the DIC.
Also, inline documentation of this code needs to be finished once it is accepted.
Doctrine's EventManager implementation has several advantages over the
EventDispatcher implementation of Symfony2. Therefore I suggest that we
use their implementation.
Advantages:
* Event Listeners are objects, not callbacks. These objects have handler
methods that have the same name as the event. This helps a lot when
reading the code and makes the code for adding an event listener shorter.
* You can create Event Subscribers, which are event listeners with an
additional getSubscribedEvents() method. The benefit here is that the
code that registers the subscriber doesn't need to know about its
implementation.
* All events are defined in static Events classes, so users of IDEs benefit
of code completion
* The communication between the dispatching class of an event and all
listeners is done through a subclass of EventArgs. This subclass can be
tailored to the type of event. A constructor, setters and getters can be
implemented that verify the validity of the data set into the object.
See examples below.
* Because each event type corresponds to an EventArgs implementation,
developers of event listeners can look up the available EventArgs methods
and benefit of code completion.
* EventArgs::stopPropagation() is more flexible and (IMO) clearer to use
than notifyUntil(). Also, it is a concept that is also used in other
event implementations
Before:
class EventListener
{
public function handle(EventInterface $event, $data) { ... }
}
$dispatcher->connect('core.request', array($listener, 'handle'));
$dispatcher->notify('core.request', new Event(...));
After (with listeners):
final class Events
{
const onCoreRequest = 'onCoreRequest';
}
class EventListener
{
public function onCoreRequest(RequestEventArgs $eventArgs) { ... }
}
$evm->addEventListener(Events::onCoreRequest, $listener);
$evm->dispatchEvent(Events::onCoreRequest, new RequestEventArgs(...));
After (with subscribers):
class EventSubscriber
{
public function onCoreRequest(RequestEventArgs $eventArgs) { ... }
public function getSubscribedEvents()
{
return Events::onCoreRequest;
}
}
$evm->addEventSubscriber($subscriber);
$evm->dispatchEvent(Events::onCoreRequest, new RequestEventArgs(...));
Rules are :
- If one of the ESI has validation cache strategy, the whole page will be
forced to validate.
- In none of the ESI has validation, the response will feature a Cache-Control
directive with s-maxage value equals to the smallest TTL of ESIs.