ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Coro/eg/myhttpd
Revision: 1.10
Committed: Wed Apr 11 02:50:01 2007 UTC (17 years, 2 months ago) by root
Branch: MAIN
Changes since 1.9: +1 -0 lines
Log Message:
*** empty log message ***

File Contents

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