ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/docs/pws2002/webserver.sdf
Revision: 1.2
Committed: Wed Dec 26 13:47:07 2001 UTC (24 years, 9 months ago) by root
Branch: MAIN
Changes since 1.1: +759 -21 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 !init OPT_STYLE="paper"
2    
3     !define DOC_NAME "Wie ich mit Perl Apache und thttpd erschlug"
4     !define DOC_AUTHOR "Marc Lehmann <pcg@goof.com>"
5     !build_title
6    
7     H1: Ein reißerischer Titel
8    
9     Ja, theatralisch war ich schon immer ;)
10    
11     H2: Das Problem, das es zu lösen galt
12    
13     Wie bei allen meiner Programme und Module, stand am Anfang ein
14     Problem: Ich hatte Festplatten. Damals grooose Festplatten
15     (insgesamt 320Gb). Mit Dateien drauf. Groooosen Dateien. Beliiiebten
16     Dateien. Filme! Animes, um genau zu sein.
17    
18 root 1.2 Und weil man so etwas nur sehr schwer bekommt, dachte ich mir: packe sie
19     auf einen Webserver und bring' sie unter das Volk.
20 root 1.1
21     Und da fingen die Probleme an: "beliebt" ist leicht
22     untertrieben. Innerhalb von fünf Tagen wuchs die Zahl der gleichzeitig
23     offenen Verbindungen von 100 (erster Tag) auf 700. Natürlich hatte ich
24     da schon längst von apache auf thttpd umgestellt. Einen Prozeß (oder
25     Thread, was das gleiche ist) pro Verbindung war indiskutabel (das war mir
26     schon vorher klar), ein Webserver wie thttpd, der alle Verbindungen in
27     einem Prozeß bedient, ist eine Notwendigkeit.
28    
29     H2: Die Probleme mit thttpd
30    
31     Schon nach kurzer Zeit stellte sich dann heraus, daß thttpd Bugs,
32     häßliche, zerstörerische Bugs, hatte. Ein solcher Bug war, daß thttpd
33     zum Fileserven mmap+write benutze. Bei Dateien, die 100-700MB gross
34     sind, ist der virtuelle Speicher schnell aufgebraucht, so daß mmap
35     fehlschlägt. thttpd reagiert darauf mit einem "500 Internal Error". Und
36 root 1.2 verloren. Da er den mmap-cache auch so einfach nicht wieder hergibt, macht
37 root 1.1 thttpd seine eigene Denial-of-Service attack...
38    
39     Aber, ich bin ja ein kompetenter C-Programmierer (ah, wie ich das
40     hasse! C!), also habe ich thttpd umgeschrieben, so daß er auf read+write
41     zurückfällt bei grossen Dateien.
42    
43     Danach ging es, wenn man "ging" als "korrekt, aber langsam" versteht. 7
44     Mbit ist nicht gerade berauschend (auf einem PC, unsere SGI von 1995 mit
45     13x4GB SCSI-Platten brachte es auf sage und schreibe 20MBit!).
46    
47     H2: Warum neuschreiben in Perl schöner war
48    
49     Es sollte klar sein, daß das vertreiben dieser Filme nicht legal
50     ist. Tatsächlich gibt es eine Art ungeschriebene Übereinkunft zwischen
51     den japanischen Firmen und "Anbietern" wie mir: solange ein Film/Serie
52     ausserhalb Japans nicht lizensiert wurde, wird das kopieren geduldet,
53     da es ja einen Markt schafft (zwischen Lizensierung und Verkauf liegen
54     manchmal Jahre). Zudem steckt in den Untertitelungen von Fans auch viel
55     Arbeit (manche Firmen kaufen die Rechte der Untertitelungen von den Fans
56     oder schreiben mir, wenn eine Serie lizensiert wird, so daß ich sie
57     sperren kann).
58    
59     Ich lege "ausserhalb Japans" etwas inkorrekt aus als: "wenn
60     in Land X keine Lizenz bekannt ist, dürfen Leute aus Land X
61     downloaden". Also muss ich irgendwie das Land feststellen, am besten
62     über whois-Anfragen. Letzteres ist ein schwieriges Problem (da ARIN, die
63     amerikanische IP-Addressen-"Registry", leider ein völlig chaotisches und
64     teilweise nicht parsebares Format hat, während RIPE (Europa) und APNIC
65     (Asien&Pazifik) ein standardisiertes Format verwenden) und müsste zudem
66     asynchron im Webserver stattfinden, etwas, was ich nicht in C machen
67     wollte.
68    
69     Nebenbei wollte ich nicht glauben, daß der Rechner "nur" ca. 7Mbit
70     liefern konnte, in Perl müsste man doch mehr Leistung erreichen.
71    
72     H1: Der Webserver - und Coro
73    
74     "Zufällig" hatte ich eine Woche vor dieser Erkenntnis das Coro-Modul in
75     einer benutzbaren Form fertiggestellt. Die Stärke von Coro ist weniger
76     Effizienz oder Geschwindigkeit der Programme, sondern die Effizienz und
77     Geschwindigkeit, mit der Programme geschrieben werden können.
78    
79     In weniger als einer Stunde hatte ich einen HTTP/1.0-Webserver,
80     der virtual hosts, nph-cgi und byte-ranges (wichtig fürs resumes)
81     unterstützte. In 367 Zeilen (ist als Beispiel eg/myhttpd in der
82     Coro-Distribution).
83    
84     H1: Das Programm
85    
86     Wie jeder andere "forkende" Server, erzeugt auch dieser Webserver eine Listen-Socket
87     und wartet auf Verbindungen:
88    
89     !block perl
90     my $port = new Coro::Socket
91     LocalAddr => $SERVER_HOST,
92     LocalPort => $SERVER_PORT,
93     ReuseAddr => 1,
94     Listen => 1,
95     or die "unable to start server";
96    
97     my $connections = new Coro::Semaphore $MAX_CONNECTS;
98    
99     # Starte den "Accept-Prozeß"
100     async {
101     slog 1, "accepting connections";
102     while () {
103     $connections->down;
104     async {
105     $_[0] or return; # accept liefert manchmal undef
106     eval { conn->new($_[0])->handle };
107     close $_[0];
108     slog 1, "$@" if $@ && !ref $@;
109     $connections->up;
110     } $port->accept;
111     }
112     };
113    
114     # Springe in die Event-Hauptschleife
115     loop;
116     !endblock
117    
118     Das Coro::Socket-Modul hat (ungefähr) die gleiche Bedienung und Semantik
119     wie IO::Socket::INET, der Aufruf ist also nicht neues.
120    
121     Der nächste Abschnitt erzeugt eine Coro::Semaphore. Jedesmal, bevor eine
122     Verbindung 'accepted' wird, wird diese Semaphore gelockt und erst wieder
123     freigegeben, wenn die Socket wieder geschlossen wird. Dadurch wird die
124     Zahl der offenen Filehandles effektiv begrenzt, was, zumindest bei meinem
125     Server, dringend notwendig war (1000-2000 Verbindungen).
126    
127     Die Schleife um den C<accept>-Aufruf besteht im wesentlichen aus:
128    
129     !block perl
130     async {
131     eval { ... Verbindung behandeln ...};
132     } $port->accept;
133     !endblock
134    
135     Das muß man rückwärts lesen, denn der Aufruf von C<async> kann erst
136     stattfinden, wenn alle Argumente vollständig sind, und C<$port->accept>
137     kann eine sehr lange Zeit dauern, wenn keine Verbindungen aufgebaut
138     werden. Wenn C<accept> zurückkehrt, wird eine neue Coroutine erzeugt.
139    
140     Die Klasse C<conn> implementiert ein "Verbindungsobjekt", C<handle>
141     verarbeitet die Anfragen.
142    
143     H2: Lesen des HTTP-Headers
144    
145     Ein HTTP-Header besteht aus mehreren (nichtleeren) Zeilen, gefolgt von
146     einer leeren Zeile. Ein idealer Job für C<$/>, aber in Programmen mit
147     Coroutinen ist C<$/> gefährlich, da die Variable von allen Coroutinen
148     gemeinsam benutzt wird.
149    
150     Da dies nur problematisch bei Coro::Handle ist (normaler I/O blockiert den
151     Prozeß, ein C<local $/> reicht also), besitzt die C<readline>-Methode ein
152     optionales zweites Argument - den effektiven Wert von C<$/>.
153    
154     !block perl
155     # Lese den HTTP-Request
156     $fh->timeout($::REQ_TIMEOUT);
157     my $req = $fh->readline("\015\012\015\012");
158     $fh->timeout($::RES_TIMEOUT);
159    
160     defined $req or
161     $self->err(408, "request timeout");
162    
163     $req =~ /^(?:\015\012)?
164     (GET|HEAD) \040+
165     ([^\040]+) \040+
166     HTTP\/([0-9]+\.[0-9]+)
167     \015\012/gx
168     or $self->err(403, "method not allowed", { Allow => "GET,HEAD" });
169    
170     $2 >= 2
171     or $self->err(506, "http protocol version not supported");
172    
173     $self->{method} = $1;
174     $self->{uri} = $2;
175     !endblock
176    
177     C<readline> liefert den {{ganzen}} Request-Header, die Regex parsed
178     nur die erste Zeile (eine Leerzeile am Anfang stammt evt. noch von
179     einem kaputten vorherigen POST-Request. Kann ohne Keep-Alive zwar nicht
180     passieren, aber falls persistente Connections jemals implementiert werden
181     (sie wurden es), ist es besser, wenn der Rest damit klarkommt).
182    
183     Bei dieser (und der folgenden) Regex wurde auf Geschwindigkeit
184     geachtet: Im Normalfall (kein Syntaxfehler) ist das Parsen linear, d.h.
185     keine komplizierten Alternativen, kein non-greedy-modifier (?).
186    
187     Die Methode C<err> (um etwas abzuschweifen) ist der General-Abbruch. Sie gibt
188     eine absolut primitive Fehlerseite aus und ruft dann:
189    
190     !block perl
191     die bless {}, err::;
192     !endblock
193    
194     ... auf. C<err> ist eine echte Exception-Klasse, nur eben vollkommen leer ;) Dadurch wird
195     auch die folgende Zeile im Hauptprogramm klar:
196    
197     !block perl
198     slog 1, "$@" if $@ && !ref $@;
199     !endblock
200    
201     Zurück zum Header-Parsen:
202    
203     !block perl
204     # Parse die Header
205     {
206     my (%hdr, $h, $v);
207    
208     $hdr{lc $1} .= ",$2"
209     while $req =~ /\G
210     ([^:\000-\040]+):
211     [\008\040]*
212     ((?: [^\015\012]+ | \015\012[\008\040] )*)
213     \015\012
214     /gxc;
215    
216     $req =~ /\G\015\012$/
217     or $self->err(400, "bad request");
218    
219     $self->{h}{$h} = substr $v, 1
220     while ($h, $v) = each %hdr;
221     }
222    
223     $self->{server_port} = $self->{h}{host} =~ s/:([0-9]+)$// ? $1 : 80;
224     !endblock
225    
226     Da HTTP-Header case-insensitive sind, Perl das von Haus aus aber
227     nicht unterstützt, wird alle Header (z.B. "Content-Length") in
228     Kleinbuchstaben gespeichert ("content-length"). Die Regex in der Schleife
229     ist meines Wissens nach vollkommen korrekt und ließe sich auch auf
230     andere RFC-2822-Header anwenden (News, Mail, ...). Auch hier wurde auf
231     Geschwindgkeit geachtet.
232    
233     Der einfache (aber falsche) Weg wäre, den Kopf in Zeilen zu zerlegen
234     und jede Zeile als Header betrachten. RFC2822-Header sind jedoch
235     mehrzeilig. Für Perl jedoch kein Problem: durch den g und den c-Modifier
236     und das \G am Anfang der Regex parsed Perl genau dort weiter, wo die
237     vorherige Regex aufgehört hat.
238    
239     Die while-Schleife hört auf, sobald der Header zu Ende ist oder ein
240     illegaler Header auftaucht. Das prüft die nächste Zeile, denn nach dem
241     Header sollte eine einzelne, leere Zeile folgen.
242    
243     Die folgende Schleife kopiert den Header in den Hash C<$self->{h}>.
244    
245     Hier würde ich mich Fragen, was denn das C<.= ",$2"> und das C<substr $v,
246     1> da zu suchen haben. Nun, manche Header können mehrfach vorkommen (tun
247     dies auch), und das ist so ungefähr gleichwertig mit einem Header, in dem
248     mehrere durch Komma getrennte Werte vorkommen. Das steht auch in C<%hdr>,
249     mit einem "," am Anfang, was vom C<substr> weggeschnitten wird.
250    
251     In neueren Perl-Versionen (5.8 ;) geht das (dank Value-Sharing) mit einem:
252    
253     !block perl
254     $_ = substr $_, 1 for values %hdr;
255     !endblock
256    
257     Aber ich bin ja portabel...
258    
259     Jetzt muß der Request bearbeitet werden:
260    
261     !block perl
262     $self->map_uri;
263     $self->respond;
264     !endblock
265    
266     Ok, nicht sehr beeindruckend. Hier das wichtigste von C<map_uri>:
267    
268     !block perl
269     my $uri = $self->{uri};
270    
271     # mach aus der uri was pfad-ähnliches
272     $uri =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr hex $1/ge;
273     $uri =~ s%//+%/%g;
274     $uri =~ s%/\.(?=/|$)%%g;
275     1 while $uri =~ s%/[^/]+/\.\.(?=/|$)%%;
276    
277     $uri =~ m%^/?\.\.(?=/|$)%
278     and $self->err(400, "bad request");
279    
280     $self->{name} = $uri;
281    
282     # now do the path mapping
283     $self->{path} = "$::DOCROOT/$host$uri";
284     !endblock
285    
286     Die vielen Regexes dienen dazu, "%xx", ".." und ähnliches in der URI aufzulösen. Am Ende sollte
287     nirgendwo mehr ein ".." vorkommen, sonst haben wir ein Problem a'la Microsoft:
288    
289     !block perl
290     213.114.180.157 - - [11/Dec/2001:04:44:44 +0100] "GET /msadc/..%255c../..%255c../..%255c/..%c1%1c../..%c1%1c../..%c1%1c../winnt/system32/cmd.exe?/c+dir HTTP/1.0" 404 265
291     213.114.180.157 - - [11/Dec/2001:04:44:44 +0100] "GET /scripts/..%c1%1c../winnt/system32/cmd.exe?/c+dir HTTP/1.0" 404 231
292     !endblock
293    
294     (ein tail -f auf ein beliebiges accesslog zu einer beliebigen Zeit
295     reicht vollkommen aus, um sowas zu finden. Man beachte, daß Microsoft
296     Zugriffschecks und ähnliches macht, {{bevor}} die URI geparsed
297     wird. Hätten sie es blind nach Standard gemacht, wäre der Code
298     sicherlich einfacher. Ach ja, der Hack hätte auch nicht funktioniert,
299     aber Microsoft macht's gerne etwas besser als es im Standard steht. Das
300     nennt man dann "Feature", glaube ich).
301    
302     C<respond> ist auch nicht weiter interessant:
303    
304     !block perl
305     my $path = $self->{path};
306    
307     stat $path
308     or $self->err(404, "not found");
309    
310     # ich glaube nicht, daß ich mir den nächsten Kommentar
311     # hätte verkneifen können ;)
312     # idiotic netscape sends idiotic headers AGAIN
313     my $ims = $self->{h}{"if-modified-since"} =~ /^([^;]+)/
314     ? str2time $1 : 0;
315    
316     if (-d _ && -r _) {
317     # Verzeichnis
318     if ($path !~ /\/$/) {
319     # Redirect erzeugen, um einen "/" am Ende zu bekommen
320     # (macht sich besser ;)
321     $self->err(301, "moved permanently", { Location => "http://".$self->server_hostport."$self->{uri}/" });
322     } else {
323     ... verzeichnis anzeigen
324     }
325     } elsif (-f _ && -r _) {
326     -x _ and $self->err(403, "forbidden");
327    
328     $self->handle_file($queue_file);
329     } else {
330     $self->err(404, "not found");
331     }
332     !endblock
333    
334     H1: Asynchrone Ein-/Ausgabe, und warum Linux Müll ist
335    
336     Eigentlich wäre jetzt C<handle_file> an der Reihe (oder die Werbepause,
337     machen wir zuerst die Werbepause).
338    
339     Ich habe den Server geschrieben, um mehr Möglichkeiten und mehr
340     Freiheiten zu erhalten, mit dem Server herumzuexperimentieren.
341    
342     Er war nicht schneller als thttpd. Ein C<strace> (ist fast immer sehr
343     lehrreich) zeigte, daß der Serevr (wie thttpd) ca 90% der Realzeit im
344     C<read>-syscall steckte.
345    
346     Das ist auch logisch, wenn mans weiß: bei, sagen wir, 800 gleichzeitigen
347     Downloads kann man sich den read-ahead sonstwo hinstecken: er findet nicht
348     statt (außer in Linux, aber damit rechne ich gleich ab). Deshalb ist
349     jeder Zugriff auch ein Seek (minimum), und dauert somit mindestens 0.01s,
350     manchmal auch doppelt oder dreimal so lang. Und in 0.01s könnte ich schon
351     mehr als 100kb/s ins Internet blasen (100Mbit Netzwerkkarte).
352    
353     Leider kann der Webserver in dieser Zeit auch nichts anderes tun, er steht
354     ja und wartet auf die Platte, obwohl er doch so viele andere Filehandles
355     zu bedienen hätte.
356    
357     Viele Threads würden hier helfen (nein, falsch, aber dazu komme ich
358     gleich), und viele Java-Anhänger kennen nur den einen Weg (klar, die
359     Sprache {{kann}} es ja auch nicht anders, und wenn man nur einen Hammer
360     hat, sieht eben alles wie ein Nagel aus), aber die wahre Lösung heißt:
361    
362     "Asynchrone Ein-/Ausgabe"
363    
364     Natürlich kann Linux das nicht. Noch nicht. Aber man hat ja Threads, was
365     ja (Java zeigt es uns!) dasselbe ist, aber Perl kann leider keine Threads
366     (nein, wirklich, Perl kann keine Threads).
367    
368     Also muss XS her. Dank Linux kann ich auch einfach gcc verlangen, und dank gcc
369     kann ich closures benutzen, und dank closures kann ich errno ohne Assembler
370     abfangen, und wenn man diesen Horror in ein Modul packt, merkts auch keiner:
371    
372     !block perl
373     #undef errno
374     #include <asm/unistd.h>
375    
376     static int
377     aio_proc(void *thr_arg)
378     {
379     aio_thread *thr = thr_arg;
380     aio_req req;
381     int errno;
382    
383     /* we rely on gcc's ability to create closures. */
384     _syscall3(int,lseek,int,fd,off_t,offset,int,whence)
385     _syscall3(int,read,int,fd,char *,buf,off_t,count)
386     _syscall3(int,write,int,fd,char *,buf,off_t,count)
387     _syscall3(int,open,char *,pathname,int,flags,mode_t,mode)
388     _syscall1(int,close,int,fd)
389    
390     sigprocmask (SIG_SETMASK, &fullsigset, 0);
391    
392     /* then loop */
393     while (read (reqpipe[0], (void *)&req, sizeof (req)) == sizeof (req))
394     {
395     req->thread = thr;
396     errno = 0;
397    
398     if (req->type == REQ_READ || req->type == REQ_WRITE)
399     {
400     if (lseek (req->fd, req->offset, SEEK_SET) == req->offset)
401     {
402     if (req->type == REQ_READ)
403     req->result = read (req->fd, req->dataptr, req->length);
404     else
405     req->result = write(req->fd, req->dataptr, req->length);
406     }
407     }
408     else if (req->type == REQ_OPEN)
409     ...
410    
411     req->errorno = errno;
412     write (respipe[1], (void *)&req, sizeof (req));
413     }
414    
415     return 0;
416     }
417     !endblock
418    
419     Ja, dies ist kein Perl, sondern C (aber nicht ANSI), aber dieses Jahr soll
420     auf dem Perl-Workshop ja mehr praktisches gezeigt werden und, so leid es
421     mir tut, hier kommt die Wirklichkeit angerollt.
422    
423     Aber etwas langsamer: Das Linux-AIO-Modul ist relativ klein (C ist eben
424     umständlich). Es startet im wesentlichen einen (oder mehrere) Thread(s),
425     die in einer Schleife auf Anfragen (von Perl) warten, diese abarbeiten und
426     das Ergebnis zurückmelden. Da dies in einem Thread passiert, kann der
427     eigentliche Webserver weiterarbeiten.
428    
429     Als Anfragen kann man read() und write() stellen, aber auch open(), denn
430     open kann sehr lange dauern. Eigentlich sollte man auch stat() einbauen,
431     aber dazu bin ich noch nicht gekommen.
432    
433     Die Anwendung ist recht einfach, statt einem:
434    
435     !block perl
436     sysread $fh, $buf, $h > $bufsize ? $bufsize : $h
437     or last;
438     !endblock
439    
440     (C<last> springt aus einer hier nicht gezeigten Schleife), schreibt man "einfach":
441    
442     !block perl
443     my $current = $Coro::current;
444     my $r;
445    
446     aio_read($fh, $l, ($h > $bufsize ? $bufsize : $h),
447     $buf, 0, sub {
448     $r = $_[0];
449     $current->ready;
450     });
451     &Coro::schedule;
452     last unless $r;
453     !endblock
454    
455     Ok, gelogen, es sieht umständlich und langsam aus. Es ist auch
456     umständlich und langsam. Es ist genau so, wie ich es damals zum
457     Testen geschrieben hatte, ohne Abstraktion (IO::Handle => Coro::Handle
458     => Linux::AIO::Handle??). Es sieht auch heute noch so aus, denn es
459 root 1.2 funktioniert und war bisher nicht das (gedankliche) Nadelöhr.
460 root 1.1
461 root 1.2 C<aio_read> besitzt die gleichen Argumente wie C<sysread> und dann noch
462 root 1.1 eins (den File-Offset) und dann noch eins (eine Code-Referenz, die
463     aufgerufen wird, wenn die Daten gelesen wurden, es heißt ja nicht umsonst
464     {{asynchrone}} Ein-/Ausgabe) und dann war's das aber.
465    
466     Nachdem der Lesevorgang "in Auftrag" gegeben wurde, legt sich die
467     Coroutine schlafen: C<schedule> kehrt nicht von sich aus zurück.
468    
469     Der Callback speichert die Anzahl der gelesenen Byte ($! ist übrigens
470 root 1.2 korrekt gesetzt) in C<$r> und weckt die Coroutine wieder auf, die -
471     früher oder später - nach C<schedule> weiterläuft ($! ist dann
472     natürlich nicht mehr korrekt ;).
473 root 1.1
474     H2: Linux...
475    
476 root 1.2 Jetzt kommts. AIO brachte den Webserver sofort auf 20Mbit. Toll, so schnell war
477 root 1.1 er noch nie. Aber irgendwie... irgendwie... war das wenig, denn über "Kunden"
478     konnte sich der Webserver nicht beklagen.
479    
480     Was habe ich nach der Ursache gesucht.
481    
482     Und gesucht.
483    
484 root 1.2 Naja, vielleicht ist IDE/ATA/PC-Hardware einfach Müll, dachte ich....
485 root 1.1
486     Und schließlich habe ich bemerkt, daß zwar ca. 5MB/s von den Festplatten
487 root 1.2 gelesen wurde, aber irgendwie nur wenig mehr als 2MB/s im Netz landeten.
488 root 1.1 Merkwürdig.
489    
490 root 1.2 Nach über einer Woche(!) Diskussion auf der Kernel-Liste war das Problem
491 root 1.1 gefunden: Read-Ahead. Was normalerweise absolut notwendig ist, war hier
492     Fatal: der Webserver liest (sagen wir mal) 256Kbyte, der Kernel hängt
493     128Kbyte Read-Ahead dran und 600 Filehandles später wird der Speicher
494     knapp und der Kernel schmeißt weg - genau, die Read-Ahead-Daten.
495    
496 root 1.2 Gut, es ließ sich fixen (falls man "abschalten des Read-Ahead" so nennen
497     mag). Ich hatte noch ein anderes Problem: Zehn parallele Threads (und
498     damit ca. 10 parallele Read-Requests) sollten es dem Kernel erlauben,
499     wesentlich effizienter über die Plattenoberfläche zu Rasen als
500     einer. Leider braucht ein einzelner Read-Request, dann nicht 10 (oder
501     9 mal, wie ich gehofft hätte) so lange, wie bei einem Thread sondern
502     schlichtweg 80 oder 90 mal so lange. In Mbit: 7. Irgendwie ging dieses
503     Problem auf der Kernel-Liste unter, und ergo benutze ich nur einen
504     Thread. Sieht im Prozesslisting eh' viel hübscher aus, und ich predige ja
505     sowieso, das Threads böse sind.
506 root 1.1
507     Und wenn die FreeBSD-Leute sich jetzt toll fühlen: wenn ich unter FreeBSD
508     auf einen Event (kqueue) warte, bleiben alle Threads stehen. Yeah ;)
509    
510     (Aber die VM von FreeBSD ist geil!)
511    
512     H1: Back to the roots...
513    
514     ... Perl!
515    
516     Gut. Ich war schließlich bei ca. 70Mbit (immer pro Sekunde, übrigens ;)
517     angekommen. Die restlichen 10Mbit (am Wochenende sogar 20!) waren ganz
518 root 1.2 einfach zu erreichen: Diesmal waren es {{wirklich}} die Festplatten: Wer
519 root 1.1 viel Platz braucht (und kein Geld dafür ausgeben will) landet
520     zwangsläufig bei 80GB-Maxtor-Platten. Genau, die langsamen.
521    
522     Eine Beschränkung auf (willkürliche) 400 Datenverbindungen gleichzeitig
523     (also eine Art "Download-Queue") brachte diesen erhofften Durchbruch - in
524     nur zwei Zeilen:
525    
526     !block perl
527     # Am Programmanfang
528     my $transfers = new Coro::Semaphore 400;
529    
530     # In handle_file, nach dem der HTTP-Response-Header
531     # geschickt wurde:
532 root 1.2 my $guard = $transfers->guard;
533 root 1.1 !endblock
534    
535 root 1.2 C<guard> ist ähnlich wie C<down>, liefert aber ein Objekt, das in der
536 root 1.1 C<DESTROY>-Methode ein C<up> macht - also sobald sicht die Funktion
537     beendet, abgebrochen wird, abstürzt....
538    
539     H2: Ach ja, C<handle_file> und die bösen Windows-User
540    
541 root 1.2 Nun war der Server schnell und es wurde Zeit, den Missbrauch zu
542     bekämpfen. Missbrauch hauptsächlich in Form von Windows. Bei 400
543     Download-Slots ist man manchmal schon überrascht, daß 60 davon nicht
544     nur dieselbe Datei herunterladen, sondern auch noch von derselben
545     IP-Addresse stammen! Gut, 60 ist eher selten, aber Downloadmanager (für
546     nicht-Windows-User: sowas wie wget), die standardmässig 4 (8, 10)
547     Verbindungen aufmachen und dies noch nicht einmal abschalten können, sind
548     unter Windows sehr verbreitet. Wozu Hirn, wenn man dafür Windows hat ;)
549    
550     Nun denn, bei anderen Servern mag das egal sein (dafür gibts bei Apache
551     ja MaxClients, am besten gleich auf 10000 setzen...), aber Bandbreite ist
552     kostbar, und die sollte gerecht verteilt werden.
553    
554     Was für ein Glück, daß ich einen Single-Process-Webserver geschrieben habe,
555     denn um sogenannte "Segmented Downloads" zu verbieten, muss ich mir nur am
556     Anfang jedes Requests die URI merken:
557    
558     !block perl
559     weaken (local $uri{$self->{remote_id}}{$self->{uri}}{$self*1} = $self);
560     !endblock
561    
562     Das sieht kompliziert aus, ist es aber nicht. Wenn man für
563     C<$self->{remote_id}> "Client-ID" schreibt, für C<$self->{uri}> URI,
564     sieht es so aus (C<$self*1> verbraucht etwas weniger Speicherplatz als
565     C<$self>).
566    
567 root 1.1 !block perl
568 root 1.2 weaken (
569     local $uri{Client-ID} {URI} {$self} = $self
570     );
571 root 1.1 !endblock
572    
573 root 1.2 Die Client-ID ist normalerweise die IP-Addresse (bei Proxies ausserdem
574     die "dahinterliegende"). In C<%uri> wird also jede Verbindung unter ihrer
575     Client-ID und URI gespeichert. Das C<local> sorgt dafür, daß dieser
576     Vermerk beim Request-Ende wird entfernt wird. Das C<weaken> (aus dem Modul
577     C<Convert::Scalar>, C<Scalar::Util> oder C<WeakRef>) stellt sicher, daß
578     die gespeicherte Referenz das Objekt nicht am DESTROY hindert.
579    
580     Wenn die Funktion C<handle_file> einen potentiellen "Segmented Download"
581     erkennt (und zwar daran, daß nur ein Teil der Datei übertragen werden
582     soll) überprüft sie die Zahl der vorhandenen Verbindungen:
583    
584 root 1.1 !block perl
585 root 1.2 if (keys %{$uri{$self->{remote_id}}{$self->{uri}}} > 1) {
586     # fehler
587     !endblock
588    
589     Eine Zeitlang (perl-5.7.2 bis 5.7.2-devel-10000irgendwas) ging das
590     sogar ohne C<keys>, aber das alte (nutzlose) Verhalten von Hashes im
591     skalaren Kontext wurde anscheinend doch irgendwo benutzt und wurde wieder
592     hergestellt.
593    
594     In der Praxis kann es passieren, daß ein Download abgebrochen wurde und der
595     Retry-Request am Server ankommt, bevor der alte Request beendet wurde. Um
596     falsches Alarmschlagen zu verhindern, wartet der Server ca. 15s, bevor er
597     Massnahmen ergreift:
598    
599     !block perl
600     # check for segmented downloads
601     if ($l && $::NO_SEGMENTED) {
602     my $timeout = $::NOW + 15;
603     while (keys %{$uri{$self->{remote_id}}{$self->{uri}}} > 1) {
604     if ($timeout <= $::NOW) {
605     $self->block($::BLOCKTIME, "segmented downloads are forbidden");
606     } else {
607     $httpevent->wait;
608     }
609     }
610     }
611 root 1.1 !endblock
612    
613 root 1.2 Die Methode C<block> macht sich ein kleines Vermerkle, daß für die
614     nächsten zwei Stunden (z.B.) jeden Download verhindert - eine sehr
615     effektive Erziehungsmethode, wenn man sich das leisten kann...
616    
617     H2: Eine Shell zum Debuggen - und mehr
618    
619     Ein Webserver, in den man "hineintelnetten" kann und während der Laufzeit
620     debuggen - das ist schon was feines. Normalerweise ist sowas zu aufwendig,
621     aber in Perl:
622    
623 root 1.1 !block perl
624 root 1.2 my $port = new Coro::Socket LocalPort => $CMDSHELL_PORT, ReuseAddr => 1, Listen => 1
625     or die "unable to bind cmdshell port: $!";
626    
627     async {
628     async \&shell, scalar $port->accept
629     while 1;
630     };
631 root 1.1 !endblock
632    
633 root 1.2 So einfach kann es sein, einen Server zu programmieren: Listening-Port
634     erzeugen und bei jedem C<accept> eine Coroutine starten:
635    
636 root 1.1 !block perl
637 root 1.2 sub shell {
638     my $fh = shift;
639     while (defined (print $fh "cmd> "), $_ = <$fh>) {
640     s/\015?\012$//;
641     if ($cmd eq "quit") {
642     print $fh "bye bye.\n";#d#
643     last;
644     } elsif ($cmd eq "squit") {
645     print $fh "server quit.\n";#d#
646     Event::unloop;
647     last;
648     } elsif ($cmd eq "print") {
649     my @res = eval $_;
650     print $fh "eval: $@\n" if $@;
651     print $fh "RES = ", (join " : ", @res), "\n";
652     } els....
653     }
654     }
655     }
656 root 1.1 !endblock
657    
658 root 1.2 Das liest so lange Kommandos, bis der Client die Verbindung dichtmacht
659     ({{C:<$fh>}} liefert C<undef>), der Server beendet wird (C<Event::unloop>
660     spricht aus der Hauptschleife), oder "quit" eingegegen wird.
661    
662     Das wichtigste Komamndo ist C<print>. Damit führe ich häufig
663     Server-Upgrades im Betrieb durch:
664    
665 root 1.1 !block perl
666 root 1.2 cmd> print do 'access.pl'
667     RES = 1
668     cmd> print $VERSION = 0.711
669     RES = 1
670 root 1.1 !endblock
671    
672 root 1.2 Dies ist das - für mich - beeindruckendste an diesem Webserver.
673    
674     H1: PÖRL
675    
676     Wenn es in den Tagungsband passt, so ist hier der Quellcode von
677     C<httpd.pl>, der den Kern des Webservers implementiert. Dem Coro-Modul
678     selbst liegt eine ältere "general purpose"-Version als C<eg/myhttpd> bei.
679    
680     !block perl
681     use Coro;
682     use Coro::Semaphore;
683     use Coro::Event;
684     use Coro::Socket;
685     use Coro::Signal;
686    
687     use HTTP::Date;
688     use POSIX ();
689    
690     no utf8;
691     use bytes;
692    
693     # at least on my machine, this thingy serves files
694     # quite a bit faster than apache, ;)
695     # and quite a bit slower than thttpd :(
696    
697     $SIG{PIPE} = 'IGNORE';
698    
699     our $accesslog;
700     our $errorlog;
701    
702     our $NOW;
703     our $HTTP_NOW;
704    
705     Event->timer(interval => 1, hard => 1, cb => sub {
706     $NOW = time;
707     $HTTP_NOW = time2str $NOW;
708     })->now;
709    
710     if ($ERROR_LOG) {
711     use IO::Handle;
712     open $errorlog, ">>$ERROR_LOG"
713     or die "$ERROR_LOG: $!";
714     $errorlog->autoflush(1);
715     }
716    
717     if ($ACCESS_LOG) {
718     use IO::Handle;
719     open $accesslog, ">>$ACCESS_LOG"
720     or die "$ACCESS_LOG: $!";
721     $accesslog->autoflush(1);
722     }
723    
724     sub slog {
725     my $level = shift;
726     my $format = shift;
727     my $NOW = (POSIX::strftime "%Y-%m-%d %H:%M:%S", gmtime $::NOW);
728     printf "$NOW: $format\n", @_;
729     printf $errorlog "$NOW: $format\n", @_ if $errorlog;
730     }
731    
732     our $connections = new Coro::Semaphore $MAX_CONNECTS || 250;
733     our $httpevent = new Coro::Signal;
734    
735     our $queue_file = new transferqueue $MAX_TRANSFERS;
736     our $queue_index = new transferqueue 10;
737    
738     my @newcons;
739     my @pool;
740    
741     # one "execution thread"
742     sub handler {
743     while () {
744     if (@newcons) {
745     eval {
746     conn->new(@{pop @newcons})->handle;
747     };
748     slog 1, "$@" if $@ && !ref $@;
749    
750     $httpevent->broadcast; # only for testing, but doesn't matter much
751    
752     $connections->up;
753     } else {
754     last if @pool >= $MAX_POOL;
755     push @pool, $Coro::current;
756     schedule;
757     }
758     }
759     }
760    
761     sub listen_on {
762     my $listen = $_[0];
763    
764     push @listen_sockets, $listen;
765    
766     # the "main thread"
767     async {
768     slog 1, "accepting connections";
769     while () {
770     $connections->down;
771     push @newcons, [$listen->accept];
772     #slog 3, "accepted @$connections ".scalar(@pool);
773     if (@pool) {
774     (pop @pool)->ready;
775     } else {
776     async \&handler;
777     }
778    
779     }
780     };
781     }
782    
783     my $http_port = new Coro::Socket
784     LocalAddr => $SERVER_HOST,
785     LocalPort => $SERVER_PORT,
786     ReuseAddr => 1,
787     Listen => 50,
788     or die "unable to start server";
789    
790     listen_on $http_port;
791    
792     if ($SERVER_PORT2) {
793     my $http_port = new Coro::Socket
794     LocalAddr => $SERVER_HOST,
795     LocalPort => $SERVER_PORT2,
796     ReuseAddr => 1,
797     Listen => 50,
798     or die "unable to start server";
799    
800     listen_on $http_port;
801     }
802    
803     package conn;
804    
805     use Socket;
806     use HTTP::Date;
807     use Convert::Scalar 'weaken';
808     use Linux::AIO;
809    
810     Linux::AIO::min_parallel $::AIO_PARALLEL;
811    
812     Event->io(fd => Linux::AIO::poll_fileno,
813     poll => 'r', async => 1,
814     cb => \&Linux::AIO::poll_cb);
815    
816     our %conn; # $conn{ip}{self} => connobj
817     our %uri; # $uri{ip}{uri}{self}
818     our %blocked;
819     our %mimetype;
820    
821     sub read_mimetypes {
822     local *M;
823     if (open M, "<mime_types") {
824     while (<M>) {
825     if (/^([^#]\S+)\t+(\S+)$/) {
826     $mimetype{lc $1} = $2;
827     }
828     }
829     } else {
830     print "cannot open mime_types\n";
831     }
832     }
833    
834     read_mimetypes;
835    
836     sub new {
837     my $class = shift;
838     my $fh = shift;
839     my $peername = shift;
840     my $self = bless { fh => $fh }, $class;
841     my (undef, $iaddr) = unpack_sockaddr_in $peername
842     or $self->err(500, "unable to decode peername");
843    
844     $self->{remote_addr} =
845     $self->{remote_id} = inet_ntoa $iaddr;
846    
847     $self->{time} = $::NOW;
848    
849     weaken ($Coro::current->{conn} = $self);
850    
851     $::conns++;
852     $::maxconns = $::conns if $::conns > $::maxconns;
853    
854     $self;
855     }
856    
857     sub DESTROY {
858     #my $self = shift;
859     $::conns--;
860     }
861    
862     sub slog {
863     my $self = shift;
864     main::slog($_[0], "$self->{remote_id}> $_[1]");
865     }
866    
867     sub response {
868     my ($self, $code, $msg, $hdr, $content) = @_;
869     my $res = "HTTP/1.1 $code $msg\015\012";
870    
871     if (exists $hdr->{Connection}) {
872     if ($hdr->{Connection} =~ /close/) {
873     $self->{h}{connection} = "close"
874     }
875     } else {
876     if ($self->{version} < 1.1) {
877     if ($self->{h}{connection} =~ /keep-alive/i) {
878     $hdr->{Connection} = "Keep-Alive";
879     } else {
880     $self->{h}{connection} = "close"
881     }
882     }
883     }
884    
885     $res .= "Date: $HTTP_NOW\015\012";
886    
887     while (my ($h, $v) = each %$hdr) {
888     $res .= "$h: $v\015\012"
889     }
890     $res .= "\015\012";
891    
892     $res .= $content if defined $content and $self->{method} ne "HEAD";
893    
894     my $log = (POSIX::strftime "%Y-%m-%d %H:%M:%S", gmtime $NOW).
895     " $self->{remote_id} \"$self->{uri}\" $code ".$hdr->{"Content-Length"}.
896     " \"$self->{h}{referer}\"\n";
897    
898     print $accesslog $log if $accesslog;
899     print STDERR $log;
900    
901     $self->{written} +=
902     print {$self->{fh}} $res;
903     }
904    
905     sub err {
906     my $self = shift;
907     my ($code, $msg, $hdr, $content) = @_;
908    
909     unless (defined $content) {
910     $content = "$code $msg\n";
911     $hdr->{"Content-Type"} = "text/plain";
912     $hdr->{"Content-Length"} = length $content;
913     }
914     $hdr->{"Connection"} = "close";
915    
916     $self->response($code, $msg, $hdr, $content);
917    
918     die bless {}, err::;
919     }
920    
921     sub handle {
922     my $self = shift;
923     my $fh = $self->{fh};
924    
925     my $host;
926    
927     $fh->timeout($::REQ_TIMEOUT);
928     while() {
929     $self->{reqs}++;
930    
931     # read request and parse first line
932     my $req = $fh->readline("\015\012\015\012");
933    
934     unless (defined $req) {
935     if (exists $self->{version}) {
936     last;
937     } else {
938     $self->err(408, "request timeout");
939     }
940     }
941    
942     $self->{h} = {};
943    
944     $fh->timeout($::RES_TIMEOUT);
945    
946     $req =~ /^(?:\015\012)?
947     (GET|HEAD) \040+
948     ([^\040]+) \040+
949     HTTP\/([0-9]+\.[0-9]+)
950     \015\012/gx
951     or $self->err(405, "method not allowed", { Allow => "GET,HEAD" });
952    
953     $self->{method} = $1;
954     $self->{uri} = $2;
955     $self->{version} = $3;
956    
957     $3 =~ /^1\./
958     or $self->err(506, "http protocol version $3 not supported");
959    
960     # parse headers
961     {
962     my (%hdr, $h, $v);
963    
964     $hdr{lc $1} .= ",$2"
965     while $req =~ /\G
966     ([^:\000-\040]+):
967     [\008\040]*
968     ((?: [^\015\012]+ | \015\012[\008\040] )*)
969     \015\012
970     /gxc;
971    
972     $req =~ /\G\015\012$/
973     or $self->err(400, "bad request");
974    
975     $self->{h}{$h} = substr $v, 1
976     while ($h, $v) = each %hdr;
977     }
978    
979     # remote id should be unique per user
980     my $id = $self->{remote_addr};
981    
982     if (exists $self->{h}{"client-ip"}) {
983     $id .= "[".$self->{h}{"client-ip"}."]";
984     } elsif (exists $self->{h}{"x-forwarded-for"}) {
985     $id .= "[".$self->{h}{"x-forwarded-for"}."]";
986     }
987    
988     $self->{remote_id} = $id;
989    
990     weaken (local $conn{$id}{$self*1} = $self);
991    
992     if ($blocked{$id}) {
993     $self->err_blocked
994     if $blocked{$id}[0] > $::NOW;
995    
996     delete $blocked{$id};
997     }
998    
999     # find out server name and port
1000     if ($self->{uri} =~ s/^http:\/\/([^\/?#]*)//i) {
1001     $host = $1;
1002     } else {
1003     $host = $self->{h}{host};
1004     }
1005    
1006     if (defined $host) {
1007     $self->{server_port} = $host =~ s/:([0-9]+)$// ? $1 : 80;
1008     } else {
1009     ($self->{server_port}, $host)
1010     = unpack_sockaddr_in $self->{fh}->sockname
1011     or $self->err(500, "unable to get socket name");
1012     $host = inet_ntoa $host;
1013     }
1014    
1015     $self->{server_name} = $host;
1016    
1017     weaken (local $uri{$id}{$self->{uri}}{$self*1} = $self);
1018    
1019     eval {
1020     $self->map_uri;
1021     $self->respond;
1022     };
1023    
1024     die if $@ && !ref $@;
1025    
1026     last if $self->{h}{connection} =~ /close/i;
1027    
1028     $httpevent->broadcast;
1029    
1030     $fh->timeout($::PER_TIMEOUT);
1031     }
1032     }
1033    
1034     sub block {
1035     my $self = shift;
1036    
1037     $blocked{$self->{remote_id}} = [$::NOW + $_[0], $_[1]];
1038     $self->slog(2, "blocked ip $self->{remote_id}");
1039     $self->err_blocked;
1040     }
1041    
1042     # uri => path mapping
1043     sub map_uri {
1044     my $self = shift;
1045     my $host = $self->{server_name};
1046     my $uri = $self->{uri};
1047    
1048     # some massaging, also makes it more secure
1049     $uri =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr hex $1/ge;
1050     $uri =~ s%//+%/%g;
1051     $uri =~ s%/\.(?=/|$)%%g;
1052     1 while $uri =~ s%/[^/]+/\.\.(?=/|$)%%;
1053    
1054     $uri =~ m%^/?\.\.(?=/|$)%
1055     and $self->err(400, "bad request");
1056    
1057     $self->{name} = $uri;
1058    
1059     # now do the path mapping
1060     $self->{path} = "$::DOCROOT/$host$uri";
1061    
1062     $self->access_check;
1063     }
1064    
1065     sub _cgi {
1066     my $self = shift;
1067     my $path = shift;
1068     my $fh;
1069    
1070     # no two-way xxx supported
1071     if (0 == fork) {
1072     open STDOUT, ">&".fileno($self->{fh});
1073     if (chdir $::DOCROOT) {
1074     $ENV{SERVER_SOFTWARE} = "thttpd-myhttpd"; # we are thttpd-alike
1075     $ENV{HTTP_HOST} = $self->{server_name};
1076     $ENV{HTTP_PORT} = $self->{server_port};
1077     $ENV{SCRIPT_NAME} = $self->{name};
1078     exec $path;
1079     }
1080     Coro::State::_exit(0);
1081     } else {
1082     die;
1083     }
1084     }
1085    
1086     sub server_hostport {
1087     $_[0]{server_port} == 80
1088     ? $_[0]{server_name}
1089     : "$_[0]{server_name}:$_[0]{server_port}";
1090     }
1091    
1092     sub respond {
1093     my $self = shift;
1094     my $path = $self->{path};
1095    
1096     if ($self->{name} =~ s%^/internal/([^/]+)%%) {
1097     if ($::internal{$1}) {
1098     $::internal{$1}->($self);
1099     } else {
1100     $self->err(404, "not found");
1101     }
1102     } else {
1103    
1104     stat $path
1105     or $self->err(404, "not found");
1106    
1107     $self->{stat} = [stat _];
1108    
1109     # idiotic netscape sends idiotic headers AGAIN
1110     my $ims = $self->{h}{"if-modified-since"} =~ /^([^;]+)/
1111     ? str2time $1 : 0;
1112    
1113     if (-d _ && -r _) {
1114     # directory
1115     if ($path !~ /\/$/) {
1116     # create a redirect to get the trailing "/"
1117     # we don't try to avoid the :80
1118     $self->err(301, "moved permanently", { Location => "http://".$self->server_hostport."$self->{uri}/" });
1119     } else {
1120     $ims < $self->{stat}[9]
1121     or $self->err(304, "not modified");
1122    
1123     if (-r "$path/index.html") {
1124     # replace directory "size" by index.html filesize
1125     $self->{stat} = [stat ($self->{path} .= "/index.html")];
1126     $self->handle_file($queue_index);
1127     } else {
1128     $self->handle_dir;
1129     }
1130     }
1131     } elsif (-f _ && -r _) {
1132     -x _ and $self->err(403, "forbidden");
1133    
1134     if (keys %{$conn{$self->{remote_id}}} > $::MAX_TRANSFERS_IP) {
1135     my $timeout = $::NOW + 10;
1136     while (keys %{$conn{$self->{remote_id}}} > $::MAX_TRANSFERS_IP) {
1137     if ($timeout < $::NOW) {
1138     $self->block($::BLOCKTIME, "too many connections");
1139     } else {
1140     $httpevent->wait;
1141     }
1142     }
1143     }
1144    
1145     $self->handle_file($queue_file);
1146     } else {
1147     $self->err(404, "not found");
1148     }
1149     }
1150     }
1151    
1152     sub handle_dir {
1153     my $self = shift;
1154     my $idx = $self->diridx;
1155    
1156     $self->response(200, "ok",
1157     {
1158     "Content-Type" => "text/html",
1159     "Content-Length" => length $idx,
1160     "Last-Modified" => time2str ($self->{stat}[9]),
1161     },
1162     $idx);
1163     }
1164    
1165     sub handle_file {
1166     my ($self, $queue) = @_;
1167     my $length = $self->{stat}[7];
1168     my $hdr = {
1169     "Last-Modified" => time2str ((stat _)[9]),
1170     };
1171    
1172     my @code = (200, "ok");
1173     my ($l, $h);
1174    
1175     if ($self->{h}{range} =~ /^bytes=(.*)$/) {
1176     for (split /,/, $1) {
1177     if (/^-(\d+)$/) {
1178     ($l, $h) = ($length - $1, $length - 1);
1179     } elsif (/^(\d+)-(\d*)$/) {
1180     ($l, $h) = ($1, ($2 ne "" || $2 >= $length) ? $2 : $length - 1);
1181     } else {
1182     ($l, $h) = (0, $length - 1);
1183     goto ignore;
1184     }
1185     goto satisfiable if $l >= 0 && $l < $length && $h >= 0 && $h >= $l;
1186     }
1187     $hdr->{"Content-Range"} = "bytes */$length";
1188     $hdr->{"Content-Length"} = $length;
1189     $self->err(416, "not satisfiable", $hdr, "");
1190    
1191     satisfiable:
1192     # check for segmented downloads
1193     if ($l && $::NO_SEGMENTED) {
1194     my $timeout = $::NOW + 15;
1195     while (keys %{$uri{$self->{remote_id}}{$self->{uri}}} > 1) {
1196     if ($timeout <= $::NOW) {
1197     $self->block($::BLOCKTIME, "segmented downloads are forbidden");
1198     #$self->err_segmented_download;
1199     } else {
1200     $httpevent->wait;
1201     }
1202     }
1203     }
1204    
1205     $hdr->{"Content-Range"} = "bytes $l-$h/$length";
1206     @code = (206, "partial content");
1207     $length = $h - $l + 1;
1208    
1209     ignore:
1210     } else {
1211     ($l, $h) = (0, $length - 1);
1212     }
1213    
1214     $self->{path} =~ /\.([^.]+)$/;
1215     $hdr->{"Content-Type"} = $mimetype{lc $1} || "application/octet-stream";
1216     $hdr->{"Content-Length"} = $length;
1217    
1218     $self->response(@code, $hdr, "");
1219    
1220     if ($self->{method} eq "GET") {
1221     $self->{time} = $::NOW;
1222    
1223     my $current = $Coro::current;
1224    
1225     my ($fh, $buf, $r);
1226    
1227     open $fh, "<", $self->{path}
1228     or die "$self->{path}: late open failure ($!)";
1229    
1230     $h -= $l - 1;
1231    
1232     if (0) { # !AIO
1233     if ($l) {
1234     sysseek $fh, $l, 0;
1235     }
1236     }
1237    
1238     my $transfer = $queue->start_transfer($h);
1239     my $locked;
1240     my $bufsize = $::WAIT_BUFSIZE; # initial buffer size
1241    
1242     while ($h > 0) {
1243     unless ($locked) {
1244     if ($locked ||= $transfer->try($::WAIT_INTERVAL)) {
1245     $bufsize = $::BUFSIZE;
1246     $self->{time} = $::NOW;
1247     }
1248     }
1249    
1250     if ($blocked{$self->{remote_id}}) {
1251     $self->{h}{connection} = "close";
1252     die bless {}, err::;
1253     }
1254    
1255     if (0) { # !AIO
1256     sysread $fh, $buf, $h > $bufsize ? $bufsize : $h
1257     or last;
1258     } else {
1259     aio_read($fh, $l, ($h > $bufsize ? $bufsize : $h),
1260     $buf, 0, sub {
1261     $r = $_[0];
1262     Coro::ready($current);
1263     });
1264     &Coro::schedule;
1265     last unless $r;
1266     }
1267     my $w = syswrite $self->{fh}, $buf
1268     or last;
1269     $::written += $w;
1270     $self->{written} += $w;
1271     $l += $r;
1272     }
1273    
1274     close $fh;
1275     }
1276     }
1277    
1278     1;
1279     !endblock perl
1280    
1281     A1: Module
1282    
1283     - Coro (Coroutinen)
1284     - Event (der Event-Standard, nur leider unmaintained)
1285     - Linux::AIO (asynchrone Ein-/Ausgabe)
1286     - Convert::Scalar (für C<weaken>)
1287     - HTTP::Date (code-reuse made easy with CPAN, hat mir sicherlich 5 Minuten gespart)
1288     - Date::Parse (siehe HTTP::Date, ich liebe CPAN ;)
1289     - BerkeleyDB (Caching der Directory-Listings und WHOIS-Requests)
1290 root 1.1
1291    
1292    
1293    
1294    
1295    
1296    
1297