ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Coro/myhttpd/httpd.pl
Revision: 1.35
Committed: Thu Aug 30 12:35:28 2001 UTC (22 years, 10 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.34: +14 -7 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 use Coro;
2     use Coro::Semaphore;
3     use Coro::Event;
4     use Coro::Socket;
5    
6 root 1.32 use HTTP::Date;
7    
8 root 1.1 no utf8;
9     use bytes;
10    
11     # at least on my machine, this thingy serves files
12     # quite a bit faster than apache, ;)
13     # and quite a bit slower than thttpd :(
14    
15     $SIG{PIPE} = 'IGNORE';
16 root 1.27
17     our $accesslog;
18    
19     if ($ACCESS_LOG) {
20     use IO::Handle;
21     open $accesslog, ">>$ACCESS_LOG"
22     or die "$ACCESS_LOG: $!";
23     $accesslog->autoflush(1);
24     }
25    
26 root 1.1 sub slog {
27     my $level = shift;
28     my $format = shift;
29     printf "---: $format\n", @_;
30     }
31    
32 root 1.32 our $connections = new Coro::Semaphore $MAX_CONNECTS || 250;
33 root 1.34
34     our $wait_factor = 0.95;
35    
36     our @transfers = (
37 root 1.35 [(new Coro::Semaphore $MAX_TRANSFERS_SMALL || 50), 1],
38     [(new Coro::Semaphore $MAX_TRANSFERS_LARGE || 50), 1],
39 root 1.34 );
40 root 1.1
41 root 1.6 my @newcons;
42 root 1.1 my @pool;
43    
44 root 1.2 # one "execution thread"
45 root 1.1 sub handler {
46     while () {
47 root 1.6 my $new = pop @newcons;
48     if ($new) {
49 root 1.1 eval {
50 root 1.6 conn->new(@$new)->handle;
51 root 1.1 };
52     slog 1, "$@" if $@ && !ref $@;
53     $connections->up;
54     } else {
55     last if @pool >= $MAX_POOL;
56     push @pool, $Coro::current;
57     schedule;
58     }
59     }
60     }
61    
62 root 1.4 my $http_port = new Coro::Socket
63     LocalAddr => $SERVER_HOST,
64     LocalPort => $SERVER_PORT,
65     ReuseAddr => 1,
66 root 1.13 Listen => 50,
67 root 1.4 or die "unable to start server";
68    
69     push @listen_sockets, $http_port;
70    
71 root 1.32 our $NOW;
72     our $HTTP_NOW;
73    
74     Event->timer(interval => 1, hard => 1, cb => sub {
75     $NOW = time;
76     $HTTP_NOW = time2str $NOW;
77 root 1.34 })->now;
78 root 1.32
79 root 1.2 # the "main thread"
80 root 1.1 async {
81     slog 1, "accepting connections";
82     while () {
83     $connections->down;
84 root 1.6 push @newcons, [$http_port->accept];
85 root 1.1 #slog 3, "accepted @$connections ".scalar(@pool);
86     if (@pool) {
87     (pop @pool)->ready;
88     } else {
89     async \&handler;
90     }
91    
92     }
93     };
94    
95     package conn;
96    
97     use Socket;
98     use HTTP::Date;
99 root 1.2 use Convert::Scalar 'weaken';
100 root 1.16 use Linux::AIO;
101    
102     Linux::AIO::min_parallel $::AIO_PARALLEL;
103    
104 root 1.29 my $aio_requests = new Coro::Semaphore $::AIO_PARALLEL * 4;
105    
106 root 1.16 Event->io(fd => Linux::AIO::poll_fileno,
107 root 1.17 poll => 'r', async => 1,
108 root 1.21 cb => \&Linux::AIO::poll_cb);
109 root 1.16
110 root 1.26 our %conn; # $conn{ip}{self} => connobj
111     our %uri; # $uri{ip}{uri}{self}
112 root 1.3 our %blocked;
113 root 1.9 our %mimetype;
114    
115     sub read_mimetypes {
116     local *M;
117 root 1.10 if (open M, "<mime_types") {
118 root 1.9 while (<M>) {
119     if (/^([^#]\S+)\t+(\S+)$/) {
120     $mimetype{lc $1} = $2;
121     }
122     }
123     } else {
124 root 1.10 print "cannot open mime_types\n";
125 root 1.9 }
126     }
127 root 1.1
128 root 1.10 read_mimetypes;
129    
130 root 1.1 sub new {
131     my $class = shift;
132 root 1.6 my $peername = shift;
133 root 1.1 my $fh = shift;
134 root 1.2 my $self = bless { fh => $fh }, $class;
135 root 1.6 my (undef, $iaddr) = unpack_sockaddr_in $peername
136     or $self->err(500, "unable to decode peername");
137 root 1.7
138 root 1.3 $self->{remote_addr} = inet_ntoa $iaddr;
139 root 1.11 $self->{time} = $::NOW;
140 root 1.2
141     # enter ourselves into various lists
142 root 1.3 weaken ($conn{$self->{remote_addr}}{$self*1} = $self);
143    
144 root 1.13 $::conns++;
145    
146 root 1.2 $self;
147     }
148    
149     sub DESTROY {
150     my $self = shift;
151 root 1.13
152     $::conns--;
153    
154 root 1.19 $self->eoconn;
155 root 1.3 delete $conn{$self->{remote_addr}}{$self*1};
156 root 1.19 }
157    
158     # end of connection
159     sub eoconn {
160 root 1.26 my $self = shift;
161 root 1.13 delete $uri{$self->{remote_addr}}{$self->{uri}}{$self*1};
162 root 1.1 }
163    
164     sub slog {
165 root 1.4 my $self = shift;
166 root 1.29 main::slog($_[0], ($self->{remote_id} || $self->{remote_addr}) ."> $_[1]");
167 root 1.1 }
168    
169 root 1.4 sub response {
170 root 1.1 my ($self, $code, $msg, $hdr, $content) = @_;
171 root 1.17 my $res = "HTTP/1.1 $code $msg\015\012";
172 root 1.1
173 root 1.28 $self->{h}{connection} ||= $hdr->{Connection};
174    
175 root 1.32 $res .= "Date: $HTTP_NOW\015\012";
176 root 1.1
177     while (my ($h, $v) = each %$hdr) {
178     $res .= "$h: $v\015\012"
179     }
180 root 1.10 $res .= "\015\012";
181 root 1.4
182 root 1.13 $res .= $content if defined $content and $self->{method} ne "HEAD";
183 root 1.1
184 root 1.27 my $log = "$self->{remote_addr} \"$self->{uri}\" $code ".$hdr->{"Content-Length"}." \"$self->{h}{referer}\"\n";
185    
186     print $accesslog $log if $accesslog;
187     print STDERR $log;
188 root 1.2
189 root 1.11 $self->{written} +=
190     print {$self->{fh}} $res;
191 root 1.1 }
192    
193     sub err {
194     my $self = shift;
195     my ($code, $msg, $hdr, $content) = @_;
196    
197     unless (defined $content) {
198 root 1.35 $content = "$code $msg\n";
199 root 1.1 $hdr->{"Content-Type"} = "text/plain";
200     $hdr->{"Content-Length"} = length $content;
201     }
202 root 1.17 $hdr->{"Connection"} = "close";
203 root 1.1
204 root 1.4 $self->response($code, $msg, $hdr, $content);
205 root 1.1
206     die bless {}, err::;
207     }
208    
209     sub handle {
210     my $self = shift;
211     my $fh = $self->{fh};
212    
213 root 1.29 my $host;
214    
215 root 1.17 $fh->timeout($::REQ_TIMEOUT);
216     while() {
217     $self->{reqs}++;
218 root 1.1
219     # read request and parse first line
220     my $req = $fh->readline("\015\012\015\012");
221    
222 root 1.17 unless (defined $req) {
223     if (exists $self->{version}) {
224     last;
225     } else {
226     $self->err(408, "request timeout");
227     }
228     }
229    
230     $self->{h} = {};
231 root 1.1
232 root 1.17 $fh->timeout($::RES_TIMEOUT);
233 root 1.3 my $ip = $self->{remote_addr};
234    
235     if ($blocked{$ip}) {
236     $self->err_blocked($blocked{$ip})
237     if $blocked{$ip} > $::NOW;
238    
239     delete $blocked{$ip};
240     }
241    
242     if (%{$conn{$ip}} > $::MAX_CONN_IP) {
243 root 1.31 my $delay = 120;
244     while (%{$conn{$ip}} > $::MAX_CONN_IP) {
245     if ($delay <= 0) {
246     $self->slog(2, "blocked ip $ip");
247     $self->err_blocked;
248     } else {
249     Coro::Event::do_timer(after => 3);
250     $delay -= 3;
251     }
252     }
253 root 1.3 }
254    
255 root 1.1 $req =~ /^(?:\015\012)?
256     (GET|HEAD) \040+
257     ([^\040]+) \040+
258     HTTP\/([0-9]+\.[0-9]+)
259     \015\012/gx
260 root 1.14 or $self->err(405, "method not allowed", { Allow => "GET,HEAD" });
261 root 1.1
262     $self->{method} = $1;
263     $self->{uri} = $2;
264 root 1.17 $self->{version} = $3;
265    
266 root 1.20 $3 =~ /^1\./
267 root 1.17 or $self->err(506, "http protocol version $3 not supported");
268 root 1.1
269     # parse headers
270     {
271     my (%hdr, $h, $v);
272    
273     $hdr{lc $1} .= ",$2"
274     while $req =~ /\G
275     ([^:\000-\040]+):
276     [\008\040]*
277     ((?: [^\015\012]+ | \015\012[\008\040] )*)
278     \015\012
279     /gxc;
280    
281     $req =~ /\G\015\012$/
282     or $self->err(400, "bad request");
283    
284     $self->{h}{$h} = substr $v, 1
285     while ($h, $v) = each %hdr;
286     }
287    
288 root 1.29 # find out server name and port
289     if ($self->{uri} =~ s/^http:\/\/([^\/?#]*)//i) {
290     $host = $1;
291     } else {
292     $host = $self->{h}{host};
293     }
294    
295     if (defined $host) {
296     $self->{server_port} = $host =~ s/:([0-9]+)$// ? $1 : 80;
297     } else {
298     ($self->{server_port}, $host)
299     = unpack_sockaddr_in $self->{fh}->getsockname
300     or $self->err(500, "unable to get socket name");
301     $host = inet_ntoa $host;
302     }
303    
304     $self->{server_name} = $host;
305    
306     # remote id should be unique per user
307     $self->{remote_id} = $self->{remote_addr};
308    
309     if (exists $self->{h}{"client-ip"}) {
310     $self->{remote_id} .= "[".$self->{h}{"client-ip"}."]";
311     } elsif (exists $self->{h}{"x-forwarded-for"}) {
312     $self->{remote_id} .= "[".$self->{h}{"x-forwarded-for"}."]";
313     }
314 root 1.3
315 root 1.13 weaken ($uri{$self->{remote_addr}}{$self->{uri}}{$self*1} = $self);
316 root 1.1
317 root 1.24 eval {
318     $self->map_uri;
319     $self->respond;
320     };
321    
322 root 1.26 $self->eoconn;
323    
324 root 1.24 die if $@ && !ref $@;
325 root 1.17
326 root 1.29 last if $self->{h}{connection} =~ /close/ || $self->{version} < 1.1;
327 root 1.17
328     $fh->timeout($::PER_TIMEOUT);
329     }
330 root 1.1 }
331    
332     # uri => path mapping
333     sub map_uri {
334     my $self = shift;
335 root 1.29 my $host = $self->{server_name};
336 root 1.1 my $uri = $self->{uri};
337    
338     # some massaging, also makes it more secure
339     $uri =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr hex $1/ge;
340     $uri =~ s%//+%/%g;
341     $uri =~ s%/\.(?=/|$)%%g;
342     1 while $uri =~ s%/[^/]+/\.\.(?=/|$)%%;
343    
344     $uri =~ m%^/?\.\.(?=/|$)%
345     and $self->err(400, "bad request");
346    
347     $self->{name} = $uri;
348    
349     # now do the path mapping
350     $self->{path} = "$::DOCROOT/$host$uri";
351 root 1.7
352     $self->access_check;
353 root 1.1 }
354    
355     sub _cgi {
356     my $self = shift;
357     my $path = shift;
358     my $fh;
359    
360     # no two-way xxx supported
361     if (0 == fork) {
362     open STDOUT, ">&".fileno($self->{fh});
363     if (chdir $::DOCROOT) {
364     $ENV{SERVER_SOFTWARE} = "thttpd-myhttpd"; # we are thttpd-alike
365 root 1.29 $ENV{HTTP_HOST} = $self->{server_name};
366     $ENV{HTTP_PORT} = $self->{server_port};
367 root 1.1 $ENV{SCRIPT_NAME} = $self->{name};
368 root 1.10 exec $path;
369 root 1.1 }
370     Coro::State::_exit(0);
371     } else {
372 root 1.29 die;
373 root 1.1 }
374     }
375    
376 root 1.29 sub server_hostport {
377     $_[0]{server_port} == 80
378     ? $_[0]{server_name}
379     : "$_[0]{server_name}:$_[0]{server_port}";
380     }
381    
382 root 1.1 sub respond {
383     my $self = shift;
384     my $path = $self->{path};
385    
386     stat $path
387     or $self->err(404, "not found");
388    
389 root 1.10 $self->{stat} = [stat _];
390    
391 root 1.1 # idiotic netscape sends idiotic headers AGAIN
392     my $ims = $self->{h}{"if-modified-since"} =~ /^([^;]+)/
393     ? str2time $1 : 0;
394    
395     if (-d _ && -r _) {
396     # directory
397     if ($path !~ /\/$/) {
398     # create a redirect to get the trailing "/"
399 root 1.29 # we don't try to avoid the :80
400     $self->err(301, "moved permanently", { Location => "http://".$self->server_hostport."$self->{uri}/" });
401 root 1.1 } else {
402 root 1.10 $ims < $self->{stat}[9]
403 root 1.1 or $self->err(304, "not modified");
404    
405 root 1.25 if (-r "$path/index.html") {
406     $self->{path} .= "/index.html";
407     $self->handle_file;
408     } else {
409     $self->handle_dir;
410 root 1.1 }
411     }
412     } elsif (-f _ && -r _) {
413     -x _ and $self->err(403, "forbidden");
414     $self->handle_file;
415     } else {
416     $self->err(404, "not found");
417     }
418     }
419    
420     sub handle_dir {
421     my $self = shift;
422 root 1.10 my $idx = $self->diridx;
423    
424     $self->response(200, "ok",
425     {
426     "Content-Type" => "text/html",
427     "Content-Length" => length $idx,
428     },
429     $idx);
430 root 1.1 }
431    
432     sub handle_file {
433     my $self = shift;
434 root 1.34 my $length = $self->{stat}[7];
435     my $queue = $::transfers[$length >= $::TRANSFER_SMALL];
436 root 1.1 my $hdr = {
437     "Last-Modified" => time2str ((stat _)[9]),
438     };
439    
440     my @code = (200, "ok");
441     my ($l, $h);
442    
443     if ($self->{h}{range} =~ /^bytes=(.*)$/) {
444     for (split /,/, $1) {
445     if (/^-(\d+)$/) {
446     ($l, $h) = ($length - $1, $length - 1);
447     } elsif (/^(\d+)-(\d*)$/) {
448     ($l, $h) = ($1, ($2 ne "" || $2 >= $length) ? $2 : $length - 1);
449     } else {
450     ($l, $h) = (0, $length - 1);
451     goto ignore;
452     }
453 root 1.26 goto satisfiable if $l >= 0 && $l < $length && $h >= 0 && $h >= $l;
454 root 1.1 }
455     $hdr->{"Content-Range"} = "bytes */$length";
456 root 1.24 $hdr->{"Content-Length"} = $length;
457     $self->err(416, "not satisfiable", $hdr, "");
458 root 1.1
459     satisfiable:
460 root 1.4 # check for segmented downloads
461 root 1.10 if ($l && $::NO_SEGMENTED) {
462 root 1.30 my $delay = 180;
463 root 1.29 while (%{$uri{$self->{remote_addr}}{$self->{uri}}} > 1) {
464     if ($delay <= 0) {
465 root 1.30 $self->err_segmented_download;
466 root 1.29 } else {
467     Coro::Event::do_timer(after => 3); $delay -= 3;
468     }
469 root 1.4 }
470     }
471    
472 root 1.1 $hdr->{"Content-Range"} = "bytes $l-$h/$length";
473     @code = (206, "partial content");
474     $length = $h - $l + 1;
475    
476     ignore:
477     } else {
478     ($l, $h) = (0, $length - 1);
479     }
480    
481 root 1.9 $self->{path} =~ /\.([^.]+)$/;
482     $hdr->{"Content-Type"} = $mimetype{lc $1} || "application/octet-stream";
483 root 1.1 $hdr->{"Content-Length"} = $length;
484    
485 root 1.4 $self->response(@code, $hdr, "");
486 root 1.1
487     if ($self->{method} eq "GET") {
488 root 1.32 $self->{time} = $::NOW;
489    
490 root 1.35 my $fudge = $queue->[0]->waiters;
491     $fudge = $fudge ? ($fudge+1)/$fudge : 1;
492    
493     $queue->[1] *= $fudge;
494 root 1.34 my $transfer = $queue->[0]->guard;
495 root 1.32
496 root 1.35 if ($fudge != 1) {
497     $queue->[1] /= $fudge;
498     $queue->[1] = $queue->[1] * $::wait_factor
499     + ($::NOW - $self->{time}) * (1 - $::wait_factor);
500     }
501 root 1.32 $self->{time} = $::NOW;
502 root 1.35
503     $self->{fh}->writable or return;
504 root 1.32
505 root 1.16 my ($fh, $buf, $r);
506     my $current = $Coro::current;
507 root 1.1 open $fh, "<", $self->{path}
508     or die "$self->{path}: late open failure ($!)";
509    
510     $h -= $l - 1;
511    
512 root 1.19 if (0) {
513     if ($l) {
514     sysseek $fh, $l, 0;
515     }
516     }
517    
518 root 1.1 while ($h > 0) {
519 root 1.19 if (0) {
520     sysread $fh, $buf, $h > $::BUFSIZE ? $::BUFSIZE : $h
521     or last;
522     } else {
523 root 1.29 undef $buf;
524     $aio_requests->down;
525 root 1.19 aio_read($fh, $l, ($h > $::BUFSIZE ? $::BUFSIZE : $h),
526     $buf, 0, sub {
527     $r = $_[0];
528     $current->ready;
529     });
530     &Coro::schedule;
531 root 1.29 $aio_requests->up;
532 root 1.19 last unless $r;
533     }
534 root 1.11 my $w = $self->{fh}->syswrite($buf)
535 root 1.1 or last;
536 root 1.11 $::written += $w;
537     $self->{written} += $w;
538 root 1.16 $l += $r;
539 root 1.1 }
540 root 1.32
541     close $fh;
542 root 1.1 }
543 root 1.7 }
544    
545 root 1.2 1;