Re: Return a 200 0K status and send the content of the request later

Vincent Veyron <[email protected]> Fri, 16 May 2025 01:11:48 +0200
Newsgroups gmane.comp.apache.mod-perl
Message-ID <[email protected]>
This is a multi-part message in MIME format.

--Multipart=_Fri__16_May_2025_01_11_48_+0200_kyPRWsC.d8Y8Mb6y
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable

On Tue, 13 May 2025 22:57:05 -0400
Ed Sabol <[email protected]> wrote:

Hi Ed,

>=20
> Well, the standard way of doing that is you fork a child process to run t=
he system command while the parent process loops and prints a period (or wh=
atever you prefer) every 1 second (make sure you call flush() after printin=
g!) while waiting for the child process to finish.=20

Indeed. As it happens, the Paris Perl Mongueurs (http://paris.mongueurs.net=
/) held their monthly meeting last Wednesday night, and I requested advice =
from the fine members of the club. They rapidly concluded that's what I sho=
uld do.

I have a working prototype, it's modeled after https://stackoverflow.com/qu=
estions/471681/how-do-i-fork-properly-with-mod-perl2/. The page prints the =
asterisks while the fork builds the tar file, and adds the summary with fil=
e_size, interval, etc... when it detects the file.

It probably needs some polishing, critics welcome. Excuse my French.

https://pastebin.com/wn2Cw5KF

(not sure what line 114 'warn "started\n" ;' is for, since all STDs are red=
irected to /dev/null?)

>Lots of good examples of this can be found here:
>=20
> https://stackoverflow.com/questions/3193091/showing-progress-whilst-runni=
ng-a-system-command-in-perl/
>=20

Thank you for this. Interesting answer with waitpid, but have not tested yet

> I also recommend that you still put an upper time limit on your subproces=
s and not just loop infinitely. 30 minutes to an hour seems like a reasonab=
le choice. Depends on how big your tar files can be, of course.

Gee, just when I thought I was done with this ;-) But you're right

--=20
					Bien =E0 vous, Vincent Veyron

https://legalcase.libremen.com
Open source legal case, contract and insurance claims management software


--Multipart=_Fri__16_May_2025_01_11_48_+0200_kyPRWsC.d8Y8Mb6y
Content-Type: text/x-perl;
 name="export_raw_data.pm"
Content-Disposition: attachment;
 filename="export_raw_data.pm"
Content-Transfer-Encoding: quoted-printable

package Marica::Base::Rapports::export_raw_data ;

use utf8 ;

use strict ;

use POSIX 'setsid';

use Time::HiRes qw(gettimeofday tv_interval);

use Apache2::Const -compile =3D> qw(OK REDIRECT) ;

sub handler {

    binmode(STDOUT, ":utf8") ;

    my $r =3D shift ;

    my $req =3D Apache2::Request->new($r) ;

    #r=C3=A9cup=C3=A9rer les arguments
    my ( %args, @args ) ;

    @args =3D $req->param ;

    for (@args) {

	$args{$_} =3D Encode::decode_utf8( $req->param($_) ) ;

	#nix those sql injection/htmlcode attacks!
	$args{$_} =3D~ tr/<>;/-/ ;

	#les double-quotes viennent interf=C3=A9rer avec le html
	$args{$_} =3D~ tr/"/'/ ;

    }

    my $id_client =3D $r->pnotes('session')->{id_client} ;

    my $content =3D '<h1 style=3D"text-align: center;">' . _( 'Exportation =
des donn=C3=A9es', $r ) . '</h1>' ;

    #nom du r=C3=A9pertoire de collecte des fichiers/donn=C3=A9es/documents=
 dans /base/listing
    my $token_id =3D map +(0..9,"a".."z","A".."Z")[rand(10+26*2)], 1..32 ;
   =20
    my $recipient_dir =3D $r->document_root() . '/base/listing/' . $r->pnot=
es('session')->{_session_id} ;

    my $result_file_name =3D  $r->pnotes('session')->{_session_id} ;

    my $final_file_name =3D '' ;

    #effacer les listings pr=C3=A9c=C3=A9dents de la session pour d=C3=A9ma=
rrer propre
    #il faut le faire avant de lancer le fork, sinon le test -e d=C3=A9tect=
e le fichier pr=C3=A9c=C3=A9dent encore pr=C3=A9sent
    $ENV{'PATH'} =3D '/bin:/usr/bin' ;

    delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

    my @args =3D ( 'rm', '-r', $r->document_root() . '/base/listing/' . $r-=
>pnotes('session')->{_session_id} ) ;

    system(@args) =3D=3D 0 or warn "system @args failed: $?" ;

    my @args =3D ( 'rm', $r->document_root() . '/base/listing/' . $r->pnote=
s('session')->{_session_id} . '.tar.gz' ) ;

    system(@args) =3D=3D 0 or warn "system @args failed: $?" ;

    if ( defined $args{go_for_it} ) {
=09
	$ENV{'PATH'} =3D '/bin:/usr/bin' ;

	delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

	$r->content_type('text/html; charset=3Dutf-8') ;

	$r->print('<!DOCTYPE html><html lang =3D "fr"><head><meta http-equiv=3D"Co=
ntent-Type" content=3D"text/html; charset=3Dutf-8"><title>' . $r->hostname =
. '</title></head><body><h3>Building tar file</h3><p>*') ;
=09
	$r->rflush ; #clear the request buffer

	my $t0 =3D [gettimeofday];
=09
	#on s=C3=A9lectionne la base qui va bien
	my @databases =3D $r->dir_config->get('db_name') ;
=09
	my $demo_user_name =3D $r->dir_config('demo_username') ;

	my $database =3D ( $r->pnotes('session')->{username} =3D~ /$demo_user_name=
/ ) ? $databases[1] : $databases[0] ;

	#on fait un fork pour lancer le long process
	#le fork cr=C3=A9e le .tar.gz =C3=A0 t=C3=A9l=C3=A9charger
	$SIG{CHLD} =3D 'IGNORE';
=09
	defined (my $kid =3D fork) or die "Cannot fork: $!\n" ;

	if ($kid) {
	   =20
	    #print "Parent $$ has finished, kid's PID: $kid\n" ;

	} else {

	    # chdir to '/' stops the process from preventing an unmount
	    chdir '/'                 or die "Can't chdir to /: $!" ;

	    open STDIN, '/dev/null'   or die "Can't read /dev/null: $!" ;

	    open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!" ;

	    open STDERR, '>/tmp/log'  or die "Can't write to /tmp/log: $!" ;

	    setsid                    or die "Can't start a new session: $!" ;

	    my $oldfh =3D select STDERR ;

	    local $| =3D 1 ;

	    select $oldfh ;
#	    warn "started\n" ;

	    #cr=C3=A9er les r=C3=A9pertoires de stockage
	    mkdir $recipient_dir ;
	    mkdir '/tmp/marica_data' ;

	    #dump du sch=C3=A9ma de la base
	    my @args =3D ('pg_dump', '-s', '-f', $recipient_dir . '/marica.out', $=
database) ;

	    system(@args) =3D=3D 0 or warn "system @args failed: $?" ;

	    #la commande d'exportation des donn=C3=A9es du client ressemble =C3=A0=
 =C3=A7a:
	    #psql -f /home/lib/Marica/Base/Procedures/Reversibility/export_raw_dat=
a.sql -v id_client=3D13 -v database=3Dmarica postgres
	    #on cr=C3=A9e provisoirement les fichiers data dans /tmp/marica_data
	    @args =3D ('psql', '-f', '/home/lib/Marica/Base/Procedures/Reversibili=
ty/export_raw_data.sql', '-v', 'id_client=3D' . $r->pnotes('session')->{id_=
client}, '-v',  'database=3D' . $database, 'postgres') ;

	    system(@args) =3D=3D 0 or warn "system @args failed: $!" ;

	    #if faut les d=C3=A9placer dans $recipient_dir
	    @args =3D ('mv', '/tmp/marica_data', $recipient_dir . '/data') ;

	    system(@args) =3D=3D 0 or warn "system @args failed: $!" ;

	    #copier le fichier create_db.sh et remplacer =3Did_client par la bonne=
 valeur
	    my $id_client =3D $r->pnotes('session')->{id_client} ;

	    my $script ;

	    open (my $fh_in, "<:encoding(UTF-8)", '/home/lib/Marica/Base/Procedure=
s/Reversibility/create_db.sh') or die "Can't open create_db.sh : $!" ;

	    while ( <$fh_in> ) {

		$script .=3D $_;
	=09
	    }

	    $script =3D~ s/=3Did_client/=3D$id_client/ ;
	   =20
	    close $fh_in ;
	   =20
	    #cr=C3=A9ation du fichier pour le client
	    my $out_file =3D  $recipient_dir . '/create_db.sh' ;

	    open (my $fh_out, ">:encoding(UTF-8)", $out_file) or warn "Can't open =
$out_file : $!" ;

	    #ajouter le BOM pour que les tableurs s'ouvrent avec le bon encodage (=
utf8)
	    #on peut aussi utiliser chr(65279);
	    #MS-Office a besoin de =C3=A7a pour identifier l'encodage
	    print $fh_out chr(0xFEFF) ;

	    print $fh_out $script ;

	    close $fh_out ;
	   =20
	    #copier le fichier import_raw_data.sql
	    @args =3D ('cp', '/home/lib/Marica/Base/Procedures/Reversibility/impor=
t_raw_data.sql', $recipient_dir) ;

	    system(@args) =3D=3D 0 or warn "system @args failed: $!" ;

	    #cr=C3=A9ation du fichier tar compress=C3=A9
	    my $temp_name =3D $result_file_name . '.tmp' ;
	   =20
	    @args =3D ( 'tar', '-czf', $r->document_root() . '/base/listing/' . $t=
emp_name, $recipient_dir) ;

	    system(@args) =3D=3D 0 or warn "system @args failed: $!" ;

	    #renommer proprement le fichier tar pour d=C3=A9tection par la boucle =
do { }
	    $final_file_name =3D $result_file_name . '.tar.gz' ;

	    rename $r->document_root() . '/base/listing/' . $temp_name , $r->docum=
ent_root() . '/base/listing/' . $final_file_name ;

	    CORE::exit(0); # terminate the process

	} #	if ($kid)=20

	#pendant que le fork travaille, on v=C3=A9rifie si le fichier .tar.gz fina=
l existe
	#tant qu'il n'est pas pr=C3=A9sent, on envoie un nouvel =C3=A9l=C3=A9ment =
toutes les secondes
	my $i =3D 1 ;
=09
	do {

	    my $final_result =3D $r->document_root() . '/base/listing/' . $result_=
file_name . '.tar.gz' ;

	    $i =3D 0 if -e $final_result ;
	   =20
	    $r->print('*') ;

	    $r->rflush; #clear the request buffer

	    sleep( 1 ) ;
	   =20
	} while ( $i ) ;

	$r->print( '</p>' ) ;
=09
	$r->rflush; #clear the request buffer
=09
	my $t1 =3D [gettimeofday] ;

	my $t0_t1 =3D tv_interval $t0, $t1 ;

	my $file_size =3D -s $r->document_root() . '/base/listing/' . $result_file=
_name . '.tar.gz' ;

	$content =3D '<pre>
using db : ' . $database . '
recipient_dir : ' . $recipient_dir . '
file size : ' . $file_size . '=20
interval : ' . $t0_t1 . '
    </pre>' ;

	my $download_link =3D '<a href=3D"/base/listing/' . $result_file_name . '.=
tar.gz">T=C3=A9l=C3=A9charger ' . $result_file_name . '.tar.gz</a>' ;
=09
	my $download_zone =3D '
<h3>Lien</h3>
<p>' . $download_link . '
</p></body>' ;

	$content .=3D $download_zone ;
=09
	$r->print( $content ) ;

	return Apache2::Const::OK ;

    } else {

	$content .=3D presentation($r) ;

    } #    if ( defined $args{go_for_it} )

    $r->content_type('text/html; charset=3Dutf-8') ;

    $r->no_cache(1) ;

    print $content ;

    return Apache2::Const::OK ;
   =20
}

1 ;


sub presentation {

    my $r =3D shift ;

    my $content =3D '<h2>Instructions</h2>' ;

    $content .=3D '<p>Cette proc=C3=A9dure cr=C3=A9e un fichier contenant t=
outes les donn=C3=A9es stock=C3=A9es dans la base pour votre compte</p>' ;

    $content .=3D '<p>Ce fichier est une archive tar contenant :</p>' ;

    $content .=3D '
<ul>
<li>le fichier create_db.sh qui cr=C3=A9e la base de donn=C3=A9es Postgresq=
l pour accueillir les donn=C3=A9es (nomm=C3=A9e "import_raw_data")</li>
<li>le fichier marica.out contenant le schema de la base de donn=C3=A9es</l=
i>
<li>le fichier import.sql qui importe les donn=C3=A9es</li>
<li>le r=C3=A9pertoire data qui contient les donn=C3=A9es (s=C3=A9parateur =
de donn=C3=A9es "TAB")</li>
</ul>' ;

    $content .=3D '<h3>Utilisation</h3>
<ul>
<li>Cr=C3=A9er le r=C3=A9pertoire /tmp/marica</li>
<li>Extraire de l\'archive tar les 4 =C3=A9l=C3=A9ments ci-dessus, et les p=
lacer dans /tmp/marica </li>
<li>Rendre create_db.sh executable</li>
<li>Executer create_db.sh en tant que super-utilisateur</li>
</ul>' ;

   =20
    my $form =3D '
<form action=3Dexport_raw_data method=3DPOST>
<p><input type=3Dsubmit value=3D"Exporter les donn=C3=A9es"><input type=3Dh=
idden name=3Dgo_for_it value=3D0></p>
</form>
    ' ; =20

    $content .=3D $form ;
   =20
    return $content ;
   =20

} #sub presentation


--Multipart=_Fri__16_May_2025_01_11_48_+0200_kyPRWsC.d8Y8Mb6y--