slloyd has asked for the wisdom of the Perl Monks concerning the following question:
I am writing a threaded proxy server for windows (see complete code below).. It seems to mostly work as it is. To use this proxy server, you simply have to set your browser connection settings to use Localhost port 8080 as a proxy.
There are a few problems though. For some reason, it will get in a non-responsive state. Would anyone care to help me figure out what I am doing wrong?
Any help would be greatly appreciated.
BTW, I am writing this proxy server mainly as a way for me to learn how to use threads on windows. I do not want to use any other modules if possible.
#!/usr/bin/perl ################################################################# ### Use and Requires use strict; use threads qw[ yield ]; use threads::shared; use Thread::Queue; use Thread::Queue::Any; use Socket; ################################################################# ### Initialize $|=1; my $worker_cnt=6; #Number of worker threads to start my $hostname = `hostname`; $hostname=strip($hostname); my $hostip = (gethostbyname($hostname))[4]; my %SocketInfo=( hostname => $hostname, hostip => $hostip, ); ################################################################# ### Create thread Queues print "creating thread queues ..."; my $Qwork = new Thread::Queue::Any; my $Qprint = new Thread::Queue::Any; my %ThreadStatus : shared = (); my %ClientAddr : shared = (); print "Done\n"; ################################################################# ### Start up worker threads my @threads=(); print "starting 1 writer thread ... \n"; my $cthread = threads->new(\&writer); push(@threads,$cthread); print "starting $worker_cnt worker threads ... "; for(my $x=0;$x<$worker_cnt;$x++){ $cthread = threads->new(\&worker); push(@threads,$cthread); } print "Done\n"; my $threadcnt=@threads; ################################################################# ### Connect to Socket my $port = 8080; my $address=''; my $Server; my ($name, $aliases, $protocol) = getprotobyname('tcp'); if ($port !~ /^\d+$/) {($name, $aliases, $port) = getservbyport($port, + 'tcp');} my $proto = getprotobyname('tcp'); socket($Server,PF_INET,SOCK_STREAM,$proto) || die "Socket Connection E +rror : $!"; setsockopt($Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die + "Setting Socket Option Error: $!"; bind($Server, sockaddr_in($port, INADDR_ANY)) || die "Cannot bind Sock +et : $!"; listen($Server,SOMAXCONN) || die "Error listening on Socket: $!"; binmode $Server; select($Server);$| = 1; select(STDOUT);$|= 1; ################################################################# ### Listener on Socket for Client Requests # Use fileno to pass Globs between threads # http://www.perlmonks.org/index.pl?node_id=395373 print "Listening on port $port\n"; my $Client; while (my $addr = accept($Client, $Server)){ $Qwork->enqueue(fileno $Client,$addr,""); #sleep for 1 millisecond to avoid pegging the CPU select(undef,undef,undef,.01); } ################################################################# ### send a Die message to each thread by putting a die message in the + queue for each thread to get. $Qwork->enqueue(["",'DIE_NOW']) for 1 .. @threads; ################################################################# ### Join threads - cleanup and exit #$_->join for @threads; - this should work but causes a crash.. Let w +indows clean up the threads. use POSIX qw[_exit]; _exit 0; ################################################################# ### Subs sub writer { #Grab items from the Qprint queue and print them to STDOUT my $tid = threads->tid(); $ThreadStatus{$tid}='idle'; while(1){ select(undef,undef,undef,.01) until $Qprint->pending(); $ThreadStatus{$tid}='working'; my ($thrid,$msg)=$Qprint->dequeue; last if length($msg) && $msg=~/^DIE NOW$/is; print "[$thrid] $msg\n"; #Show the state of each thread foreach my $tid (sort(keys(%ThreadStatus))){ print STDOUT "\t$tid is $ThreadStatus{$tid}\n"; } $ThreadStatus{$tid}='idle'; #sleep for 1 millisecond to avoid pegging the CPU select(undef,undef,undef,.01); } return 1; } sub worker { my $tid = threads->tid(); $ThreadStatus{$tid}='idle'; while(1){ select(undef,undef,undef,.01) until $Qwork->pending(); $ThreadStatus{$tid}='working'; my ($fno,$addr,$action)=$Qwork->dequeue; last if length($action) && $action=~/^DIE NOW$/is; open($Client, "+<&=$fno") || last; #Process Request; processRequest($Client,$addr,$tid); close($Client); $ThreadStatus{$tid}='idle'; } return 1; } sub processRequest{ my $Client=shift || return; my $addr=shift || return; my $tid=shift; $Qprint->enqueue($tid,"Processing"); $ThreadStatus{$tid}='processing'; my $socket_format = 'S n a4 x8'; my @tmp=unpack($socket_format,$addr); my $port=$tmp[1]; my $inetaddr = $tmp[2]; my @inetaddr = unpack('C4',$inetaddr); my $client_ip=join(".", @inetaddr); my $client_host = gethostbyaddr($inetaddr, AF_INET); my %Request=(); my @request=readSocket(\%Request,$Client); my ($UrlSock,$err)=connectToHost($Request{host},80); #Send Request to the internet $request[0] =~ s/http:\/\/[^\/]+//; $request[0] =~ s/HTTP\/1.1/HTTP\/1.0/; foreach my $line (@request){ print $UrlSock $line; } if(length($Request{postdata})){ print $UrlSock $Request{postdata}; } #Get Reply Header my %Reply=(); my @reply=&readSocket(\%Reply,$UrlSock); if($Reply{'content-type'}=~/^(image|application)/is){ binmode $UrlSock; binmode $Client; } #Print the Reply Header to the browser client print $Client @reply; $Qprint->enqueue($tid,"Listening for $Reply{'content-type'} from $ +Request{host}"); $ThreadStatus{$tid}='waiting for reply [0]'; my $clen=0; my $linecnt=0; while (<$UrlSock>) { my $line=$_; $linecnt++; $ThreadStatus{$tid}="reading reply line $linecnt"; print $Client $line; if($Reply{'content-length'} && $Reply{'content-length'} > 0){ $clen += length($line); last if $clen >= $Reply{'content-length'}; } last if $line=~/\<\/html\>/is; last if $line=~/^0$/s; if($line=~/\<\/body\>$/is){ print $Client qq|</html>|; last; } } } ######################## sub readSocket{ my $hash=shift || return "No Hash Reference"; my $Socket=shift || return "No Socket"; binmode $Socket; my @lines=(); %{$hash}=(); #Read in First line and process it my $firstline=<$Socket>; $hash->{request}=strip($firstline); push(@lines,$firstline); $hash->{url} = ($firstline =~ m|(http://\S+)|)[0]; if($hash->{url}=~/\?(.+)/s){ $hash->{getdata}=$1; } #Method,host,port my ($method,$host,$port); #POST http://www.basgetti.com/cgi-bin/wasql.pl HTTP/1.1 #CONNECT www.netteller.com:443 HTTP/1.1 if($firstline =~ m!(GET|POST|HEAD) http://([^/:]+):?(\d*)!){ $method=$1; $host=$2; $port=$3 } elsif($firstline=~m!(CONNECT) ([^/:]+):?(\d*)!){ $method=$1; $host=$2; $port=$3 } $hash->{method}=$method; $hash->{host}=$host; $hash->{port}=$port; #Read in rest of Header while (<$Socket> ) { next if (/(Proxy-Connection:|Keep-Alive:|Accept-Encoding:)/); if(/^([a-z\-]+)\:(.+)/is){ my $attr=lc(strip($1)); my $val=lc(strip($2)); $hash->{$attr}=$val; } push(@lines,$_); last if ($_ =~ /^[\s\x00]*$/); } my $len=$hash->{'content-length'}?$hash->{'content-length'}:0; if ($method=~/^POST$/is && $len) { my $data=''; my $dlen=0; my $bytes=$len>2048?2048:$hash->{'content-length'}; while($dlen<$len){ my $cdata; my $n=read($Socket,$cdata,$bytes); $data .= $cdata; $dlen=length($data); last if $n==0; last if !defined $n; select(undef,undef,undef,.01); } $hash->{'postdata'}=$data; } $hash->{url}=~s/\?.*//s; return @lines; } ################## sub connectToHost { # Create a socket that connects to a certain host # connectToHost($MainSock, $remote_hostname, $port) my ($remote_hostname, $port) = @_; my $Sock; my ($socket_format, $proto, $packed_port, $cur); my ($remote_addr, @remote_ip, $remote_ip); my ($local_port, $remote_port); if ($port !~ /^\d+$/) { $port = (getservbyname($port, "tcp"))[2]; $port = 80 unless ($port); } $proto = (getprotobyname('tcp'))[2]; $remote_addr = (gethostbyname($remote_hostname))[4]; if (!$remote_addr) { return (undef,"Unknown host: $remote_hostname"); } @remote_ip = unpack("C4", $remote_addr); $remote_ip = join(".", @remote_ip); $socket_format = 'S n a4 x8'; $local_port = pack($socket_format, &AF_INET, 0, $SocketInfo{hostip +}); $remote_port = pack($socket_format, &AF_INET, $port, $remote_addr) +; socket($Sock, &AF_INET, &SOCK_STREAM, $proto) || return (undef,"So +cket Error: $!"); bind($Sock, $local_port) || return (undef,"Socket Bind Error: $!") +; connect($Sock, $remote_port) || return (undef,"Socket Connect Erro +r: $!"); $cur = select($Sock); $| = 1; # Disable buffering on socket. select($cur); return $Sock; } ############### sub strip{ #usage: $str=strip($str); #info: strips off beginning and endings returns, newlines, tabs, a +nd spaces my $str=shift; if(length($str)==0){return;} $str=~s/^[\r\n\s\t]+//s; $str=~s/[\r\n\s\t]+$//s; return $str; }
Janitored by holli - added readmore tags
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Windows Threaded Proxy Server
by BrowserUk (Patriarch) on May 26, 2005 at 21:40 UTC | |
by slloyd (Hermit) on May 28, 2005 at 04:52 UTC | |
|
Re: Windows Threaded Proxy Server
by jfroebe (Parson) on May 26, 2005 at 18:04 UTC |