ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Coro/eg/myhttpd
Revision: 1.1
Committed: Thu Aug 9 02:57:54 2001 UTC (22 years, 10 months ago) by root
Branch: MAIN
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/usr/bin/perl
2    
3     use Coro;
4     use Coro::Semaphore;
5     use Coro::Event;
6     use Coro::Socket;
7    
8     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     $MAX_CONNECTS = 500; # maximum simult. connects
16     $REQ_TIMEOUT = 60; # request timeout
17     $RES_TIMEOUT = 180; # response timeout
18     $MAX_POOL = 20; # max. number of idle workers
19     $DOCROOT = "/usr/www/htdocs"; # document root
20     $INDEXPROG = "/usr/www/bin/dols"; # indexing program (nph-cgi script)
21     $SERVER_HOST = "0.0.0.0"; # host to bind on
22     $SERVER_PORT = 80; # port to listen on
23    
24     my $port = new Coro::Socket
25     LocalAddr => $SERVER_HOST,
26     LocalPort => $SERVER_PORT,
27     ReuseAddr => 1,
28     Listen => 1,
29     or die "unable to start server";
30    
31     $SIG{PIPE} = 'IGNORE';
32    
33     sub slog {
34     my $level = shift;
35     my $format = shift;
36     #printf "---: $format\n", @_;
37     }
38    
39     my $connections = new Coro::Semaphore $MAX_CONNECTS;
40    
41     my @fh;
42     my @pool;
43    
44     sub handler {
45     while () {
46     my $fh = pop @fh;
47     if ($fh) {
48     eval {
49     conn->new($fh)->handle;
50     };
51     close $fh;
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     async {
63     slog 1, "accepting connections";
64     while () {
65     $connections->down;
66     push @fh, $port->accept;
67     #slog 3, "accepted @$connections ".scalar(@pool);
68     if (@pool) {
69     (pop @pool)->ready;
70     } else {
71     async \&handler;
72     }
73    
74     }
75     };
76    
77     loop;
78     print "ende\n";#d#
79    
80     package conn;
81    
82     use Socket;
83     use HTTP::Date;
84    
85     sub new {
86     my $class = shift;
87     my $fh = shift;
88     my (undef, $iaddr) = unpack_sockaddr_in $fh->getpeername
89     or $self->err(500, "unable to get peername");
90     $self->{remote_address} = inet_ntoa $iaddr;
91     bless { fh => $fh }, $class;
92     }
93    
94     sub slog {
95     main::slog(@_);
96     }
97    
98     sub print_response {
99     my ($self, $code, $msg, $hdr, $content) = @_;
100     my $res = "HTTP/1.0 $code $msg\015\012";
101    
102     $hdr->{Date} = time2str time; # slow? nah.
103    
104     while (my ($h, $v) = each %$hdr) {
105     $res .= "$h: $v\015\012"
106     }
107     $res .= "\015\012$content" if defined $content;
108    
109     print {$self->{fh}} $res;
110     }
111    
112     sub err {
113     my $self = shift;
114     my ($code, $msg, $hdr, $content) = @_;
115    
116     unless (defined $content) {
117     $content = "$code $msg";
118     $hdr->{"Content-Type"} = "text/plain";
119     $hdr->{"Content-Length"} = length $content;
120     }
121    
122     $self->slog($msg) if $code;
123    
124     $self->print_response($code, $msg, $hdr, $content);
125    
126     die bless {}, err::;
127     }
128    
129     sub handle {
130     my $self = shift;
131     my $fh = $self->{fh};
132    
133     #while() {
134     $self->{h} = {};
135    
136     # read request and parse first line
137     $fh->timeout($::REQ_TIMEOUT);
138     my $req = $fh->readline("\015\012\015\012");
139     $fh->timeout($::RES_TIMEOUT);
140    
141     defined $req or
142     $self->err(408, "request timeout");
143    
144     $req =~ /^(?:\015\012)?
145     (GET|HEAD) \040+
146     ([^\040]+) \040+
147     HTTP\/([0-9]+\.[0-9]+)
148     \015\012/gx
149     or $self->err(403, "method not allowed", { Allow => "GET,HEAD" });
150    
151     $2 ne "1.0"
152     or $self->err(506, "http protocol version not supported");
153    
154     $self->{method} = $1;
155     $self->{uri} = $2;
156    
157     # parse headers
158     {
159     my (%hdr, $h, $v);
160    
161     $hdr{lc $1} .= ",$2"
162     while $req =~ /\G
163     ([^:\000-\040]+):
164     [\008\040]*
165     ((?: [^\015\012]+ | \015\012[\008\040] )*)
166     \015\012
167     /gxc;
168    
169     $req =~ /\G\015\012$/
170     or $self->err(400, "bad request");
171    
172     $self->{h}{$h} = substr $v, 1
173     while ($h, $v) = each %hdr;
174     }
175    
176     $self->{server_port} = $self->{h}{host} =~ s/:([0-9]+)$// ? $1 : 80;
177    
178     $self->map_uri;
179     $self->respond;
180     #}
181     }
182    
183     # uri => path mapping
184     sub map_uri {
185     my $self = shift;
186     my $host = $self->{h}{host} || "default";
187     my $uri = $self->{uri};
188    
189     # some massaging, also makes it more secure
190     $uri =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr hex $1/ge;
191     $uri =~ s%//+%/%g;
192     $uri =~ s%/\.(?=/|$)%%g;
193     1 while $uri =~ s%/[^/]+/\.\.(?=/|$)%%;
194    
195     $uri =~ m%^/?\.\.(?=/|$)%
196     and $self->err(400, "bad request");
197    
198     $self->{name} = $uri;
199    
200     # now do the path mapping
201     $self->{path} = "$::DOCROOT/$host$uri";
202     }
203    
204     sub server_address {
205     my $self = shift;
206     my ($port, $iaddr) = unpack_sockaddr_in $self->{fh}->getsockname
207     or $self->err(500, "unable to get socket name");
208     ((inet_ntoa $iaddr), $port);
209     }
210    
211     sub server_host {
212     my $self = shift;
213     if (exists $self->{h}{host}) {
214     return $self->{h}{host};
215     } else {
216     return (($self->server_address)[0]);
217     }
218     }
219    
220     sub server_hostport {
221     my $self = shift;
222     my ($host, $port);
223     if (exists $self->{h}{host}) {
224     ($host, $port) = ($self->{h}{host}, $self->{server_port});
225     } else {
226     ($host, $port) = $self->server_address;
227     }
228     $port = $port == 80 ? "" : ":$port";
229     $host.$port;
230     }
231    
232     sub _cgi {
233     my $self = shift;
234     my $path = shift;
235     my $fh;
236    
237     # no two-way xxx supported
238     if (0 == fork) {
239     open STDOUT, ">&".fileno($self->{fh});
240     if (chdir $::DOCROOT) {
241     $ENV{SERVER_SOFTWARE} = "thttpd-myhttpd"; # we are thttpd-alike
242     $ENV{HTTP_HOST} = $self->server_host;
243     $ENV{HTTP_PORT} = $self->{server_host};
244     $ENV{SCRIPT_NAME} = $self->{name};
245     exec $::INDEXPROG;
246     }
247     Coro::State::_exit(0);
248     } else {
249     }
250     }
251    
252     sub respond {
253     my $self = shift;
254     my $path = $self->{path};
255    
256     stat $path
257     or $self->err(404, "not found");
258    
259     # idiotic netscape sends idiotic headers AGAIN
260     my $ims = $self->{h}{"if-modified-since"} =~ /^([^;]+)/
261     ? str2time $1 : 0;
262    
263     if (-d _ && -r _) {
264     # directory
265     if ($path !~ /\/$/) {
266     # create a redirect to get the trailing "/"
267     my $host = $self->server_hostport;
268     $self->err(301, "moved permanently", { Location => "http://$host$self->{uri}/" });
269     } else {
270     $ims < (stat _)[9]
271     or $self->err(304, "not modified");
272    
273     if ($self->{method} eq "GET") {
274     if (-r "$path/index.html") {
275     $self->{path} .= "/index.html";
276     $self->handle_file;
277     } else {
278     $self->handle_dir;
279     }
280     }
281     }
282     } elsif (-f _ && -r _) {
283     -x _ and $self->err(403, "forbidden");
284     $self->handle_file;
285     } else {
286     $self->err(404, "not found");
287     }
288     }
289    
290     sub handle_dir {
291     my $self = shift;
292     $self->_cgi($::INDEXPROG);
293     }
294    
295     sub handle_file {
296     my $self = shift;
297     my $length = -s _;
298     my $hdr = {
299     "Last-Modified" => time2str ((stat _)[9]),
300     };
301    
302     my @code = (200, "ok");
303     my ($l, $h);
304    
305     if ($self->{h}{range} =~ /^bytes=(.*)$/) {
306     for (split /,/, $1) {
307     if (/^-(\d+)$/) {
308     ($l, $h) = ($length - $1, $length - 1);
309     } elsif (/^(\d+)-(\d*)$/) {
310     ($l, $h) = ($1, ($2 ne "" || $2 >= $length) ? $2 : $length - 1);
311     } else {
312     ($l, $h) = (0, $length - 1);
313     goto ignore;
314     }
315     goto satisfiable if $l >= 0 && $l < $length && $h >= 0 && $h > $l;
316     }
317     $hdr->{"Content-Range"} = "bytes */$length";
318     $self->err(416, "not satisfiable", $hdr);
319    
320     satisfiable:
321     $hdr->{"Content-Range"} = "bytes $l-$h/$length";
322     @code = (206, "partial content");
323     $length = $h - $l + 1;
324    
325     ignore:
326     } else {
327     ($l, $h) = (0, $length - 1);
328     }
329    
330     if ($self->{path} =~ /\.html$/) {
331     $hdr->{"Content-Type"} = "text/html";
332     } else {
333     $hdr->{"Content-Type"} = "application/octet-stream";
334     }
335    
336     $hdr->{"Content-Length"} = $length;
337    
338     $self->print_response(@code, $hdr, "");
339    
340     if ($self->{method} eq "GET") {
341     my ($fh, $buf);
342     open $fh, "<", $self->{path}
343     or die "$self->{path}: late open failure ($!)";
344    
345     if ($l) {
346     sysseek $fh, $l, 0
347     or die "$self->{path}: cannot seek to $l ($!)";
348     }
349    
350     $h -= $l - 1;
351    
352     while ($h > 0) {
353     $h -= sysread $fh, $buf, $h > 4096 ? 4096 : $h;
354     print {$self->{fh}} $buf
355     or last;
356     }
357     }
358    
359     close $fh;
360     }