HTTPサーバを作る
ふつうのLinuxプログラミングもやっと16章。HTTPサーバを作るってところまで来ました。
17章になるともうちょっと複雑になるようなので、そのまえにPerlでも同じコードを書いてみた。
なにごともやってみることが大事、ということで。
ふつうのLinuxプログラミング
-
- -
http://takatoshi.dyndns.org/trac/tktsh/browser/LinuxPrograming/trunk/httpd.pl?rev=15
#!/usr/bin/perl use strict; use warnings; use sigtrap qw( die PIPE ); use File::Spec; use FileHandle; use POSIX; use Data::Dumper; use constant { HTTP_MINOR_VERSION => 0, SERVER_NAME => 'Unkoupd', SERVER_VERSION => '0.01', MAX_REQUEST_BODY_LENGTH => 1024 * 1024, }; # 困ったこと # 1. データ構造(構造体に当たるもの)を明示的に定義できない # 2. 入力を一気に読んでる main(@ARGV); sub main { my @args = @_; @args or die "Usage: $0 <docroot>?n"; service(*STDIN, *STDOUT, $args[0]); } sub log_exit { @_ = map {$_ ? $_ : ''} @_; print STDERR join(q{}, @_)."?n"; exit(1); } sub service { my ($in, $out, $docroot) = @_; my $request = read_request($in); respond_to($request, $out, $docroot); } # 複数行のヘッダーフィールドには対応しない sub read_request { my $in = shift; my $request = {}; my @reqs = <$in>; read_request_line($request, shift @reqs); while (my $h = read_header_field(shift @reqs)) { push @{$request->{headers}}, $h; } $request->{length} = content_length($request); if ($request->{length} != 0) { if ($request->{length} > MAX_REQUEST_BODY_LENGTH) { die 'request body too long'; } $request->{body} = join q{}, @reqs; } else { undef $request->{body}; } #print Dumper($request); return $request; } sub read_request_line { my ($r, $line) = @_; $line or die "no request line"; if ($line =~ /^(?w+)?s+([^?s]+)?s+HTTP?/1?.(?d)$/) { $r->{method} = uc($1); $r->{path} = $2; $r->{protocol_minor_version} = $3; } else { die "parse error on request line: $line"; } } sub read_header_field { my $line = shift; $line or return; my $header; if ($line =~ /^([^:]+):?s*(.+)$/) { $header->{name} = $1; $header->{value} = $2; } return $header; } sub content_length { my $r = shift; my $length = lookup_header_field_value($r, 'Content-Length'); $length or return 0; $length < 0 and die 'negative Content-Length value'; return $length; } sub lookup_header_field_value { my ($r, $name) = @_; foreach my $header (@{$r->{headers}}) { if (lc($header->{name}) eq lc($name)) { return $header->{value}; } } return; } sub respond_to { my ($r, $out, $docroot) = @_; if ($r->{method} eq 'GET') { do_file_response(@_); } elsif ($r->{method} eq 'HEAD') { do_file_response(@_); } elsif ($r->{method} eq 'POST') { method_not_allowed($r, $out); } else { not_implemented($r, $out); } } sub do_file_response { my ($r, $out, $docroot) = @_; my $info = get_fileinfo($docroot, $r->{path}); if (!$info->{ok}) { not_found($r, $out); return; } output_common_header_fields($r, $out, '200 OK'); printf $out "Content-Length: %ld?r?n", $info->{size}; printf $out "Content-Type: %s?r?n", guess_content_type($info); printf $out "?r?n"; if ($r->{method} ne 'HEAD') { my $fh = FileHandle->new("< $info->{path}") or die "failed to open $info->{path}: $!"; my $data = do{local $/; <$fh>}; print $out $data; $fh->close; } } sub get_fileinfo { my ($docroot, $urlpath) = @_; my $info = {}; $info->{path} = File::Spec->catfile($docroot, $urlpath); $info->{ok} = 0; -f $info->{path} or return $info; $info->{ok} = 1; $info->{size} = -s $info->{path}; return $info; } sub output_common_header_fields { my ($r, $out, $status) = @_; # 'Tue, 10 Apr 2007 17:03:25 GMT' format POSIX::setlocale(LC_TIME, 'en_US'); my $date = POSIX::strftime('%a, %d %b %Y %H:%M:%S GMT', gmtime(time)); printf $out "HTTP/1.%d %s?r?n", HTTP_MINOR_VERSION, $status; printf $out "Date: %s?r?n", $date; printf $out "Server: %s/%s?r?n", SERVER_NAME, SERVER_VERSION; printf $out "Connection: close?r?n"; } sub guess_content_type { my $info = shift; return 'text/plain'; } sub not_found { my ($r, $out) = @_; output_common_header_fields($r, $out, '404 Not Found'); } sub method_not_allowed { my ($r, $out) = @_; output_common_header_fields($r, $out, '405 Method Not Allowed'); } sub not_implemented { my ($r, $out) = @_; output_common_header_fields($r, $out, '501 Not Implemented'); } 1; __END__