Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re^2: Adding simple HTTP controls to existing code

by brachism (Novice)
on Jul 05, 2021 at 13:19 UTC ( [id://11134668]=note: print w/replies, xml ) Need Help??


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

Oversimplifying my use case, imagine a Perl program running on a headless Linux computer. A simple Jukebox playing random tracks from a play list, managed by parameters, endlessly running. Occasionally it also periodically plays announcements, such as song metadata, commercials, weather, etc between tracks.

I want to be able to send simple HTTP commands to the headless server to, for example, change playlists, advance to next, repeat current, reshuffle, or manage the frequency of announcements, etc.

These commands from a web browser (wget, curl, etc) via cell phone, computer, etc would take the form of http://host:port/CMD=XXXX. Where XXXX might be ATPN = play-next, ATRC = repeat-current; ATLP3 = load playlist #3; ATPP = pause playback; ATRP = resume playback, ATATF60 = adjust time announcement frequency to 60 minutes, etc. These command generally just modify variable in the host.

I do this often on Arduino/NodeMCU’s using the ESP8266WiFi.h library. In setup() I initialize the server. In loop() I simply check, validate, process, and reply to any client request each iteration.

Replies are listed 'Best First'.
Re^3: Adding simple HTTP controls to existing code
by karlgoethebier (Abbot) on Jul 05, 2021 at 15:11 UTC

      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.

        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.)

        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 }

        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
        «…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»

Re^3: Adding simple HTTP controls to existing code
by bliako (Monsignor) on Jul 06, 2021 at 11:56 UTC

    I misunderstood your use-case. You need something which listens+possibly requests, asynchronously and not something which does just requests asynchronously. So, above will be useful in replying to your clients.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (6)
As of 2024-03-28 14:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found