ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/docs/pws2002/webserver.sdf
Revision: 1.4
Committed: Thu Feb 7 16:46:58 2002 UTC (24 years, 8 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +133 -133 lines
Log Message:
*** empty log message ***

File Contents

# Content
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 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 Dateien. Filme! Animes, um genau zu sein.
17
18 Und weil man so etwas nur sehr schwierig bekommt, dachte ich mir: packe sie
19 auf einen Webserver und bring' sie unter das Volk.
20
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ß 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 fehlschlägt. thttpd reagiert darauf mit einem "500 Internal Error". Und
36 verloren. Da er den mmap-Cache auch so einfach nicht wieder hergibt, macht
37 thttpd seine eigene Denial-of-Service-Attack...
38
39 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
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 außerhalb 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 dass 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, 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
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 Wie jeder andere "forkende" Server, erzeugt auch dieser Webserver eine
90 listen-Socket und wartet auf Verbindungen:
91
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 # Starte den "Accept-Prozess"
103 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 wie IO::Socket::INET, der Aufruf ist also nicht Neues.
123
124 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
130 Die Schleife um den C<accept>-Aufruf besteht im Wesentlichen aus:
131
132 !block perl
133 async {
134 eval { ... Verbindung behandeln ...};
135 } $port->accept;
136 !endblock
137
138 Das muss man rückwärts lesen, denn der Aufruf von C<async> kann erst
139 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 Prozess, ein C<local $/> reicht also), besitzt die C<readline>-Methode ein
155 optionales zweites Argument - den effektiven Wert von C<$/>.
156
157 !block perl
158 # Lies den HTTP-Request
159 $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 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
187 Bei dieser (und der folgenden) Regex wurde auf Geschwindigkeit
188 geachtet: Im Normalfall (kein Syntaxfehler) ist das Parsen O(n), d.h.
189 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 [\010\040]*
216 ((?: [^\015\012]+ | \015\012[\010\040] )*)
217 \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 nicht unterstützt, werden alle Header (z.B. "Content-Length") in
232 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 und das \G am Anfang setzt der Parser genau dort wieder auf, wo die
241 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 Hier würde ich mich fragen, was denn das C<.= ",$2"> und das C<substr $v,
250 1> da zu suchen haben. Nun, manche Header können mehrfach vorkommen (tun
251 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
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 Hier das Wichtigste von C<map_uri>:
271
272 !block perl
273 my $uri = $self->{uri};
274
275 # mach' aus der URI was Pfad-ähnliches
276 $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 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
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 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
307 C<respond> ist auch nicht weiter kompliziert:
308
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 $self->err(301, "moved permanently", {
327 Location => "http://".$self->server_hostport."$self->{uri}/"
328 });
329 } 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 H1: Asynchrone Ein-/Ausgabe, und warum Linux "Müll" ist
342
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 Freiheiten zu erhalten, mit ihm herumzuexperimentieren.
348
349 Er war nicht schneller als thttpd. Ein C<strace> (ist fast immer sehr
350 lehrreich) zeigte, daß der Server (wie thttpd) ca 90% der Realzeit im
351 C<read>-syscall steckte.
352
353 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 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 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
369 "Asynchrone Ein-/Ausgabe"
370
371 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 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 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
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 auf dem Perl-Workshop ja mehr Praktisches gezeigt werden und, so leid es
440 mir tut, hier kommt die Wirklichkeit angerollt.
441
442 Aber etwas langsamer: Das Linux-AIO-Modul ist relativ klein (C ist eben
443 umständlich). Es startet im Wesentlichen einen (oder mehrere) Thread(s),
444 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 Die Anwendung ist recht einfach, statt eines:
453
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 funktioniert und war bisher nicht das (gedankliche) Nadelöhr.
479
480 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
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 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
492 H2: Linux...
493
494 Jetzt kommts. AIO brachte den Webserver sofort auf 20Mbit. Toll, so schnell war
495 er noch nie. Aber irgendwie... irgendwie... war das wenig, denn über "Kunden"
496 konnte er sich nicht beklagen.
497
498 Was habe ich nach der Ursache gesucht.
499
500 Und gesucht.
501
502 Naja, vielleicht ist IDE/ATA/PC-Hardware einfach Müll, dachte ich....
503
504 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 Merkwürdig.
507
508 Nach über einer Woche(!) Diskussion auf der Kernel-Liste war das Problem
509 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 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 Thread. Sieht im Prozesslisting eh' viel hübscher aus, und ich predige ja
523 sowieso, das Threads böse sind.
524
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 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
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 einfach zu erreichen: Diesmal waren es {{wirklich}} die Festplatten: Wer
538 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 my $guard = $transfers->guard;
552 !endblock
553
554 C<guard> ist ähnlich wie C<down>, liefert aber ein Objekt, das in der
555 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 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 nicht-Windows-User: sowas wie wget), die standardmäßig 4 (8, 10)
566 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 Was für ein Glück, dass ich einen Single-Process-Webserver geschrieben habe,
574 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 !block perl
587 weaken (
588 local $uri{Client-ID} {URI} {$self} = $self
589 );
590 !endblock
591
592 Die Client-ID ist normalerweise die IP-Addresse (bei Proxies außerdem
593 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 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 die gespeicherte Referenz das Objekt nicht am DESTROY hindert.
598
599 Wenn die Funktion C<handle_file> einen potenziellen "Segmented Download"
600 erkennt (und zwar daran, dass nur ein Teil der Datei übertragen werden
601 soll) überprüft sie die Zahl der vorhandenen Verbindungen:
602
603 !block perl
604 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 In der Praxis kann es passieren, dass ein Download abgebrochen wurde und der
614 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 Maßnahmen ergreift:
617
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 !endblock
631
632 Die Methode C<block> macht sich ein kleines Vermerkle, das für die
633 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 debuggen - das ist schon was Feines. Normalerweise ist sowas zu aufwendig,
640 aber in Perl:
641
642 !block perl
643 my $port = new Coro::Socket LocalPort => $CMDSHELL_PORT,
644 ReuseAddr => 1, Listen => 1
645 or die "unable to bind cmdshell port: $!";
646
647 async {
648 async \&shell, scalar $port->accept
649 while 1;
650 };
651 !endblock
652
653 So einfach kann es sein, einen Server zu programmieren: Listening-Port
654 erzeugen und bei jedem C<accept> eine Coroutine starten:
655
656 !block perl
657 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 !endblock
677
678 Das liest Kommandos, bis der Client die Verbindung dichtmacht
679 ({{C:<$fh>}} liefert C<undef>), der Server beendet wird (C<Event::unloop>
680 spricht aus der Hauptschleife), oder "quit" eingegeben wird.
681
682 Das wichtigste Komamndo ist C<print>. Damit führe ich häufig
683 Server-Upgrades im Betrieb durch:
684
685 !block perl
686 cmd> print do 'access.pl'
687 RES = 1
688 cmd> print $VERSION = 0.711
689 RES = 1
690 !endblock
691
692 Dies ist das - für mich - Beeindruckendste an diesem Webserver.
693
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
1311
1312
1313
1314
1315
1316
1317