File Coverage

blib/lib/Plack/Middleware/IPMatch.pm
Criterion Covered Total %
statement 45 50 90.0
branch 5 10 50.0
condition 3 7 42.8
subroutine 9 9 100.0
pod 2 2 100.0
total 64 78 82.0


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