hotti: DBI::errstr fetch() without execute()

Mahlzeit,

derzeit schreibe ich einen kommandozeilen-orientierten PMA (Perl My Admin), da übergebe ich das Statement per POST, untenstehend der entsprechende Zweig mit Perl-Code:

  
		elsif($aktion eq 'sql'){  
			read(STDIN, my $st, $ENV{CONTENT_LENGTH});  
			my $aref = $dbo->{DBH}->selectall_arrayref($st);  
			if($DBI::errstr){  
				print "\nFehler: $DBI::errstr\n";  
			}  
			else{  
				print "Ergebnis: \n\n";  
				foreach my $r(@$aref){  
					print join("\t", @$r),"\n";  
				}  
			}  
  

Nun ist es so, dass ein create table-Statement oder ein drop table-Statement zwar ausgeführt wird, aber DBI einen $DBI::errstr wirft, siehe Betreff. Was bedeutet dieser $DBI::errstr?

Bitte mal um Hinweise,
Hotti

  1. $DBI::errstr ist eine globale Variable, die im Fehlerfall gesetzt wird - frag also erst den Fehlerfall ab, bevor Du die Variable auswertest... UND RTFM!

    1. Moin,

      $DBI::errstr ist eine globale Variable, die im Fehlerfall gesetzt wird - frag also erst den Fehlerfall ab, bevor Du die Variable auswertest... UND RTFM!

      Auf jeden Fall möchte ich es nicht versäumen, mich für diese arrogante Antwort zu bedanken. Was hast Du denn in The F* Manual gefunden zu diesem Thema?

      Hotti

      --
      Wenn der Kommentar nicht zum Code passt, kann auch der Code falsch sein.
  2. Nun ist es so, dass ein create table-Statement oder ein drop table-Statement zwar ausgeführt wird, aber DBI einen $DBI::errstr wirft, siehe Betreff. Was bedeutet dieser $DBI::errstr?

    Vermutlich weil ein selectall_arrayref nicht nur ein execute ausführt, sondern auch versucht die Daten zu lesen, die es aber bei einem DROP oder CREATE TABLE nicht gibt.

    Struppi.

    1. Nun ist es so, dass ein create table-Statement oder ein drop table-Statement zwar ausgeführt wird, aber DBI einen $DBI::errstr wirft, siehe Betreff. Was bedeutet dieser $DBI::errstr?

      Vermutlich weil ein selectall_arrayref nicht nur ein execute ausführt, sondern auch versucht die Daten zu lesen, die es aber bei einem DROP oder CREATE TABLE nicht gibt.

      Ja, mein Lieber, das ist richtig. Es geht eigentlich eher um die Programmiertechnik, diese verschiedenen 'Arten' von Statements so zu handeln, dass stets ein stabiler Zustand des Scripts sichergestellt ist mit unmissverständlichen Ausgaben an den DAU ;)

      Also, ich habs hinbekommen mit 'RTFM', und da steht was von '0E0' für genau diesen Fall, dass keine Rows zu erwarten sind. Habe fertisch,

      Grüße aus Oppenheim,
      Hotti

      === Perl My Admin =============
      SQL an Host: rolfrost.de
      Mehrzeilige Eingabe, senden mit ^Z in neuer Zeile

      show tables
      ^Z
      DB-Verbindung OK
      Aktion: sql
      Ergebnis: 6 Rows

      book
      log
      moon
      onkz
      unicode
      vendors

      Script beenden mit ^Z oder neues mehrzeiliges Statement eingeben und senden mit
      ^Z in neuer Zeile

      create table test(name varchar(1))
      ^Z
      DB-Verbindung OK
      Aktion: sql

      Statement OK

      Script beenden mit ^Z oder neues mehrzeiliges Statement eingeben und senden mit
      ^Z in neuer Zeile

      drop table test
      ^Z
      DB-Verbindung OK
      Aktion: sql

      Statement OK

      Script beenden mit ^Z oder neues mehrzeiliges Statement eingeben und senden mit
      ^Z in neuer Zeile

      drop table test
      ^Z
      DB-Verbindung OK
      Aktion: sql

      Fehler: Unknown table 'test'

      Script beenden mit ^Z oder neues mehrzeiliges Statement eingeben und senden mit
      ^Z in neuer Zeile

  3. einen kommandozeilen-orientierten PMA (Perl My Admin)

    Gibt's schon. Sag mal, möchtest du nicht endlich Teil der Community werden und *nützliche* Dinge mit Perl anstellen?

    1. Moin,

      einen kommandozeilen-orientierten PMA (Perl My Admin)
      Gibt's schon.

      Das ist ja das Schlimme, irgendwie gibt es alles schon. Meiner ist schöner ;)
      Das Script ist schlappe zwei A4-Seiten lang und wird auf der Kdo-Zeile gestartet:

      d:\home>pma.pl -?
      Perl My Admin, Options
      -h Hostname
      -u Benutzername .htaccess
      -p Passwort     .htaccess
      -m Masterscript relativ like /cgi-bin/script

      und ist mit CPAN-Modulen IO::Handle, HTTP::Headers, HTTP::Request und LWP::UserAgent gebaut. Nach dem Start gibts eine shell, die mit einem CGI-Script auf dem Server kommuniziert, das CGI wiederum redet mit dem MySQL-Server. Der Umweg über HTTP deswegen, weil der Provider den Zugriff zum MySQL-Server nur vom Webserver aus erlaubt.

      Sag mal, möchtest du nicht endlich Teil der Community werden und *nützliche* Dinge mit Perl anstellen?

      Klar, freilich doch! Gib Dich doch mal zu erkennen, kurze Mail an mich, das würde mich sehr freuen!!! Vielleicht gibt es eine Nische, wo ich mal was entwickeln kann, im Moment habe ich ein bischen Zeit.

      Hotti

      --
      1;
      1. Ich schlage vor, du besuchst die Perlmongers in Darmstadt und Frankfurt. Der SDL-Wettbewerb kann derzeit noch Mitstreiter gebrauchen.

        1. Ich schlage vor, du besuchst die Perlmongers in Darmstadt und Frankfurt.

          Ok, mach ich! Das ist ja nicht weit von mir ;)

          Der SDL-Wettbewerb kann derzeit noch Mitstreiter gebrauchen.

          Mal sehen, was mir möglich ist, danke für die Links (Studium heute abend).

          Btw., eine 'Nische' in CPAN hätte ich schon, LWP::UserAgent::?

          Wobei '?' für 'IO' oder 'Handle' stehen könnte. Hintergrund: Die Reponse wird nicht als Scalar geliefert sondern in einem Handle bereitgestellt, woraus z.B. eine binäre Datenstruktur (Sequenz, Bytes) gelesen werden kann. Denkbar wäre auch IO::Handle als Baseclass. In welchen Zweig der auf CPAN vorhandenen Klassenhierarchie würdest Du das einhängen? Oder gibts das schon?

          Als Workaround könnte $LWP::UserAgent::response->content in ein temporäres IO::Handle geschrieben werden (Wrapper).

          Hotti

          --
          Wenn der Kommentar nicht zum Code passt, kann auch der Code falsch sein.
          1. Moin Moin!

            Btw., eine 'Nische' in CPAN hätte ich schon, LWP::UserAgent::?

            Wobei '?' für 'IO' oder 'Handle' stehen könnte. Hintergrund: Die Reponse wird nicht als Scalar geliefert sondern in einem Handle bereitgestellt, woraus z.B. eine binäre Datenstruktur (Sequenz, Bytes) gelesen werden kann. Denkbar wäre auch IO::Handle als Baseclass. In welchen Zweig der auf CPAN vorhandenen Klassenhierarchie würdest Du das einhängen? Oder gibts das schon?

            Als Workaround könnte $LWP::UserAgent::response->content in ein temporäres IO::Handle geschrieben werden (Wrapper).

            Die Nische ist schon gefüllt mit IO::Any und IO::All, und ja, das müffelt nach PHP und reißt bei unsachgemäßer Anwendung riesige Lücken.

            Zu neuen Modulen: perlnewmod: Preparing the ground Diskutiere Ideen für neue Module auch mal bei perlmonks.

            Alexander

            --
            Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
            1. Moin, mooin,

              Als Workaround könnte $LWP::UserAgent::response->content in ein temporäres IO::Handle geschrieben werden (Wrapper).

              Die Nische ist schon gefüllt mit IO::Any und IO::All, und ja, das müffelt nach PHP....

              Nicht wirklich, igitt ;)

              Mein Wrapper, soeben getestet:

                
              # UserAgent  
              my $ua = LWP::UserAgent->new;  
              # den Request formal zusammenbauen  
              my $req = HTTP::Request->new('GET', $url, $head);  
              # Request senden  
              my $res = $ua->request($req);  
              # Response in TmpHandle schreiben  
              my $th = TmpHandle->new;  
              print $th $res->content;  
              $th->rewind;  
                
              # ab hier kann die Sequenz bytegenau gelesen werden  
                
              
              

              und reißt bei unsachgemäßer Anwendung riesige Lücken.

              Naja, mein TmpHandle muss ich auch noch verbessern.

              Zu neuen Modulen: perlnewmod: Preparing the ground Diskutiere Ideen für neue Module auch mal bei perlmonks.

              Heut' abend...

              Alexander

              Sag mal, bist du aus HH?

              Horst

              1. Moin Moin!

                Mein Wrapper, soeben getestet:

                UserAgent

                my $ua = LWP::UserAgent->new;

                den Request formal zusammenbauen

                my $req = HTTP::Request->new('GET', $url, $head);

                Request senden

                my $res = $ua->request($req);

                Response in TmpHandle schreiben

                my $th = TmpHandle->new;
                print $th $res->content;
                $th->rewind;

                ab hier kann die Sequenz bytegenau gelesen werden

                  
                Was zum Geier macht TmpHandle?  
                  
                In-Memory à la `open my $handle,'>',\$buffer`{:.language-perl}? Das gibt's schon zweimal fertig, nennt sich [IO::Scalar](http://search.cpan.org/perldoc?IO::Scalar) bzw. [IO::Stringy](http://search.cpan.org/dist/IO-stringy/) (ja, das ist mittlerweile ein einziges Paket). Prüfst Du, ob Du überhaupt genügend Speicher hast? Oder verläßt Du Dich darauf, dass Perl sich beim OS so viel realen und virtuellen Speicher holen kann, wie gerade nötig? Notfalls durch exzessives Swapping? Nach 2 bis 3 GByte ist (32-bittiges Perl vorausgesetzt) ohnehin Schluß, weil Dir die Adressbits ausgehen. Und Perl gibt keinen Speicher ans OS zurück. Selbst dann nicht, wenn Du die riesigen Variablen wieder löschst.  
                  
                Oder eine temporäre Datei? Auch die gibt's ferig, heißt [File::Temp](http://search.cpan.org/perldoc?File::Temp) und im Gegensatz zu selbstgefrickelten Lösungsversuchen kann man File::Temp tatsächlich SICHER benutzen, wenn man sich an die Spielregeln hält (lokales Laufwerk, nur mit Handles arbeiten, anonyme Files benutzen wenn das OS mitspielt). Ein geeignetes übersetztes Perl (mit large file support, egal ob 32 oder 64 Bit) kann Daten bis zur Größe des lokalen Dateisystems verarbeiten, auf jeden Fall mehrere TByte. Ohne dass Dir RAM und/oder Swap ausgehen. Der Haken ist, dass der Zugriff u.U. etwas langsamer ist. Wenn das OS nicht völlig merkbefreit ist, wird es Deine Arbeitsdatei ohnehin erst einmal im Cache (d.h. im RAM) halten, auch wenn es die Daten im Hintergrund in den rotierenden Rost der Festplatte meißeln läßt.  
                  
                
                > > und reißt bei unsachgemäßer Anwendung riesige Lücken.  
                > Naja, mein TmpHandle muss ich auch noch verbessern.  
                  
                Nee, durch bewährten, getesteten und funktionierenden Code ersetzen.  
                  
                
                > > Zu neuen Modulen: [perlnewmod: Preparing the ground](http://perldoc.perl.org/perlnewmod.html#Step-by-step:-Preparing-the-ground) Diskutiere Ideen für neue Module auch mal bei [perlmonks](http://www.perlmonks.org/).  
                >   
                > Heut' abend...  
                  
                ... und einige Tage länger. Diskutiere mal wirklich bis zum Ende, ob das, was Du gerade bastelst, nicht längst fertig irgendwo bei CPAN rumliegt.  
                  
                
                > > Alexander  
                > Sag mal, bist du aus HH?  
                  
                Nö, aber seit einigen Jahren in HH.  
                  
                Alexander
                
                -- 
                Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
                
                1. Moin, moin,

                  Was zum Geier macht TmpHandle?

                  Das habe ich gerade eben in die Tonne getreten! Mein neues 'Opfer' heißt ab heute und sofort:

                  Net::HTTP - Low-level HTTP connection (client)

                  ... und Schluss mit dem Gefrickel ;)

                  Schönes Wochenende,
                  Hotti

          2. Oder gibts das schon?

            Natürlich. Während des Lesens der HTTP-Response lässt sich der Inhalt bereits stückchenweise mit :content_cb auslesen, oder wenn das schon komplett ist und als Scalar vorliegt, mit open als Handle öffnen.

  4. Moin Moin!

      elsif($aktion eq 'sql'){  
    
      
    Och nö! Laß mich raten: Diese `elsif ($action eq 'irgendwas')`{:.language-perl} stapelst Du vom Fundament bis über das Dach. Sagt Dir der Begriff "Dispatch Table" etwas? [LMGTFY](http://www.google.com/search?&q=dispatch+table+site%3Aperlmonks.org).  
      
      
    
    > ~~~perl
      
    
    > 			read(STDIN, my $st, $ENV{CONTENT_LENGTH});  
    > 
    
    

    Bastelst Du Dir Deine eigene CGI-Schnittstelle? OK, das kann ich im Ansatz nachvollziehen, denn die Interna von CGI sind nicht sonderlich hübsch, voller Altlasten, und das ganze HTML-Gebastel gehört dort überhaupt nicht rein. Aber deswegen gibt es auch einige Alternativen, wie z.B. CGI::Minimal, CGI::Simple, CGI::Lite. Und wenn Du Dich auf ein Framework wie Catalyst oder Plack einläßt, bekommst Du quasi gratis oben drauf noch Support für mod_perl, FastCGI und auch Standalone-Webserver.

      	my $aref = $dbo->{DBH}->selectall_arrayref($st);  
      	if($DBI::errstr){  
      		print "\nFehler: $DBI::errstr\n";  
      	}  
    
      
    Kaputt.  
      
    Warum nutzt Du nicht $dbo->{'DBH'}->errstr()? Die globalen Variablen werden quasi ständig überschrieben, die [DBI-Dokumentation warnt davor](http://search.cpan.org/~timb/DBI-1.616/DBI.pm#DBI_Dynamic_Attributes), sie zu benutzen: "Warning: these attributes are provided as a convenience but they do have limitations. Specifically, they have a short lifespan: because they are associated with the last handle used, they should only be used immediately after calling the method that "sets" them. If in any doubt, use the corresponding method call."  
      
    Warum nutzt Du nicht [RaiseError](http://search.cpan.org/~timb/DBI-1.616/DBI.pm#RaiseError), wie in der DBI-Doku empfohlen? Damit mußt Du überhaupt nicht mit den error-Methoden herumfrickeln, wenn etwas schief läuft, löst DBI eine Exception aus.  
      
    
    > ~~~perl
      
    
    > 			else{  
    > 				print "Ergebnis: \n\n";  
    > 				foreach my $r(@$aref){  
    > 					print join("\t", @$r),"\n";  
    > 				}  
    > 			}  
    > 
    
    

    Warum müllst Du den Speicer mit einem Ergebnis voll, dass Du ohnehin zeilenweise benutzen willst?

    Nutze eine while-Schleife in Kombination mit fetchrow_arrayref() alias fetch() oder meinetwegen fetchrow_array(). So hast Du immer nur eine Zeile im Speicher.

    Für maximale Geschwindigkeit und minimales Umkopieren kannst Du fetch() in Kombination mit bind_col() nutzen, auf die Art schreibt DBI gleich in die Variable, mit der Du (typischerweise innerhalb der while-Schleife) weiterarbeiten willst.

    Was bedeutet dieser $DBI::errstr?
    Bitte mal um Hinweise,

    RTFM. Mehrfach.

    Die DBI-Doku ist alles andere als trivial, und man merkt ganz klar, dass das DBI einen weiten Weg gegangen ist. Aber wenn Du mit dem DBI arbeiten willst oder mußt, solltest Du sie mehr als nur einmal gelesen und große Teile verstanden haben. Die Mailing-Liste DBI-Users solltest Du Dir vielleicht auch antun.

    Einige Anmerkungen zur DBI-Doku aus langjähriger Erfahrung:

    Parameter gehören auf gar keinen Fall ins SQL, sondern haben immer via Platzhalter übergeben zu werden. Das steht leider nicht so deutlich in der DBI-Doku. Deswegen ist die $dbh->quote()-Methode auch vollkommen überflüssig bis gefährlich, und sollte auf lange Sicht verschwinden oder nur noch DBDs zugänglich sein.

    $dbh->quote_identifier() ist eine völlig andere Geschichte, aber ebenfalls mit Vorsicht zu genießen. Wenn man sich bei Identifiern auf /^[A-Za-z][A-Za-z0-9_]+$/ beschränkt, kann man sich das Quoting sparen (und zwar komplett, schon gar keine Backticks à la MySQL!), SQL ist case insensitive, und alles ist schön. Mit quote_identifier() wird SQL plötzlich case sensitive (jedenfalls bei einigen RDBMS) und häßliche Dinge können passieren. Auch das steht nicht so deutlich in der Doku.

    $dbh->do(...) einerseits und $sth=$dbh->prepare(); $sth->execute(); ... $sth->finish(); andererseits sind nicht zwingend identisch, auch wenn der erste Satz der Beschreibung von $dbh->do() das vermuten läßt. DBDs können durchaus Abkürzungen nehmen, und mindestens neuere Versionen von DBD::ODBC tun das auch.

    Viele DBDs unterstützen mittlerweile Unicode, DBD::Oracle schon recht lange, DBD::Pg auch schon eine ganze Weile, DBD::ODBC seit MJE meinen Patch eingepflegt und die Weiterentwicklung übernommen hat, DBD::mysql mittlerweile auch, bei anderen DBDs sieht es etwas schlechter aus. Fast immer muß man wenigstens über ein DBD-spezifisches Attribut den Unicode-Support einschalten, denn aus Kompatibilitätsgründen ist der Standardwert aus. Manche DBs brauchen außerdem noch ein paar Extras in den DB-Einstellungen (z.B. Oracle und PgSQL). Wenn Unicode einmal aktiviert ist, kann man direkt Unicode in die DB schreiben und wieder lesen, ohne sich mit Encode herumschlagen zu müssen. Und andere Tools, die auf der DB aufsetzen (und natürlich die DB selbst), sehen ebenfalls Unicode und nicht irgendwelche wirren Byte-Folgen.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
    1. Moin,

      [..]

      He, Du packst mich an meinem Ehrgeiz (ist ok). DBI ist wirklich nicht trivial, aber ich mache schon Fortschritte.

      Schönes Wochenende,
      Hotti

    2. Hello again,
      etwas ausführlicher,

        elsif($aktion eq 'sql'){  
      
      
      >   
      > Och nö! Laß mich raten: Diese `elsif ($action eq 'irgendwas')`{:.language-perl} stapelst Du vom Fundament bis über das Dach. Sagt Dir der Begriff "Dispatch Table" etwas? [LMGTFY](http://www.google.com/search?&q=dispatch+table+site%3Aperlmonks.org).  
        
      Ja, natürlich, damit habe ich auch schon gearbeitet, der Code schrumpft zusammen, aber ist später u.U. schwerer nachzuvollziehen, da sind Kontrollstrukturen manchmal besser.  
        
      
      > > ~~~perl
        
      
      > > 			read(STDIN, my $st, $ENV{CONTENT_LENGTH});  
      > > 
      
      

      Bastelst Du Dir Deine eigene CGI-Schnittstelle?

      Nein. Ich mache nur den Body frei von Control-Elementen, die der UA schickt. Es ist eine Client-Server-Anwendung zum Datenbank-Management über HTTP. Der body kann verschiedene Inhalte haben, auch Binaries oder reine SQL-Statements. Was am Server zu tun ist, wird im Custom-Request-Header übergeben (Control) und der Body wird Bytegenau aus STDIN gelesen. Da STDIN ein Handle ist, können mit read() auch Sequenzen gelesen werden, also nicht über {CONTENT_LENGTH}, sondern auch mal so:

      read(STDIN, my $record, $record_len);

        	my $aref = $dbo->{DBH}->selectall_arrayref($st);  
        	if($DBI::errstr){  
        		print "\nFehler: $DBI::errstr\n";  
        	}  
      
      
      >   
      > Kaputt.  
      >   
      > Warum nutzt Du nicht $dbo->{'DBH'}->errstr()? Die globalen Variablen ..  
        
      Das ist eine Altlast, hier bin ich gerade dabei, die DB-Zugriffe in Subklassen mit eigenen Namespaces zu organisieren, da wird $dbo->{DBH} nicht mehr verwendet, sondern es werden nur noch Methoden der Subklasse aufgerufen, das betrifft auch die Fehlerbehandlung.  
        
      
      > Warum nutzt Du nicht [RaiseError](http://search.cpan.org/~timb/DBI-1.616/DBI.pm#RaiseError), wie in der DBI-Doku empfohlen?  
        
      Coming soon ;)  
        
      
      > Warum müllst Du den Speicer mit einem Ergebnis voll, dass Du ohnehin zeilenweise benutzen willst?  
        
      Es sind kleine Datenmengen, da darf ich auch maln komplettes Array in den RAM legen.  
        
      
      > Nutze eine while-Schleife in Kombination mit [fetchrow_arrayref() alias fetch()](http://search.cpan.org/~timb/DBI-1.616/DBI.pm#fetchrow_arrayref) oder meinetwegen [fetchrow_array()](http://search.cpan.org/~timb/DBI-1.616/DBI.pm#fetchrow_array). So hast Du immer nur eine Zeile im Speicher.  
        
      
      > Für maximale Geschwindigkeit und minimales Umkopieren kannst Du fetch() in Kombination mit [bind_col()](http://search.cpan.org/~timb/DBI-1.616/DBI.pm#bind_col) nutzen, auf die Art schreibt DBI gleich in die Variable, mit der Du (typischerweise innerhalb der while-Schleife) weiterarbeiten willst.  
        
      Wenn ein DB-Abfrage-Ergebnis zum Browser geht, hat die Anzahl der Zeilen \_immer einen endlichen Wert, das darf ich als Array in den Speicher legen. Mit fetchrow\_ zu arbeiten, hieße: Ausgabe der Daten mit der Darstellung zu vermischen, aber ich trenne das lieber. Liegt das Ergebnis als Array im Speicher, wird das zur Darstellung nicht kopiert, sondern eine Referenz übergeben, dann erst gehts zur Darstellung (HTML).  
        
      Anders ist es beim Management, da gehen schonmal Binaries mit einigen MB vom Server raus auf STDOUT und fetchrow\_ wäre serverseitig angebracht. Clientseitig wäre es natürlich ungeschickt, eine solche HTTP-Response erst als String UA-seitig in ein temp. Handle zu schreiben, naheliegender ist es doch, gleich die Response aus dem Socket zu lesen.  
        
      Hotti  
        
        
      
      
      1. Moin Moin!

        Hello again,
        etwas ausführlicher,

          elsif($aktion eq 'sql'){  
        
        
        > >   
        > > Och nö! Laß mich raten: Diese `elsif ($action eq 'irgendwas')`{:.language-perl} stapelst Du vom Fundament bis über das Dach. Sagt Dir der Begriff "Dispatch Table" etwas? [LMGTFY](http://www.google.com/search?&q=dispatch+table+site%3Aperlmonks.org).  
        >   
        > Ja, natürlich, damit habe ich auch schon gearbeitet, der Code schrumpft zusammen, aber ist später u.U. schwerer nachzuvollziehen, da sind Kontrollstrukturen manchmal besser.  
          
        Geschmackssache. Ich hab mittlerweile meine Lektion in Sachen Wartbarkeit gelernt und verkneife mir Routinen, die über 20 Bildschirmseiten gehen. Manche predigen angesichts von High-Tech-Equipment wie dem [VT52](http://en.wikipedia.org/wiki/VT52), dass eine Routine mit allem Brimborium nicht mehr als 24 Zeilen zu je maximal 80 Zeichen belegen darf. Für Frischlinge, die Software-Entwicklung gerade lernen, ist das eine zwar unbequeme, aber gute Regel. Nur muß man lernen, wann man sie brechen darf. Ich lege eine Bildschirmseite mittlerweile als etwa 50 Zeilen à 100 Zeichen aus, das ist so in etwa die nutzbare Fläche in meinem Standard-Editor. Wenn ich darüber hinausgehe, läuft in der Regel etwas kollosal falsch, oder ich hacke gerade ein Wegwerf-Script zusammen.  
          
        Wenn Du mit Dispatch-Tabellen immer noch keine Übersicht in Deinem Code hast, dann hast Du zu viel Müll in Deiner Dispatcher-Routine:  
          
        ~~~perl
          
        use 5.010;  
          
        sub dispatcher  
        {  
            my ($mode,$arg)=@_;  
            state $dispatch={  
                foo => \&foo,  
                bar => \&bar,  
                baz => \&bazinate,  
                fuh => \&foo,  
            };  
            my $handler=$dispatch->{$mode} or die "Bad mode '$mode'";  
            return $handler->($arg);  
        }  
        
        

        Als Alternative zur klassischen Dispatch-Tabelle nutze ich auch gerne einen Parameter nach einer grundlegenden Plausibilitätsprüfung auch direkt als Methodenname:

          
        sub dispatcher  
        {  
            my ($self,$mode,$arg)=@_;  
            $mode=~/^([a-z]+)$/ or die "Bad mode '$mode'";  
            my $method="do_$1"; # untaint, prefix  
            $self->can($method) or die "Bad mode '$mode'";  
            return $self->$method($arg);  
        }  
        
        

        Dabei sind zwei Besonderheiten zu beachten:

        1. Ich nutze den Taint Mode für alle Programme, die mit nicht 100% vertrauenswürdigen Daten umgehen müssen. Daher der "Umweg" über Capturing und $1.

        2. Böse Dinge können geschehen, wenn der Aufrufer quasi beliebige Methoden aufrufen kann. Man denke nur an new, import, DESTROY, AUTOLOAD. Deswegen sorge ich mit dem "do_"-Prefix dafür, dass solche "bösen" Methodennamen gar nicht erst auftreten können. Als Nebeneffekt sind alle vom Dispatcher aufgerufenen Methoden direkt an ihrem Namen als solche zu erkennen.

          	read(STDIN, my $st, $ENV{CONTENT_LENGTH});  
        
        
        > >   
        > > Bastelst Du Dir Deine eigene CGI-Schnittstelle?  
        >   
        > Nein. Ich mache nur den Body frei von Control-Elementen, die der UA schickt.  
          
        Öm, was machst Du? HTTP verschickt Zeichen und Bytes, aber keine Controls.  
          
        
        > Es ist eine Client-Server-Anwendung zum Datenbank-Management über HTTP. Der body kann verschiedene Inhalte haben, auch Binaries  
          
        Das läßt sich prima in POST- (oder PUT-)Requests verpacken, mit minimalem Overhead. Da alle Meta-Daten als Text ohne relevante Längenbegrenzungen übertragen werden, sollten selbst Terabyte-große HTTP-Requests und -Responses kein Problem für die HTTP- und CGI-Protokolle sein -- allenfalls für Netzwerke und die beteiligten Rechner.  
          
        
        > oder reine SQL-Statements.  
          
        SQL-Statements sind Strings. Ein sehr kleines Subset aller möglichen Strings. HTTP kann Strings problemlos übertragen, genau wie die CGI-Schnittstelle. Kein Grund, selbst irgendetwas zusammen zu frickeln.  
          
        
        > Was am Server zu tun ist, wird im Custom-Request-Header übergeben (Control) und der Body wird Bytegenau aus STDIN gelesen. Da STDIN ein Handle ist, können mit read() auch Sequenzen gelesen werden, also nicht über {CONTENT\_LENGTH}, sondern auch mal so:  
        >   
        > read(STDIN, my $record, $record\_len);  
          
        Du vertraust Deinem Client. Viel zu sehr für meinen Geschmack (es sei denn, wir reden über ein sehr kleines, sehr gut abgeschottetes Netz). Was passiert, wenn ich Dir einen HTTP-Request sende, bei dem die Längenangabe und die tatsächliche Länge nicht zusammenpassen. Was passiert, wernn ich $record\_len durch einen geeignet konstruierten HTTP-Request auf falsche Werte setze?  
          
        
        > Es sind kleine Datenmengen, da darf ich auch maln komplettes Array in den RAM legen.  
          
        Ja.  
          
        
        > Wenn ein DB-Abfrage-Ergebnis zum Browser geht, hat die Anzahl der Zeilen \_immer einen endlichen Wert,  
          
        10^10 ist ein endlicher Wert. [10^100](http://en.wikipedia.org/wiki/Googol) auch. Für letzteren Wert wirst Du vermutlich nie genügend Atome zusammen bekommen, um auch nur Zeilen mit einem Bit Informationsgehalt abzuspeichern. Und für [10^(10^100)](http://en.wikipedia.org/wiki/Googolplex), ebenfalls ein endlicher Wert, brauchst Du noch einen größeren Haufen Paralleluniversen.  
          
        Im Ernst: 20 GByte sind auch ein endlicher Wert, und im DB-Umfeld kein nennenswertes Problem. Die sprengen Dir aber viele heute aktuelle Server, und erst recht mit 32 Bit compilierte Perls.  
          
        
        > das darf ich als Array in den Speicher legen.  
          
        Nicht immer.  
          
        
        >  Mit fetchrow\_ zu arbeiten, hieße: Ausgabe der Daten mit der Darstellung zu vermischen, aber ich trenne das lieber.  
          
        Ich weiß, was Du meinst, aber das schreibst Du nicht ...  
          
        
        >   Liegt das Ergebnis als Array im Speicher, wird das zur Darstellung nicht kopiert, sondern eine Referenz übergeben, dann erst gehts zur Darstellung (HTML).  
          
        Riecht nach Templates. Und dann hast Du die Daten doch doppelt im Speicher: Einmal die Rohdaten aus der DB, und dann nochmal in HTML-Darstellung umgerechnete Daten. Die dürften in vielen Fällen mehr Platz benötigen als die Rohdaten, schon allein wegen Escaping und den zusätzlichen HTML-Elementen.  
          
        Du kannst an einige Template-Engines auch Objekte übergeben und auf denen Methoden aufrufen. Damit hättest Du am Ende auch alle Daten im Speicher, aber immer nur eine Zeile mit Rohdaten, der Rest ist in der HTML-Darstellung vorhanden.  
          
        
        > Anders ist es beim Management, da gehen schonmal Binaries mit einigen MB vom Server raus auf STDOUT und fetchrow\_ wäre serverseitig angebracht. Clientseitig wäre es natürlich ungeschickt, eine solche HTTP-Response erst als String UA-seitig in ein temp. Handle zu schreiben, naheliegender ist es doch, gleich die Response aus dem Socket zu lesen.  
          
        Bei Megabytes würde ich mir noch keine ernsthaften Sorgen machen. Wenn Du die großen Binaries getrennt von den kleineren Restdaten überträgst, mußt Du Dir über das ein- und auspacken auch keine Sorgen machen. Sobald der Client die HTTP-Header des HTTP-Response Binaries verdaut hat, kannst Du natürlich aus dem Socket lesen. Dann mußt Du Dich aber selbst um Dinge wie Encoding, Keep-Alive, Byte-Ranges und Kompression kümmern, oder Du verweigerst sie direkt beim Request.  
          
        Warum reitest Du eigentlich so auf Handles herum? Du willst doch ohnehin einen (binären) String verarbeiten, und so lange der nicht zu groß für den Speicher wird, kannst Du den String direkt weiterverarbeiten. Da sind außer dem HTTP-Socket überhaupt keine Handles im Spiel.  
          
        STDIN und STDOUT sind im HTTP-Umfeld übrigens extrem CGI-spezifisch, mit anderen, wesentlich schnelleren Systemen wie mod\_perl und FastCGI gibt es die in dieser Form schlicht nicht. Sie werden bestenfalls über eine (mehr oder weniger bremsende) Kompatibilitätsschicht emuliert.  
          
        Alexander
        
        -- 
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
        
        1. hi mein Lieber,

          Warum reitest Du eigentlich so auf Handles herum?

          Ich bin halt ein Low-Level-Fuzzi ;)

          Du willst doch ohnehin einen (binären) String verarbeiten, und so lange der nicht zu groß für den Speicher wird, kannst Du den String direkt weiterverarbeiten. Da sind außer dem HTTP-Socket überhaupt keine Handles im Spiel.

          BinärSequenzen in einen String (scalar) schreiben ist ok, aber Lesen erfordert ein Handle, ein String ist dafür ungeeignet. Eine Binary wird Byte für Byte (oder SeqenzLänge für SequenzLänge) aus einem Handle gelesen mit der Funktion, die aus einem Handle lesen kann: read().

          STDIN und STDOUT sind im HTTP-Umfeld übrigens extrem CGI-spezifisch, mit anderen, wesentlich schnelleren Systemen wie mod_perl und FastCGI gibt es die in dieser Form schlicht nicht. Sie werden bestenfalls über eine (mehr oder weniger bremsende) Kompatibilitätsschicht emuliert.

          Vielleicht kann ich Dich ein bischen neugierig machen: Ich entwickle z.Z. (solange ich die Zeit noch habe) ein System zum Übertragen von Binaries über HTTP als Client-Server-Anwendung. Die Binaries können Multipart-Messages sein, MP3s oder DB-Dumps oder was auch immer (auch Text), das System ist unabhängig von Inhalten und damit auch unabhängig von Zeichenkodierungen; es geht eben nur um Bytes (Darstellung auch, aber später und woanders).

          Dieses System ist, so ganz nebenbei, auch als schlanke Alternative zu XML oder Multipart-Messages nach dem MIME-Standard brauchbar und funktioniert derzeit in meinen Tests einwandfrei.

          Zu STDIN am Server: Ich habe u.a. ein Script, das ist nicht über Parameter, sondern per (custom) HTTP-Header gesteuert. Damit ist der Message-Body (STDIN) frei von Parametern, Boundaries oder sonstigen Controls, die dem Script mitteilen, was es machen soll, das steht alles im Header. STDIN kann somit bytegenau gelesen werden, da steht z.B. eine Binary und warum sollte ich die erst irgendwo anders hinschreiben!? Zu unnötigen Kopieren kannst Du ja selbst ein Liedchen singen;)

          Btw.:

            
          use strict;  
          use Data::Dump;  
          use myConfig qw($cfg);  
            
          $| = 1;  
            
          my %attr = (  
          	'host' => 'rolfrost',  
          	'method' => 'GET',  
          	'uri' => '/cgi-bin/geheim.cgi',  
          	'callback' => \&callback,  
          	'auth' => $cfg->{http}->{auth}, # Authorization: Basic encode_base64('usr:pass','')  
          );  
            
          binmode STDOUT;  
            
          my $s = Net::HTTP10->new(%attr) or die "Kein Socket";  
          #print Data::Dump::dump $s;  
          $s->request;  
          my $href = $s->read_headers;  
          print Data::Dump::dump $href;  
          #$s->body_callback; # Läuft ;)  
          
          

          Ja, die gute alte Version HTTP/1.0. Kein Transfer-Encoding und keine Chunks, Body as Rock ;)

            
          package Net::HTTP10; # mein neues Modul  
            
          use strict;  
          use IO::Socket;  
          use vars qw(@ISA);  
          @ISA = qw(IO::Socket);  
          use warnings;  
          use Carp;  
          
          

          Schönen Sonntag,
          Grüße an Alle,
          Rolf alias Hotti

          1. Moin Moin!

            Warum reitest Du eigentlich so auf Handles herum?

            Ich bin halt ein Low-Level-Fuzzi ;)

            Nö, Du frickelst ziemlich planlos herum.

            Du willst doch ohnehin einen (binären) String verarbeiten, und so lange der nicht zu groß für den Speicher wird, kannst Du den String direkt weiterverarbeiten. Da sind außer dem HTTP-Socket überhaupt keine Handles im Spiel.

            BinärSequenzen in einen String (scalar) schreiben ist ok, aber Lesen erfordert ein Handle, ein String ist dafür ungeeignet.

            Woher kommt denn diese Fehlinformation? Ist die aus Deinem Kopf gewachsen oder hat Dir jemand diesen Unsinn eingeflüstert?

            So lange ich in Perl 5 einen String nicht explizit als Unicode-String kennzeichne (Encode, :utf8- und :encoding-Layer für Filehandles, XS-Module die Unicode-Strings liefern wie z.B. DBD::ODBC, ...), gilt: 1 Zeichen = 1 Byte. Das kann ich notfalls per use bytes; erzwingen. Anders als in C gibt es in Perl keine "magischen" Bytes in Strings, die irgendeine besondere Bedeutung haben. Ein String kann jedes beliebige Byte enthalten, und so viele Bytes, wie es der zur Verfügung stehende reale und virtuelle Speicher erlaubt.

            Eine Binary wird Byte für Byte (oder SeqenzLänge für SequenzLänge) aus einem Handle gelesen mit der Funktion, die aus einem Handle lesen kann: read().

            Ach ja?

              
            #!/usr/bin/perl  
              
            use strict;  
            use warnings;  
            use 5.010;  
              
            my $data=''; $data.=chr(rand(256)) for 1..30;  
              
            while ($data=~/\G(.{5})/g) {  
            	my $rec=$1;  
            	printf '%02x',ord substr($rec,$_,1) for 0..4;  
            	print "\n";  
            }  
              
            say "---";  
              
            my $d=$data;  
            while (length($d)>=5) {  
            	my $rec=substr($d,0,5,'');  
            	say unpack('H10',$rec);  
            }  
              
              
            say "---";  
              
            my @recs=unpack('(H10)*',$data);  
            say for @recs;  
            
            

            unpack, pack, perlpacktut, substr, \G in perlop

            Vielleicht kann ich Dich ein bischen neugierig machen: Ich entwickle z.Z. (solange ich die Zeit noch habe) ein System zum Übertragen von Binaries über HTTP als Client-Server-Anwendung. Die Binaries können Multipart-Messages sein, MP3s oder DB-Dumps oder was auch immer (auch Text), das System ist unabhängig von Inhalten und damit auch unabhängig von Zeichenkodierungen; es geht eben nur um Bytes (Darstellung auch, aber später und woanders).

            Ist schon fertig, wird im allgemeinen als HTTP-Upload bezeichnet. Ursprung in RFC 1867 von 1995, PUT als Alternative zu POST in RFC 2616 von 1999.

            Dieses System ist, so ganz nebenbei, auch als schlanke Alternative zu XML

            Was soll XML in dieser Diskussion? Klar, wenn ich irgendwelche Daten in XML stopfe, kann ich die fast beliebig aufblähen.

            oder Multipart-Messages nach dem MIME-Standard

            Die brauchst Du bei PUT nicht.

            brauchbar und funktioniert derzeit in meinen Tests einwandfrei.

            Du hast HTTP PUT neu erfunden. Gratulation.

            Zu STDIN am Server: Ich habe u.a. ein Script, das ist nicht über Parameter, sondern per (custom) HTTP-Header gesteuert. Damit ist der Message-Body (STDIN) frei von Parametern, Boundaries oder sonstigen Controls, die dem Script mitteilen, was es machen soll, das steht alles im Header. STDIN kann somit bytegenau gelesen werden, da steht z.B. eine Binary und warum sollte ich die erst irgendwo anders hinschreiben!? Zu unnötigen Kopieren kannst Du ja selbst ein Liedchen singen;)

            STDIN funktioniert nur, weil Du die CGI-Schnittstelle benutzt. Die vom Netz kommenden Daten werden dafür eine ganze Weile durch die Gegend geschaufelt, bis sie Deinem CGI häppchenweise angefüttert werden. Sobald du eine schnelle Schnittstelle zum Webserver (mod_perl, FastCGI, ...) benutzt, funktioniert das nicht mehr, außer über eine lahme Emulationsschicht.

            Dein Webserver hat STDIN garantiert geschlossen oder auf /dev/null umgebogen, wie es sich für einen Daemon gehört.

            Btw.:

            use strict;
            use Data::Dump;
            use myConfig qw($cfg);

            $| = 1;

            my %attr = (
            'host' => 'rolfrost',
            'method' => 'GET',
            'uri' => '/cgi-bin/geheim.cgi',
            'callback' => &callback,
            'auth' => $cfg->{http}->{auth}, # Authorization: Basic encode_base64('usr:pass','')
            );

            binmode STDOUT;

            my $s = Net::HTTP10->new(%attr) or die "Kein Socket";

            $! fehlt.

            #print Data::Dump::dump $s;
            $s->request;
            my $href = $s->read_headers;

            Kommt read_headers mit [link:http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2@title=umbrochenen Header-Zeilen] klar? Und ja, die [link:http://ftp.ics.uci.edu/pub/ietf/http/rfc1945.html#Message-Headers@title=gibt's auch bei HTTP/1.0].

            Weitere Gemeinheiten des Protokolls erspare ich uns an dieser Stelle mal.

            print Data::Dump::dump $href;
            #$s->body_callback; # Läuft ;)

            
            >   
            > Ja, die gute alte Version HTTP/1.0. Kein Transfer-Encoding und keine Chunks, Body as Rock ;)  
              
            Kein HTTP PUT.  
              
            Keine Name-based virtual Hosts. Und damit für Leute, die kleinen Webspace bei großen Hostern nutzen, absolut unbrauchbar. Denn ohne HTTP/1.1 mit Host-Header enden alle Anfragen beim Default-VHost, der bestenfalls ein 404 Not Found zurückgibt.  
              
            Strato antwortet so auf einen HTTP/1.0 GET-Request (GET / HTTP/1.0):  
              
            HTTP/1.1 200 OK  
            Date: Mon, 21 Mar 2011 10:42:11 GMT  
            Server: Apache/2.2.17 (Unix)  
            Last-Modified: Wed, 22 Sep 2004 15:30:14 GMT  
            ETag: "fa92-3de-3e4af6c172d80"  
            Accept-Ranges: bytes  
            Content-Length: 990  
            Connection: close  
            Content-Type: text/html  
              
            <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">  
            <HTML>  
             <HEAD>  
              <TITLE>Bitte benutzen sie nicht die IP Adresse des Servers</TITLE>  
             </HEAD>  
            <!-- Background white, links blue (unvisited), navy (visited), red (active) -->  
             <BODY  
              BGCOLOR="#FFFFFF"  
              TEXT="#000000"  
              LINK="#0000FF"  
              VLINK="#000080"  
              ALINK="#FF0000"  
             >  
            <BR><BR><BR><BR>  
              <H1 ALIGN="CENTER">  
                Bitte benutzen sie nicht die IP Adresse des Servers, sondern immer  
                www.&lt;Wunschname&gt;.de !!  
              </H1>  
              <HR>  
              <DIV ALIGN="CENTER">  
               <TABLE BORDER="0">  
                 <TR>  
                   <TD ALIGN="center" colspan=2>  
                       <A HREF="http://www.wunschname.de"><IMG SRC="http://www.strato.de/setup/images\_setup/visual.gif" BORDER="0" ALT="www.WUNSCHNAME.de"></A>  
                   </TD>  
                 <TR></TR>  
                   <TD ALIGN="center">  
                       <A HREF="http://www.strato.de"><IMG SRC="http://www.strato.de/images/wir/navlinks/a.gif" HEIGHT="149" BORDER="0" ALT="STRATO AG"></A>  
                   </TD>  
                 </TR>  
               </TABLE>  
              </DIV>  
             </BODY>  
            </HTML>  
              
            Man beachte den Status und das Protokoll:  
              
                HTTP/1.1 200 OK  
              
            Das ist dem Inhalt nach eine 404-Seite, und sollte IMHO auch mit dem Status 404 ausgeliefert werden, nicht mit Status 200.  
              
            Außerdem antwortet der Server auf eine HTTP/1.0-Anfrage mit einer HTTP/1.1-Response, die ein HTTP/1.0-Client vermutlich gar nicht vollständig verstehen kann. Hier verläßt sich Strato bzw. der Apache 2.2 offenbar sehr darauf, dass HTTP/1.0-Clients erstens fast ausgestorben sind und zweitens die ganzen zusätzlichen HTTP/1.1-Header stumpf ignorieren.  
              
            Die gleiche Response bekommt man auch für willkürlich ausgewürfelte Requests wie "GET /kjfhwkehfiwufhweiuhfwiuefhweiufhweiufhweiufhweiufhweifuwhefiu HTTP/1.0", und, was besonders problematisch sein kann, auch für "GET /cgi-bin/geheim.cgi HTTP/1.0".  
              
            
            >   
            > ~~~perl
              
            
            > package Net::HTTP10; # mein neues Modul  
            >   
            > use strict;  
            > use IO::Socket;  
            > use vars qw(@ISA);  
            > @ISA = qw(IO::Socket);  
              
            [link:http://perldoc.perl.org/vars.html@title=use vars ist obsolete]. [link:http://perldoc.perl.org/functions/our.html@title=our] ist das neue use vars.  
              
            Und speziell für Vererbung nutzt man seit Jahren [link:http://perldoc.perl.org/base.html@title=use base] oder das schlankere [link:http://perldoc.perl.org/parent.html@title=use parent].  
              
            
            > use warnings;  
              
            Warum erst hier?  
              
            
            > use Carp;  
            > 
            
            

            HTTP::Request, HTTP::Response, LWP::UserAgent

            Alexander

            --
            Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
            1. moin, moin,

              BinärSequenzen in einen String (scalar) schreiben ist ok, aber Lesen erfordert ein Handle, ein String ist dafür ungeeignet.

              Woher kommt denn diese Fehlinformation? Ist die aus Deinem Kopf gewachsen oder hat Dir jemand diesen Unsinn eingeflüstert?

              Ungeeignet heißt ja nicht, dass es nicht machbar ist. Aber warum sollte ich eine Bytesequenz als string empfangen zumal es (wie ich schrieb), nicht nur um Text geht? Und warum mit einer zeichenorientierten Funktion wie substr() durch den String wursteln, wo es doch viel einfacher ist, die Bytesequenzen gleich aus einem Socket(Handle) zu lesen.

              Oder mal eine Multipart-Message, Texte, Bilder, MP3's: Alles zusammen in einer Sequenz. Oder Sammlungen von Objekten, die verschiedene Anzahl an Attributen haben und die auch noch mit unterschiedlicher Länge. Das alles und viel mehr ist als Binärstruktur viel einfacher zu übertragen als multipart-mixed nach dem MIME Standard. Wenn ich was neu erfinde, dann ist es nicht PUT, nicht die CGI-Schnittstelle, nicht HTTP und auch nicht TCP, ich wende das nur an.

              Wie kommst Du eigentlich darauf, dass ich planlos frickle? An _dem Thema bin ich schon seit über einem Jahr dran. Zugegeben, mein Code sieht stellenweise noch aus wie eine Fahrt im offenen Geländewagen, aber ich bin jetzt mehr und mehr dabei, das was sich bewährt hat, sauber in Module zu fassen. Bewährt hat sich z.B. die Übertragung von Objekten als Binary und der Einbau der Objekte als Content in meiner Website.

              Und malso ganz nebenbei gefragt: Glaubst Du wirklich, Du müsstest mir Links zu pack() und unpack() schicken? Meine Güte, das ist Handwerkszeugs, mit dem ich arbeite ;)

              Viele Grüße,
              Hotti

            2. hi again,

              Keine Name-based virtual Hosts. Und damit für Leute, die kleinen Webspace bei großen Hostern nutzen, absolut unbrauchbar. Denn ohne HTTP/1.1 mit Host-Header enden alle Anfragen beim Default-VHost, der bestenfalls ein 404 Not Found zurückgibt.

              Bei mir funktioniert der Host-Header mit HTTP/1.0 im Request einwandfrei und name-based virt. Hosts sind name-based adressierbar.

              Außerdem antwortet der Server auf eine HTTP/1.0-Anfrage mit einer HTTP/1.1-Response, die ein HTTP/1.0-Client vermutlich gar nicht vollständig verstehen kann.

              Wenn im Request HTTP/1.0 steht, kann der Server in der Response auch HTTP/1.1 verwenden. Was er nicht machen darf in diesem Fall ist z.B. ein Transfer-Encoding: chunked. So habe ich das bisher verstanden und die Praxis bestätigt mir das.

              Hotti