Perl & LWP::UserAgent & Cookie setzen
Silvio
- perl
-1 pl1 Christian Kruse0 Silvio-2 pl0 Robert B.- perl
- sicherheit
Hi,
laut Doku zu LWP::UserAgent kann ich mit LWP::UserAgent bei einem Request auch einen Cookie mit übergeben. Was ich aus der Doku nicht ersehen kann/ verstehe, ist wie das funktioniert.
Ich finde folgende Zeilen:
1.)
my $jar = $ua->cookie_jar;
$ua->cookie_jar( $cookie_jar_obj );
Warum wird die Variable $jar definiert, wenn diese dann nicht verwendet wird?
Und ich sehe auch nicht, wie $cookie_jar_obj aufgebaut sein muss.
2.)
$ua->cookie_jar({ file => "$ENV{HOME}/.cookies.txt" });
Und hier verstehe ich nicht, was das ganze macht.
Und an welcher Stelle setze ich nun eigentlich den Cookie (Name, Wert, Expiration, ....)?
Für evtl Tipps jetzt schon mal 'Danke!' 😀
Und ich sehe auch nicht, wie $cookie_jar_obj aufgebaut sein muss.
Da nimmst Du den Data::Dumper und guckst Dir das an.
$ua->cookie_jar({ file => "$ENV{HOME}/.cookies.txt" });
LWP-UserAgent legt Cookies in Dateien ab. Für einem Request kann aus solchen Dateien der Cookie gelesen und entsprechend der Policy gesendet werden. Genauso wie das auch ein Browser tut.
Und an welcher Stelle setze ich nun eigentlich den Cookie (Name, Wert, Expiration, ....)?
Serverseitig in einem Responseheader.
MfG
Danke schon mal für deine Antwort, aber leider hilft mir die gar nich weiter 😟
Das mit dem Aufbau ansehen, verstehe ich nicht. Wenn ich nicht weiß, wie ich die Variable befüllen muss, macht es doch auch keinen Sinn, mir diese anzuschauen?!
Mit der Datei, die LWP schreibt: Ist das zwingend? Muss ich den Admin dann fragen, welches Verzeichnis auf dem Server freigegeben ist?
Und auch "Serverseitig in einem Responseheader.": Meine Frage zielte eher auf ein genaues WIE ab.
Vielen Dank
Tach!
Das mit dem Aufbau ansehen, verstehe ich nicht. Wenn ich nicht weiß, wie ich die Variable befüllen muss, macht es doch auch keinen Sinn, mir diese anzuschauen?!
Ich vermute mal, du hast grad eine andere Vorstellung vom Funktionieren des Systems und bist deshalb betriebsblind, um die relativ klare Beschreibung in der von dir verlinkten Dokumentation zu verstehen. Nicht weiter schlimm, geht mir auch manchmal so.
Du befüllst an der Stelle gar nichts. Der bist hier auf der Client-Seite und als ein solcher legt dein LWP::UserAgent da selbständig die Cookies rein, die er vom Server empfängt. Du brauchst dazu nichts weiter tun, es reicht hier lediglich die Datenhaltung anzugeben, in der die Cookies zwischen den Requests aufgehoben werden sollen. Du selbst erzeugst an der Stelle keine Cookies, denn das wäre ein Unterschieben von Cookies, die der Server gar nicht gesendet hat.
Mit der Datei, die LWP schreibt: Ist das zwingend? Muss ich den Admin dann fragen, welches Verzeichnis auf dem Server freigegeben ist?
Die Cookies müssen zwischen den Requests irgendwo in einem dauerhaften Speicher abgelegt sein, damit sie nicht verloren gehen.
Und auch "Serverseitig in einem Responseheader.": Meine Frage zielte eher auf ein genaues WIE ab.
Das hängt von deinem Server ab, genauer: von der Software, die dort die Requests beantwortet und die Response zusammenstellt. Da müssen Set-Cookie-Header hinzugefügt werden.
dedlfix.
Das mit dem Aufbau ansehen, verstehe ich nicht. Wenn ich nicht weiß, wie ich die Variable befüllen muss, macht es doch auch keinen Sinn, mir diese anzuschauen?!
Doch, der Dumper zeigt Dir ganz genau wie Objekte aufgebaut sind, er zeigt Dir auch, welche Klasseninstanzen in einem Objekt verbaut sind. Gerade die libwww (LWP) Suite erfordert das Zusammenspiel mehrerer Klassen und deren Instanzen, da ist Data::Dumper ein überaus nützliches Werkzeug beim Entwickeln von Anwendungen mit LWP.
Mit der Datei, die LWP schreibt: Ist das zwingend? Muss ich den Admin dann fragen, welches Verzeichnis auf dem Server freigegeben ist?
Nicht unbedingt. Ein Cookie kann auch im Speicher gehalten werden und dafür gibt es sogar eine Schalter, steht in der Dokumentation. Das persistente Ablegen von Cookies ist ja nur dann erforderlich, wenn diese wiederverwendet werden sollen in einem neuen Prozess. Oder wenn Du zum Debuggen nachschauen willst, was in der Cookiedatei geschrieben steht.
Und auch "Serverseitig in einem Responseheader.": Meine Frage zielte eher auf ein genaues WIE ab.
Der Response-Header heißt Set-Cookie
und der Wert einschl. Parameter ist ein String. Es gibt viele Möglichkeiten, z.B. CGI::Cookie
und HTTP::Headers
- Class encapsulating HTTP Message headers zum Erzeugen von HTTP-Response Headers.
MfG
Hallo pl,
und der Preis für die am wenigsten hilfreiche Antwort geht an…
LG,
CK
Hallo Silvio,
laut Doku zu LWP::UserAgent kann ich mit LWP::UserAgent bei einem Request auch einen Cookie mit übergeben.
Naja, jain. Der Cookie Jar ist ein Container, in dem LWP::UserAgent
Cookies speichert, die bei einem Request geschickt werden; beim nächsten Request würden sie dann wieder mitgeschickt.
Ich finde folgende Zeilen:
1.)
my $jar = $ua->cookie_jar; $ua->cookie_jar( $cookie_jar_obj );
Warum wird die Variable $jar definiert, wenn diese dann nicht verwendet wird?
Und ich sehe auch nicht, wie $cookie_jar_obj aufgebaut sein muss.
Das ist nur ein Code-Beispiel. Es macht keinen Sinn ausser zu zeigen, wie die API benutzt wird. $jar
und $cookie_jar_obj
müssen ein Objekt sein, dass extract_cookies()
und add_cookie_header()
implementiert. Vorgesehen ist dafür HTTP::Cookies
.
2.)
$ua->cookie_jar({ file => "$ENV{HOME}/.cookies.txt" });
Und hier verstehe ich nicht, was das ganze macht.
Das Gibt LWP::UserAgent
eine Datei als Cookie-Store; die Cookies werden dann, wie oben beschrieben, in dieser Datei automatisch gespeichert und beim nächsten Request wieder mitgesendet.
Und an welcher Stelle setze ich nun eigentlich den Cookie (Name, Wert, Expiration, ....)?
Du musst ein HTTP::Cookies
-Objekt erstellen und dem mit set_cookie()
einen neuen Cookie einfügen, etwa so:
use LWP::UserAgent;
use HTTP::Cookies;
my $ua = LWP::UserAgent->new;
my $cookie_jar = HTTP::Cookies->new;
$cookie_jar -> set_cookie(0, 'cookie_name', 'cookie_value', '/');
$ua->cookie_jar($cookie_jar);
# …
Die genauen Parameter von set_cookie
kannst du der Dokumentation entnehmen.
LG,
CK
Wollte mich noch mal bei allen bedanken.
Ich glaube, jetzt hab ich das Prinzip verstanden.
Ich glaube, jetzt hab ich das Prinzip verstanden.
Sehr gut. Und mal ganz ehrlich, LWP::UserAgent ist längst ein Fall für die Pathologie. Befasse Dich mehr mit HTTP und entwickle eine eigene Library an der Du später immer wieder Deine Freude hast! Mit IO::Socket::INET
bzw. IO::Socket::SSL
hast Du eine gute Basis auf die Du aufsetzen kannst.
Viel Erfolg.
Moin,
Sehr gut. Und mal ganz ehrlich, LWP::UserAgent ist längst ein Fall für die Pathologie.
wenn ich das richtig sehe, ist deine einzige Kritik an LWP die „Fülle“ benötigten Objekten. Funktional macht die Bibliothek doch das, was sie verspricht – und ist AFAIK im Standardlieferumfang.
Zudem sehe ich in deiner ausführlichen Begründung so Dinge wie …
Request-Header lassen sich sehr schön in einem Hash abbilden, ebenso auch Cookies.
…
use CGI; return $self->{CGI}->param(@_);
… ohne Kommentar. Nach dem Perl Jam beim 31c3 und 32c3 sollte zumindest darauf hingewiesen werden, was passiert, wenn Hashes mit Arrays kollidieren und wie man Hashes oder Arrays in URL-Parameter bekommt. Wie man sie herausbekommt steht in dem Snippet oben.
Befasse Dich mehr mit HTTP und entwickle eine eigene Library an der Du später immer wieder Deine Freude hast! Mit
IO::Socket::INET
bzw.IO::Socket::SSL
hast Du eine gute Basis auf die Du aufsetzen kannst.
Du schlägst tatsächlich vor, das Rad zum wiederholten Mal neu zu erfinden und Gefahr zu laufen neue Sicherheitslücken einzubauen? HTTP::Tiny sieht doch ganz gut aus.
Viele Grüße
Robert
wenn ich das richtig sehe, ist deine einzige Kritik an LWP die „Fülle“ benötigten Objekten.
Nicht nur. Ich meine auch die Anwendung derer, was nicht gerade trivial ist wie dieser Thread hier ja zeigt.
das Rad zum wiederholten Mal neu zu erfinden
Diese Anmerkung ist unsachlich. Wer HTTP studiert, wird ja das Rad nicht neu erfinden sondern allenfalls eigene Module entwickeln die möglicherweise dasselbe machen wie andere Module auch.
Es kommt aber darauf an, es besser zu machen, zweckmäßiger und handhabungssicherer.
Das PerlModul CGI.pm
hat übrigens eine Reihe von Unzulänglichkeiten die hauptsächlich darin bestehen, daß es nicht erweiterbar ist auf beliebige Content-Types die Requests mit sich bringen. Also JSON, XML usw. und auch die besagte Unterscheidung Hash/Array in dedizierten Enctypes.
Außerdem schleppt das Modul jede Menge CODE mit sich rum der im Zeitalter moderner TemplateEngines überflüssig ist.
Tatsächlich kenne ich niemanden der jemals HTML mit CGI.pm-Funktionen erzeugt hat.
MfG
Moin,
was ist daran,
das Rad zum wiederholten Mal neu zu erfinden
Diese Anmerkung ist unsachlich.
unsachlich? Du bestätigst es doch direkt:
Wer HTTP studiert, wird ja das Rad nicht neu erfinden sondern allenfalls eigene Module entwickeln die möglicherweise dasselbe machen wie andere Module auch.
Das PerlModul
CGI.pm
hat übrigens eine Reihe von Unzulänglichkeiten die hauptsächlich darin bestehen, daß es nicht erweiterbar ist auf beliebige Content-Types die Requests mit sich bringen.
Jeder hat so seinen eigenen Fokus. Für mich ist die Sicherheit bereits ein Killerkriterium. Aber warum nutzt du es eigentlich, wenn du davon nicht überzeugt bist, davor gewarnt wird und es wohl eine gute Alternative gibt?
Viele Grüße
Robert
Da liegt ein Missverständnis vor, denn CGI.pm nutze ich nicht. Gründe dafür, CGI.pm nicht zu nutzen, gibt es, seit es dieses Modul gibt und die liegen, wie ich bereits schrieb, u.a. in der Unzulänglichkeit des Enctype application/x-www-form-urlencoded
ein Array als Hash abzubilden.
D.h., das diesbezügliche Problem von CGI.pm besteht darin, dass dieses Modul nicht auf andere Enctypes erweiterbar ist. Von daher benutze ich da schon immer eine Eigenentwicklung die ein Schichtenmodell implementiert, was beliebig erweiterbar ist.
MfG
wie ist denn dann das use CGI zu verstehen?
Als Beispiel für aggregierte Instanzen ebenda. Ausführlich hier beschrieben
MfG
OK, ich formuliere die Frage anders: Ist das benutzte CGI
-Modul das CGI.pm oder dein eigenes?
OK, ich formuliere die Frage anders: Ist das benutzte CGI-Modul das CGI.pm oder dein eigenes?
Ich verstehe die Frage nicht. Das hier gezeigte Beispiel:
use CGI;
my $main = bless{
CGI => CGI->new()
}, 'main';
# Delegiere die gleichnamige Methode
sub param{
my $self = shift;
return $self->{CGI}->param(@_);
}
# das wird damit möglich
$main->param('foo');
benutzt überhaupt kein Modul sondern macht Aggregation/Delegation anschaulich. D.h. anstelle CGI.pm kann jedes andere Modul gesetzt werden, das Beispiel ist davon völlig unabhängig.
Selbstverständlich jedoch funktioniert das Beispiel sowohl mit CGI.pm als auch mit xCGI.pm (meiner Eigenentwicklung), falls das die Frage war.
MfG
Moin,
ich verstehe die Antwort nicht, denn:
use CGI;
benutzt überhaupt kein Modul …
Was macht denn das use
sonst hier?
Viele Grüße
Robert
Weißt was, ich schreibe Dir einfach mal, was ich verwende:
use xCGI;
my $main = bless{
CGI => xCGI->new()
}, 'main';
# Delegiere die gleichnamige Methode
sub param{
my $self = shift;
return $self->{CGI}->param(@_);
}
# das wird damit möglich
$main->param('foo');
Das Beispiel bleibt ja dasselbe. Nur in der Praxis kann mein xCGI Enctypes parsen die CGI.pm nicht kennt und demzufolge hat param() auch entspechende Rückgabewerte.
Das gibt das Beispiel nicht her aber das ist auch nicht die Zweckbestimmung des Beispiels.
MfG
Moin,
nach dem, was wir hier bislang diskutiert haben, ist das nicht das Selbe. Und einen Großteil dieser Diskussion hätte man sich mit dem richtigen Beispiel auch sparen können.
Viele Grüße
Robert
Ist Deine Frage nun beantwortet?
Ja, nach fünf Mal nachhaken ist diese Frage beantwortet.
P.S.: Ich könnte mir vorstellen, dass weniger hartnäckige und geduldige Zeitgenossen da schon mal ein -
drücken.
Kann ich alles nicht nachvollziehen bei dem Umfang meiner Ausführungen und außerdem hab ich zum Thema Aggregation/Delegation einen weiteren umfangreichen Artikel verlinkt.
P.S.: Ich könnte mir vorstellen, dass weniger hartnäckige und geduldige Zeitgenossen da schon mal ein > - drücken.
Ja es gibt solche Leute. Die merken dabei jedoch gar nicht, dass sie selbst auf ihren Problemen sitzen bleiben wenn sie einfach nur ihren persönlichen Fust ablassen. Und daß ich für sowas absolut kein Verständnis habe, und noch weniger für ein Bewertungssystem was derartiges unterstützt, hab ich an anderer Stelle ausführlich aufgeschieben.
MfG
Moin,
Kann ich alles nicht nachvollziehen bei dem Umfang meiner Ausführungen und außerdem hab ich zum Thema Aggregation/Delegation einen weiteren umfangreichen Artikel verlinkt.
wir reden ganz offensichtlich aneinander vorbei. Meine Frage taucht hier noch einmal auf und hat sich ausschließlich auf dein CGI-Modul bezogen. Wenn ich über diesen einen speziellen Punkt rede, dann bringen mich Artikel, über Aggregation, in denen das Wort „CGI“ nicht einmal vorkommt, nicht weiter.
Ich weiß echt nicht, wie ich das nächste Mal meine Frage noch expliziter stellen soll, damit klar, was ich wissen will …
Viele Grüße
Robert
Welche Frage hast Du denn zu meinem xCGI Modul die ich noch nicht beantwortet habe? Hier steht ja das Wesentliche, hast Du es gelesen? MfG
Moin,
die Frage ist doch schon beantwortet.
Dein Artikel mag für einen Aspekt sinnvoll sein, ist an dieser Stelle aber schon wieder sehr speziell am Thema vorbei. Ich komme mir vor wie anno 2011 im RBB Wahlstudio.
Und da von meiner Seite alle Fragen beanwortet sind, können wir den Thread hier allein weiterdriften lassen.
Viele Grüße
Robert
HTTP::Tiny
This constructor returns a new HTTP::Tiny object. Valid attributes include:
agent — A user-agent string (defaults to 'HTTP-Tiny/$VERSION'). If agent — ends in a space character, the default user-agent string is appended.
cookie_jar — An instance of HTTP::CookieJar — or equivalent class that supports the add and cookie_header methods
Das ist doch derselbe Mist -- dem Konstruktor eine Instanz einer anderen nicht verwandten Klasse zu übergeben. Warum wird die cookie_header methods nicht zu einer Methode der eigenen Instanz gemacht? Warum wird hier nicht Vererbung genutzt? MfG
Moin,
HTTP::Tiny
This constructor returns a new HTTP::Tiny object. Valid attributes include:
agent — A user-agent string (defaults to 'HTTP-Tiny/$VERSION'). If agent — ends in a space character, the default user-agent string is appended. cookie_jar — An instance of HTTP::CookieJar — or equivalent class that supports the add and cookie_header methods
Das ist doch derselbe Mist -- dem Konstruktor eine Instanz einer anderen nicht verwandten Klasse zu übergeben. Warum wird die cookie_header methods nicht zu einer Methode der eigenen Instanz gemacht? Warum wird hier nicht Vererbung genutzt?
Darüber kann man sich wohl sehr gut streiten, worauf ich keine Lust habe. Ich verstehe das so, als dass cookie_jar hier als Interface verwendet wird – in der OOP ein vollkommen legitimes Anliegen. Man hat wohl einfach gesagt, wir bauen für Cookies ein Objekt, egal woher sie kommen (aus einer Datei oder per HTTP), anstatt ein Objekt für „alles HTTP“ und eines für Dateien. „There is more than one way to do it.“
Viele Grüße
Robert
bauen für Cookies ein Objekt
Klar warum nicht. Aber dann bitteschön nicht in den Konstruktor übergeben sondern im Konstruktor als Instanz erstellen und die Methoden welche die Instanz mibringt zu eigenen Methoden machen. Siehe Beispiel, untenstehend.
Oder später aggregieren über eine Factory.
Darüber kann man sich wohl sehr gut streiten, worauf ich keine Lust habe.
Es ist eine Frage der Zweckmäßigkeit. Eine Übergabe von Instanzen in den Konstruktor anderer Klassen hat sich immer wieder als problematisch erwiesen. MfG
Tach!
Aber dann bitteschön nicht in den Konstruktor übergeben sondern im Konstruktor als Instanz erstellen
Wie löst du bei dieser Vorgehensweise Anforderungen, dass die Requests jeweils ihre eigene Datenhaltung verwenden sollen, weil sie zu unterschiedlichen Servern gehen, und in dieser Datenhaltung sich bereits Cookies aus früheren Requests befinden?
Wie löst du damit Anforderungen, dass der eine gern in Dateien, der andere aber gern in einem Shared Memory und der dritte eine Datenbank und ein vierter einer die derzeit noch unbekannte Art und Weise als Ablage der Cookie-Daten verwenden möchte?
Es ist eine Frage der Zweckmäßigkeit. Eine Übergabe von Instanzen in den Konstruktor anderer Klassen hat sich immer wieder als problematisch erwiesen.
Andere empfinden Dependency Injection sehr wohl als zweckmäßig, weil durch die erzwungene Übergabe, ohne die die Instanz oder Methode/Funktion nicht arbeiten kann, klar hervorgeht, was genau zur Ausführung benötigt wird und was man als Voraussetzung schaffen muss. Zudem erhöht das die Wiederverwendbarkeit, weil man seiner Kreativität freien Lauf lassen kann, welche konkrete Implementierung man da übergibt.
Warum wird hier nicht Vererbung genutzt?
Vererbung erhöht die Verflechtung und die Abhängigkeiten untereinander. Dass man mehr und mehr dazu übergeht, Vererbung sparsamer zu verwenden und andere Formen der Zusammenarbeit zwischen den Programmteilen verwendet, wird sicher nicht deshalb geschehen, weil es sich als unzweckmäßig erwiesen hat.
Ich muss nicht mit dem Bäcker verwandt sein. Es reicht vollkommen, wenn mir eine Instanz eines Backwarenfachgeschäfts zur Verfügung steht, um meinen Nahrungsbedarf zu decken. Zudem reicht es, wenn ich statt einer Instanz der Klasse Backwarenfachgeschäft lediglich definiere, dass die mir übergebene Instanz die Methode erwerbeNahrung() enthält. Das macht mich flexibler und lässt Spielraum für meine Kreativität als Verbraucher, weil der Nahrungsbedarf schließlich auch auf vielfältige andere Weise gedeckt werden kann. Und dafür ist es auch nicht notwendig, dass all diese Erfüllungsgehilfen eine gemeinsame Basis haben. Mein Garten mit dem Erdbeerbeet hat zum Beispiel so gut wie keine Gemeinsamkeiten mit dem Supermarkt.
dedlfix.
Wie löst du bei dieser Vorgehensweise Anforderungen, dass die Requests jeweils ihre eigene Datenhaltung verwenden sollen, weil sie zu unterschiedlichen Servern gehen, und in dieser Datenhaltung sich bereits Cookies aus früheren Requests befinden?
Gar nicht. Diese Anforderung löst allein die Instanz der Cookie Klasse. Siehe also dort.
Wie löst du damit Anforderungen, dass der eine gern in Dateien, der andere aber gern in einem Shared Memory und der dritte eine Datenbank und ein vierter einer die derzeit noch unbekannte Art und Weise als Ablage der Cookie-Daten verwenden möchte?
Das auch.
Vererbung erhöht die Verflechtung und die Abhängigkeiten untereinander.
Richtig. Speziell in diesem Fall "Cookie" ist Vererbung nicht angbracht.
ndere empfinden Dependency Injection sehr wohl als zweckmäßig, weil durch die erzwungene Übergabe, ohne die die Instanz oder Methode/Funktion nicht arbeiten kann, klar hervorgeht, was genau zur Ausführung benötigt wird und was man als Voraussetzung schaffen muss. Zudem erhöht das die Wiederverwendbarkeit, weil man seiner Kreativität freien Lauf lassen kann, welche konkrete Implementierung man da übergibt.
Was ich beschrieben habe ist Dependency Injection. Nur die Umsetzung in HTTP::Tiny
ist unzweckmäßig weil eine Übergabe an den Konstruktor erfolgt. Das heißt nämlich, daß die Abhängigkeit außerhalb der Klasse hergestellt wird. Und das ist schlecht. Wies besser geht schrieb ich auch.
SSL_options — A hashref of SSL_* — options to pass through to IO::Socket::SSL
Hier jedoch ist Vererbung angebracht.
MfG
Tach!
Was ich beschrieben habe ist Dependency Injection.
Definier doch mal bitte, was du unter DI verstehst.
Nur die Umsetzung in
HTTP::Tiny
ist unzweckmäßig weil eine Übergabe an den Konstruktor erfolgt. Das heißt nämlich, daß die Abhängigkeit außerhalb der Klasse hergestellt wird.
Genau das ist (eine/die häufigste Art von) Dependency Injection, so wie ich die Definition kenne und wie sie an vielen anderen Stellen verwendet wird.
Und das ist schlecht. Wies besser geht schrieb ich auch.
Sich die Dinge selbst im Konstruktor zu erstellen ist genau das, was DI nicht ist. Du löst auf diese Weise zwar auch eine Abhängigkeit, aber der entscheidende Punkt der Injection, der Zuführung von außen, fehlt.
dedlfix.
Aggregation/Delegation ist die bessere Lösung. Es läuft jedoch auf DI hinaus, weil auch mit DI Methoden delegiert werden können. Wie ich anderswo schrieb: Design Patterns nicht überbewerten.
Genau das ist (eine/die häufigste Art von) Dependency Injection, so wie ich die Definition kenne und wie sie an vielen anderen Stellen verwendet wird.
Genau damit habe ich sehr viele und allesamt schlechte Erfahrungen machen müssen, also wenn die Abhängigkeit außerhalb der eigenen Klasse hergestellt wird und Instanzen einfach so in den Konstruktor übergeben werden. Und genau diese schlechten Erfahrungen sind eben das Resultat wenn ein bestimmtes Design Pattern als Anforderung steht.
DI so wie Du sie kennst, führt zu schwer wartbaren Code, erschwert Debugging wie Fehlersuche und Qualitätssicherung. Diese Erfahrung gebe ich gerne weiter. MfG
Tach!
DI so wie Du sie kennst, führt zu schwer wartbaren Code, erschwert Debugging wie Fehlersuche und Qualitätssicherung. Diese Erfahrung gebe ich gerne weiter.
Erfahrungen kann man nicht weitergeben, die muss man selbst machen. Meine Erfahrungen sind in der Hinsicht andere. Und ich kann mir nicht vorstellen, dass DI "wie ich sie kenne" von so vielen anderen verwendet wird, wenn sie damit schlecht fahren. Fakten, nachvollziehbar, sind Punkte, die ich berücksichtigen kann, aber kein pauschales "ich habe schlechte Erfahrungen". Die können sich auf eine Menge Ursachen begründen, die anderswo nicht gegeben sind. Ohne dieses Wissen kann ich nicht beurteilen, ob die Erfahrungen auch für andere Fälle relevant sind.
dedlfix.
Natürlich muss jeder seine Erfahrungen selber machen. Aber speziell auf die ausführliche Begründung kann jeder selber kommen: Nämlich durch Überlegung.
Hallo pl,
DI, so wie dedlfix sie beschreibt, führt zu stärker entkoppeltem Code. Das ist abstrakter und bewirkt, dass man dem Sourcecode erstmal nicht mehr ansieht, welche konkrete Klasse da eigentlich mit welcher interagiert.
Insofern hast Du recht.
Andererseits bist Du auf diese Abstraktion angewiesen, wenn Du testbaren Code schreiben willst (sei es im Sinne von test driven development, oder sei es lediglich im Sinne von a posteriori geschriebenen Unittests).
Klassen, die sich ihre Abhängigkeiten selbst beschaffen, sind nicht isoliert testbar. Isolierte Tests sind aber wichtig, um automatisiert Codeteile testen zu können und damit eine fortlaufende Integration von Softwareänderungen zu ermöglichen.
Wenn sich Klassen ihre Abhängigkeiten über Aufrufe einer Factory beschaffen, brauchst Du eine konfigurierbare Factory, um bei Unittests die Mocks/Stubs unterschieben zu können. D.h. du verschiebst die Injektion aus den Konstruktorparametern in die Factory. Aber eigentlich hast Du nur einen DI-Container gebaut.
Ob man die Injektion aus dem DI Container in die Klasse nun über Konstruktorparameter löst, oder ob sich der Konstruktor die Abhängigkeiten aus dem DI Container abholt, macht oft keinen Unterschied. Es schafft aber eine unnötige Abhängigkeit (nämlich von der Klasse, deren Objekt erzeugt wird, zum DI-Container) und ZWINGT Dich, den DI Container zu verwenden. In Unit Tests ist es aber viel einfacher, ohne den Container zu arbeiten und die Mocks direkt einzuspritzen.
Übrigens kann man auch Factory-Klassen einspritzen und auf diese Weise erreichen, dass nicht alle abhängigen Objekte a priori existieren müssen. Das echte Objekt wird erzeugt (und ggf. sogar dynamisch geladen), sobald die erste Methode aufgerufen wird. Geht alles. Und die Klasse, der da was eingespritzt wird, weiß überhaupt nicht, was passiert. UND DARAUF KOMMT'S AN. Maximale Entkoppelung.
Rolf
Klassen, die sich ihre Abhängigkeiten selbst beschaffen, sind nicht isoliert testbar. Isolierte Tests sind aber wichtig, um automatisiert Codeteile testen zu können und damit eine fortlaufende Integration von Softwareänderungen zu ermöglichen.
Meine Antwort hierzu ist die Factory.
Wenn sich Klassen ihre Abhängigkeiten über Aufrufe einer Factory beschaffen, brauchst Du eine konfigurierbare Factory, um bei Unittests die Mocks/Stubs unterschieben zu können. D.h. du verschiebst die Injektion aus den Konstruktorparametern in die Factory. Aber eigentlich hast Du nur einen DI-Container gebaut.
Nein. Der Unterschied von Lazy Delegation (über die Factory) zu legacy DI besteht darin, dass die Abhängigkeit nicht außerhalb der eigenen Klasse hergestellt wird sondern in einer eigenen Methode. Also sowas:
# file param.pm
require xCGI;
*param = sub{
my $self = shift;
$self->{CGI} = xCGI->new() unless defined $self->{CGI};
return $self->{CGI}->param(@_);
};
Mocking: Sofern in dieser Datei keine package definiert ist, kann sie mit beliebigen Instanzen beliebiger Klassen aufgerufen werden. Also auch mit einer Attrappe (Mock). Zu Konfigurieren ist da nichts.
MfG
Ich weiß, der Thread driftet ab. Ich mag aber nicht stehen lassen, dass lazy initialization die Weiterentwicklung von DI sei. Das erscheint mir als ein Irrtum.
# file param.pm require xCGI; *param = sub{ my $self = shift; $self->{CGI} = xCGI->new() unless defined $self->{CGI}; return $self->{CGI}->param(@_); };
Ich kann kein Perl und will es auch nicht können, daher ist das für mich zu 75% kryptischer Zeichensalat.
Aber soviel scheint mir klar: Das ist ein Script, das eine Instanz einer xCGI Klasse holt und in einem CGI Property ablegt, sofern das Property nicht schon gefüllt ist. Und dann delegiert es den Aufruf an die param Methode dieses Objekts.
Ja, das ist lazy delegation. Und wer xCGI mocken will, muss vor dem ersten Aufruf an $self->{CGI} etwas zuweisen. Und da raucht es doch schon:
Ich kann das nicht als etwas besseres als Dependency Injection ansehen. Das ist keine echte Inversion Of Control. Hier ist auch kein Tooling möglich (sprich: ein automatischer DI Container), statt dessen habe ich das alles als Boilerplate im Code drinstehen.
Bei DI habe ich eine saubere Konstruktorleiste, in der ich typisiert die Interfaces anfordern kann, deren Implementation das Objekt braucht. Ein DI-Container kann mit Reflection-Mechanismus automatisch bestimmen, welche Interface-Implementierungen ein Objekt benötigt, so dass mein Wunsch an den DI-Container, dass ich doch bitte eine Implementierung des IFoo Interface bräuchte, konfigurativ auf die Klasse FooImpl gemappt werden kann; diese hat einen Konstruktor, der ein IBar, IMumpf und IZing Interface braucht und die besorgt der DI Container dann auch gleich. Dass IZing während der Initialisierungsphase als singleton-Objekt im Container abgelegt wurde, muss dann auch keiner mehr wissen. Irgendwer hat ein IFoo Objekt angefordert, und schwupps ist ein ganzer Objektbaum dabei entstanden. Teils mit Bezügen auf existierende Objekte, teils mit neuen Objekten.
Ja, das ist lazy delegation. Und wer xCGI mocken will, muss vor dem ersten Aufruf an $self->{CGI} etwas zuweisen.
Nein. Es geht ja gar nicht darum xCGI zu mocken, sondern Methoden der eigenen Instanz, die z.B. xCGI injezieren. In der Praxis wird ein Mock erstellt, mit dem Mock die Methode aufgerufen und hinterher geprüft ob $mock->can('Methodname')
eine Codereferenz liefert. can() ist eine Methode der Klasse UNIVERSAL, von dieser Klasse erbt jede Perl-Package.
Über @UNIVERSAL::ISA kannst Du übrigens auch Klassen automatisch laden. Wenn Du in diesem Array die Klasse 'Mock' einträgst und in Klasse Mock einen entsprechenden AUTOLOAD definierst, kann jeder Mock-Instanz Instanzen anderer Klassen unmittelbar selbst injezieren ohne die vorher mit use aufrufen zu müssen. So kann auch xCGI direkt gemockt werden.
MfG
Hallo pl,
eins hast Du erreicht: Ich weiß nicht mehr, ob ich zu dumm bin um dir zu folgen, oder ob Du einfach Themenwiesel spielst und bewusst kreuz und quer hüpfst.
Also: Doch, es geht bei DI gerade darum, Abhängigkeiten zu anderen Klassen nicht innerhalb einer Klasse herzustellen. Maximale Entkoppelung ist nicht nur eine Modeerscheinung, die von irgendwelchen geldgeilen Anzugträgern als neues Paradigma verkauft wird. Die ganze Entwicklung der Programmiertechnik läuft darauf hinaus, immer stärker abstrahieren zu können und feste Abhängigkeiten aufzulösen. Nur so kann ich nach Bedarf unterschiedliche Implementierungen eines Interface bereitstellen. Das brauche ich, um die Klasse in unterschiedlichen Kontexten einsetzen zu können. Zum Beispiel weil irgendein Stück Code abhängig vom verwendeten Webserver oder Betriebssystem ist. Dann verberge ich diese Plattformspezialität hinter einem Interface und gebe ich dem Request-Worker eine pro Plattform passende Implementierung mit. Oder ich will unittesten, dann gibt's einen Mock.
Oder ich will nicht, dass eine bestimmte Komponente initialisiert wird, bevor ich sie tatsächlich nutze. Dann injiziere ich meiner Klasse einen generischen LazyInitializer. Die Klasse muss dann in den meisten Sprachen wissen, dass sie einen LazyInitializer bekommt, und wenn die fragliche Komponente gebraucht wird, ruft sie auf dem getComponent() oder getValue() oder wie auch immer man das nennt auf[1]. Und der LazyInitializer erzeugt dann einmalig die Komponente und cached die Instanz. In .net, mit der Unity DI Library, kann Unity das automatisch. Es ist aber auch nicht so schwierig, das von Hand zu bauen (solange man keine Multithreading-Themen hat, die können tricky sein) :). Ich baue aber KEINESFALLS den LazyInitializer direkt in meine Klasse ein.
Rolf
Ich kenne Perl nicht, aber vermutlich gelingt da mit bless der gleiche Trick wie ich es in den 90ern in Smalltalk erlebt habe: Der LazyInitializer tut so, als wäre er das eigentliche Objekt, aber beim ersten Zugriff auf Properties oder Methoden beschafft er es und verwandelt sich dann in das Objekt (re-bless). Dummerweise auch dann, wenn man das Ding mit dem Debugger betrachten will. Irgendwie cool, aber trotzdem brrr... ↩︎
Mocking ist ja quasi eine Entkopplung. Das Beispiel ausführlich und da habe ich noch einmal das Wesentliche hervorgehoben, was Methoden einer Factory diesbezüglich auzeichnet: Sie definieren keine Package, sie sind classless.
Du kannst Perl mögen oder nicht, Perl OOP Implementierung erscheint mir in jedem Punkt logisch und in vielen praktischen Fällen logischer als OOP Implementierungen anderer PLs.
Jede PL kennt Objekte die jedoch nicht immer gleich sichtbar machen, ob sie Instanzen von Klassen sind oder nur Objekte. Diesem Umstand kommt bless() entgegen, das segnet ein Objekt mit dem Namen einer Klasse und macht damit das Objekt zu einer Klasseninstanz.
Was absolut brr.. pfui
wäre, ist eine Klasseninstanz umzublessen. Das ist in Perl möglich, weil Perl seine OOP Regeln nicht so streng und pragmatisch definiert wie andere PLs.
Das heißt aber noch lange nicht, dass man mit Perl deswegen ständig mit allen Chartas der OOP brechen muss was manche Schurken tatsächlich unverfroren machen und sich damit auch noch brüsten diese Ferkel.
So ist in meinem Beispiel die Klasse Mock ja selbst eine Attrappe und muss gar nicht Mock heißen sondern es kann eine beliebige Klasse sein, auch die main-Class die es in Perl grundsätzlich immer gibt.
Definiert PHP denn eine main-Class, bzw. ist es in einem Verbund von mehreren zusammenwirkenden PHP-Skriptn immer gleich sichbar wo sich die main gerade befindet? Klares Nein.
$mock = bless{}
ohne weiteres Argument nimmt die aktuelle __PACKAGE__
und ja natürlich wird man zur Qualitätssicherung eine dedizierte package Mock
einschließlich einer Konfiguration definieren damit alles seine Ordnung hat und übersichtlich bleibt.
Legacy DI schließlich, wo die Abhängigkeit außerhalb der Klasse hergestellt wird, löst die Kapselung auf. Das kann so gewünscht sein, bitteschön aber es ist ein Bruch mit der OOP Charta. Und führt vielfach zu Problemen, wer damit schafft, wird schon wissen was er tut. Die meisten die ich kennenlernen durfte, wusten es jedoch nicht. Die haben die Sache noch verschlimmert indem sie den Ablauf ihrer Programme dann auch noch über klassenweite und gar globale Variablen steuerten die von Instanzen verändert werden durften.
Static hat man daher in Perl bewusst hinausgezögert und das Schlüsselwort static erst ab 5.10 eingeführt. Und auch ab da muss dieses Feature explizit eingeschaltet werden damit man daran erinnert wird, dass es auch zweckmäßiger geht, denn: Im Effekt ist jede Property einer Instanz quasi statisch solange die Instanz lebt.
Fazit: Mit Mocking erkauft man sich eine Qualitätssicherung um den Preis die OOP Charta stets irgendwo aufzubrechen. Und sei es nur die Kapselung.
Schöne Grüße, scheiß Wetter 😉
Hallo pl,
Beim Wetter stimme ich Dir zu. Das ist in Köln auch recht übel.
Aber der Rest wird jetzt langsam doch wirr. Oder ich verwirrt? Passiert gerade das, was Dir schon andere geschrieben haben: Du verwendest eine eigene Sprache, deren Begriffe Homonyme zur allgemein verwendeten Begriffswelt der Informatik sind - aber nicht identisch damit?
Mocking implementiert keine Entkopplung, es ist andersrum. Mocking wird erst durch Entkopplungsmechanismen möglich. Mocking bedeutet nach meiner Kenntnis, dass irgendeine Komponente A eine Komponente B benötigt, statt dessen aber zu Testzwecken eine Komponente B' bekommt, die so tut als wäre sie B. Bei Dir wäre das eine Möglichkeit, der xCGI Klasse ein gefälschtes Environment vorzugaukeln, aus dem sie den Query-String lädt, statt den Test-Querystring ins echte Environment zu schreiben.
Dieser Environment-Accessor, den Du dann mocken müsstest, wäre dann eine möglichst kleine und dumme Klasse, die man selbst nicht unittesten kann, weil sie dazu da ist, auf die echte Umgebung zuzugreifen. Das Environment zu manipulieren, um ein Testszenario zu schaffen, ist kein Unittest. Es ist ein Test - ja - aber kein UNIT Test.
Classless methods, wie Du sie beschrieben hast - also letztlich Funktionen, die wissen, dass sie als ersten Parameter ein Klassenobjekt bekommen - führen nach meinem Verständnis zu einem globalen Chaos, weil sie dann nicht mehr Teil einer Klasse (und damit eines Namens-Containers) sind. Du setzt in deinem Beispiel doch implizit voraus, dass eine Methode param nur innerhalb der xCGI Klasse existiert. Das kann ich nicht als gutes Beispiel nachvollziehen.
Dein Codebeispiel finde ich auch merkwürdig:
my $name = 'param';
require "$name.pm";
if( $mock->can($name) ) {
if( $mock->param('mock') ){
print "Methode $name funktional";
}
}
Überall wird mit $name gearbeitet (was vermutlich analog zu PHP auch innerhalb eines Strings aufgelöst wird), nur der Aufruf selbst geht dann direkt auf die param Methode. Was hilft Dir die Flexibilität in $name, wenn sie im eigentlichen Aufruf nicht passiert?! (Dass Du nicht prüfst, ob 123 zurückkommt, liegt vermutlich daran dass das nicht das Testziel in diesem Test war...).
Rolf
Was hilft Dir die Flexibilität in $name, wenn sie im eigentlichen Aufruf nicht passiert?! (Dass Du nicht prüfst, ob 123 zurückkommt, liegt vermutlich daran dass das nicht das Testziel in diesem Test war...).
In Perl ist das alles machbar. Vielleicht bist Du nur deshalb so verwirrt, weil Du Perl nicht ausreichend kennst. Man kann es nämlich auch so machen:
# Prüfe ob die Methode verfügbar ist
if( my $code = $mock->can($name) ) {
$RV = $mock->$code('mock');
}
Und über den $RV (return Value) ganz genau prüfen ob es dem Kriterium entspricht. Den Artikel habe ich entsprechend ergänzt und ja, ein mocking wo die Abhängigkeit außerhalb der Klasse hergestellt wird ist ja nach wie vor auch möglich (klassisch DI) siehe ebenda.
CGI/1.1 definiert eine Schittstelle die %ENV (Serverumgebung) und STDIN/STDOUT nutzt. Wenn Du da was testen willt, musst Du Dummy-Daten auch da bereitstellen wo sie, je nach $ENV{CONTENT_TYPE}
geparst werden sollen und je nach $ENV{CONTENT_LENGTH}
überhaupt zu erwarten sind.
Transparent wie HTTP nunmal ist, kann man Unittests selbstverständlich auch über HTTP schleifen. Das setzt natürlich ein Framework voraus, was Unittests via HTTP untersützt. Mein Framework unterstützt das.
Bis dann 😉
Wenn Du also alles, das Nachladen der Abhängigkeit, die Injection und die Funktionalität zusammen prüfen willst, sieht das so aus:
# Instanz der Klasse Mock erstellen
my $mock = bless{};
# setze einen definierten Parameter
$ENV{QUERY_STRING} = q(mock=123);
# die zu prüfende Methode parst den Parameter aus %ENV
print $mock->param('mock'); # 123
Da eine außenstehende Methode erst kompiliert werden muss, definiert die Klasse Mock eine AUTOLOAD Methode und den Pfad zum Factory-Verzeichnis.
Moin,
Aber dann bitteschön nicht in den Konstruktor übergeben sondern im Konstruktor als Instanz erstellen und die Methoden welche die Instanz mibringt zu eigenen Methoden machen.
Der Sinn von OOP ist doch die Wiederverwendbarkeit. Wenn ich im Konstruktur erst die Instanz erzeuge, heißt das im Umkehrschluss, dass ich für jedes HTTP-Objekt ein Cookie-Objekt habe, also eine 1:1-Beziehung. Wenn ich aber nun ein Cookie-Objekt mit mehreren HTTP-Objekten teilen will, also 1:n, dann geht das mit deinem Ansatz nicht mehr.
Darüber kann man sich wohl sehr gut streiten, worauf ich keine Lust habe.
Es ist eine Frage der Zweckmäßigkeit. Eine Übergabe von Instanzen in den Konstruktor anderer Klassen hat sich immer wieder als problematisch erwiesen.
Um auf das Beispiel zurückzukommen: Der Konstruktor bekommt zwei Instanzen übergeben, eine vom Typ String, eine vom Typ HTTP::CookieJar. Was soll daran, insbesondere auch am ersten Parameter, problematisch sein? Verstehe ich dich richtig, dass Konstruktoren generell keine Parameter aufnehmen sollten?
Viele Grüße
Robert
Die Wiederverwendbarkeit kann man ebenfalls kapseln ohne dass Abhängigkeiten außerhalb einer Klasse hergestellt werden müssen. Z.B. in einer Factory.
Da Problem ist ja nicht die Übergabe einer Instanz schlechthin in den Konstruktor einer anderen Klasse. Das Problem ist, dass die Daten-Kapselung verlorengeht wenn Abhängigkeiten außerhalb der Klasse hergestellt werden. Ein Cookieobjekt arbeitet mit Request und Response zusammen und wird dadurch ggf. auch verändert und da fließen Daten. Solche Veränderungen und der Datenfluß werden infolge DI nach außen getragen -- was daran sollte denn da geteilt werden? Nein! Und nochmals Nein, alles was einen Request/Response Zyklus betrifft, darf sich nur in der Instanz derjenigen Klasse abspielen die diesen Zyklus handelt!
Verstehe ich dich richtig, dass Konstruktoren generell keine Parameter aufnehmen sollten?
Nein, im Gegenteil! Sie müssen außer Parameter welche im Konstrukt für die eigenen Instanz benötigt werden auch die Parameter bekommen die zum Erstellen aggregierter Instanzen erforderlich sind. Also kriegt bspw. der Konstruktor für einen HTTPClient einen Dateinamen übergeben, nämlich den den das in den HTTPClient eingebaute Cookie-Objekt braucht damit es weiß wo die Dinger gespeichert werden sollen.
Und dieses CookieObjekt wird nicht übergeben sondern
MfG
Moin,
Die Wiederverwendbarkeit kann man ebenfalls kapseln ohne dass Abhängigkeiten außerhalb einer Klasse hergestellt werden müssen. Z.B. in einer Factory.
Damit habe ich dann aber trotzdem im Endeffekt ein 1:1-Verhältnis von Cookie- zu HTTP-Objekten und verliere den Vorteil, den ein Cookie-Objekt für mehrere HTTP-Objekte haben kann.
Da Problem ist ja nicht die Übergabe einer Instanz schlechthin in den Konstruktor einer anderen Klasse.
Hier steht etwas Anderes:
Eine Übergabe von Instanzen in den Konstruktor anderer Klassen hat sich immer wieder als problematisch erwiesen.
Das Problem verstehe ich nicht:
Das Problem ist, dass die Daten-Kapselung verlorengeht wenn Abhängigkeiten außerhalb der Klasse hergestellt werden. Ein Cookieobjekt arbeitet mit Request und Response zusammen und wird dadurch ggf. auch verändert und da fließen Daten. Solche Veränderungen und der Datenfluß werden infolge DI nach außen getragen -- was daran sollte denn da geteilt werden? Nein! Und nochmals Nein, alles was einen Request/Response Zyklus betrifft, darf sich nur in der Instanz derjenigen Klasse abspielen die diesen Zyklus handelt!
Wieso geht die Datenkapselung verloren, wenn ich dafür wohl definierte Schnittstellen bereitstelle? Und es ist in der OOP doch vorgesehen, dass Objekte sich gegenseitig „Nachrichten“ schicken.
Im Bezug auf das Cookie-Beispiel: Nehmen wir an, ich verwende ein Cookie-Objekt für mehrere Requests, dann werden dessen Daten, die zuvor bspw. aus einer Datei gelesen worden sind, geteilt. Und ein Cookie, welches per HTTP gesetzt wird, aktualisiert den Zustand des Cookie-Objekts, der in allen nachfolgenden Requests zur Verfügung steht. Was soll daran schlecht sein?
Also kriegt bspw. der Konstruktor für einen HTTPClient einen Dateinamen übergeben, nämlich den den das in den HTTPClient eingebaute Cookie-Objekt braucht damit es weiß wo die Dinger gespeichert werden sollen.
Und dieses CookieObjekt wird nicht übergeben sondern
- Im Konstruktor des HTTPCLient erstellt
- und an Ort und Stelle in die Instanz des HTTPClient eingebaut.
Das heißt an statt über das eine Cookie-Objekt im Arbeitsspeichere mehrere Request-Objekte synchron zu halten würdest du das dann über eine Datenbank oder Datei realisieren?
Viele Grüße
Robert
Wieso geht die Datenkapselung verloren, wenn ich dafür wohl definierte Schnittstellen bereitstelle? Und es ist in der OOP doch vorgesehen, dass Objekte sich gegenseitig „Nachrichten“ schicken.
Das ist alles richtig. Aber wenn das CookieObjekt außerhalb der HTTPClient-Instanz erstellt wurde, sind Daten die nur den HTTPClient und seine Prozesse betreffen, über das nach wie vor draußen liegende CookieObjekt, eben über dessen Schnittstellen, auch draußen zugänglich. MfG
Moin,
Aber wenn das CookieObjekt außerhalb der HTTPClient-Instanz erstellt wurde, sind Daten die nur den HTTPClient und seine Prozesse betreffen, über das nach wie vor draußen liegende CookieObjekt, eben über dessen Schnittstellen, auch draußen zugänglich.
Viele Grüße
Robert
In Perl sind Instanzen von Klassen Referenzen. Beispiel zum aktuellen Thema:
package Cookie;
sub new{
my $class = shift;
return bless{}, $class;
}
package HTTP;
sub new{
my $class = shift;
my $cookie = shift; # Objekt
return bless{COOKIE => $cookie}, $class;
}
sub request{
my $self = shift;
$self->{COOKIE}{text} = "Huch, hab mir einen Cookie eingefangen";
}
package main;
my $cookie = Cookie->new;
my $http = HTTP->new($cookie);
$http->request;
print Dumper $cookie;
Ausgabe des Dump:
$VAR1 = bless( {
'text' => \'Huch, hab mir einen Cookie eingefangen'
}, 'Cookie' );
Daten also außen zugänglich. q.e.d.
Moin,
sorry, dann habe ich dein Beispiel genau falsch herum verstanden. Ich habe es so gelesen, als dass das Cookie-Objekt die Referenz „zurück laufen“ und damit auf das HTTP-Objekt zugreifen könnte.
Das zweite Beispiel ist genau so, wie ich mir das Teilen der Informationen des einen Cookie-Objekts mit mehreren HTTP-Objekten vorstelle.
Viele Grüße
Robert