Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Re^4: Adding simple HTTP controls to existing code

by brachism (Novice)
on Jul 05, 2021 at 16:48 UTC ( #11134671=note: print w/replies, xml ) Need Help??


in reply to Re^3: Adding simple HTTP controls to existing code
in thread Adding simple HTTP controls to existing code

Sorry, for possibly being too verbose in this thread. First, thanks for replying.

I have a Perl program. The 1,300-ish line program runs on a headless box. It works great. It blissfully runs along managing a series of tasks. It also has absolutely nothing to do with the network.

I simply need that program to also listen on the network for very infrequent simple HTTP requests. Those requests, if valid, simply tweak parameters in that Perl program altering it functionality. The client, in this case is not Perl. The client is just some simple web browser passing a parameter within the URL. No parallel, one host, and one client.

The core of my program is basically an endless loop, that fires roughly every 200ms. If there is something for it to do, it does it. I just want to add into that loop, a listener for requests. If we got a request in the last 200-ish ms, process it, and continue on. If not, continue on.

The solutions I found online and tried, all so far seem to be blocking. In other words, when I check for a request, it just listens and waits forever. Some did have timeout features, but waiting and timeout out repeatedly for infrequent traffic just seems wasteful.

I just figured this would be easier. On a NodeMCU ESP8266 this is just a few lines of code.

#include <ESP8266WiFi.h> Void loop() { … if (WiFi.status() != WL_CONNECTED) ConnectWifi(); client = server.available(); if (client) { // HAVE A REQUEST while (!client.available()) delay(10); String request = client.readStringUntil('\r'); client.flush(); // PROCESS REQUEST } // CONTINUE DOING ALL THE NON-NETWORK STUFF sleep(50); … }

I will investigate Corion suggestions. This is basic stuff, so I'm sure I’ll figure out something.

Replies are listed 'Best First'.
Re^5: Adding simple HTTP controls to existing code
by tybalt89 (Prior) on Jul 05, 2021 at 17:46 UTC

    Something like this? Only handles a single request at a time, though.

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11134663 use warnings; use IO::Socket; use IO::Select; my $listen = IO::Socket::INET->new( LocalPort => 8080, # setup Listen => 256, Reuse => 1) or die $@; my $sel = IO::Select->new($listen); sub checkforrequests { for my $fh ( $sel->can_read(0) ) # 0 for poll { my $socket = $fh->accept; my $request = ''; 1 while sysread $socket, $request, 4096, length $request and not $request =~ /\n\r?\n/; # empty line test print $request =~ s/^/got HTTP packet: /gmr; # FIXME for testing # FIXME process request here print $socket "OK\n"; # FIXME sample response $request =~ /quit/ and die "exiting on 'quit'\n"; # FIXME for test +ing } } while( 1 ) # your loop as I understand it { select undef, undef, undef, 0.2; # FIXME your non-network stuff checkforrequests(); # this is added to your loop }

    I used wget to talk to it, like this

    wget -q -O - http://localhost:8080/CMD=changeplaylist

    and I get an output of

    got HTTP packet: GET /CMD=changeplaylist HTTP/1.1 got HTTP packet: User-Agent: Wget/1.21.1 got HTTP packet: Accept: */* got HTTP packet: Accept-Encoding: identity got HTTP packet: Host: localhost:8080 got HTTP packet: Connection: Keep-Alive got HTTP packet:

    A clever client could hang it if it tried. Should probably be done with a more extensive multi-connection handler, but they get bigger :)
    ( Which is why monks, including me, will suggest using an async package.)

      Excellent... For my basic need, this works. Thanks!

Re^5: Adding simple HTTP controls to existing code
by tybalt89 (Prior) on Jul 05, 2021 at 21:38 UTC

    For completeness (or overkill, if you prefer), here's a version with no blocking reads that can handle multiple clients with overlapped packets and only processes a packet when it is complete. It does that by using a hash to store partial data per connection until a complete packet arrives.

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11134663 use warnings; use IO::Socket; use IO::Select; my %data; my $listen = IO::Socket::INET->new( LocalPort => 8080, Listen => 256, Reuse => 1) or die $@; my $sel = IO::Select->new($listen); sub checkforrequests { for my $fh ( $sel->can_read(0) ) # 0 for poll { if( $fh == $listen ) { my $socket = $listen->accept; $data{$socket} = ''; $sel->add( $socket ); print "new client $socket\n"; } elsif( sysread $fh, $data{$fh}, 4096, length $data{$fh} ) { if( $data{$fh} =~ s/^(.*?\n\r?\n)//s ) # any whole HTTP commands { my $command = $1; print $command =~ s/^/got HTTP packet: /gmr; # FIXME process request here print $fh "OK\n"; # FIXME sample response shutdown $fh, 1; $command =~ /quit/ and die "exiting on 'quit' command\n"; } } else { delete $data{$fh}; $sel->remove( $fh ); print "client left $fh\n"; } } } while( 1 ) # your loop as I understand it { select undef, undef, undef, 0.2; # FIXME your non-network stuff checkforrequests(); # this is added to your loop }
Re^5: Adding simple HTTP controls to existing code
by huck (Prior) on Jul 05, 2021 at 18:33 UTC

    Mine is a little different

    use strict; use warnings; select (STDOUT); $| = 1; select(STDOUT); # http://127.0.0.1/exit # http://127.0.0.1/hi # http://127.0.0.1/null # http://127.0.0.1/unknown use IO::Handle; use IO::Socket; use IO::Select; my $server = IO::Socket::INET->new( Proto => 'tcp', PeerAddr => "127.0.0.1", # only + us LocalPort => 80, Listen => SOMAXCONN, Reuse => 1); die "can't setup server on 80" unless $server; my $sel_read = IO::Select->new(); my $sel_write = IO::Select->new(); my $sel_error = IO::Select->new(); $sel_read-> add($server); my $cmdrun={}; $cmdrun->{'/exit'} =sub{exit;} ; $cmdrun->{'/hi'} =sub{my $fh=shift; print $fh "\nhi\n";} ; $cmdrun->{'/null'} =sub{} ; while(1) { checkforrequests(); } # superloop exit; sub checkforrequests { my $timeout=1; my ($rd_ptr,$wr_ptr,$er_ptr)=IO::Select->select($sel_read,undef,$s +el_error,$timeout); for my $fh (@$rd_ptr) { if ($fh == $server) { my $client = $server->accept; $client->autoflush(1); $sel_read->add($client); } # server else { my $line=<$fh>; print "line:$line"; my ($junk,$post)=split(' ',$line,3); my $cmd=$cmdrun->{$post}; if ($cmd) {&$cmd($fh);} else { print $fh "\n\nNO\n"; } $sel_read->remove($fh); close $fh; } # client } # for fh } # checkforrequests

      Thanks Huck.

      I was able to quickly adapt Tybalt89's suggestion in my code. I will give your suggestion a try as well.

      Comparing and contrasting different solutions while learning is always helpful. Thanks again

        They have a similar base

        Mine does a two step between accept and read, a client CAN create the connection and wait a bit before writing, so mine wont block there. But in general http: clients shouldnt matter.

        His has a better read, the way i commonly use it is a lot like his, but i just collect data, then have another loop that looks for completed lines so i dont block on the \n. IN both of these examples we block on the \n read, mine till \n, his to blank line.

        I added a cmd lookup table for what to execute

        I only look at the first line sent.

        I added the /null command to show you what might happen if you dont send anything back

Re^5: Adding simple HTTP controls to existing code
by karlgoethebier (Abbot) on Jul 08, 2021 at 08:20 UTC
    «…This is basic stuff…»

    It isn’t. BTW and if it matters: Net::Curl::Simple is non-blocking:

    «Net::Curl excells in asynchronous operations, thanks to a great design of libcurl(3). To take advantage of that power Net::Curl::Simple interface allways uses asynchronous mode. If you want a blocking request, you must either set callback to undef or call join() method right away.»

    «The Crux of the Biscuit is the Apostrophe»

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://11134671]
help
Chatterbox?
and the web crawler heard nothing...

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



    Results (76 votes). Check out past polls.

    Notices?