home *** CD-ROM | disk | FTP | other *** search
/ CLIX - Fazer Clix Custa Nix / CLIX-CD.cdr / mac / lib / HTTP / Daemon.pm < prev    next >
Encoding:
Perl POD Document  |  1996-12-04  |  13.2 KB  |  583 lines  |  [TEXT/McPL]

  1. # $Id: Daemon.pm,v 1.13 1996/11/13 13:22:21 aas Exp $
  2. #
  3.  
  4. use strict;
  5.  
  6. package HTTP::Daemon;
  7.  
  8. =head1 NAME
  9.  
  10. HTTP::Daemon - a simple http server class
  11.  
  12. =head1 SYNOPSIS
  13.  
  14.   use HTTP::Daemon;
  15.   use HTTP::Status;
  16.  
  17.   $d = new HTTP::Daemon;
  18.   print "Please contact me at: <URL:", $d->url, ">\n";
  19.   while ($c = $d->accept) {
  20.       $r = $c->get_request;
  21.       if ($r) {
  22.       if ($r->method eq 'GET' and $r->url->path eq "/xyzzy") {
  23.               # this is *not* recommened practice
  24.           $c->send_file_response("/etc/passwd");
  25.       } else {
  26.           $c->send_error(RC_FORBIDDEN)
  27.       }
  28.       }
  29.       $c = undef;  # close connection
  30.   }
  31.  
  32. =head1 DESCRIPTION
  33.  
  34. Instances of the I<HTTP::Daemon> class are HTTP/1.1 servers that
  35. listens on a socket for incoming requests. The I<HTTP::Daemon> is a
  36. sub-class of I<IO::Socket::INET>, so you can do socket operations
  37. directly on it.
  38.  
  39. The accept() method will return when a connection from a client is
  40. available. The returned value will be a reference to a object of the
  41. I<HTTP::Daemon::ClientConn> class which is another I<IO::Socket::INET>
  42. subclass. Calling the get_request() method on this object will read
  43. data from the client and return an I<HTTP::Request> object reference.
  44.  
  45. This HTTP daemon does not fork(2) for you.  Your application, i.e. the
  46. user of the I<HTTP::Daemon> is reponsible for forking if that is
  47. desirable.  Also note that the user is responsible for generating
  48. responses that conforms to the HTTP/1.1 protocol.  The
  49. I<HTTP::Daemon::ClientConn> provide some methods that make this easier.
  50.  
  51. =head1 METHODS
  52.  
  53. The following is a list of methods that are new (or enhanced) relative
  54. to the I<IO::Socket::INET> base class.
  55.  
  56. =over 4
  57.  
  58. =cut
  59.  
  60.  
  61. use vars qw($VERSION @ISA $PROTO);
  62.  
  63. $VERSION = sprintf("%d.%02d", q$Revision: 1.13 $ =~ /(\d+)\.(\d+)/);
  64.  
  65. use IO::Socket ();
  66. @ISA=qw(IO::Socket::INET);
  67.  
  68. $PROTO = "HTTP/1.1";
  69.  
  70. =item $d = new HTTP::Daemon
  71.  
  72. The object constructor takes the same parameters as the
  73. I<IO::Socket::INET> constructor.  It can also be called without
  74. specifying any parameters. The daemon will then set up a listen queue
  75. of 5 connections and allocate some random port number.  A server
  76. that want to bind to some specific address on the standard HTTP port
  77. will be constructed like this:
  78.  
  79.   $d = new HTTP::Daemon
  80.         LocalAddr => 'www.someplace.com',
  81.         LocalPort => 80;
  82.  
  83. =cut
  84.  
  85. sub new
  86. {
  87.     my($class, %args) = @_;
  88.     $args{Listen} ||= 5;
  89.     $args{Proto}  ||= 'tcp';
  90.     my $self = $class->SUPER::new(%args);
  91.     return undef unless $self;
  92.  
  93.     my $host = $args{LocalAddr};
  94.     unless ($host) {
  95.     require Sys::Hostname;
  96.     $host = Sys::Hostname::hostname();
  97.     }
  98.     ${*$self}{'httpd_server_name'} = $host;
  99.     $self;
  100. }
  101.  
  102.  
  103. =item $c = $d->accept
  104.  
  105. Same as I<IO::Socket::accept> but will return an
  106. I<HTTP::Daemon::ClientConn> reference.  It will return undef if you
  107. have specified a timeout and no connection is made within that time.
  108.  
  109. =cut
  110.  
  111. sub accept
  112. {
  113.     my $self = shift;
  114.     my $sock = $self->SUPER::accept(@_);
  115.     if ($sock) {
  116.     $sock = bless $sock, "HTTP::Daemon::ClientConn";
  117.     ${*$sock}{'httpd_daemon'} = $self;
  118.     }
  119.     $sock;
  120. }
  121.  
  122.  
  123. =item $d->url
  124.  
  125. Returns a URL string that can be used to access the server root.
  126.  
  127. =cut
  128.  
  129. sub url
  130. {
  131.     my $self = shift;
  132.     my $url = "http://";
  133.     $url .= ${*$self}{'httpd_server_name'};
  134.     my $port = $self->sockport;
  135.     $url .= ":$port" if $port != 80;
  136.     $url .= "/";
  137.     $url;
  138. }
  139.  
  140.  
  141. =item $d->product_tokens
  142.  
  143. Returns the name that this server will use to identify itself.  This
  144. is the string that is sent with the I<Server> response header.
  145.  
  146. =cut
  147.  
  148. sub product_tokens
  149. {
  150.     "libwww-perl-daemon/$HTTP::Daemon::VERSION";
  151. }
  152.  
  153.  
  154. package HTTP::Daemon::ClientConn;
  155.  
  156. use vars '@ISA';
  157. use IO::Socket ();
  158. @ISA=qw(IO::Socket::INET);
  159.  
  160. use HTTP::Request  ();
  161. use HTTP::Response ();
  162. use HTTP::Status;
  163. use HTTP::Date qw(time2str);
  164. use URI::URL qw(url);
  165. use LWP::MediaTypes qw(guess_media_type);
  166. use Carp ();
  167.  
  168. my $CRLF = "\015\012";   # "\r\n" is not portable
  169.  
  170. =back
  171.  
  172. The I<HTTP::Daemon::ClientConn> is also a I<IO::Socket::INET>
  173. subclass. Instances of this class are returned by the accept() method
  174. of the I<HTTP::Daemon>.  The following additional methods are
  175. provided:
  176.  
  177. =over 4
  178.  
  179. =item $c->get_request
  180.  
  181. Will read data from the client and turn it into a I<HTTP::Request>
  182. object which is then returned. Will return undef if reading of the
  183. request failed.  If it fails, then the I<HTTP::Daemon::ClientConn>
  184. object ($c) should be discarded.
  185.  
  186. The $c->get_request method support HTTP/1.1 content bodies, including
  187. I<chunked> transfer encoding with footer and I<multipart/*> types.
  188.  
  189. =cut
  190.  
  191. sub get_request
  192. {
  193.     my $self = shift;
  194.     my $buf = "";
  195.     
  196.     my $timeout = $ {*$self}{'io_socket_timeout'};
  197.     my  $fdset = "";
  198.     vec($fdset, $self->fileno,1) = 1;
  199.  
  200.   READ_HEADER:
  201.     while (1) {
  202.     if ($timeout) {
  203.         return undef unless select($fdset,undef,undef,$timeout);
  204.     }
  205.     my $n = sysread($self, $buf, 1024, length($buf));
  206.     return undef if $n == 0;  # unexpected EOF
  207.     if ($buf =~ /^\w+[^\n]+HTTP\/\d+\.\d+\015?\012/) {
  208.         last READ_HEADER if $buf =~ /(\015?\012){2}/;
  209.     } elsif ($buf =~ /\012/) {
  210.         last READ_HEADER;  # HTTP/0.9 client
  211.     }
  212.     }
  213.     $buf =~ s/^(\w+)\s+(\S+)(?:\s+(HTTP\/\d+\.\d+))?[^\012]*\012//;
  214.     my $proto = $3 || "HTTP/0.9";
  215.     ${*$self}{'httpd_client_proto'} = $proto;
  216.     my $r = HTTP::Request->new($1, url($2, $self->daemon->url));
  217.     $r->protocol($proto);
  218.  
  219.     my($key, $val);
  220.   HEADER:
  221.     while ($buf =~ s/^([^\012]*)\012//) {
  222.     $_ = $1;
  223.     s/\015$//;
  224.     if (/^([\w\-]+)\s*:\s*(.*)/) {
  225.         $r->push_header($key, $val) if $key;
  226.         ($key, $val) = ($1, $2);
  227.     } elsif (/^\s+(.*)/) {
  228.         $val .= " $1";
  229.     } else {
  230.         last HEADER;
  231.     }
  232.     }
  233.     $r->push_header($key, $val) if $key;
  234.  
  235.     my $te  = $r->header('Transfer-Encoding');
  236.     my $ct  = $r->header('Content-Type');
  237.     my $len = $r->header('Content-Length');
  238.  
  239.     if ($te && lc($te) eq 'chunked') {
  240.     # Handle chunked transfer encoding
  241.     my $body = "";
  242.       CHUNK:
  243.     while (1) {
  244.         if ($buf =~ s/^([^\012]*)\012//) {
  245.         my $chunk_head = $1;
  246.         $chunk_head =~ /^([0-9A-Fa-f]+)/;
  247.         return undef unless length($1);
  248.         my $size = hex($1);
  249.         last CHUNK if $size == 0;
  250.  
  251.         my $missing = $size - length($buf);
  252.         $missing += 2; # also read CRLF at chunk end
  253.         $body .= $buf;
  254.         $buf = "";
  255.         # must read rest of chunk and append it to the $body
  256.         while ($missing > 0) {
  257.             if ($timeout) {
  258.             return undef unless select($fdset,undef,undef,$timeout);
  259.             }
  260.             my $n = sysread($self, $body, $missing, length($body));
  261.             return undef if $n == 0;
  262.             $missing -= $n;
  263.         }
  264.         substr($body, -2, 2) = ''; # remove CRLF at end
  265.  
  266.         } else {
  267.         # need more data in order to have a complete chunk header
  268.         if ($timeout) {
  269.             return undef unless select($fdset,undef,undef,$timeout);
  270.         }
  271.         my $n = sysread($self, $buf, 2048, length($buf));
  272.         return undef if $n == 0;
  273.         }
  274.     }
  275.     $r->content($body);
  276.  
  277.     # pretend it was a normal entity body
  278.     $r->remove_header('Transfer-Encoding');
  279.     $r->header('Content-Length', length($body));
  280.  
  281.     my($key, $val);
  282.       FOOTER:
  283.     while (1) {
  284.         if ($buf !~ /\012/) {
  285.         # need at least one line to look at
  286.         if ($timeout) {
  287.             return undef unless select($fdset,undef,undef,$timeout);
  288.         }
  289.         my $n = sysread($self, $buf, 2048, length($buf));
  290.         return undef if $n == 0;
  291.         } else {
  292.         $buf =~ s/^([^\012]*)\012//;
  293.         $_ = $1;
  294.         s/\015$//;
  295.         last FOOTER if length($_) == 0;
  296.  
  297.         if (/^([\w\-]+)\s*:\s*(.*)/) {
  298.             $r->push_header($key, $val) if $key;
  299.             ($key, $val) = ($1, $2);
  300.         } elsif (/^\s+(.*)/) {
  301.             $val .= " $1";
  302.         } else {
  303.             return undef;  # bad syntax
  304.         }
  305.         }
  306.     }
  307.     $r->push_header($key, $val) if $key;
  308.  
  309.     } elsif ($te) {
  310.     # Unknown transfer encoding
  311.     $self->send_error(501);
  312.     return undef;
  313.  
  314.     } elsif ($ct && lc($ct) =~ m/^multipart\/\w+\s*;.*boundary\s*=\s*(\w+)/) {
  315.     # Handle multipart content type
  316.     my $boundary = "$CRLF--$1--$CRLF";
  317.     while (index($buf, $boundary) < 0) {
  318.         # end marker not yet found
  319.         if ($timeout) {
  320.         return undef unless select($fdset,undef,undef,$timeout);
  321.         }
  322.         my $n = sysread($self, $buf, 2048, length($buf));
  323.         return undef if $n == 0;
  324.     }
  325.     $r->content($buf);
  326.  
  327.     } elsif ($len) {
  328.     # Plain body specified by "Content-Length"
  329.  
  330.     $len -= length($buf);
  331.     while ($len > 0) {
  332.         if ($timeout) {
  333.         return undef unless select($fdset,undef,undef,$timeout);
  334.         }
  335.         my $n = sysread($self, $buf, $len, length($buf));
  336.         return undef if $n == 0;
  337.         $len -= $n;
  338.     }
  339.     $r->content($buf);
  340.  
  341.     }
  342.  
  343.     $r;
  344. }
  345.  
  346.  
  347. =item $c->antique_client
  348.  
  349. Returns TRUE if the client speaks the HTTP/0.9 protocol, i.e. no
  350. status code or headers should be returned.
  351.  
  352. =cut
  353.  
  354. sub antique_client
  355. {
  356.     my $self = shift;
  357.     ${*$self}{'httpd_client_proto'} eq 'HTTP/0.9';
  358. }
  359.  
  360.  
  361. =item $c->send_status_line( [$code, [$mess, [$proto]]] )
  362.  
  363. Sends the status line back to the client.
  364.  
  365. =cut
  366.  
  367. sub send_status_line
  368. {
  369.     my($self, $status, $message, $proto) = @_;
  370.     return if $self->antique_client;
  371.     $status  ||= RC_OK;
  372.     $message ||= status_message($status);
  373.     $proto   ||= $HTTP::Daemon::PROTO;
  374.     print $self "$proto $status $message$CRLF";
  375. }
  376.  
  377.  
  378. sub send_crlf
  379. {
  380.     my $self = shift;
  381.     print $self $CRLF;
  382. }
  383.  
  384.  
  385. =item $c->send_basic_header( [$code, [$mess, [$proto]]] )
  386.  
  387. Sends the status line and the "Date:" and "Server:" headers back to
  388. the client.
  389.  
  390. =cut
  391.  
  392. sub send_basic_header
  393. {
  394.     my $self = shift;
  395.     return if $self->antique_client;
  396.     $self->send_status_line(@_);
  397.     print $self "Date: ", time2str(time), $CRLF;
  398.     my $product = $self->daemon->product_tokens;
  399.     print $self "Server: $product$CRLF" if $product;
  400. }
  401.  
  402.  
  403. =item $c->send_response( [$res] )
  404.  
  405. Takes a I<HTTP::Response> object as parameter and send it back to the
  406. client as the response.
  407.  
  408. =cut
  409.  
  410. sub send_response
  411. {
  412.     my $self = shift;
  413.     my $res = shift;
  414.     if (!ref $res) {
  415.     $res ||= RC_OK;
  416.     $res = HTTP::Response->new($res, @_);
  417.     }
  418.     unless ($self->antique_client) {
  419.     $self->send_basic_header($res->code, $res->message, $res->protocol);
  420.     print $self $res->headers_as_string($CRLF);
  421.     print $self $CRLF;  # separates headers and content
  422.     }
  423.     print $self $res->content;
  424. }
  425.  
  426.  
  427. =item $c->send_redirect( $loc, [$code, [$entity_body]] )
  428.  
  429. Sends a redirect response back to the client.  The location ($loc) can
  430. be an absolute or a relative URL. The $code must be one the redirect
  431. status codes, and it defaults to "301 Moved Permanently"
  432.  
  433. =cut
  434.  
  435. sub send_redirect
  436. {
  437.     my($self, $loc, $status, $content) = @_;
  438.     $status ||= RC_MOVED_PERMANENTLY;
  439.     Carp::croak("Status '$status' is not redirect") unless is_redirect($status);
  440.     $self->send_basic_header($status);
  441.     $loc = url($loc, $self->daemon->url) unless ref($loc);
  442.     $loc = $loc->abs;
  443.     print $self "Location: $loc$CRLF";
  444.     if ($content) {
  445.     my $ct = $content =~ /^\s*</ ? "text/html" : "text/plain";
  446.     print $self "Content-Type: $ct$CRLF";
  447.     }
  448.     print $self $CRLF;
  449.     print $self $content if $content;
  450. }
  451.  
  452.  
  453. =item $c->send_error( [$code, [$error_message]] )
  454.  
  455. Send an error response back to the client.  If the $code is missing a
  456. "Bad Request" error is reported.  The $error_message is a string that
  457. is incorporated in the body of the HTML entity body.
  458.  
  459. =cut
  460.  
  461. sub send_error
  462. {
  463.     my($self, $status, $error) = @_;
  464.     $status ||= RC_BAD_REQUEST;
  465.     Carp::croak("Status '$status' is not an error") unless is_error($status);
  466.     my $mess = status_message($status);
  467.     $error  ||= "";
  468.     unless ($self->antique_client) {
  469.         $self->send_basic_header($status);
  470.         print $self "Content-Type: text/html$CRLF";
  471.         print $self $CRLF;
  472.     }
  473.     print $self <<EOT;
  474. <title>$status $mess</title>
  475. <h1>$status $mess</h1>
  476. $error
  477. EOT
  478.     $status;
  479. }
  480.  
  481.  
  482. =item $c->send_file_response($filename)
  483.  
  484. Send back a response with the specified $filename as content.  If the
  485. file happen to be a directory we will generate a HTML index for it.
  486.  
  487. =cut
  488.  
  489. sub send_file_response
  490. {
  491.     my($self, $file) = @_;
  492.     if (-d $file) {
  493.     $self->send_dir($file);
  494.     } elsif (-f _) {
  495.     # plain file
  496.     local(*F);
  497.     sysopen(F, $file, 0) or 
  498.       return $self->send_error(RC_FORBIDDEN);
  499.     my($ct,$ce) = guess_media_type($file);
  500.     my($size,$mtime) = (stat _)[7,9];
  501.     unless ($self->antique_client) {
  502.         $self->send_basic_header;
  503.         print $self "Content-Type: $ct$CRLF";
  504.         print $self "Content-Encoding: $ce$CRLF" if $ce;
  505.         print $self "Content-Length: $size$CRLF";
  506.         print $self "Last-Modified: ", time2str($mtime), "$CRLF";
  507.         print $self $CRLF;
  508.     }
  509.     $self->send_file(\*F);
  510.     return RC_OK;
  511.     } else {
  512.     $self->send_error(RC_NOT_FOUND);
  513.     }
  514. }
  515.  
  516.  
  517. sub send_dir
  518. {
  519.     my($self, $dir) = @_;
  520.     $self->send_error(RC_NOT_FOUND) unless -d $dir;
  521.     $self->send_error(RC_NOT_IMPLEMENTED);
  522. }
  523.  
  524.  
  525. =item $c->send_file($fd);
  526.  
  527. Copies the file back to the client.  The file can be a string (which
  528. will be interpreted as a filename) or a reference to a glob.
  529.  
  530. =cut
  531.  
  532. sub send_file
  533. {
  534.     my($self, $file) = @_;
  535.     my $opened = 0;
  536.     if (!ref($file)) {
  537.     local(*F);
  538.     open(F, $file) || return undef;
  539.     $file = \*F;
  540.     $opened++;
  541.     }
  542.     my $cnt = 0;
  543.     my $buf = "";
  544.     my $n;
  545.     while ($n = sysread($file, $buf, 8*1024)) {
  546.     last if $n <= 0;
  547.     $cnt += $n;
  548.     print $self $buf;
  549.     }
  550.     close($file) if $opened;
  551.     $cnt;
  552. }
  553.  
  554.  
  555. =item $c->daemon
  556.  
  557. Return a reference to the corresponding I<HTTP::Daemon> object.
  558.  
  559. =cut
  560.  
  561. sub daemon
  562. {
  563.     my $self = shift;
  564.     ${*$self}{'httpd_daemon'};
  565. }
  566.  
  567. =back
  568.  
  569. =head1 SEE ALSO
  570.  
  571. L<IO::Socket>, L<Apache>
  572.  
  573. =head1 COPYRIGHT
  574.  
  575. Copyright 1996, Gisle Aas
  576.  
  577. This library is free software; you can redistribute it and/or
  578. modify it under the same terms as Perl itself.
  579.  
  580. =cut
  581.  
  582. 1;
  583.