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