Do something after rendering in mojolicious
Asked Answered
A

2

5

How can I make my code do something after HypnoToad sends a page? (Note: I am answering my own question. I am posting this, because StackOverflow pointed me to a previous question that did not directly solve my problem, although it did contain the clue that I needed.)

Example code:

use Mojolicious::Lite;
get "/index" => sub {
   my $c = shift;
   $c->render("renderThis");
   # Do something after rendering
};
app->start('daemon', '-l', 'http://*:8080');

__DATA__
@@ renderThis.html.ep
% layout  "template" ;
<h1>Hello World</h1>

@@ layouts/template.html.ep
<!DOCTYPE html>
<html><head></head><body>
%= content
</body></html>

render seems to buffer its http output and sends it after the code block is finished. I want something to execute after the page gets sent. The buffering can be observed by putting the following in place of the "Do something" comment.

   sleep 15;
   say "Wow, that was a long time!";

I am on win7, so a unix-only solution won't work.

Arenaceous answered 30/11, 2019 at 5:8 Comment(0)
O
4

You can attach code to the finish event on the transaction. Most other methods would not be guaranteed to wait until the response has actually been sent, since it happens in an asynchronous fashion.

use Mojolicious::Lite;
get "/index" => sub {
   my $c = shift;
   $c->render("renderThis");
   $c->tx->on(finish => sub {
      sleep 15; # this is a really bad idea, use a timer instead
      say "That was a long time, but at least the page got sent quickly.";
   });
};
Overcast answered 2/12, 2019 at 16:59 Comment(1)
Thank you. That's exactly what I was looking for. I still kinda like my hacky solution, ...because it's hacky. But your answer is definitely the correct way to do it. I did find that with timer I had to put a small number in the time slot to make my actual code work correctly. I ended up using .01 rather than 0.Arenaceous
A
2

The solution in the previous question did not work for me. (That is, the answer that simone links to in his comment. I didn't try the fork solution provided by mob.) My clue came from the comment by Сухой27. Here is my solution:

use Mojolicious::Lite;
use Mojo::IOLoop;
get "/index" => sub {
   my $c = shift;
   $c->render("renderThis");
   Mojo::IOLoop->timer(0 => sub {
      sleep 15;
      say "That was a long time, but at least the page got sent quickly.";
   });
};
app->start('daemon', '-l', 'http://*:8080');

__DATA__
@@ renderThis.html.ep
% layout  "template" ;
<h1>Hello World</h1>

@@ layouts/template.html.ep
<!DOCTYPE html>
<html><head></head><body>
%= content
</body></html>

The timer takes its code out of its caller's execution flow, so the calling function completes and clears its buffers before the timer's code executes (even if the time argument is shorter than the time it takes for the caller to complete).

Note: I got that explanation from experimentation, not code inspection, so I don't know whether the caller flushes ALL of its buffers before the timer runs its code. All I know is that render's http response goes out and STDOUT gets flushed to the console before the timer code runs. I generalized those observations to make the above statement.

Arenaceous answered 30/11, 2019 at 5:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.