File Coverage

blib/lib/Net/Squid/ReverseProxy.pm
Criterion Covered Total %
statement 12 213 5.6
branch 0 80 0.0
condition 0 38 0.0
subroutine 4 11 36.3
pod 6 6 100.0
total 22 348 6.3


line stmt bran cond sub pod time code
1             package Net::Squid::ReverseProxy;
2              
3 1     1   23547 use 5.006;
  1         5  
  1         45  
4 1     1   7 use strict;
  1         3  
  1         39  
5 1     1   6 use Carp qw/croak/;
  1         15  
  1         74  
6              
7 1     1   7 use vars qw/$VERSION/;
  1         2  
  1         2562  
8             $VERSION = '0.04';
9              
10              
11             sub new {
12              
13 0     0 1   my $class = shift;
14 0           my %arg = @_;
15              
16 0 0 0       unless (defined $arg{'squid_version'} &&
      0        
17             defined $arg{'squid_conf'} &&
18             defined $arg{'squid'} ) {
19 0           croak "the path to both squid and squid.conf as well as squid version are required";
20             }
21              
22 0 0 0       unless (-f $arg{'squid_conf'} && -w $arg{'squid_conf'} ) {
23 0           croak "squid config file doesn't exist or isn't writable";
24             }
25              
26 0 0 0       unless (-f $arg{'squid'} && -x $arg{'squid'} ) {
27 0           croak "squid program doesn't exist or isn't executable";
28             }
29              
30 0           bless \%arg, $class;
31             }
32              
33             sub init_reverseproxy {
34              
35 0     0 1   my $self = shift;
36 0           my %arg = @_;
37              
38 0           my $cfg = $self->{'squid_conf'};
39 0           my $squid = $self->{'squid'};
40 0           my $version = $self->{'squid_version'};
41              
42 0   0       my $cache_mem = $arg{'cache_mem'} || 50;
43 0   0       my $maximum_object_size = $arg{'maximum_object_size'} || 2048;
44 0   0       my $maximum_object_size_in_memory = $arg{'maximum_object_size_in_memory'} || 64;
45 0   0       my $cache_dir_size = $arg{'cache_dir_size'} || 50;
46 0   0       my $visible_hostname = $arg{'visible_hostname'} || 'localhost.localdomain';
47              
48 0 0         if ($arg{'cache_dir'} ) {
49 0           my $uid = (stat $arg{'cache_dir'})[4];
50 0           my $user = (getpwuid $uid)[0];
51              
52 0 0         if ($user ne 'nobody') {
53 0           croak "init failed, $arg{'cache_dir'} must be owned by nobody";
54             }
55             }
56              
57 0   0       my $cache_dir = $arg{'cache_dir'} || '/tmp/squidcache';
58              
59 0           my $module_dir = $INC{'Net/Squid/ReverseProxy.pm'};
60 0           $module_dir =~ s/\.pm$//;
61              
62 0           my @cfg; my $fd;
63              
64 0 0         open $fd, "<", "$module_dir/squidcfg" or croak "can't open template file $!";
65 0           while (<$fd>) {
66 0           push @cfg,$_;
67              
68 0 0         if (/ARG INPUT BEGIN/) {
69 0           push @cfg,
70             "cache_mem $cache_mem MB\n",
71             "maximum_object_size $maximum_object_size KB\n",
72             "maximum_object_size_in_memory $maximum_object_size_in_memory KB\n",
73             "cache_dir ufs $cache_dir $cache_dir_size 16 256\n",
74             "visible_hostname $visible_hostname\n";
75              
76 0 0         if ( $version < 3.0 ) {
77 0           push @cfg, "acl all src all\n";
78             }
79             }
80             }
81 0           close $fd;
82              
83 0 0         open $fd, "<", $cfg or croak $!;
84 0           my @oldcfg = <$fd>;
85 0           close $fd;
86              
87 0           my $fdw;
88              
89 0 0         open $fdw,">",$cfg or croak $!;
90 0           print $fdw @cfg;
91 0           close $fdw;
92              
93 0           system "$squid -k kill >/dev/null 2>&1";
94 0           system "$squid -z >/dev/null 2>&1 && $squid";
95              
96 0 0         if ($? == 0) {
97 0           return 1;
98              
99             } else {
100              
101 0 0         open $fdw,">",$cfg or croak $!;
102 0           print $fdw @oldcfg;
103 0           close $fdw;
104              
105 0           croak "init failed, can't run 'squid -z' and startup squid";
106             }
107             }
108              
109              
110             sub add_dstdomain_proxy {
111              
112 0     0 1   my $self = shift;
113 0           my %arg = @_;
114              
115 0           my $cfg = $self->{'squid_conf'};
116 0           my $squid = $self->{'squid'};
117              
118 0           my $site_dst = $arg{'dstdomain'};
119 0           my @ip = @{$arg{'original_server'}};
  0            
120 0   0       my $algor = $arg{'load_balance'} || '';
121              
122 0 0 0       unless ($site_dst && @ip) {
123 0           return;
124             }
125              
126 0           my @newconf;
127             my %cache_peer_access;
128 0           my $fd;
129              
130 0           $cache_peer_access{'origin'} = 'origin_0_0';
131 0 0         open $fd, "<", $cfg or croak $!;
132 0           while(<$fd>) {
133 0 0         last if /SITE END/;
134 0 0         if (/^cache_peer_access/) {
135 0           $cache_peer_access{'origin'} = (split)[1];
136             }
137             }
138 0           close $fd;
139              
140 0           my $idmax = (split /\_/, $cache_peer_access{'origin'})[-2];
141 0           $idmax++;
142              
143 0 0         open $fd, "<", $cfg or croak $!;
144 0           while(<$fd>) {
145              
146 0 0         if (/SITE END/) {
147 0           my $int = 1;
148 0           for my $ip (@ip) {
149 0           my ($site_ip,$site_port) = split/\:/,$ip;
150 0   0       $site_port ||= 80;
151 0           push @newconf,
152             "cache_peer $site_ip parent $site_port 0 no-query originserver name=origin_${idmax}_$int $algor\n";
153 0           $int++;
154             }
155              
156 0           push @newconf,"acl service_$idmax dstdomain $site_dst\n";
157              
158 0           for my $int (1 .. scalar(@ip) ) {
159 0           push @newconf, "cache_peer_access origin_${idmax}_$int allow service_$idmax\n";
160             }
161             }
162              
163 0           push @newconf,$_;
164             }
165 0           close $fd;
166              
167 0 0         open $fd, "<", $cfg or croak $!;
168 0           my @oldcfg = <$fd>;
169 0           close $fd;
170              
171 0           my $fdw;
172              
173 0 0         open $fdw, ">", $cfg or croak $!;
174 0           print $fdw @newconf;
175 0           close $fdw;
176              
177 0           my @err = `$squid -k reconfig 2>&1`;
178 0 0         if (@err) {
179              
180 0 0         open $fdw,">",$cfg or croak $!;
181 0           print $fdw @oldcfg;
182 0           close $fdw;
183              
184 0           system "$squid -k reconfig >/dev/null 2>&1";
185 0           return;
186              
187             } else {
188 0           return 1;
189             }
190             }
191              
192              
193             sub remove_dstdomain_proxy {
194              
195 0     0 1   my $self = shift;
196 0   0       my $domain = shift || return;
197              
198 0           my $cfg = $self->{'squid_conf'};
199 0           my $squid = $self->{'squid'};
200              
201 0           $domain = quotemeta($domain);
202              
203 0           my @id; my $fd;
204              
205 0 0         open $fd, "<", $cfg or croak $!;
206 0           while(<$fd>) {
207 0 0         if (/^acl\s+service_(\d+)\s+dstdomain\s+$domain$/) {
208 0           push @id, $1;
209             }
210             }
211 0           close $fd;
212              
213 0           my @cfg;
214 0 0         open $fd, "<", $cfg or croak $!;
215 0           while(<$fd>) {
216 0           my $next = 0;
217 0           for my $id (@id) {
218 0 0 0       $next=1 if (/origin_${id}_/ || /service_${id}\s+/);
219             }
220 0 0         next if $next;
221 0           push @cfg,$_;
222             }
223 0           close $fd;
224            
225 0 0         open $fd, "<", $cfg or croak $!;
226 0           my @oldcfg = <$fd>;
227 0           close $fd;
228              
229 0           my $fdw;
230              
231 0 0         open $fdw, ">", $cfg or croak $!;
232 0           print $fdw @cfg;
233 0           close $fdw;
234              
235 0           my @err = `$squid -k reconfig 2>&1`;
236 0 0         if (@err) {
237              
238 0 0         open $fdw,">",$cfg or croak $!;
239 0           print $fdw @oldcfg;
240 0           close $fdw;
241              
242 0           system "$squid -k reconfig >/dev/null 2>&1";
243 0           return;
244              
245             } else {
246 0           return 1;
247             }
248             }
249              
250              
251             sub exists_dstdomain_proxy {
252              
253 0     0 1   my $self = shift;
254 0   0       my $domain = shift || return;
255              
256 0           $domain = quotemeta($domain);
257 0           my $cfg = $self->{'squid_conf'};
258 0           my $exist = 0;
259 0           my $fd;
260              
261 0 0         open $fd,"<",$cfg or croak $!;
262 0           while (<$fd>) {
263 0 0         if (/^acl\s+service_(\d+)\s+dstdomain\s+$domain$/) {
264 0           $exist = 1;
265 0           last;
266             }
267             }
268 0           close $fd;
269              
270 0           return $exist;
271             }
272              
273              
274             sub _get_dstdomain_sites {
275              
276 0     0     my $self = shift;
277              
278 0           my $cfg = $self->{'squid_conf'};
279 0           my %sites;
280             my %service;
281 0           my %peers;
282 0           my $fd;
283              
284 0 0         open $fd, "<", $cfg or croak $!;
285 0           while(<$fd>) {
286 0 0         if (/SITE BEGIN/) {
287 0           while(<$fd>) {
288 0 0         last if /SITE END/;
289 0           chomp;
290              
291 0           my @elem = split;
292              
293 0 0         if ($elem[0] eq 'cache_peer_access') {
294 0           push @{$service{$elem[3]}}, $elem[1];
  0            
295             }
296             }
297             }
298             }
299 0           close $fd;
300            
301 0 0         open $fd, "<", $cfg or croak $!;
302 0           for my $s (keys %service) {
303 0           my @lines = grep {/^acl\s+$s\s+/} <$fd>;
  0            
304 0           my $line = shift @lines;
305 0           chomp $line;
306 0           $sites{$s} = (split/\s+/,$line)[-1];
307 0           seek($fd,0,0);
308             }
309 0           close $fd;
310              
311 0 0         open $fd, "<", $cfg or croak $!;
312 0           for my $s (keys %service) {
313 0           for my $p (@{$service{$s}}) {
  0            
314 0           my @lines = grep {/name=$p\s+/} <$fd>;
  0            
315 0           my $line = shift @lines;
316 0           chomp $line;
317 0           $peers{$p} = [ (split/\s+/,$line)[1,3,-1] ];
318 0           seek($fd,0,0);
319             }
320             }
321 0           close $fd;
322              
323 0           return \%service,\%sites,\%peers;
324             }
325              
326              
327             sub list_dstdomain_proxies {
328              
329 0     0 1   my $self = shift;
330              
331 0           my ($svr,$site,$peer) = $self->_get_dstdomain_sites();
332 0           my @exist;
333            
334 0           for my $s ( sort { (split/\_/,$a)[-1] <=> (split/\_/,$b)[-1] } keys %$site ) {
  0            
335 0           my @ip;
336 0           my $algor = '';
337 0           my $domain = $site->{$s};
338 0           my $original = $svr->{$s};
339              
340 0           for my $name (@$original) {
341 0           push @ip, $peer->{$name}->[0] . ":" . $peer->{$name}->[1];
342 0 0         if ($peer->{$name}->[2] !~ /name=/) {
343 0           $algor = $peer->{$name}->[2];
344             }
345             }
346              
347 0           push @exist, [$s,$domain,$algor,[@ip]];
348             }
349              
350 0           return \@exist;
351             }
352              
353              
354             1;
355              
356              
357             =head1 NAME
358              
359             Net::Squid::ReverseProxy - setup a HTTP reverse proxy with Squid
360              
361             =head1 VERSION
362              
363             Version 0.04
364              
365              
366             =head1 SYNOPSIS
367              
368             use Net::Squid::ReverseProxy;
369              
370             my $squid = Net::Squid::ReverseProxy->new(
371             'squid' => '/path/to/squid',
372             'squid_conf' => '/path/to/squid.conf',
373             'squid_version' => '3.0');
374              
375             $squid->init_reverseproxy;
376             sleep 1;
377              
378             $squid->add_dstdomain_proxy('dstdomain' => 'www.example.com',
379             'original_server' => ['192.168.1.10'])
380             or die "can't add dstdomain";
381              
382             $squid->add_dstdomain_proxy('dstdomain' => 'mail.example.com',
383             'original_server' => ['192.168.1.20',
384             '192.168.1.30:8080'],
385             'load_balance' => 'round-robin')
386             or die "can't add dstdomain";
387              
388             print "The dstdomain www.example.com exists? ";
389             print $squid->exists_dstdomain_proxy('www.example.com') ? "yes\n" : "no\n";
390              
391             use Data::Dumper;
392             print Dumper $squid->list_dstdomain_proxies;
393              
394             $squid->remove_dstdomain_proxy('www.example.com')
395             or die "can't remove dstdomain";
396              
397             =head1 METHODS
398              
399             =head2 new()
400              
401             Create an object. Please specify the full path of both squid
402             executable program and squid config file, with the version number
403             of squid. Currently squid-2.7, 3.0, 3.1 branches were tested.
404              
405             my $squid = Net::Squid::ReverseProxy->new(
406             'squid' => '/path/to/squid',
407             'squid_conf' => '/path/to/squid.conf',
408             'squid_version' => '3.0');
409              
410             Before using this module, you must have squid installed in
411             the system. You could get the latest source from its official
412             website squid-cache.org, then install it following the words in
413             INSTALL document. For example,
414              
415             % ./configure --prefix=/usr/local/squid
416             % make
417             # make install
418              
419              
420             =head2 init_reverseproxy()
421              
422             Warnning: the config file will be overwritten by this method, you
423             should execute the method only once at the first time of using this
424             module. It's used to initialize the setting for squid reverse proxy.
425              
426             To keep backward compatibility, there is a method of
427             init_squid_for_reverseproxy() which is an alias to this method.
428              
429             You could pass the additional arguments like below to the method:
430              
431             $squid->init_reverseproxy(
432             'cache_mem' => 200,
433             'maximum_object_size' => 4096,
434             'maximum_object_size_in_memory' => 64,
435             'cache_dir_size' => 1024,
436             'visible_hostname' => 'squid.domain.com',
437             'cache_dir' => '/data/squidcache',
438             );
439              
440             cache_mem: how large memory (MB) squid will use for cache, default 50
441              
442             maximum_object_size: the maximum object size (KB) squid will cache with,
443             default 2048
444              
445             maximum_object_size_in_memory: the maximum object size (KB) squid will
446             cache with in memory, default 64
447              
448             cache_dir_size: how large disk (MB) squid will use for cache, default 50
449              
450             visible_hostname: visiable hostname, default localhost.localdomain
451              
452             cache_dir: path to cache dir, default /tmp/squidcache
453              
454              
455             After calling this method, you MUST sleep at least one second to wait for
456             squid to finish starting up before any further operation.
457              
458             If initialized correctly, it will make squid run and listen on TCP port
459             80 for HTTP requests. If initialized failed, you may check /tmp/cache.log
460             for details.
461              
462              
463             =head2 add_dstdomain_proxy()
464              
465             Add a rule of reverseproxy based on dstdomain (destination domain).
466             For example, you want to reverse-proxy the domain www.example.com,
467             whose backend webserver is 192.168.1.10, then do:
468              
469             $squid->add_dstdomain_proxy('dstdomain' => 'www.example.com',
470             'original_server' => ['192.168.1.10']);
471              
472             Here 'dstdomain' means destination domain, 'original_server' means backend
473             webserver. If you have two backend webservers, one is 192.168.1.20, whose
474             http port is 80 (the default), another is 192.168.1.30, whose http port is
475             8080, then do:
476              
477             $squid->add_dstdomain_proxy('dstdomain' => 'www.example.com',
478             'original_server' => ['192.168.1.20',
479             '192.168.1.30:8080'],
480             'load_balance' => 'round-robin');
481              
482             Here 'load_balance' specifies an algorithm for balancing http requests among
483             webservers. The most common used algorithms are round-robin and sourcehash.
484             The latter is used for session persistence mostly. See squid.conf's document
485             for details. If you want all traffic go to the first webserver, and only when
486             the first webserver gets down, the traffic go to the second webserver,
487             then don't specify a load_balance algorithm here.
488              
489              
490             =head2 exists_dstdomain_proxy()
491              
492             Whether a reverseproxy rule for the specified destination domain exists.
493              
494             $squid->exists_dstdomain_proxy('www.example.com');
495              
496             Returns 1 for exists, 0 for non-exists.
497              
498              
499             =head2 list_dstdomain_proxies()
500              
501             List all reverseproxy rules in the config file. It returns a data structure
502             of a reference to AoA, so you will dump it with Data::Dumper.
503              
504             use Data::Dumper;
505             print Dumper $squid->list_dstdomain_proxies;
506              
507              
508             =head2 remove_dstdomain_proxy()
509              
510             Remove reverseproxy rule(s) for the specified destination domain.
511              
512             $squid->remove_dstdomain_proxy('www.example.com');
513              
514              
515             =head1 AUTHOR
516              
517             Jeff Pang
518              
519              
520             =head1 BUGS/LIMITATIONS
521              
522             If you have found bugs, please send email to , I will
523             appreciate it much.
524              
525              
526             =head1 SUPPORT
527              
528             You can find documentation for this module with the perldoc command.
529              
530             perldoc Net::Squid::ReverseProxy
531              
532             For the general knowledge of installing and setup squid, please reference
533             documents and wiki on squid-cache.org, or subscribe to squid user's mailing
534             list, or, you can email me in private. For Chinese you could download and
535             read the Chinese version of "Squid: The Definitive Guide" translated by me:
536              
537             http://squidcn.spaces.live.com/blog/cns!B49104BB65206A10!229.entry
538              
539              
540             =head1 COPYRIGHT & LICENSE
541              
542             Copyright 2009 Jeff Pang, all rights reserved.
543              
544             This program is free software; you can redistribute it and/or modify
545             it under the same terms as Perl itself.
546