Das Freenet-Projekt (http://www.freenetproject.org) hat es sich zur Aufgabe gemacht, jedem die die Möglichkeit freier Meinungsäußerung zu garantieren. Neben der Implementierung einer Lösung für dieses Problem durch das Projekt geht es in diesem Artikel darum, wie man in Perl selbst Informationen herunterlädt bzw. veröffentlicht.
Das Freenet-Projekt (http://www.freenetproject.org) hat es sich zur Aufgabe gemacht, jedem die die Möglichkeit freier Meinungsäußerung zu garantieren. Als Mittel zur Umsetzung hat es ein virtuelles Netzwerk gewählt, in dem es nicht realistisch möglich sein wird, Einspeisung von neuen Daten zu verhindern oder den Urheber von Veröffentlichungen ausfindig zu machen, solange er dies nicht selbst zugibt. Darüberhinaus ist es für Betreiber von Freenet-Knoten nicht möglich, zu wissen, was auf ihrer Festplatte gespeichert wurde oder wo die Daten herstammen, so daß es ab einer bestimmten Netzwerkgröße nicht mehr möglich ist, dem Betreiber einer Freenet-Node nachzuweisen, daß er illegale Inhalte besitzt bzw. weitergegeben hat oder davon gewusst hat.
In China beispielsweise wird eine frühe Version von Freenet für ein rein chinesisches Freenet-Netzwerk aktiv benutzt, um politische Inhalte verbreiten zu können, ohne Maßnahmen von der Regierung zu befürchten.
Freenet speichert nur relativ kleine Dokumente (<= 1MB) am Stück. Diese Dokumente können beliebige Daten enthalten, z.B. HTML-Seiten. Diese Datenblöcke können mit einem für jeden Block eindeutigen Schlüssel abgerufen werden, z.B. in einem Web-Browser.
Hier ist ein Beispiel für eine solche HTML-Seite:
Die hier verwendete URL ist:
http://129.13.162.73:8888/...
...SSK@Sc6qV~D6iFhaYord6HtbjJ8MaEYPAgM/YoYo//Controversy.html
http://129.13.162.73:8888 ist die Adresse meines
Freenet-Daemons, üblicherweise
http://127.0.0.1:8888, der Sicherheit wegen sollte man
immer einen eigenen Freenet-Daemon laufen lassen. Der
Schlüssel der ``Freesite'' ist
SSK@Sc6qV~D6iFhaYord6HtbjJ8MaEYPAgM und in dieser wird
wiederum das Unterdokument YoYo//Controversy.html
angezeigt.
Diese Seite ist Teil eines sehr bekannten Einsprungpunktes ins Freenet. Generell bevorzugt das Freenet-Projekt keine bestimmten Einsprungpunkte. Die Regel ist: kennt man den Einsprungpunkt nicht, findet man die Inhalte nicht; es ist sogar unmöglich zu bestimmten, wieviel oder welche Informationen im Freenet gespeichert sind: Ein Index a'la google ist prinzipbedingt nicht möglich.
Obwohl es einige Freenet-Spider und Directory-Freesites gibt ist der überwältigende Teil der Information im Freenet nicht darüber zu erreichen.
Neben HTML-Inhalten gibt es auch Mail-, Foren- und Chat-Systeme im Freenet.
Die Einspeisung von Daten kann von überall im Netz geschehen. Mehrfacheinspeisung ist möglich (und meistens notwendig) und führt nicht zur Dupliziering von Daten.
Natürlich gibts es auch einige Nachteile, die teilweise mit dem Design und teilweise mit der Implementierung zusammenhängen. So ist der einzige verfügbare Freenet-Daemon in Java implementiert und daher leider extrem unportabel (läuft praktisch nur auf Linux/x86 und Windows, da das benötige Sun-Java-JDK auf anderen Platformen nicht verfügbar oder veraltet ist), sehr langsam und vor allem speicherhungrig, und häufig auch sehr instabil.
Die aktuellen Versionen des Freenet-Daemons sind alle als ``nicht sicher'' deklariert, da erst die Release 1.0 verspricht, alle Ideen umgesetzt zu haben, wovon das Projekt noch 0.5 Versionen entfernt ist :)
Designbedingt ist das Freenet eher langsam (extrem hohe Latenz, akzeptabler Durchsatz) und für interaktive Benutzung teilweise ungeeignet, bzw. eine echte Geduldsprobe.
Durch die Art der Speicherung kann nicht garantiert werden, daß Informationen überhaupt wiedergefunden werden. ``Unpopuläre'' Information verschwindet, sofern sie nicht regelmäßig eingespeist oder abgerufen wird, automatisch wieder aus dem Freenet.
Herkömmliche File-Sharing-Netzwerke haben zwei große Nachteile: Entweder sind sie nicht wirklich anonym (selbst anonymisierende Netzwerke oder Proxies sind üblicherweise nicht vor Zugriff durch Regierungen sicher (ein bekanntes Beispiel ist JAP, http://anon.inf.tu-dresden.de, das zwar vollmundig vollkommene Anonymität garantiert, aber schon einmal durch die Polizei gezwungen wurde, Log-Informationenen herauszugeben, wozu die Betreiber nach deutschem Recht verpflichtet sind). Oder sie skalieren nicht, da sie Broadcast-Algorithmen benutzen, die größere Netzwerke von vorneherein ausschließen (bekanntes Beispiel dafür ist gnutella).
Das erste Problem umgeht Freenet (Achtung, vereinfacht!), indem es jeden Datenblock hasht und aus diesem Hash einen Schlüssel für die Verschlüsselung generiert und die Daten damit verschlüsselt (sic).
Der Schlüssel wird ein zweitesmal gehasht. Dieser Hash wird zur eindeutigen ID des Datenblocks. Diese ID und der verschlüsselte Datenblock werden ins Freenet eingespeist. Dabei wird immer derselbe Key generiert (da er nur von den Daten abhängt), und der verschlüsselte Block ist ebenfalls immer gleich. Daher kann er problemlos mehrfach und von unterschiedlichen Parteien eingespeist werden ohne die Daten faktisch mehrfach im Freenet abzulegen.
Das ist sicher, da ein kryptographisch sicherer Hash (der die Grundlage des Systems bilden muß) nur in eine Richtung funktioniert: Aus der ID (== Hash des Hashes der Daten) kann nicht auf den Schlüssel geschlossen werden. Speichert also ein Netzknoten die Daten und die ID dazu, kann man die Daten zwar abrufen, jedoch nicht entschlüsseln. Selbst der Besitzer eines Knotens kann mit vollkommenen Wissen über alle Vorgänge seines Knotens die Inhalte nicht lesen.
Eine weitere Konsequenz dieses Verfahrens ist die Tatsache, das Dokumente niemals verändert werden können: eine Änderung bewirkt eine Änderung des Schlüssels und damit eine neue ID, praktisch eine völlig neue URL.
Möchte man z.B. das grüne Blatt der Freesite ``Thought Crime'' herunterladen so wird man mit folgendem Freenet-Key konfrontiert:
CHK@pfAv9IejYPLQwaTLXDguEkUhiNUMAwI,blzDbhN~8Q28esq4JrfRWw
CHK steht für Content-Hash-Key, der
häufigste Schlüsseltyp im Freenet, der das oben
beschriebene Verfahren benutzt. Der CHK besteht aus zwei Teilen:
der ID, unter der der Schlüssel im Freenet abgelegt wird
(pfAv9IejYPLQwaTLXDguEkUhiNUMAwI) und dem
Schlüssel, mit dem die Daten verschlüsselt wurden
(blzDbhN~8Q28esq4JrfRWw), durch ein Komma
getrennt.
Das sind übrigens (mehr oder weniger) base64-encodete Daten, dekodiert sieht der Schlüssel so aus:
CHK@ ID = a5f02ff487a360f2d0c1a4cb5c382e12452188d50c0302 Key = 6e5cc36e137ef10dbc7acab826b7d15b
(Man kann daraus tatsächlich ablesen, das der Datenblock 4k groß (0x0c am Ende der ID bedeutet 2**12) ist und Twofish benutzt. Aber derartige Details überläßt man lieber einem Perl-Modul).
Nur der erste Teil wird als Anfrage ins Freenet geschickt, der zweite Teil bleibt im lokalen Freenet-Daemon. Wird nun der Datenblock geliefert, kann er mit dem Schlüssel dekodiert werden und wird an den Benutzer geliefert.
Das zweite Problem (der Skalierbarkeit) wird durch lineare statt exponentielle Suche gelöst: mit jeder Anfrage wird eine Hops-To-Live-Angabe (HTL, ähnlich wie das TTL in IPv4) verknüpft. Diese Zahl, die üblicherweise zwischen 5 und 25 (maximal) liegt, gibt an, wie viele Knoten die Anfrage maximal weitergeleitet wird. Jeder Knoten, der die Daten nicht lokal vorrätig hat, leitet sie an denjenigen Knoten weiter, der die Daten am wahrscheinlichsten hat. Da das Verfahren linear ist und nicht exponentiell, skaliert es ebenfalls linear mit der Netzwerkgröße.
Lädt man das Dokument, wird man mit Metadaten und ``normalen'' Daten konfrontiert.
Metadaten:
Revision=1 EndPart Document Info.Format=image/png End
Daten:
PNG^Z...
Nun stellt sich die Frage, wie man sicherstellt, daß Daten im Freenet bleiben, da es nur endliche Speicherkapazität hat. Die Antwort ist überraschend: Es geht nicht. Wenn ein Knoten sich entscheidet, Daten zu löschen oder Platz für neue Daten zu schaffen, gehen zwangsläufig andere Daten verloren. Dagegen hilft nur wiederholtes Einfügen und der Wunsch, daß die eigenen Daten beliebt genug sind, damit sie abgerufen werden und damit länger überleben.
Absolute Anonymität und nichtnachvollziehbarkeit von Transaktionen ist eben nicht vereinbar mit garantierter Datenspeicherung. Könnte man garantiert nachweisen, das ein Dokument existiert, so wäre es für einen Knotenbetreiber schlecht möglich, Wissen über die Art der Daten abzustreiten, die er speichert.
Der zweite gebräuchliche Schlüsseltyp, mit dem man im
Freenet konfrontiert wird, ist ein SSK-Schlüssel
(SSK steht für Signed Subspace Key).
Hier ist einer:
SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM
Zwei Dinge unterscheiden Sie von CHK-Schlüsseln:
Erstens ist ein SSK-Schlüssel eigentlich ein Schlüsselpaar, ein Schlüssel (der private Schlüssel) muss für das Einfügen von Daten benutzt werden, der zweite Schlüssel (der öffentliche Key) wird für das Auslesen der Daten benutzt. Da die Daten signiert sind, kann nur der oder die Besitzer des privaten Schlüssels Daten unter diesem einfügen, während die Besitzer des öffentlichen Schlüssels überprüfen können, ob die Daten tatsächlich mit dem richtigen Schlüssel signiert wurden.
Zweitens ist die ID, unter dem diese Daten abgelegt werden, nicht von den Daten sondern nur vom ``Namen'' (dem Key) abhängig, man kann also URIs erzeugen auf Dateien, die noch nicht eingefügt wurden.
Diese Art Schlüssel ist relativ ineffizient und kann keine großen Datenmengen speichern (<= 32KB!), SSKs werden also vorwiegend für Weiterleitungen auf CHKs benutzt.
Der obige SSK gehört übrigens zur ``Freenet Explained''-Freesite, die die einzelnen Schlüsseltypen (und mehr) erklärt. Finden kann man sie hier:
SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/3//
bzw., wenn man einen Freenet-Daemon am Laufen hat, hier:
http://127.0.0.1:8888/SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/3//
Das ganze wäre relativ witzlos, wenn man nur irgendwelche
langweiligen Tools und/oder Java benutzen kann. Es gibt zwei
Perl-Module (bzw. Modulfamilien), Net::Freenet::FCP
(http://www.sf.net/projects/perlfcp)
und Net::FCP.
Ersteres ist älter (und vielleicht besser benannt) und konzentriert sich mehr auf die Verarbeitung von Metadaten, ist aber recht schwach beim eigentlichen Protokoll-Handling. Letzteres ist sehr gut beim Protokoll-Handling und stark beim Kodieren und Dekodieren von Daten, überläßt das Metadatenhandling aber dem Programmierer.
Net::FCP stammt von mir und ist entstanden, weil
ich das andere Modul, das es nur im Freenet gab, nicht
herunterladen konnte (tja, so ist das eben als Freenetter). Daher
gehe ich auch nur auf dieses Modul ein :)
Zuerst braucht man einen Freenet-Daemon (http://www.freenetproject.org). Die Installation ist unglaublich komplex (unter Windows gibt es einen Installer, unter Unix ein paar Skripte). Der Daemon braucht eine Weile, bis er warm wird (Minuten bis Stunden), was man durch Surf-Versuche unterstützen sollte.
Das Net::FCP-Paket gibt es ganz normal per
CPAN.
Die Abkürzung FCP steht für Freenet Client Protocol und ist das Protokoll zwischen Freenet-Anwendungen und dem Freenet-Daemon. Es ist unglaublich ineffizient und noch dazu nicht verschlüsselt. Dies ist der Grund für die Empfehlung, den Daemon nur auf dem lokalen Rechner laufen zu lassen: wäre dumm, wenn man aus dem hochsicheren Freenet über eine ungesicherte Internetverbindung die Windows-Sourcen herunterlädt und sich erwischen läßt (ist alles schon vorgekommen) ...
In Perl geht dies denkbar einfach:
use Net::FCP; my $fcp = new Net::FCP;
Man kann dem Konstruktor von Net::FCP direkt Namen
und Port des Freenet-Daemons übergeben. Üblicherweise
holt er sich diese aber aus den Environment-Variablen
FREDHOST und FREDPORT, die auf
127.0.0.1 bzw. 8481 defaulten.
Übrigens wird nur eine virtuelle Verbindung aufgebaut.
Möchte man sicherstellen, daß tatsächlich ein
Daemon erreichbar ist, kann man einen
client_hello-Request ausführen oder einfach
loslegen.
Im Normalfall heißt das also:: no configuration required.
Hat man ein Net::FCP-Objekt, kann man schon alle
Requests durchführen, die man machen möchte:
my ($meta, $data) = @{ $fcp->client_get (
"freenet:SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/3//"
15
) };
Der client_get-Request erwartet zwei Argumente:
eine Freenet-URI
(freenet:<freenet-schlüssel>) und eine HTL.
Letztere muss man nicht angeben, man sollte es aber, und vor allem
sollte man dem Benutzer die Wahl der HTL überlassen. Das
dritte, optionale, Argument ist für spezielle Anwendungen: am
besten ignorieren.
Als Ergebnis erhält man immer eine Array-Referenz mit den
Metadaten und den Daten. Immer. Sollte ein Fehler auftreten wird
eine Exception ausgelöst (auf Perl: das Modul
died). eval {} hilft also im
Zweifelsfalle, für einfache Anwendungen ist das aber
overkill.
Bricht das Programm ab, so sieht das folgendermaßen aus:
Net::FCP::Exception<<short_data,reason:unexpected eof or internal node error>>
Oder, wenn die Daten nicht gefunden wurden:
Net::FCP::Exception<<data_not_found,>>
Letzteres bedeutet übrigens nicht aufgeben, sondern nochmal versuchen, z.B. mit einer höheren HTL. Ein fehlgeschlagener Versuch mit hoher HTL bedeutet immer noch nicht aufgeben. Daemons in der ``Nähe'' wissen nun, das die Daten verlangt werden. Nach einiger Zeit ist es wahrscheinlich, das die Daten auf einmal in der Nähe liegen: daher nie aufgeben, nochmal probieren. Niemand hat behauptet, man bekäme die Daten einfach aus dem Freenet wieder heraus...
Die Metadaten sind als Textdokument gespeichert. Das Format
dieser Metadaten ist aber relativ ``krank'', weshalb das Perl-Modul
einen Hash mit den geparsten Daten liefert (genaugenommen ein
Net::FCP::Metadata-Objekt, aber dieses Modul ist noch
in Entwicklung).
Die Metadaten und die eigentlichen Nutzdaten kann man ausgeben:
use Data::Dumper; print STDERR Dumper $meta; print $data;
Damit haben wir im wesentlichen den Quelltext für das
``Tool'' eg/fetch1, mit dem man einen
Freenet-Datenblock herunterladen kann.
Ich benutze es üblicherweise so:
eg/fetch1 SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/3// >data
Das schreibt die Daten in eine Datei und gibt gleichzeitig dessen Metadaten aus.
Für den Key
SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/3// ergibt
dies:
'version' => { 'revision' => '1' },
'document' => [
{
'info' => { 'format' => 'image/png' },
'redirect' => { 'target' => 'freenet:CHK@pMKW...KAwI,7...-25C2w' },
'name' => 'activelink.png'
},
{
'info' => { 'format' => 'text/plain' },
'redirect' => { 'target' => 'freenet:CHK@2hew...KAwI,o...21z91w' },
'name' => 'description.txt'
},
{
'info' => { 'format' => 'text/html' },
'redirect' => { 'target' => 'freenet:CHK@kME~...QAwI,3...dy4OkA' },
'name' => 'index.html'
},
{
'info' => { 'format' => 'text/html' },
'redirect' => { 'target' => 'freenet:SSK@0OhV...PAgM/fx/4' },
'name' => '.next'
},
{
'info' => { 'format' => 'text/html' },
'redirect' => { 'target' => 'freenet:CHK@kME~...QAwI,3...dy4OkA' }
}
],
'raw' => 'Version
Revision=1
[... gekürzt...]
Info.Format=text/html
End
'
};
Die eigentlichen Daten sind leer (null Byte gross), das Dokument enthät also nur Metadaten! Man beachte vor allem die teilweise recht tiefe Verschachtelung.
Der Key $metadata->{raw} enthält die
Metadaten, wie sie vom Freenet kamen. Dies ist notwendig, da man
manchmal die Metadaten zum Prüfen in der exakten Form
benötigt, wie sie hochgeladen wurden. Ansonsten kann man sie
ignorieren.
Der Key $metadata->{document} enthält
Informationen über das Dokument oder in diesem Fall die
Dokumente: Ein Hash pro Dokument. Schaut man sich den Inhalt an
(ein Array), so sieht man in jedem Hash ein name-Key,
der den Namen des Subdokuments angibt (bis auf den letzten, darauf
komme ich gleich).
Schaut man sich dir Freenet-URIs dieser Freesite an, so sieht man derartige URIs:
SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/3// # Hauptseite SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/3//activelink.png # Icon SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/3//description.txt # für Spider
Wenn diese Dokumente mit client_get anfordert,
bekommt man immer obiges Dokument. Freenet ignoriert nämlich
alles, was hinter dem // steht und liefert daher immer
das gleiche Dokument aus dem SSK-Bereich. (Darauf verlassen soltle
man sich nicht unbedingt, in Zukunft reagiert der Freenet-Daemon
vielleicht anders. Als Freenetter hat mans eben nicht leicht).
Der Teil hinter den beiden Slashes (//) ist nun das
Unterdokument, das man über den Namen identifizieren kann:
$activelink = grep $_->{name} eq "activelink.png",
@{ $metadata->{document} };
Fehlt der name-Eintrag, ist es der Eintrag mit dem
leeren Namen, in diesem Fall der erste Link, der nichts nach den
// stehen hat. Der name-Key kann
vorhanden und leer sein: Freenet überprüft die Daten
nicht, daher gibt es durchaus unterschiedliche Auslegungen für
das genaue Format.
Die Metadaten sind etwas genauer in dem *hüstel* etwas veralteten *hüstel* Freenet Explained-Dokument beschrieben.
Möchte man nun das activelink.png
herunterladen muss man in < $activelink-{redirect}
>> nachsehen. Meistens verweist dies auf einen
CHK-Schlüssel. Letztere sind sozusagen
Allgemeingut: jeder kann sie benutzen, und da sie nicht
änderbar sind, kann sie auch niemand fälschen, daher muss
man sie nicht signieren.
Auf $activelink->{info}{format} sollte man sich
übrigens nicht verlassen.
Und sollte kein redirect-Key vorhanden sein, so ist
das Dokument im mitgelieferten Datenblock. Alles ganz logisch,
einfach, und klar, wie man sieht.
Egal, der CHK-Key für activelink.png liefert
folgendes:
'version' => { 'revision' => '1' },
Und die Daten stellen ein PNG dar. Naja, nicht gerade üppig, die Metadaten, aber man hätt's ja im Link auf dieses Dokument sehen können. Als Freenetter hat mans nicht leicht.
Aber mit diesen Erklärungen kann man schon einfache
Freenet-Spider bauen. Die wichtigsten Tools sind veraltete und
spärliche Dokumente wie Freenet Explained und Tools
wie Data::Dumper. ``Veraltet'' ist übrigens nicht
immer schlecht, da viele Dokumente auch alt sind oder sich eh'
nicht exakt an den ``Standard'' halten. Mit der Zeit wird hier
sicher eine Besserung eintreten.
Oder: ``Wenn man Inhalte nicht mehr verändern kann, wie verändert man sie?''
Die offensichtliche Methode ist: ``Man macht sie veränderbar''. In der Freenet-Gemeinde geistert seit langer Zeit der Mythos des TUK-Schlüssels, des Time Updatable Keys. Diese Schlüssel tauchen immer auf, wenn jemand über veränderbare Schlüssel spricht. Leider weiss niemand, wie man sie implementieren soll, und ganz persönlich fürchte ich, es wird niemals veränderbare Keys geben.
Die nächstliegende Methode nutzt die Eigenschaft von SSKs (genauer: redirects) aus, das man den Namen sofort generieren kann, den Inhalt aber erst später einfügt.
Dies nutzt man aus, indem man Editionen herausgibt und durchnummeriert. Jede Edition enthält einen Link auf die nächste. Bis man die nächste Edition einfügt, wird der Link-Inhalt nicht gefunden.
Üblicherweise benutzt man in HTML ein
IMG-Element mit Link auf ein Bild aus der
nächsten Edition. Sieht man das Bild, weiss man, die
nächste Edition existiert und kann sich hinklicken.
Freenet Explained benutzt diese Methode. Der Link auf Edition 4 lautet:
SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/4// # Link-Ziel SSK@0OhVDWutibbBMbXmbxNXW0M6YFoPAgM/fx/4//activelink.png # IMG-SRC
Beides existierte nicht, als ich diesen Artikel schrieb (bzw. Freenet konnte es nicht finden :). Wenn eine neue Edition herausgegeben werden soll, fügt der Autor einfach einen neuen Datenblock mit vielen Redirects unter obigem SSK-Key ein.
Ein .next-Eintrag ist ebenfalls vorhanden: manche
Spider folgen diesem und können auf diese Weise immer die
aktuelle Edition anzeigen, sofern sie häufig genug
scannen.
Editionen sind einfach für den Autor einer Freesite und erfordern keine spezielle Unterstützung. In der Benutzung sind sie allerdings umständlich und häufige Updates sind auch nicht effizient.
Daher hat sich eine zweite Methode entwickelt, die gerade bei häufig geänderten Inhalten besser funktioniert:
Freesites, die ``Date Based Redirects'' benutzen (kurz ``DBR-Sites'') funktionieren ähnlich wie Editionen-basierte Freesites, nur wird statt einer fortlaufenden Nummer die aktuelle Zeit benutzt.
Die Freesite The Freenet Help Index
(SSK@rjYFfgPHfolmcStiaoxESFfBXz8PAgM/ FreenetHelp//)
benutzt diese Methode. Folgende Metadaten wurden dazu
hinterlegt:
'version' => { 'revision' => '1' },
'document' => [
{ 'date_redirect' => {
'increment' => '15180',
'target' => 'SSK@rjYFfgPHfolmcStiaoxESFfBXz8PAgM/FreenetHelp'
}
}
],
Statt einem einfachen redirect gibt es jetzt einen
date_redirect-Eintrag, und darin ein
target (dürfte bekannt sein) und den Key
increment. Letzterer darf wie üblich fehlen, man
sollte dann 86400 annehmen (ein Tag hat 86400
Sekunden). In diesem Beispiel wird 86400 genommen (Hexadezimal
15180).
Habe ich eigentlich schon erwähnt, daß jede Zahl im FCP-Protokoll oder im Freenet hexadezimal kodiert wird? Also, praktisch immer, außer wenn es mal nicht so ist. Und wozu man einen Offset addiert, ist mir auch schleierhaft. In der freien Wildbahn habe ich sowieso noch keinen gesehen.
Der Link in target ist nicht vollständig
(unter anderem fehlt hinten ein //). Der komplette
Link wird generiert, ``indem die aktuelle Zeit (POSIX-Zeit,
üblicherweise das, was time liefert), auf
increment-Schritte gerundet und offset
addiert, als Big-Endian-Hexadezimalzahl nach dem ersten Slash mit
folgendem Minuszeichen eingefügt wird.''
Und jetzt in Perl - zum Verstehen:
my $doc = $metadata->{document][0]; # Oder [1] oder ...
my $increment = (hex $doc->{increment}) || 86400;
my $offset = (hex $doc->{offset}) || 0;
my $target = $doc->{target} || die;
my ($head, $tail) = split /\//, $target, 2;
my $NOW = time; my $time = $NOW - $NOW % $increment + $offset;
my $result = sprintf "%s/%x-%s", $head, $time, $tail;
Für ``jetzt'' (time() == 1084388445) liefert
der Algorithmus folgenden Link:
SSK@rjYFfgPHfolmcStiaoxESFfBXz8PAgM/40a16900-FreenetHelp
Und tatsächlich, unter diesem Key findet man wieder ein Dokument mit vielen Redirects.
DBRs haben den Vorteil, ``automatisch'' aktuell zu sein. Solange man nur Redirects einfügt, ist die Belastung durch neue das Einfügen vieler neuer Daten gering, Freenet kommt damit zurecht und löscht alte DBRs bei Bedarf automatisch (ohne natürlich zu wissen, das sich in den Datenblöcken DBRs befinden, denn die kann ja niemand dekodieren, der nicht den Schlüssel besitzt).
Der Nachteil ist, das man eine aktuelle Uhrzeit braucht um die
Site zu finden. Schlimmer noch: die Freesite muss
regelmäßig neu eingefügt werden, eben alle
increment Sekunden.
DBR-Freesites, die nicht mehr maintained werden, verschwinden deshalb fast sofort, solange man nicht einen Zeitpunkt kennt, zu dem sie noch (bzw. schon!) existierte.
All dieses sollte eigentlich in einem Metadata-Modul stattfinden. So weit bin ich aber nicht, da mein Hauptziel das effiziente Herunterladen großer Dateien ist.
Womit ich beim Thema wäre.
Transfers im Freenet zeichnen sich durch zwei Eigenschaften aus: hohe Latenz und vergleichsweise hoher Durchsatz. Dies ist ungewöhnlich, ergibt sich aber aus dem Suchverfahren: die Suche nach einem Datenblock kann sehr lange dauern (sogar einige Male fehlschlagen bevor die Daten eintreffen), sie sind jedoch einfach parallelisierbar.
Wie in Java üblich, erreichte man dies früher durch massive Benutzung von Threads. Der Freenet-Referenz-Daemon ist davon inzwischen abgekommen (zu langsam, zu viel Speicher und zu hohe Komplexität), doch viele Clients haben auch heute noch Voreinstellungen wie `Anzahl der Threads pro Download'' und viele sind auch so implementiert.
Da Perls aktuelle Thread-Implementierung ein Prozessmodell simuliert ist der Overhead ebenfalls entsprechend hoch, und man erhält bei Thread-Benutzung viele ihrer Nachteile, jedoch keine nennenswerten Vorteil, außer das alles etwas langsamer läuft und man daher besser zuschauen kann :)
Für Net::FCP (oder besser: für mich) kam
es deshalb nicht in Frage, Parallelisierung auf Thread-Basis zu
implementieren. Das Problem auf den Modul-Benutzer abzuschieben
erschien mir auch nicht fair, daher unterstützt
Net::FCP die Module Coro, Event,
Glib oder Tk, bzw. deren Event-Steuerung. Man hat
also die Wahl, auf welcher Basis man sein Programm aufbaut, und es
geht auch ganz ohne Event-System (wie die obigen einfachen
Beispiele zeigen).
Net::FCP benutzt für jeden Request eine
Transaktion. Das ist ein Objekt, das den Zustand und
eintreffende Ergebnisse speichert.
Jede Anfrage, (z.B. client_get) wird intern in ein
solches Transaktionsobjekt verwandelt. Für jede ``einfache''
Methode wie client_get oder
insert_private_key gibt es eine entsprechende Methode
mit txn_-Präfix, die statt des Resultats ein
solches Objekt liefert.
Transaktionsobjekte besitzen einige Methoden, mit denen sie
konfiguriert werden können oder Ergebnisse abgefragt werden
können. Die wichtigste ist die result-Methode,
die auf das Ergebnis wartet und es zurückliefert:
# $fcp->client_get wird intern so implementiert: my $txn = $fcp->txn_client_get (...); return $tdn->result;
Es ist übrigens immer gefahrlos, Transaktionsobjekte zu
erzeugen. Etwaige Fehler bei der Durchführung werden immer
erst beim Aufruf von result und immer als
Perl-Exceptions gemeldet.
Um also alle Freenet-Objekte im Array @download
gleichzeitig anzufordern, reicht folgender Code:
map $_->result,
map $_->txn_client_get ($_),
@download;
Zuerst werden alle URIs auf eine entsprechende Transaktion gemapped, und dann von allen Transaktionen das Resultat angefordert.
Natürlich brauchen manche Zugriffe länger als andere. Wenn Transaktionen früher beendet werden, sollte man gleich eine neue starten, um Freenet am Laufen zu halten.
Dazu benötigt man ein Signal, daß eine Transaktion
beendet ist. Dies macht Net::FCP mit Hilfe eines
Callbacks:
my $txn = $fcp->txn_client_get (...)
->cb (\&callback);
Diesen setzt man mit Hilfe der cb-Methode. Alle
Methoden, die ein Transaktionsobjekt konfigurieren, liefern das
Transaktionsobjekt zurück, man kann also Aufrufketten wie im
Beispiel bilden.
Der Callback wird aufgerufen, wenn der Request (erfolgreich oder nicht) beendet wurde.
Nun braucht man allerdings die Hauptschleife des jeweiligen Event-Systems, da man ja nicht mehr auf ein bestimmtes Ergebis wartet, sondern auf ein beliebiges.
Dazu sollte man Net::FCP auf ein bestimmtes
Event-System zwingen. Das folgende Beispiel tut dies und lädt
die Kommandozeileargumente parallel herunter:
use Net::FCP qw(event=Event); # oder event=Glib, oder...
my $fcp = new Net::FCP; $fcp->txn_client_get ($_)->cb (\&finished) for @ARGV;
Event::loop; # hier passierts, loop ist die Hauptschleife von Event
Die Transaktionsobjekte werden übrigens nicht gespeichert, denn sie werden dem Callback übergeben, und nur dort werden sie gebraucht. Der Callback könnte so aussehen:
sub finished {
my ($txn) = @_;
my ($meta, $data) = @{ $txn->result };
# tue etwas
}
Oder, mit Fehlerprüfung:
sub finished {
my ($txn) = @_;
my ($meta, $data) = eval { @{ $txn->result } };
if ($@) {
warn "fehler: $@, wird einfach ignoriert :)"
} else {
warn "Lo and Behold! We got soemthing!";
# tue etwas
}
}
Da Zugriffe recht lange dauern können, schickt Freenet
regelmäig Fortschrittsreports. Man kann sich das vorstellen
wie warn und die: warn
liefert wichtige Informationen, die einen Fehler.
Die Fortschrittsanzeigen werden normalerweise nicht von
Net::FCP ausgegeben (wäre irgendwie
kontraproduktiv in einem GUI-Programm), man kann aber einen
Callback installieren:
my $fcp = Net::FCP progress => \&progress;
Ein solcher Progress-Callback könnte so aussehen:
sub progress_cb {
my ($self, $txn, $type, $attr) = @_;
warn "progress<$txn,$type," . (join ":", %$attr) . ">\n";
}
Möchte man Informationen für den Callback
bereitstellen, so bietet sich die userdata-Methode von
Transaktionsobjekten an.
Hier ist ein kompletter parallelisierender Client mit Fortschrittsanzeige:
my $fcp = Net::FCP progress => \&progress;
Ein solcher Progress-Callback könnte so aussehen:
use Net::FCP qw(event=Event); # oder event=Glib, oder...
my $fcp = new Net::FCP progress => \&progress_cb;
$fcp->txn_client_get ($_)->cb (\&finished) for @ARGV;
Event::loop; # hier passierts, loop ist die Hauptschleife von Event
sub finished {
my ($txn) = @_;
my ($meta, $data) = eval { @{ $txn->result } };
if ($@) {
warn "$txn: fehler: $@, wird einfach ignoriert :)\n"
} else {
warn "$txn: Lo and Behold! We got something!\n";
# tue etwas
}
}
sub progress_cb {
my ($self, $txn, $type, $attr) = @_;
warn "$txn: progress<$txn,$type," . (join ":", %$attr) . ">\n";
}
Startet man ihn mit einigen gültigen und ungültigen Freenet-URIs, bekommt man vieleicht folgende Ausgabe:
Net::FCP::Txn::ClientGet=HASH(0x81e0f64): ...
... fehler: Net::FCP::Exception<<uri_error, ...
... reason:Unspecified document name>>, wird einfach ignoriert :)
Net::FCP::Txn::ClientGet=HASH(0x81e7254): ...
... fehler: Net::FCP::Exception<<uri_error,reason: ...
... Unknown keytype>>, wird einfach ignoriert :)
Net::FCP::Txn::ClientGet=HASH(0x81e0c4c): ...
... progress<Net::FCP::Txn::ClientGet=HASH(0x81e0c4c), ...
... data_found,data_length:74:metadata_length:74>
Net::FCP::Txn::ClientGet=HASH(0x81e0c4c): ...
... progress<Net::FCP::Txn::ClientGet=HASH(0x81e0c4c), ...
... data,chunk:116:total:116:received:116>
Net::FCP::Txn::ClientGet=HASH(0x81e0c4c): ...
... Lo and Behold! We got something!
... usw.
Benutzt man das Coro-Modul, so kann man
Callback-basierte und Thread- (bzw. Coroutinen-) basierte
Programmierung mischen:
use Net::FCP qw(event=Coro);
my $fcp = new Net::FCP;
# Achtung. Pseudocode :)
for my $url (@urls) {
async {
my ($meta, $data) = @{ $fcp->client_get ($url) };
if ($meta ist ein splitfile) {
for (@alle_splitfile_blöcke) {
$fcp->txn_client_get (...)
->cb (\&save_splitfile_block);
}
} else {
# direkt speichern
}
};
}
Das Them Splitfiles möchte ich hier noch erwähnen, aber nicht ausführlich besprechen.
Da die Datenblöcke im Freenet (zur Zeit) nicht größer als 1MB sein können, müssen größere Dateien (Filme z.B.) aufgeteilt werden. Wenn man viele Datenblöcke für eine Datei braucht, erhöht sich die Wahrscheinlichkeit dafür, daß ein Block mal fehlt oder nicht gefundne werden kann, stark auf einen Wert, der das Herunterladen großer Dateien unmöglich macht.
Daher wird mit Fehlerkorrekturcodes die Anzahl der Blöcke um 50% erhöht. Metadaten für ein Splitfile sehen so aus:
'version' => { 'revision' => '1' },
'document' => [ {
'info' => {
'checksum' => '3bab309132f812cb2a281357b0dc1036920672e2',
'format' => 'video/mpeg',
'description' => 'Onion FEC v1.2 file inserted by Fuqid'
},
'split_file' => {
'algo_name' => 'OnionFEC_a_1_2',
'check_block_count' => '3f',
'block_count' => '7f',
'check_block' => {
'33' => 'freenet:CHK@eTNQ...3CBzTSsI~oPn6EUAwI,I4X...nyGoIRZBLg',
'32' => 'freenet:CHK@Ee1h...Kg3WZ90KXqwQZMUAwI,ppn...k1z3Sempqw',
'1e' => 'freenet:CHK@5gBB...fjO27kOtU0hZgkUAwI,Okf...wvtX~wfVNA',
[viele Zeilen fehlen hier :]
'3d' => 'freenet:CHK@iIT1...yOToFsyyytV-roUAwI,9u3...-eY7Onag1g',
'5' => 'freenet:CHK@EeVsn...CNBNVzvTTQ1l0UAwI,q6e...Oh-cc79WBA'
},
'block' => {
'33' => 'freenet:CHK@j522...h2ZK1D1q4UDnFgUAwI,Xwo...XFwI5f9iyw',
'32' => 'freenet:CHK@VU5v...y-Dnk8SCt9ZKbYUAwI,iX~...3Yk0rmpM9w',
'5d' => 'freenet:CHK@-wbX...uotK~uZbJp14zsUAwI,-bg...YvbwQeYtyA',
[viele Zeilen fehlen hier :]
'43' => 'freenet:CHK@zMXc...ee1cftvdK4~QukUAwI,o99...adXUXGjrlg',
'5' => 'freenet:CHK@1ZDiv...YTKQbDq1yCPWAUAwI,B9f...fOx~PTZkAA',
'3d' => 'freenet:CHK@ZGxa...qQIf6rbr-g8btMUAwI,f0x...mmxoJRzaLw'
},
'size' => '7e8e804'
}
} ],
}
Die Datenblöcke und die zusätzlichen Korrekturblöcke sind getrennt aufgeführt. Man braucht nur so viele Blöcke insgesamt, wie die Datei groß ist. Den Rest kann man aus den heruntergeladenen erzeugen.
Wie genau die Daten auf die Blöcker verteilt werden unterliegt einem Algorithmus und ist nicht in den Metadaten gespeichert. Der Grund ist, daß jedes Programm denselben Algorithmus benutzen muss, und Dateien daher immer gleich auf die Blöcke verteilt werden, so daß Mehrfacheinfügen ins Freenet keine Duplikate erstellt.
Den genauen Algorithmus kann man in der Datei
bin/fmd, dem Freenet Mass Downloader in der
Funktion state_splitfile nachlesen. Der Code ist weder
besonders klar, noch besonders kurz, noch besonders hübsch.
Auch dieser Teil wird irgendwann einmal in ein Modul fließen,
damit er anderen zugänglicher wird.
Interessant ist noch, daß man CHK-Inhalte, die aus dem Freenet heruntergeladen werden, einfach auf Konsistenz prüfen kann: sie enthalten schließlich den SHA1-Hash der Daten:
use Net::FCP::Util;
my ($meta, $data) = @{ $fcp->client_get ($uri) };
# Extrahiere den Hash aus der URI
my $k1 = Net::FCP::Util::extract_chk_hash $uri;
# Berechne den Hash aus den Daten
my $k2 = Net::FCP::Util::generate_chk_hask "$meta->{raw}$data";
# Idealerweie stimmen beide überein... $k1 eq $k2 or die "Key/Content mismatch!";
Zwar ist der Freenet-Daemon angeblich perfekt, aber früher passierte es tatsächlich oft, daß man korrupte Daten geliefert bekam. Retryen half. Man hat es eben nicht so leicht...
Heute ist das zwar nicht mehr der Fall (...), aber prüfen kostet fast nichts.
Das Freenet lebt von vielen Dingen: Den Entwicklern, den Benutzern die ihr Interesse bekunden, den Regierungen, die die Meinungsäußerung einschränken - aber vor allem dadurch, daß es sinnvolle Inhalte darin gibt.
Ich bin mir nicht so sicher, ob es so viele sinnvolle Inhalte gibt, aber noch gab es ja auch keine 1.0-Release...
Nun ja, solange man nichts ins Netz stellt, darf man auch nicht klagen.
Das kann man ändern, indem man so langweilige Dinge wie den Freenet Insertion Wizard oder das beliebte Fuqid benutzt (was ich noch nie getan habe). Aber die benutzen kein Perl und sind damit langweilig.
Mit Net::FCP geht es erstmal sehr einfach:
$fcp->client_put ($uri, $metadata, $data, $htl, $removelocal);
(Auch hier gibt es natürlich wieder die entsprechende
txn_-Variante).
Einfügen von Inhalten geht also fast geanuso wie Auslesen.
$uri ist eine Freenet-``URIC'' (mit oder ohne
freenet:), wie man zu dieser kommt gleich mehr.
$metadata kann entweder ein Freenet-Metadata-String
sein oder ein Hash der gleichen Form wie man ihn
zurückgeliefert bekommt. $data sind
natürlich die eigentlichen Daten und $htl sind
die Hops-To-Live, die der Key-Insert haben soll. Höhere Werte
(bis hin zu 25) speichern die Daten dauerhafter, brauchen aber auch
entsprechend länger.
$removelocal, auf 1 gesetzt, sorgt
dafür, daß der lokale Netzknoten die Daten
nicht cached. Warum man das tun sollte, ist einfach
erklärt: wenn Freenet beim Einfügen die Daten schon im
Netz vorfindet, wird der Einfügevorgang abgebrochen.
Möchte man die Daten regelmäßig ``auffrischen''
dann sorgt diese Option dafür, daß die Daten den Knoten
auf jeden Fall verlassen und erhöhen so die Chance auf
Verbreitung.
Nun die Frage wie man zu einer URI kommt. Möchte man einen
CHK-Schlüssel einfügen, so setzt man die URI einfach auf
den String CHK@. Nach dem Einfügen erhält
man die eigentliche URI als Resultat:
my $attr = $fcp->client_put ('CHK@', "", "Daten\n", 20);
print "eingefügte URI: $attr->{uri}\n";
Als Ergebnis dieses client_put-Requests erhielt
ich:
freenet:CHK@MT~LuxKHH8fugJehkcgp239h7C4KAwI,378rJzHVZbsQbd7VGMHxSg
Beim Einfügen ins Freenet kann es auch einen Key Collision-Fehler geben, und zwar genau dann, wenn Freenet die Daten schon findet, die man einfügen wollte.
Net::FCP behandelt diesen Fehler allerdings genauso
wie ein erfolgreicher Einfügevorgang, lediglich im Hash, den
es zurückliefert, wird das key_collision-Element
auf 1 gesetzt. Wenn die Daten schon vorhanden sind,
ist das Ziel schließlich auch erreicht.
Häufig möchte man den CHK-Schlüssel
aber schon im voraus Wissen. Dazu kann man entweder seinen
freundlichen Freenet-Daemon fragen:
my $key = $fcp->generate_chk ($metadata, $data);
Oder man verwendet das Net::FCP::Key::CHK-Modul,
das schneller ist und keinen Serverzugang benötigt:
use Net::FCP::Key;
my $key = (new_from_data Net::FCP::Key::CHK $metadata, $data)->chk;
SSK-Schlüssel bestehen immer aus zwei Teilen, dem
öffentlichen (public_key) und dem privaten
(private_key), die zusammen ein Paar bilden.
Neuerdings noch aus einem dritten, dem crypto_key, der
es Brute-Force-Attacken noch schwieriger machen soll.
Solch ein Schlüsselpaar kann man mit einem Aufruf von
generate_svk_pair (auch hierfür gibt es eine
txn_-Variante) erzeugen. Das ``svk'' ist übrigens
kein Schreibfehler: SSK-Schlüssel sind ein Spezialfall von
SVK-Schlüsseln, die man aber als normalsterblicher nirgendwo
direkt antrifft.
my ($public, $private, $crypto) = @{ $fcp->generate_svk_pair };
Daraus kann man beliebig viele SSK-Schlüssel erzeugen, indem man die drei Teile wie folgt zusammensetzt:
# Öffentlicher Schlüssel:
my $name = "..."; # beliebig
my $get = "SSK\@${public}PAgM,$crypto/$name";
my $put = "SSK\@$private,$crypto/$name";
Den mit $put bezeichneten Schlüssel muss man
zum Einfügen verwenden. Mit dem mit $get
bezeichneten Schlüssel kann man diese Daten wieder holen.
Letzterer kann bedenkenlos veröffentlich werden.
Das SSK-Management kann vom
Net::FCP::Key::SSK-Modul übernommen werden.
Um eine Freesite zu erzeugen, sollte man sich zuerst einen dauerhaften SSK-Schlüssel besorgen:
# perl -MNet::FCP -MNet::FCP::Key::SSK \
-e 'Net::FCP::Key::SSK->new_from_fcp (Net::FCP->new) \
->save ($ARGV[0])' mykey
Das erzeugt einen neuen SSK-Schlüssel und speichert ihn
gleich in der Datei mykey.
Dieser SSK-Schlüssel wird die Basis der Freesite.
Für das Einfügen schreibt man sich am besten ein Skript, da man ides regelmäßig wiederholen sollte. Zuerst ein paar Definitionen:
my $edition = 1; my $keyfile = "mykey"; my $htl = 10;
Das Skript legt eine editionenbasierte Freesite an und benutzt als Basis den eben erzeugten SSK-Schlüssel. Der Hops-To-Live-Wert von 10 ist etwas klein, aber so dauert das Einfügen vielleicht nur ein paar Minuten... In der Praxis sollte man 20-25 vorziehen, oder das Einfügen regelmäig wiederholen.
Als nächstes benötigen wir ein paar Module:
use Net::FCP qw(event=Event); use Net::FCP::Metadata; use Net::FCP::Key::SSK;
Generell sollte man in einem Perl-Programm den verwendeten Event-Mechanismus fest angeben. In Freenet-Modulen sollte man dies dagegen nicht tun, um dem Benutzer die Wahl zu überlassen. Stattdessen sollte man in Modulen nur Transaktionen benutzen und diese dem Benutzer zur Verfügung stellen.
Als nächstes werden ein paar notwendige Objekte erzeugt:
my $fcp = new Net::FCP; my $ssk = new_from_file Net::FCP::Key::SSK $keyfile; my $manifest = new Net::FCP::Metadata;
Die Metadaten für die Hauptseite ($manifest)
werden auch ``Manifest'' genannt, da man dort häufig nur
Verweise auf die jeweiligen Unterseiten ablegt, also sowas wie der
Grundstein einer Freesite. Das
Net::FCP::Metadata-Modul biette zur Zeit nur
spärliche Funktionalität, für unsere Zwecke reicht
das jedoch aus.
Nun noch zwei Hilfsfuntionen. Zuerst add_key:
sub add_key {
my ($name, $key, $meta, $data) = @_;
# Asynchrones Einfügen
$fcp->txn_client_put ($chk, $meta, $data, $htl, 1)
->cb (sub {
eval {
my $attr = $_[0]->result;
printf "Insert of '%s' (%d bytes) ok.\n", $name, length $data;
} or warn "Error while inserting '$name': $@";
});
}
add_key startet eine
client_put-Transaktion, ohne auf das Ende zu warten.
Im Callback wird das Resultat angezeigt, oder, wenn ein Fehler
Auftrat, eben dieser. Das Skript sollte so angelegt sein, das man
es einfach nochmal starten kann, wenn einige Schlüssel nicht
eingefügt werden konnten.
Nun noch add_file:
# Hilfsfunktion zum Einfügen von Dateien
sub add_file {
my ($name, $path, $contenttype) = @_;
# lies die Datei ein
my $data = do {
local $/;
open my $fh, "<", $path
or die "$path: $!";
<$fh>
};
my $chk = $fcp->generate_chk ("", $data); # keine metadaten
$manifest->add_redirect ($name => $chk, format => $contenttype);
add_key $name, $chk, "", $data;
}
add_file lädt eine externe Datei (< 1MB)
und berechnet deren CHK-Schlüssel. Diesen Schlüssel
fügt es als Verweis unter dem Namen $name in das
Manifest ein. Als letztes benutzt es add_key, um die
Datei einzufügen.
Das Vorausberechnen des CHK-Schlüssels macht es möglich, das Manifest paralell zu den anderen Schlüsseln einzufügen. Würde man auf den Einfügevorgang warten, müsste man das Manifest am Ende getrennt hochladen.
Nun ans Zusammenbauen. Zuerst werden (unsichtbare) Verweise auf die vorherige bzw. die nächste Edition eingefügt:
$manifest->add_redirect (".prev" => $ssk->gen_pub ($edition - 1))
if $edition > 1;
$manifest->add_redirect (".next" => $ssk->gen_pub ($edition + 1));
Dies nutzt zum einen die Eigenschaft von SSKs, einfach
vorausberechnet werden zu können, als auch die Methode
gen_pub, die den öffentlichen SSK-Schlüssel
mit optionalem Suffix generiert.
Dadurch können Benutzer auch ``per Hand'' eine neuere oder
ältere Edition suchen, indem sie einfach die Zahl
(.../2//index.html) vor dem //
ändern.
Als nächstes folgt der eigentliche Inhalt der Freesite, eine HTML-Seite, ein Bild und der Quellcode des Skriptes, der sie generiert hat:
add_file "activelink.png" => "logo.png", "image/png"; add_file "" => "index.html", "text/html"; add_file "index.html" => "index.html", "text/html"; add_file "freesite" => "freesite", "text/plain";
Die Datei index.html wird zweimal hinzugefügt,
einmal unter dem Namen ``'' und ein zweitesmal unter dem Namen
``index.html''. Dadurch kann man erreichen, was viele
HTTP-Server ebenfalls machen, nämlich eine URI, die auf ein
Verzeichnis zeigt, auf eine Datei darin zu verweisen. Das die Datei
zweimal eingefügt wird ist kein Problem, da der
CHK-Schlüssel derselbe ist (und Mehrfacheinfügen kein
Fehler).
Nachdem die Dateien hinzugefügt (und vor allem ins Manifest eingetragen) wurden, kann man das Manifest einfügen:
my $site = $ssk->gen_pub ($edition); add_key "manifest", $site, $manifest, "";
Das Manifest wird. Damit man hinterher auch weiss, was man da eingefügt hat, sollte man den Hauptschlüssel auch ausgeben:
print "site generated as $site//\n";
Und da die ganzen Einfügevorgange ja nur gestartet wurden, muss man auf deren Ende warten:
Event::loop;
Solange Event noch aktive Watcher sieht, bleibt es
in der Event-Schleife. Wenn es keinen aktiven Watcher mehr gibt,
kehrt loop automatisch zurück und das Programm
ist beendet.
Die Ausgabe sieht dann z.B. so aus:
site generated as SSK@wg...TBRylfnDlUBfjETgPAgM,Jr...UBDPfw1UO1g/1// Insert of 'freesite' (1688 bytes) ok. Insert of 'activelink.png' (1346 bytes) ok. Insert of 'index.html' (200 bytes) ok. Insert of '' (200 bytes) ok. Insert of 'manifest' (0 bytes) ok.
Wenn man einen Fehler gemacht hat, kann man einfach einen neuen SSK-Schlüssel generieren. Das heißt, solange man seine Site nicht schon veröffentlicht hat.
Die Alternative wäre z.B. ein täglicher Date-Based-Redirect, d.h. tägliches komplettes Neueinfügen. Da bügeln sich Fehler schnell von selbst aus, man muss sein Skript dann natürlich über Tage oder Monate regelmäßig starten.
Eine offensichtliche Verbesserung wäre es, HTML-Dateien
abzuändern, so daß man Links auf die nächste
Revision nicht hardcodieren muss sondern etwa als
%%NEXT%%//activelink.png schreiben könnte.
Nun ja, ich hoffe, es machen sich nun einige Leser auf, um eigene Freesites zu erstellen oder andere Anwendungen für das Freenet zu erstellen.