This is a slightly more complicated proxy than the /Web Proxy. It uses HTTP::Request and HTTP::Response objects and puts PoCoCl::HTTP in streaming mode. This proxy will not buffer entire messages in memory, is therefore lower-latency, and you get download progress for clients that provide it (and where Content-Length is defined by the upstream server!). However, this is not a suitable proxy if you need the entire page buffered in memory to manipulate or inspect---use the simple version for that.

This proxy tries to be HTTP/1.0 vs HTTP/1.1 compliant with respect to keep-alives and supports transparent proxying using the "Host:" header. There is plenty of documentation on transparent proxies, but for now review this one-liner:

 iptables -t nat -I OUTPUT -p tcp --dport 80 -j REDIRECT --to-ports 8088 -m owner ! --uid-owner the_proxy_user

Important Note: This proxy does not support HTTP/1.1 pipelining. Requests are sent to the PoCoCl::HTTP user agent as they come in but handle_http_response does not differentiate between receipt sources. If you decide to fix this, remember that pipelined requests must be sent in-order. I will update this when I get a moment to upload a clean cookbook example with pipeline support; if you beat me to it, please update this! -ewheeler

#!/usr/bin/perl
use warnings;
use strict;
$SIG{PIPE} = 'IGNORE';    # otherwise, SIGPIPE terminates the proxy.
use POE;
use POE::Component::Server::TCP;
use POE::Component::Client::HTTP;
use POE::Filter::HTTPD;
use HTTP::Response;
sub DUMP_REQUESTS ()  { 1 }
sub DUMP_RESPONSES () { 1 }
sub LISTEN_PORT ()    { 8088 }

### Spawn a web client to fetch requests through.
POE::Component::Client::HTTP->spawn(
  Alias             => 'ua',
  Streaming         => 32768,
  ConnectionManager => POE::Component::Client::Keepalive->new(
    keep_alive   => 10,     # seconds to keep connections alive
    max_open     => 100,    # max concurrent connections - total
    max_per_host => 20,     # max concurrent connections - per host
    timeout      => 30,     # max time (seconds) to establish a new connection
  ),
);

### Spawn a web server.
# The ClientInput function is called to deal with client input.
# ClientInput's callback function will receive entire HTTP requests
# because this server uses POE::Filter::HTTPD to parse its input.
#
# InlineStates let us attach our own events and handlers to a TCP
# server.  Here we attach a handler for the got_response event, which
# will be sent to us by Client::HTTP when it has fetched something.
POE::Component::Server::TCP->new(
  Alias              => "web_server",
  Address            => '127.0.0.1',
  Port               => LISTEN_PORT,
  ClientFilter       => 'POE::Filter::HTTPD',
  ClientInput        => \&handle_http_request,
  ClientDisconnected => sub { Debug("client disconnected") },
  InlineStates       => {got_response => \&handle_http_response,},
);

### Run the proxy until it is done, then exit.
POE::Kernel->run();
exit 0;

### Handle HTTP requests from the client.  Pass them to the HTTP
### client component for further processing.  Optionally dump the
### request as text to STDOUT.
sub handle_http_request {
  my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];

  # If the request is really a HTTP::Response, then it indicates a
  # problem parsing the client's request.  Send the response back so
  # the client knows what's happened.
  if ($request->isa("HTTP::Response")) {
    $heap->{client}->put($request);
    $kernel->yield("shutdown");
    return;
  }

  # Client::HTTP doesn't support keep-alives yet.
  #$request->header("Connection",       "close");
  #$request->header("Proxy-Connection", "close");
  #display_thing(scalar(localtime)."[BEFORE Req Id ". $_[SESSION]->ID."]: ", $request->as_string()) if DUMP_REQUESTS;

  # default keep-alive for HTTP/1.0 vs HTTP/1.1
  $heap->{keepalive} = 1
    if (!$request->header('Connection') && $request->protocol eq 'HTTP/1.1');
  $heap->{keepalive} = 0
    if (!$request->header('Connection') && $request->protocol eq 'HTTP/1.0');
  $heap->{keepalive} = 1
    if (lc($request->header('Connection')) eq 'keep-alive');
  $heap->{keepalive} = 1
    if (lc($request->header('Proxy-Connection')) eq 'keep-alive');

  # Remove RFC2616 hop-to-hop headers
  $request->remove_header($_) foreach (
    qw/Connection Keep-Alive Proxy-Connection Proxy-Authenticate
    Proxy-Authorization TE Trailers Upgrade/

    #Transfer-Encoding # Technically this is included, but pass it through
  );
  if ($request->uri !~ /^http:/) {
    $request->uri("http://" . $request->header("Host") . $request->uri);
    $heap->{transparent_proxy} = 1;
  }
  display_thing(scalar(localtime) . "[AFTER  Req Id " . $_[SESSION]->ID . "]: ",
    $request->as_string())
    if DUMP_REQUESTS;
  $kernel->post(
    "ua" => "request",
    "got_response", $request
  );
} ## end sub handle_http_request
### Handle HTTP responses from the POE::Component::Client::HTTP we've
### spawned at the beginning of the program.  Send each response back
### to the client that requested it.  Optionally display the response
### as text.
sub handle_http_response {
  my ($kernel, $heap) = @_[KERNEL, HEAP];

  my $http_request  = $_[ARG0]->[0];
  my $http_response = $_[ARG1]->[0];

  my $chunk = $_[ARG1]->[1];    # data chunk streaming in.
  $heap->{bytes_sent} += length $chunk if (defined($chunk));

  # Skip dead clients
  return if (!defined $heap->{client});

  if (!$heap->{sent_header}) {

    # We cannot keep-alive for systems that fail to return content-length
    if ($heap->{keepalive} && !$http_response->header('Content-Length')) {
      $http_response->header("Connection", 'close');
      $heap->{keepalive} = 0;
    }
    $heap->{client}->put($http_response);    # Send the header
    $heap->{client}->set_output_filter(POE::Filter::Stream->new);   # raw output
    $heap->{client}->put($chunk) if (defined $chunk);    # send the data chunk

    $heap->{sent_header}++;

    display_thing(scalar(localtime) . "[Req Id " . $_[SESSION]->ID . "]: ",
      $http_response->as_string())
      if DUMP_RESPONSES;
  }
  else {
    if (defined($chunk)) {
      $heap->{client}->put($chunk);
    }
    else                                                 # end of stream
    {
      if ($heap->{keepalive}) {
        Debug(
          "End of stream, resetting output state. bytes_sent=$heap->{bytes_sent} [keepalive=1]"
        );
        $heap->{client}->set_output_filter(POE::Filter::HTTPD->new);
        $heap->{sent_header} = 0;
        $heap->{bytes_sent}  = 0;
      }
      else {

        # No Keep-Alive, disconnect.
        Debug(
          "End of stream, disconnecting client. bytes_sent=$heap->{bytes_sent} [keepalive=1]"
        );
        $kernel->yield("shutdown");
      }
    }
  } ## end else [ if (!$heap->{sent_header...})]
} ## end sub handle_http_response
### Display requests and responses with brackets around them so they
### stand apart.
sub display_thing {
  my $note  = shift;
  my $thing = shift;
  $thing =~ s/^/| /mg;
  print ",", '-' x (78 - length($note)), "" . scalar($note) . "\n";
  print $thing;
  print "`", '-' x 78, "\n";
}

sub Debug { print STDERR join(", ", @_) . "\n"; }