Alexander (HH): DBI::errstr fetch() without execute()

Beitrag lesen

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