daemon.pm

来自「1. 记录每个帖子的访问人情况」· PM 代码 · 共 822 行 · 第 1/2 页

PM
822
字号
# $Id: Daemon.pm,v 1.25 2001/08/07 19:32:40 gisle Exp $#use strict;package HTTP::Daemon;=head1 NAMEHTTP::Daemon - a simple http server class=head1 SYNOPSIS  use HTTP::Daemon;  use HTTP::Status;  my $d = HTTP::Daemon->new || die;  print "Please contact me at: <URL:", $d->url, ">\n";  while (my $c = $d->accept) {      while (my $r = $c->get_request) {	  if ($r->method eq 'GET' and $r->url->path eq "/xyzzy") {              # remember, this is *not* recommened practice :-)	      $c->send_file_response("/etc/passwd");	  } else {	      $c->send_error(RC_FORBIDDEN)	  }      }      $c->close;      undef($c);  }=head1 DESCRIPTIONInstances of the I<HTTP::Daemon> class are HTTP/1.1 servers thatlisten on a socket for incoming requests. The I<HTTP::Daemon> is asub-class of I<IO::Socket::INET>, so you can perform socket operationsdirectly on it too.The accept() method will return when a connection from a client isavailable.  In a scalar context the returned value will be a referenceto a object of the I<HTTP::Daemon::ClientConn> class which is anotherI<IO::Socket::INET> subclass.  In a list context a two-element arrayis returned containing the new I<HTTP::Daemon::ClientConn> referenceand the peer address; the list will be empty upon failure.  Callingthe get_request() method on the I<HTTP::Daemon::ClientConn> objectwill read data from the client and return an I<HTTP::Request> objectreference.This HTTP daemon does not fork(2) for you.  Your application, i.e. theuser of the I<HTTP::Daemon> is reponsible for forking if that isdesirable.  Also note that the user is responsible for generatingresponses that conform to the HTTP/1.1 protocol.  TheI<HTTP::Daemon::ClientConn> class provides some methods that make this easier.=head1 METHODSThe following is a list of methods that are new (or enhanced) relativeto the I<IO::Socket::INET> base class.=over 4=cutuse vars qw($VERSION @ISA $PROTO $DEBUG);$VERSION = sprintf("%d.%02d", q$Revision: 1.25 $ =~ /(\d+)\.(\d+)/);use IO::Socket qw(AF_INET INADDR_ANY inet_ntoa);@ISA=qw(IO::Socket::INET);$PROTO = "HTTP/1.1";=item $d = new HTTP::DaemonThe constructor takes the same parameters as theI<IO::Socket::INET> constructor.  It can also be called without specifyingany parameters. The daemon will then set up a listen queue of 5connections and allocate some random port number.  A server that wantsto bind to some specific address on the standard HTTP port will beconstructed like this:  $d = new HTTP::Daemon        LocalAddr => 'www.someplace.com',        LocalPort => 80;=cutsub new{    my($class, %args) = @_;    $args{Listen} ||= 5;    $args{Proto}  ||= 'tcp';    return $class->SUPER::new(%args);}=item $c = $d->accept([$pkg])This method is the same as I<IO::Socket::accept> but returns anI<HTTP::Daemon::ClientConn> reference by default.  It returns undef ifyou specify a timeout and no connection is made within that time.  Ina scalar context the returned value will be a reference to a object ofthe I<HTTP::Daemon::ClientConn> class which is anotherI<IO::Socket::INET> subclass.  In a list context a two-element arrayis returned containing the new I<HTTP::Daemon::ClientConn> referenceand the peer address; the list will be empty upon failure.=cutsub accept{    my $self = shift;    my $pkg = shift || "HTTP::Daemon::ClientConn";    my ($sock, $peer) = $self->SUPER::accept($pkg);    if ($sock) {        ${*$sock}{'httpd_daemon'} = $self;        return wantarray ? ($sock, $peer) : $sock;    } else {        return;    }}=item $d->urlReturns a URL string that can be used to access the server root.=cutsub url{    my $self = shift;    my $url = "http://";    my $addr = $self->sockaddr;    if ($addr eq INADDR_ANY) { 	require Sys::Hostname; 	$url .= lc Sys::Hostname::hostname();    }    else {	$url .= gethostbyaddr($addr, AF_INET) || inet_ntoa($addr);    }    my $port = $self->sockport;    $url .= ":$port" if $port != 80;    $url .= "/";    $url;}=item $d->product_tokensReturns the name that this server will use to identify itself.  Thisis the string that is sent with the I<Server> response header.  Themain reason to have this method is that subclasses can override it ifthey want to use another product name.=cutsub product_tokens{    "libwww-perl-daemon/$HTTP::Daemon::VERSION";}package HTTP::Daemon::ClientConn;use vars qw(@ISA $DEBUG);use IO::Socket ();@ISA=qw(IO::Socket::INET);*DEBUG = \$HTTP::Daemon::DEBUG;use HTTP::Request  ();use HTTP::Response ();use HTTP::Status;use HTTP::Date qw(time2str);use LWP::MediaTypes qw(guess_media_type);use Carp ();my $CRLF = "\015\012";   # "\r\n" is not portablemy $HTTP_1_0 = _http_version("HTTP/1.0");my $HTTP_1_1 = _http_version("HTTP/1.1");=backThe I<HTTP::Daemon::ClientConn> is also a I<IO::Socket::INET>subclass. Instances of this class are returned by the accept() methodof I<HTTP::Daemon>.  The following additional methods areprovided:=over 4=item $c->get_request([$headers_only])Read data from the client and turn it into anI<HTTP::Request> object which is then returned.  It returns C<undef>if reading of the request fails.  If it fails, then theI<HTTP::Daemon::ClientConn> object ($c) should be discarded, and youshould not call this method again.  The $c->reason method might giveyou some information about why $c->get_request returned C<undef>.The $c->get_request method supports HTTP/1.1 request content bodies,including I<chunked> transfer encoding with footer and self delimitingI<multipart/*> content types.The $c->get_request method will normally not return until the wholerequest has been received from the client.  This might not be what youwant if the request is an upload of a multi-mega-byte file (and withchunked transfer encoding HTTP can even support infinite requestmessages - uploading live audio for instance).  If you pass a TRUEvalue as the $headers_only argument, then $c->get_request will returnimmediately after parsing the request headers and you are responsiblefor reading the rest of the request content.  If you are going tocall $c->get_request again on the same connection you better read thecorrect number of bytes.=cutsub get_request{    my($self, $only_headers) = @_;    if (${*$self}{'httpd_nomore'}) {        $self->reason("No more requests from this connection");	return;    }    $self->reason("");    my $buf = ${*$self}{'httpd_rbuf'};    $buf = "" unless defined $buf;    my $timeout = $ {*$self}{'io_socket_timeout'};    my $fdset = "";    vec($fdset, $self->fileno, 1) = 1;    local($_);  READ_HEADER:    while (1) {	# loop until we have the whole header in $buf	$buf =~ s/^(?:\015?\012)+//;  # ignore leading blank lines	if ($buf =~ /\012/) {  # potential, has at least one line	    if ($buf =~ /^\w+[^\012]+HTTP\/\d+\.\d+\015?\012/) {		if ($buf =~ /\015?\012\015?\012/) {		    last READ_HEADER;  # we have it		} elsif (length($buf) > 16*1024) {		    $self->send_error(413); # REQUEST_ENTITY_TOO_LARGE		    $self->reason("Very long header");		    return;		}	    } else {		last READ_HEADER;  # HTTP/0.9 client	    }	} elsif (length($buf) > 16*1024) {	    $self->send_error(414); # REQUEST_URI_TOO_LARGE	    $self->reason("Very long first line");	    return;	}	print STDERR "Need more data for complete header\n" if $DEBUG;	return unless $self->_need_more($buf, $timeout, $fdset);    }    if ($buf !~ s/^(\S+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012//) {	${*$self}{'httpd_client_proto'} = _http_version("HTTP/1.0");	$self->send_error(400);  # BAD_REQUEST	$self->reason("Bad request line: $buf");	return;    }    my $method = $1;    my $uri = $2;    my $proto = $3 || "HTTP/0.9";    $uri = "http://$uri" if $method eq "CONNECT";    $uri = $HTTP::URI_CLASS->new($uri, $self->daemon->url);    my $r = HTTP::Request->new($method, $uri);    $r->protocol($proto);    ${*$self}{'httpd_client_proto'} = $proto = _http_version($proto);    if ($proto >= $HTTP_1_0) {	# we expect to find some headers	my($key, $val);      HEADER:	while ($buf =~ s/^([^\012]*)\012//) {	    $_ = $1;	    s/\015$//;	    if (/^([\w\-]+)\s*:\s*(.*)/) {		$r->push_header($key, $val) if $key;		($key, $val) = ($1, $2);	    } elsif (/^\s+(.*)/) {		$val .= " $1";	    } else {		last HEADER;	    }	}	$r->push_header($key, $val) if $key;    }    my $conn = $r->header('Connection');    if ($proto >= $HTTP_1_1) {	${*$self}{'httpd_nomore'}++ if $conn && lc($conn) =~ /\bclose\b/;    } else {	${*$self}{'httpd_nomore'}++ unless $conn &&                                           lc($conn) =~ /\bkeep-alive\b/;    }    if ($only_headers) {	${*$self}{'httpd_rbuf'} = $buf;        return $r;    }    # Find out how much content to read    my $te  = $r->header('Transfer-Encoding');    my $ct  = $r->header('Content-Type');    my $len = $r->header('Content-Length');    if ($te && lc($te) eq 'chunked') {	# Handle chunked transfer encoding	my $body = "";      CHUNK:	while (1) {	    print STDERR "Chunked\n" if $DEBUG;	    if ($buf =~ s/^([^\012]*)\012//) {		my $chunk_head = $1;		unless ($chunk_head =~ /^([0-9A-Fa-f]+)/) {		    $self->send_error(400);		    $self->reason("Bad chunk header $chunk_head");		    return;		}		my $size = hex($1);		last CHUNK if $size == 0;		my $missing = $size - length($buf) + 2; # 2=CRLF at chunk end		# must read until we have a complete chunk		while ($missing > 0) {		    print STDERR "Need $missing more bytes\n" if $DEBUG;		    my $n = $self->_need_more($buf, $timeout, $fdset);		    return unless $n;		    $missing -= $n;		}		$body .= substr($buf, 0, $size);		substr($buf, 0, $size+2) = '';	    } else {		# need more data in order to have a complete chunk header		return unless $self->_need_more($buf, $timeout, $fdset);	    }	}	$r->content($body);	# pretend it was a normal entity body	$r->remove_header('Transfer-Encoding');	$r->header('Content-Length', length($body));	my($key, $val);      FOOTER:	while (1) {	    if ($buf !~ /\012/) {		# need at least one line to look at		return unless $self->_need_more($buf, $timeout, $fdset);	    } else {		$buf =~ s/^([^\012]*)\012//;		$_ = $1;		s/\015$//;		if (/^([\w\-]+)\s*:\s*(.*)/) {		    $r->push_header($key, $val) if $key;		    ($key, $val) = ($1, $2);		} elsif (/^\s+(.*)/) {		    $val .= " $1";		} elsif (!length) {		    last FOOTER;		} else {		    $self->reason("Bad footer syntax");		    return;		}	    }	}	$r->push_header($key, $val) if $key;    } elsif ($te) {	$self->send_error(501); 	# Unknown transfer encoding	$self->reason("Unknown transfer encoding '$te'");	return;    } elsif ($ct && lc($ct) =~ m/^multipart\/\w+\s*;.*boundary\s*=\s*(\w+)/) {	# Handle multipart content type	my $boundary = "$CRLF--$1--$CRLF";	my $index;	while (1) {	    $index = index($buf, $boundary);	    last if $index >= 0;	    # end marker not yet found	    return unless $self->_need_more($buf, $timeout, $fdset);	}	$index += length($boundary);	$r->content(substr($buf, 0, $index));	substr($buf, 0, $index) = '';    } elsif ($len) {	# Plain body specified by "Content-Length"	my $missing = $len - length($buf);	while ($missing > 0) {	    print "Need $missing more bytes of content\n" if $DEBUG;	    my $n = $self->_need_more($buf, $timeout, $fdset);	    return unless $n;	    $missing -= $n;	}	if (length($buf) > $len) {	    $r->content(substr($buf,0,$len));	    substr($buf, 0, $len) = '';	} else {	    $r->content($buf);	    $buf='';	}    }    ${*$self}{'httpd_rbuf'} = $buf;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?