[PATCH] testing-friendly HTTP::Daemon2, capable of HTTP-over-two-pipes

[email protected] (Ivan Shmakov) Thu, 16 Sep 2010 21:32:57 +0700
Newsgroups perl.libwww
Message-ID <[email protected]>
	In my quest for the more bizarre applications of LWP, I've
	finally got the idea of using a pair of IO handles instead of a
	socket along with the code.  Possible uses for such a feature
	include: testing, uncommon transport protocols support, using
	HTTP for parent-child IPC over a pair of pipe ()'s, and
	bypassing firewalls via the following (or similar) combo:

$ LWP-APPLICATION --proxy-command='ssh GATEWAY nc6 %s %p' 

	(See also: ProxyCommand option to ssh(1), ssh_config(5),
	--plugin=COMMAND option to fetchmail(1), etc.)

	In a separate MIME part, I've placed a simplistic example on how
	to use the Net::HTTP::Methods package with two filehandles for
	IO.

	Getting HTTP::Daemon to work in such a fashion required somewhat
	more effort (see the example in the second MIME part.)  In
	particular, it was necessary to split but one of the ClientConn
	methods into a separate ClientConn::Methods package, and to
	replace all the print () and a singular sysread () calls by the
	corresponding methods calls, $X->print () and $X->sysread ().
	All these changes are in the third MIME part.

	Example session:

X $ perl http-daemon-pair-2010-09-16.pl 
> GET /xyzzy HTTP/1.1
> Connection: close
> Host: xxx:8080
> 
< HTTP/1.1 404 Not Found
< Date: Thu, 16 Sep 2010 14:29:35 GMT
< Server: libwww-perl-daemon/5.836
< Content-Type: text/html
< Content-Length: 53
< 
< <title>404 Not Found</title>
< <h1>404 Not Found</h1>
< 
< XXX: No more requests from this connection
X $ 

	Now I wonder, is there anything I can do for such a patch and a
	module (I guess, XXX from net-http-pair-2010-09-16.pl has to be
	put into its own separate Perl module) to be accepted into LWP?

	TIA.

-- 
FSF associate member #7257.  SFD in Barnaul: http://sfd.am-1.org/
net-http-pair-2010-09-16.pl (text/x-perl, 1 KB)
### net-http-pair-2010-09-16.pl --- Net::HTTP over an IO-pair  -*- Perl -*-

use strict;
use warnings;

require IO::Handle;
require Net::HTTP::Methods;

package XXX;

use strict;
use vars qw($VERSION @ISA);
use warnings;

@ISA = qw(Net::HTTP::Methods);
sub new {
    my ($class, $input, $output, @args) = @_;
    my $self
	= $class->SUPER::new ("Host" => "localhost", @args)
	or return undef;
    ${*$self}{"XXX.input"} = $input;
    ${*$self}{"XXX.output"} = $output;
    ## .
    return $self;
}

sub print {
    my $self = shift;
    ${*$self}{"XXX.output"}->print (@_);
}

sub sysread {
    my $self = shift;
    ${*$self}{"XXX.input"}->sysread (@_);
}

sub http_connect {
    1;
}

package main;

{
    my $o
	= \*STDOUT;
    $o->print ("Hello, world!\n");
}

my $f
    = new XXX (\*STDIN, \*STDOUT)
    or die ($!);

$f->print ("Hello, world!\n");

$f->http_configure ({ "Host" => "xxx:8080" });
$f->write_request ("GET", "http://xxx:8080/hello", my %h);
my @a = $f->read_response_headers ();

print (join (", ", @a), "\n");
### net-http-pair-2010-09-16.pl ends here
http-daemon-pair-2010-09-16.pl (text/x-perl, 576 B)
### http-daemon-pair-2010-09-16.pl --- HTTP::Daemon2 / IO-pair  -*- Perl -*-

use strict;
use warnings;

use HTTP::Status;
use IO::Handle;

require HTTP::Daemon2;

my $s = new HTTP::Daemon2 ();
my $c = $s->new_conn (\*STDIN, \*STDOUT);

## basically, a copy of the example in HTTP::Daemon(3pm)
while (my $r = $c->get_request) {
    if ($r->method eq "GET" and $r->uri->path eq "/xyzzy") {
	$c->send_file_response("/dev/null");
    } else {
	$c->send_error(RC_FORBIDDEN);
    }
}
$c->close;
print STDERR "XXX: ", $c->reason, "\n";

### http-daemon-pair-2010-09-16.pl ends here
Daemon2.pm (text/x-diff, 2 KB)
### Daemon2.pm --- HTTP::Daemon over two IO handles  -*- Perl -*-

## This module is a hacked version of HTTP::Daemon, and thus inherits
## the license of the latter, which is as follows.

## This library is free software; you can redistribute it and/or modify
## it under the same terms as Perl itself.

package HTTP::Daemon2;

use strict;
use vars qw($VERSION @ISA $PROTO $DEBUG);

require HTTP::Daemon;

$VERSION = "5.836";

@ISA = qw ();

$PROTO = "HTTP/1.1";


sub new
{
    my($class, %args) = @_;
    require Symbol;
    my $self
	= bless Symbol::gensym (), $class;
    return $self;
}


sub new_conn
{
    my $self = shift;
    my $input = shift;
    my $output = shift;
    my $pkg = shift || "HTTP::Daemon::ClientConn2";
    require Symbol;
    my $conn
	= bless Symbol::gensym (), $pkg;
    ${*$conn}{'httpd_daemon'} = $self;
    ${*$conn}{"ClientConn2.input"} = $input;
    ${*$conn}{"ClientConn2.output"} = $output;
    ## .
    return
	(wantarray ? ($conn, undef) : $conn);
}


sub url
{
    my $self = shift;
    my $url
	= ($self->_default_scheme
	   . "://localhost/");
    ## .
    $url;
}


sub _default_port {
    80;
}


sub _default_scheme {
    "http";
}


sub product_tokens
{
    "libwww-perl-daemon/$HTTP::Daemon2::VERSION";
}



package HTTP::Daemon::ClientConn2;

use vars qw (@ISA);
@ISA = qw (HTTP::Daemon::ClientConn::Methods);

sub close
{
    my ($self) = @_;
    return
	unless ${*$self}{"ClientConn2.reallyClose"};
    ${*$self}{"ClientConn2.input"}->close ();
    ${*$self}{"ClientConn2.output"}->close ();
}

sub fdset
{
    my ($self) = @_;
    my $fdset = "";
    vec ($fdset, ${*$self}{"ClientConn2.input"}->fileno,  1) = 1;
    vec ($fdset, ${*$self}{"ClientConn2.output"}->fileno, 1) = 1;
    return $fdset;
}

sub print {
    # print STDERR ("print: ", join (", ", @_), "\n");
    my $self = shift;
    ${*$self}{"ClientConn2.output"}->print (@_);
}

sub sysread {
    # print STDERR ("sysread: ", join (", ", @_), "\n");
    my $self = shift;
    ${*$self}{"ClientConn2.input"}->sysread (@_);
}

### Daemon2.pm ends here
Daemon.pm.diff (text/x-diff, 5 KB)
diff --git a/lib/HTTP/Daemon.pm b/lib/HTTP/Daemon.pm
index cf61147..271ee55 100644
--- a/lib/HTTP/Daemon.pm
+++ b/lib/HTTP/Daemon.pm
@@ -73,9 +73,25 @@ sub product_tokens
 
 package HTTP::Daemon::ClientConn;
 
-use vars qw(@ISA $DEBUG);
+use vars qw(@ISA);
 use IO::Socket ();
-@ISA=qw(IO::Socket::INET);
+@ISA=qw(IO::Socket::INET HTTP::Daemon::ClientConn::Methods);
+
+
+sub fdset
+{
+    my ($self) = @_;
+    my $fdset = "";
+    vec ($fdset, $self->fileno, 1) = 1;
+    return $fdset;
+}
+
+
+
+package HTTP::Daemon::ClientConn::Methods;
+
+use vars qw(@ISA $DEBUG);
+@ISA = ();
 *DEBUG = \$HTTP::Daemon::DEBUG;
 
 use HTTP::Request  ();
@@ -103,8 +119,8 @@ sub get_request
     $buf = "" unless defined $buf;
 
     my $timeout = $ {*$self}{'io_socket_timeout'};
-    my $fdset = "";
-    vec($fdset, $self->fileno, 1) = 1;
+    my $fdset
+        = $self->fdset ();
     local($_);
 
   READ_HEADER:
@@ -331,7 +347,7 @@ sub _need_more
 	}
     }
     print STDERR "sysread()\n" if $DEBUG;
-    my $n = sysread($self, $_[0], 2048, length($_[0]));
+    my $n = $self->sysread ($_[0], 2048, length($_[0]));
     $self->reason(defined($n) ? "Client closed" : "sysread: $!") unless $n;
     $n;
 }
@@ -401,14 +417,14 @@ sub send_status_line
     $status  ||= RC_OK;
     $message ||= status_message($status) || "";
     $proto   ||= $HTTP::Daemon::PROTO || "HTTP/1.1";
-    print $self "$proto $status $message$CRLF";
+    $self->print ("$proto $status $message$CRLF");
 }
 
 
 sub send_crlf
 {
     my $self = shift;
-    print $self $CRLF;
+    $self->print ($CRLF);
 }
 
 
@@ -417,9 +433,9 @@ sub send_basic_header
     my $self = shift;
     return if $self->antique_client;
     $self->send_status_line(@_);
-    print $self "Date: ", time2str(time), $CRLF;
+    $self->print ("Date: ", time2str(time), $CRLF);
     my $product = $self->daemon->product_tokens;
-    print $self "Server: $product$CRLF" if $product;
+    $self->print ("Server: $product$CRLF") if $product;
 }
 
 
@@ -429,7 +445,7 @@ sub send_header
     while (@_) {
 	my($k, $v) = splice(@_, 0, 2);
 	$v = "" unless defined($v);
-	print $self "$k: $v$CRLF";
+	$self->print ("$k: $v$CRLF");
     }
 }
 
@@ -471,8 +487,8 @@ sub send_response
 	    $self->force_last_request;
             $res->header('connection','close'); 
 	}
-	print $self $res->headers_as_string($CRLF);
-	print $self $CRLF;  # separates headers and content
+	$self->print ($res->headers_as_string($CRLF));
+	$self->print ($CRLF);	# separates headers and content
     }
     if ($self->head_request) {
 	# no content
@@ -485,13 +501,13 @@ sub send_response
 		printf $self "%x%s%s%s", length($chunk), $CRLF, $chunk, $CRLF;
 	    }
 	    else {
-		print $self $chunk;
+		$self->print ($chunk);
 	    }
 	}
-	print $self "0$CRLF$CRLF" if $chunked;  # no trailers either
+	$self->print ("0$CRLF$CRLF") if $chunked;  # no trailers either
     }
     elsif (length $content) {
-	print $self $content;
+	$self->print ($content);
     }
 }
 
@@ -505,13 +521,13 @@ sub send_redirect
     my $base = $self->daemon->url;
     $loc = $HTTP::URI_CLASS->new($loc, $base) unless ref($loc);
     $loc = $loc->abs($base);
-    print $self "Location: $loc$CRLF";
+    $self->print ("Location: $loc$CRLF");
     if ($content) {
 	my $ct = $content =~ /^\s*</ ? "text/html" : "text/plain";
-	print $self "Content-Type: $ct$CRLF";
+	$self->print ("Content-Type: $ct$CRLF");
     }
-    print $self $CRLF;
-    print $self $content if $content && !$self->head_request;
+    $self->print ($CRLF);
+    $self->print ($content) if $content && !$self->head_request;
     $self->force_last_request;  # no use keeping the connection open
 }
 
@@ -530,11 +546,11 @@ $error
 EOT
     unless ($self->antique_client) {
         $self->send_basic_header($status);
-        print $self "Content-Type: text/html$CRLF";
-	print $self "Content-Length: " . length($mess) . $CRLF;
-        print $self $CRLF;
+        $self->print ("Content-Type: text/html$CRLF");
+	$self->print ("Content-Length: " . length($mess) . $CRLF);
+        $self->print ($CRLF);
     }
-    print $self $mess unless $self->head_request;
+    $self->print ($mess) unless $self->head_request;
     $status;
 }
 
@@ -555,11 +571,11 @@ sub send_file_response
 	my($size,$mtime) = (stat _)[7,9];
 	unless ($self->antique_client) {
 	    $self->send_basic_header;
-	    print $self "Content-Type: $ct$CRLF";
-	    print $self "Content-Encoding: $ce$CRLF" if $ce;
-	    print $self "Content-Length: $size$CRLF" if $size;
-	    print $self "Last-Modified: ", time2str($mtime), "$CRLF" if $mtime;
-	    print $self $CRLF;
+	    $self->print ("Content-Type: $ct$CRLF");
+	    $self->print ("Content-Encoding: $ce$CRLF") if $ce;
+	    $self->print ("Content-Length: $size$CRLF") if $size;
+	    $self->print ("Last-Modified: ", time2str($mtime), "$CRLF") if $mtime;
+	    $self->print ($CRLF);
 	}
 	$self->send_file(\*F) unless $self->head_request;
 	return RC_OK;
@@ -595,7 +611,7 @@ sub send_file
     while ($n = sysread($file, $buf, 8*1024)) {
 	last if !$n;
 	$cnt += $n;
-	print $self $buf;
+	$self->print ($buf);
     }
     close($file) if $opened;
     $cnt;
signature.asc (application/pgp-signature, 196 B)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEARECAAYFAkySKpsACgkQ+MvqjYjLOAy2XgCeLhEZZALI9wRg9i84VMkezufd
leYAoIg81ObngAzNq++1PiP9Q3HsIBa6
=j1/J
-----END PGP SIGNATURE-----