Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

AnyEvent::HTTPD -> Extra Callback after response?

by sectokia (Scribe)
on Jun 11, 2021 at 01:51 UTC ( #11133756=perlquestion: print w/replies, xml ) Need Help??

sectokia has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I cannot work out why in this code the callback sub is called twice. The response goes to the client after the timer expires the first time, but then it triggers all over again. Code:

use strict; use warnings; use AnyEvent::HTTPD; my $h = AnyEvent::HTTPD->new(port => 8123);; my $timer; $h->reg_cb ( '/test' => sub { my ($httpd,$req) = @_; $req->respond({ content => ['text/plain', sub { my ($data_cb) = @_; return unless $data_cb; print "Setting timer...\n"; $timer = AnyEvent->timer(after => 5, cb => sub { print "Sending response...\n"; $data_cb->("Hello"); $data_cb->(); }); }] }); }, ); my $c = AnyEvent::condvar; $c->recv();
Output:
Setting timer... Sending response... Setting timer... Sending response...
I was expecting the sub to only be called once. But it ends up being called a second time? Is there any way to avoid this? Thanks!

Replies are listed 'Best First'.
Re: AnyEvent::HTTPD -> Extra Callback after response?
by Corion (Patriarch) on Jun 11, 2021 at 05:56 UTC

    Is maybe your request to /test done twice? I'd add more debug output to the different levels of subroutines.

      I agree. It also may be helpful to know what program is used to make the request. Browsers, especially Google Chrome, is notorious for opening multiple connections at once "just in case any of them doesn't work".

      For testing basic HTTP stuff, i recommend installing LWP (cpan install LWP). This gives you the nice HEAD script. Then run
      HEAD -E http://localhost:8123/test

      This will dump the headers of the full response chain (e.g. multiple headers if there is a redirect or whatever).

      perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'
      It is not the client requesting twice. It does it even with LWP::Simple getprint. Note: I am running on windows.

      The first sub is only called on on each client connect. The second sub is gets called twice for reason.

      I can get around it as per below, by storing a flag to indicate I've already handled this 'request':

      use strict; use warnings; use AnyEvent::HTTPD; my $h = AnyEvent::HTTPD->new(port => 8123);; my $requests = {}; my $requestCounter = 0; $h->reg_cb ( '/test' => sub { my ($httpd,$req) = @_; my $request = $requestCounter; $requestCounter++; print "Starting request $request\n"; $requests->{$request}->{'state'} = 'new'; $req->respond({ content => ['text/plain', sub { my ($data_cb) = @_; return unless $data_cb; if (($requests->{$request}->{'state'} // 0) ne 'new') { $data_cb->(); return; } $requests->{$request}->{'state'} = 'handled'; print "Setting timer for $request...\n"; $requests->{$request}->{'timer'} = AnyEvent->timer(aft +er => 3, cb => sub { print "Sending response for $request...\n"; $data_cb->("You are request $request"); $data_cb->(); delete $requests->{$request}; return; }); }] }); }, ); my $c = AnyEvent::condvar; $c->recv();

        This seems to be either a timer or timeout+retry problem. If you respond right away, it works as expected.

        use strict; use warnings; use AnyEvent::HTTPD; my $h = AnyEvent::HTTPD->new(port => 8123);; my $requests = {}; my $requestCounter = 0; $h->reg_cb ( '/test' => sub { my ($httpd,$req) = @_; my $request = $requestCounter; $requestCounter++; print "Starting response for ", $req->{method}, " request $req +uest\n"; $req->respond( [200, 'ok', { 'Content-Type' => 'text/html' }, '<h1>Test</h1>' ]); } ); my $c = AnyEvent::condvar; $c->recv();

        Why do you want to wait instead of responding as soon as possible?

        If you want to wait for an event to happen, the modern way is to either use Websockets or Push notifications. If you can't do either of those (please explain why), then the proper fallback to handle this would be cyclic calls from client to server to "pull" not yet handled events.

        Leaving standard GET requests just "hanging" will lead to a number of different things:

        • Wasted resources on the server
        • Timeouts
        • Modern browsers marking the connection to your server as unreliable, therefore increasing the number of parallel TCP connections they will open to try and get a result.
        • Customer complaints
        • Getting shouted at by your system administrator

        perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'
Re: AnyEvent::HTTPD -> Extra Callback after response?
by navalned (Beadle) on Jun 12, 2021 at 03:44 UTC

    If you were trying from a browser I would guess its making a request for /test and perhaps a favicon.ico.

      I second the /favicon.ico explanation
        favicon comes in as a another request. In the code above it binds to '/test' so favicon doesn't trigger it. Also because it goes into timer, a favicon request can come and go independantly.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11133756]
Approved by kcott
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (5)
As of 2022-05-16 12:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you prefer to work remotely?



    Results (63 votes). Check out past polls.

    Notices?