File Coverage

blib/lib/Plack/Middleware/IPMatch.pm
Criterion Covered Total %
statement 42 46 91.3
branch 4 8 50.0
condition 3 7 42.8
subroutine 9 9 100.0
pod 2 2 100.0
total 60 72 83.3


line stmt bran cond sub pod time code
1             package Plack::Middleware::IPMatch;
2 2     2   22637 use strict;
  2         4  
  2         91  
3 2     2   11 use warnings;
  2         3  
  2         79  
4 2     2   653 use parent qw/Plack::Middleware/;
  2         384  
  2         14  
5 2     2   14793 use Net::IP::XS;
  2         92203  
  2         134  
6 2     2   1181 use Net::IP::Match::Trie;
  2         473  
  2         13  
7             our $VERSION = 0.02;
8              
9 2     2   2923 use Plack::Util::Accessor qw( IPFile );
  2         4  
  2         21  
10              
11             sub _build_real_ip {
12 7     7   12     my ($env) = @_;
13              
14                 my @possible_forwarded_ips
15 0         0         = grep {
16 0         0         $_->iptype
17                         !~ /^(?:LOOPBACK|LINK\-LOCAL|PRIVATE|UNIQUE\-LOCAL\-UNICAST|LINK\-LOCAL\-UNICAST|RESERVED)$/xo
18                     }
19 0         0         grep {defined}
20 35         46         map { Net::IP::XS->new($_) }
21 7   50     84         grep {defined} (
22                     $env->{'HTTP_CLIENT_IP'},
23                     split( /,\s*/xo, $env->{'HTTP_X_FORWARDED_FOR'} // '' ),
24                     $env->{'HTTP_X_FORWARDED'},
25                     $env->{'HTTP_X_CLUSTER_CLIENT_IP'},
26                     $env->{'HTTP_FORWARDED_FOR'},
27                     $env->{'HTTP_FORWARDED'},
28                     );
29              
30 7   50     385     return $possible_forwarded_ips[0]
      33        
31                     // Net::IP::XS->new( $env->{'REMOTE_ADDR'} // '' );
32             }
33              
34              
35             sub prepare_app {
36 7     7 1 5709     my $self = shift;
37              
38 7 50       29     if (my $ipfiles = $self->IPFile) {
39 7 50       170         my @ipfiles = ref $ipfiles ? @{ $ipfiles } : ($ipfiles);
  0         0  
40 7         13         for my $ipfile (@ipfiles) {
41 7         56             my $match = Net::IP::Match::Trie->new();
42              
43 7 50       530             open my $fh, "<", $ipfile or die "$!";
44 7         150             while (<$fh>) {
45 77         1553                 chomp;
46 77         354                 my ($CIDRS, $lable) = split(/[,|\s]/, $_);
47 77         258                 $match->add( $lable , [$CIDRS] );
48                         }
49 7         144             push @{ $self->{IPMatcher} }, $match;
  7         141  
50                     }
51                 }
52             }
53              
54             sub call {
55 7     7 1 25397     my $self = shift;
56 7         10     my $env = shift;
57              
58 7         106     my $ip = _build_real_ip($env)->ip;
59              
60 7         94     foreach my $matcher (@{ $self->{IPMatcher} }) {
  7         26  
61 7         36         my $label = $matcher->match_ip($ip);
62 7 50       30         $env->{IPMATCH_LABEL} = $label and last;
63                 }
64              
65 7         38     return $self->app->($env);
66             }
67              
68             1;
69             __END__
70            
71             =pod
72            
73             =encoding utf8
74            
75             =head1 NAME
76            
77             Plack::Middleware::IPMatch - 查找指定 CIDR 所对应的 LABEL
78            
79             =head1 SYNOPSIS
80            
81             enable 'Plack::Middleware::IPMatch',
82             IPFile => [ '/path/to/CT.txt', '/path/to/CNC.txt' ];
83            
84             =head1 DESCRIPTION
85            
86             Plack::Middleware::IPMatch 这个是使用, Net::IP::Match::Trie 来实现的超级快的进行 CIDR 转换成指定的 LABEL 的模块.
87             因为是使用的前缀树实现, 所以有着超级快的查询速度.
88            
89             =head1 CONFIGURATION
90            
91            
92             =head2 IPFile
93            
94             IPFile => '/path/to/CT-IP.dat';
95             IPFile => [ '/path/to/CT-IP.dat', '/path/to/CNC-IP.dat' ];
96            
97             这个需要本身有自己整理过的 IP 数据库, 然后给整个数据库存成文本格式
98            
99             =head2 IPFile 格式
100            
101             格式需要自己来收集 IP 数据, 存成如下格式的文本
102            
103             112.122.128.0/21,CNC-AH-AH
104             112.122.136.0/23,CNC-AH-AH
105             112.122.138.0/25,CNC-AH-AH
106             112.122.138.128/29,CNC-AH-AH
107             112.122.138.144/28,CNC-AH-AH
108             112.122.138.160/27,CNC-AH-AH
109             112.122.138.192/26,CNC-AH-AH
110            
111             =head1 IPMATCH_LABEL Header
112            
113             默认会在 $env 的哈希中增加 C<IPMATCH_LABEL> 的字段的 Header, 这就是查询的结果
114            
115             可以使用如下的方式来访问
116            
117             $env->{IPMATCH_LABEL}
118            
119            
120             =head1 AUTHOR
121            
122             扶凯 E<lt>iakuf@163.comE<gt>
123            
124             =head1 LICENSE
125            
126             This library is free software; you can redistribute it and/or modify
127             it under the same terms as Perl itself.
128            
129             =head1 SEE ALSO
130            
131             L<Net::IP::Match::Trie|https://metacpan.org/pod/Net::IP::Match::Trie>
132            
133             L<Plack::Middleware::GeoIP|https://metacpan.org/pod/Plack::Middleware::GeoIP>
134            
135             =cut
136