ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Coro/myhttpd/httpd.pl
Revision: 1.10
Committed: Sat Aug 11 16:34:47 2001 UTC (22 years, 11 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.9: +22 -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     no utf8;
7     use bytes;
8    
9     # at least on my machine, this thingy serves files
10     # quite a bit faster than apache, ;)
11     # and quite a bit slower than thttpd :(
12    
13     $SIG{PIPE} = 'IGNORE';
14    
15     sub slog {
16     my $level = shift;
17     my $format = shift;
18     printf "---: $format\n", @_;
19     }
20    
21     my $connections = new Coro::Semaphore $MAX_CONNECTS;
22    
23 root 1.6 my @newcons;
24 root 1.1 my @pool;
25    
26 root 1.2 # one "execution thread"
27 root 1.1 sub handler {
28     while () {
29 root 1.6 my $new = pop @newcons;
30     if ($new) {
31 root 1.1 eval {
32 root 1.6 conn->new(@$new)->handle;
33 root 1.1 };
34     slog 1, "$@" if $@ && !ref $@;
35     $connections->up;
36     } else {
37     last if @pool >= $MAX_POOL;
38     push @pool, $Coro::current;
39     schedule;
40     }
41     }
42     }
43    
44 root 1.4 my $http_port = new Coro::Socket
45     LocalAddr => $SERVER_HOST,
46     LocalPort => $SERVER_PORT,
47     ReuseAddr => 1,
48     Listen => 1,
49     or die "unable to start server";
50    
51     push @listen_sockets, $http_port;
52    
53 root 1.2 # the "main thread"
54 root 1.1 async {
55     slog 1, "accepting connections";
56     while () {
57     $connections->down;
58 root 1.6 push @newcons, [$http_port->accept];
59 root 1.1 #slog 3, "accepted @$connections ".scalar(@pool);
60 root 1.3 $::NOW = time;
61 root 1.1 if (@pool) {
62     (pop @pool)->ready;
63     } else {
64     async \&handler;
65     }
66    
67     }
68     };
69    
70     package conn;
71    
72     use Socket;
73     use HTTP::Date;
74 root 1.2 use Convert::Scalar 'weaken';
75    
76 root 1.3 our %conn; # $conn{ip}{fh} => connobj
77     our %blocked;
78 root 1.9 our %mimetype;
79    
80     sub read_mimetypes {
81     local *M;
82 root 1.10 if (open M, "<mime_types") {
83 root 1.9 while (<M>) {
84     if (/^([^#]\S+)\t+(\S+)$/) {
85     $mimetype{lc $1} = $2;
86     }
87     }
88     } else {
89 root 1.10 print "cannot open mime_types\n";
90 root 1.9 }
91     }
92 root 1.1
93 root 1.10 read_mimetypes;
94    
95 root 1.1 sub new {
96     my $class = shift;
97 root 1.6 my $peername = shift;
98 root 1.1 my $fh = shift;
99 root 1.2 my $self = bless { fh => $fh }, $class;
100 root 1.6 my (undef, $iaddr) = unpack_sockaddr_in $peername
101     or $self->err(500, "unable to decode peername");
102 root 1.7
103 root 1.3 $self->{remote_addr} = inet_ntoa $iaddr;
104 root 1.2
105     # enter ourselves into various lists
106 root 1.3 weaken ($conn{$self->{remote_addr}}{$self*1} = $self);
107    
108 root 1.2 $self;
109     }
110    
111     sub DESTROY {
112     my $self = shift;
113 root 1.3 delete $conn{$self->{remote_addr}}{$self*1};
114     delete $uri{$self->{uri}}{$self*1};
115 root 1.1 }
116    
117     sub slog {
118 root 1.4 my $self = shift;
119     main::slog($_[0], "$self->{remote_addr}> $_[1]");
120 root 1.1 }
121    
122 root 1.4 sub response {
123 root 1.1 my ($self, $code, $msg, $hdr, $content) = @_;
124     my $res = "HTTP/1.0 $code $msg\015\012";
125    
126 root 1.4 $res .= "Connection: close\015\012";
127     $res .= "Date: ".(time2str $::NOW)."\015\012"; # slow? nah. :(
128 root 1.1
129     while (my ($h, $v) = each %$hdr) {
130     $res .= "$h: $v\015\012"
131     }
132 root 1.10 $res .= "\015\012";
133 root 1.4
134 root 1.10 $res .= $content if defined $content and $self->{method} eq "GET";
135 root 1.1
136 root 1.3 print STDERR "$self->{remote_addr} \"$self->{uri}\" $code ".$hdr->{"Content-Length"}." \"$self->{h}{referer}\"\n";#d#
137 root 1.2
138 root 1.1 print {$self->{fh}} $res;
139     }
140    
141     sub err {
142     my $self = shift;
143     my ($code, $msg, $hdr, $content) = @_;
144    
145     unless (defined $content) {
146     $content = "$code $msg";
147     $hdr->{"Content-Type"} = "text/plain";
148     $hdr->{"Content-Length"} = length $content;
149     }
150    
151 root 1.4 $self->response($code, $msg, $hdr, $content);
152 root 1.1
153     die bless {}, err::;
154     }
155    
156 root 1.3 sub err_blocked {
157     my $self = shift;
158     my $ip = $self->{remote_addr};
159     my $time = time2str $blocked{$ip} = $::NOW + $::BLOCKTIME;
160 root 1.10
161     Coro::Event::do_timer(after => 5);
162    
163 root 1.3 $self->err(403, "too many connections",
164 root 1.4 {
165     "Content-Type" => "text/html",
166     "Retry-After" => $::BLOCKTIME
167     },
168 root 1.3 <<EOF);
169 root 1.4 <html><p>
170 root 1.3 You have been blocked because you opened too many connections. You
171 root 1.4 may retry at</p>
172    
173     <p><blockquote>$time.</blockquote></p>
174    
175     <p>Until then, each new access will renew the block. You might want to have a
176     look at the <a href="http://www.goof.com/pcg/marc/animefaq.html">FAQ</a>.</p>
177     </html>
178 root 1.3 EOF
179     }
180    
181 root 1.1 sub handle {
182     my $self = shift;
183     my $fh = $self->{fh};
184    
185     #while() {
186     $self->{h} = {};
187    
188     # read request and parse first line
189     $fh->timeout($::REQ_TIMEOUT);
190     my $req = $fh->readline("\015\012\015\012");
191     $fh->timeout($::RES_TIMEOUT);
192    
193     defined $req or
194     $self->err(408, "request timeout");
195    
196 root 1.3 my $ip = $self->{remote_addr};
197    
198     if ($blocked{$ip}) {
199     $self->err_blocked($blocked{$ip})
200     if $blocked{$ip} > $::NOW;
201    
202     delete $blocked{$ip};
203     }
204    
205     if (%{$conn{$ip}} > $::MAX_CONN_IP) {
206     $self->slog("blocked ip $ip");
207     $self->err_blocked;
208     }
209    
210 root 1.1 $req =~ /^(?:\015\012)?
211     (GET|HEAD) \040+
212     ([^\040]+) \040+
213     HTTP\/([0-9]+\.[0-9]+)
214     \015\012/gx
215     or $self->err(403, "method not allowed", { Allow => "GET,HEAD" });
216    
217     $2 ne "1.0"
218     or $self->err(506, "http protocol version not supported");
219    
220     $self->{method} = $1;
221     $self->{uri} = $2;
222    
223     # parse headers
224     {
225     my (%hdr, $h, $v);
226    
227     $hdr{lc $1} .= ",$2"
228     while $req =~ /\G
229     ([^:\000-\040]+):
230     [\008\040]*
231     ((?: [^\015\012]+ | \015\012[\008\040] )*)
232     \015\012
233     /gxc;
234    
235     $req =~ /\G\015\012$/
236     or $self->err(400, "bad request");
237    
238     $self->{h}{$h} = substr $v, 1
239     while ($h, $v) = each %hdr;
240     }
241    
242     $self->{server_port} = $self->{h}{host} =~ s/:([0-9]+)$// ? $1 : 80;
243 root 1.3
244     weaken ($uri{$self->{uri}}{$self*1} = $self);
245 root 1.1
246     $self->map_uri;
247     $self->respond;
248     #}
249     }
250    
251     # uri => path mapping
252     sub map_uri {
253     my $self = shift;
254     my $host = $self->{h}{host} || "default";
255     my $uri = $self->{uri};
256    
257     # some massaging, also makes it more secure
258     $uri =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr hex $1/ge;
259     $uri =~ s%//+%/%g;
260     $uri =~ s%/\.(?=/|$)%%g;
261     1 while $uri =~ s%/[^/]+/\.\.(?=/|$)%%;
262    
263     $uri =~ m%^/?\.\.(?=/|$)%
264     and $self->err(400, "bad request");
265    
266     $self->{name} = $uri;
267    
268     # now do the path mapping
269     $self->{path} = "$::DOCROOT/$host$uri";
270 root 1.7
271     $self->access_check;
272 root 1.1 }
273    
274     sub server_address {
275     my $self = shift;
276     my ($port, $iaddr) = unpack_sockaddr_in $self->{fh}->getsockname
277     or $self->err(500, "unable to get socket name");
278     ((inet_ntoa $iaddr), $port);
279     }
280    
281     sub server_host {
282     my $self = shift;
283     if (exists $self->{h}{host}) {
284     return $self->{h}{host};
285     } else {
286     return (($self->server_address)[0]);
287     }
288     }
289    
290     sub server_hostport {
291     my $self = shift;
292     my ($host, $port);
293     if (exists $self->{h}{host}) {
294     ($host, $port) = ($self->{h}{host}, $self->{server_port});
295     } else {
296     ($host, $port) = $self->server_address;
297     }
298     $port = $port == 80 ? "" : ":$port";
299     $host.$port;
300     }
301    
302     sub _cgi {
303     my $self = shift;
304     my $path = shift;
305     my $fh;
306    
307     # no two-way xxx supported
308     if (0 == fork) {
309     open STDOUT, ">&".fileno($self->{fh});
310     if (chdir $::DOCROOT) {
311     $ENV{SERVER_SOFTWARE} = "thttpd-myhttpd"; # we are thttpd-alike
312     $ENV{HTTP_HOST} = $self->server_host;
313     $ENV{HTTP_PORT} = $self->{server_host};
314     $ENV{SCRIPT_NAME} = $self->{name};
315 root 1.10 exec $path;
316 root 1.1 }
317     Coro::State::_exit(0);
318     } else {
319     }
320     }
321    
322     sub respond {
323     my $self = shift;
324     my $path = $self->{path};
325    
326     stat $path
327     or $self->err(404, "not found");
328    
329 root 1.10 $self->{stat} = [stat _];
330    
331 root 1.1 # idiotic netscape sends idiotic headers AGAIN
332     my $ims = $self->{h}{"if-modified-since"} =~ /^([^;]+)/
333     ? str2time $1 : 0;
334    
335     if (-d _ && -r _) {
336     # directory
337     if ($path !~ /\/$/) {
338     # create a redirect to get the trailing "/"
339     my $host = $self->server_hostport;
340     $self->err(301, "moved permanently", { Location => "http://$host$self->{uri}/" });
341     } else {
342 root 1.10 $ims < $self->{stat}[9]
343 root 1.1 or $self->err(304, "not modified");
344    
345     if ($self->{method} eq "GET") {
346     if (-r "$path/index.html") {
347     $self->{path} .= "/index.html";
348     $self->handle_file;
349     } else {
350     $self->handle_dir;
351     }
352     }
353     }
354     } elsif (-f _ && -r _) {
355     -x _ and $self->err(403, "forbidden");
356     $self->handle_file;
357     } else {
358     $self->err(404, "not found");
359     }
360     }
361    
362     sub handle_dir {
363     my $self = shift;
364 root 1.10 my $idx = $self->diridx;
365    
366     $self->response(200, "ok",
367     {
368     "Content-Type" => "text/html",
369     "Content-Length" => length $idx,
370     },
371     $idx);
372 root 1.1 }
373    
374     sub handle_file {
375     my $self = shift;
376     my $length = -s _;
377     my $hdr = {
378     "Last-Modified" => time2str ((stat _)[9]),
379     };
380    
381     my @code = (200, "ok");
382     my ($l, $h);
383    
384     if ($self->{h}{range} =~ /^bytes=(.*)$/) {
385     for (split /,/, $1) {
386     if (/^-(\d+)$/) {
387     ($l, $h) = ($length - $1, $length - 1);
388     } elsif (/^(\d+)-(\d*)$/) {
389     ($l, $h) = ($1, ($2 ne "" || $2 >= $length) ? $2 : $length - 1);
390     } else {
391     ($l, $h) = (0, $length - 1);
392     goto ignore;
393     }
394     goto satisfiable if $l >= 0 && $l < $length && $h >= 0 && $h > $l;
395     }
396     $hdr->{"Content-Range"} = "bytes */$length";
397     $self->err(416, "not satisfiable", $hdr);
398    
399     satisfiable:
400 root 1.4 # check for segmented downloads
401 root 1.10 if ($l && $::NO_SEGMENTED) {
402 root 1.4 if (%{$uri{$self->{uri}}} > 1) {
403     $self->slog("segmented download refused\n");
404     $self->err(400, "segmented downloads are not allowed");
405     }
406     }
407    
408 root 1.1 $hdr->{"Content-Range"} = "bytes $l-$h/$length";
409     @code = (206, "partial content");
410     $length = $h - $l + 1;
411    
412     ignore:
413     } else {
414     ($l, $h) = (0, $length - 1);
415     }
416    
417 root 1.9 $self->{path} =~ /\.([^.]+)$/;
418     $hdr->{"Content-Type"} = $mimetype{lc $1} || "application/octet-stream";
419 root 1.1 $hdr->{"Content-Length"} = $length;
420    
421 root 1.4 $self->response(@code, $hdr, "");
422 root 1.1
423     if ($self->{method} eq "GET") {
424     my ($fh, $buf);
425     open $fh, "<", $self->{path}
426     or die "$self->{path}: late open failure ($!)";
427    
428     if ($l) {
429     sysseek $fh, $l, 0
430     or die "$self->{path}: cannot seek to $l ($!)";
431     }
432    
433     $h -= $l - 1;
434    
435     while ($h > 0) {
436 root 1.5 $h -= sysread $fh, $buf, $h > $::BUFSIZE ? $::BUFSIZE : $h;
437     $self->{fh}->syswrite($buf)
438 root 1.1 or last;
439     }
440     }
441    
442     close $fh;
443 root 1.7 }
444    
445 root 1.2 1;