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.
Re^3: Adding simple HTTP controls to existing code
by karlgoethebier (Abbot) on Jul 05, 2021 at 15:11 UTC
|
| [reply] [Watch: Dir/Any] |
|
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.
| [reply] [Watch: Dir/Any] [d/l] |
|
#!/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.)
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
|
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
}
| [reply] [Watch: Dir/Any] [d/l] |
|
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
| [reply] [Watch: Dir/Any] [d/l] |
|
|
|
| [reply] [Watch: Dir/Any] |
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.
| [reply] [Watch: Dir/Any] |
|
|