blib/lib/Apache/Tika/Server.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 27 | 127 | 21.2 |
branch | 0 | 44 | 0.0 |
condition | 0 | 24 | 0.0 |
subroutine | 9 | 20 | 45.0 |
pod | 0 | 10 | 0.0 |
total | 36 | 225 | 16.0 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Apache::Tika::Server; | ||||||
2 | 1 | 1 | 65337 | use strict; | |||
1 | 3 | ||||||
1 | 33 | ||||||
3 | 1 | 1 | 5 | use Carp qw(croak); | |||
1 | 2 | ||||||
1 | 48 | ||||||
4 | # Fire up/stop a Tika instance | ||||||
5 | 1 | 1 | 562 | use Moo 2; | |||
1 | 10939 | ||||||
1 | 6 | ||||||
6 | 1 | 1 | 1798 | use Apache::Tika::DocInfo; | |||
1 | 4 | ||||||
1 | 32 | ||||||
7 | 1 | 1 | 613 | use Data::Dumper; | |||
1 | 6668 | ||||||
1 | 62 | ||||||
8 | 1 | 1 | 526 | use Promises; | |||
1 | 15144 | ||||||
1 | 12 | ||||||
9 | |||||||
10 | 1 | 1 | 680 | use Filter::signatures; | |||
1 | 24013 | ||||||
1 | 13 | ||||||
11 | 1 | 1 | 33 | use feature 'signatures'; | |||
1 | 2 | ||||||
1 | 31 | ||||||
12 | 1 | 1 | 38 | no warnings 'experimental::signatures'; | |||
1 | 4 | ||||||
1 | 1684 | ||||||
13 | |||||||
14 | =head1 SYNOPSIS | ||||||
15 | |||||||
16 | use Apache::Tika::Server; | ||||||
17 | |||||||
18 | # Launch our own Apache Tika instance | ||||||
19 | my $tika= Apache::Tika::Server->new( | ||||||
20 | jarfile => $tika_path, | ||||||
21 | ); | ||||||
22 | $tika->launch; | ||||||
23 | |||||||
24 | my $fn= shift; | ||||||
25 | |||||||
26 | use Data::Dumper; | ||||||
27 | my $info = $tika->get_all( $fn ); | ||||||
28 | print Dumper $info->meta($fn); | ||||||
29 | print $info->content($fn); | ||||||
30 | # ... | ||||||
31 | print $info->meta->{"meta:language"}; | ||||||
32 | # en | ||||||
33 | |||||||
34 | =cut | ||||||
35 | |||||||
36 | our $VERSION = '0.08'; | ||||||
37 | |||||||
38 | extends 'Apache::Tika::Async'; | ||||||
39 | |||||||
40 | sub load_module { | ||||||
41 | 0 | 0 | 0 | my( $module ) = @_; | |||
42 | 0 | $module =~ s!::!/!g; | |||||
43 | 0 | require "$module.pm" | |||||
44 | } | ||||||
45 | |||||||
46 | has pid => ( | ||||||
47 | is => 'rw', | ||||||
48 | #isa => 'Int', | ||||||
49 | ); | ||||||
50 | |||||||
51 | has port => ( | ||||||
52 | is => 'ro', | ||||||
53 | #isa => 'Int', | ||||||
54 | default => sub { 9998 }, | ||||||
55 | ); | ||||||
56 | |||||||
57 | has connection_class => ( | ||||||
58 | is => 'ro', | ||||||
59 | default => 'Apache::Tika::Connection::AEHTTP', | ||||||
60 | ); | ||||||
61 | |||||||
62 | has ua => ( | ||||||
63 | is => 'ro', | ||||||
64 | #isa => 'Str', | ||||||
65 | default => sub { load_module( $_[0]->connection_class ); $_[0]->connection_class->new }, | ||||||
66 | ); | ||||||
67 | |||||||
68 | sub cmdline { | ||||||
69 | 0 | 0 | 0 | my( $self )= @_; | |||
70 | $self->java, | ||||||
71 | 0 | @{$self->java_args}, | |||||
72 | '-jar', | ||||||
73 | $self->jarfile, | ||||||
74 | #'--port', $self->port, | ||||||
75 | 0 | @{$self->tika_args}, | |||||
0 | |||||||
76 | }; | ||||||
77 | |||||||
78 | 0 | 0 | 0 | sub spawn_child_win32( $self, @cmd ) { | |||
0 | |||||||
0 | |||||||
0 | |||||||
79 | 0 | system(1, @cmd) | |||||
80 | } | ||||||
81 | |||||||
82 | 0 | 0 | 0 | sub spawn_child_posix( $self, @cmd ) { | |||
0 | |||||||
0 | |||||||
0 | |||||||
83 | 0 | require POSIX; | |||||
84 | 0 | POSIX->import("setsid"); | |||||
85 | |||||||
86 | # daemonize | ||||||
87 | 0 | 0 | defined(my $pid = fork()) || die "can't fork: $!"; | ||||
88 | 0 | 0 | if( $pid ) { # non-zero now means I am the parent | ||||
89 | 0 | return $pid; | |||||
90 | }; | ||||||
91 | |||||||
92 | # We are the child, close about everything, then exec | ||||||
93 | 0 | 0 | chdir("/") || die "can't chdir to /: $!"; | ||||
94 | 0 | 0 | (setsid() != -1) || die "Can't start a new session: $!"; | ||||
95 | 0 | 0 | open(STDERR, ">&STDOUT") || die "can't dup stdout: $!"; | ||||
96 | 0 | 0 | open(STDIN, "< /dev/null") || die "can't read /dev/null: $!"; | ||||
97 | 0 | 0 | open(STDOUT, "> /dev/null") || die "can't write to /dev/null: $!"; | ||||
98 | 0 | exec @cmd; | |||||
99 | 0 | exit 1; | |||||
100 | } | ||||||
101 | |||||||
102 | 0 | 0 | 0 | sub spawn_child( $self, @cmd ) { | |||
0 | |||||||
0 | |||||||
0 | |||||||
103 | 0 | my ($pid); | |||||
104 | 0 | 0 | if( $^O =~ /mswin/i ) { | ||||
105 | 0 | $pid = $self->spawn_child_win32(@cmd) | |||||
106 | } else { | ||||||
107 | 0 | $pid = $self->spawn_child_posix(@cmd) | |||||
108 | }; | ||||||
109 | |||||||
110 | 0 | return $pid | |||||
111 | } | ||||||
112 | |||||||
113 | 0 | 0 | 0 | sub launch( $self ) { | |||
0 | |||||||
0 | |||||||
114 | 0 | 0 | if( !$self->pid ) { | ||||
115 | 0 | my $cmdline= join " ", $self->cmdline; # well, for Windows... | |||||
116 | #warn $cmdline; | ||||||
117 | 0 | 0 | my $pid= $self->spawn_child( $self->cmdline ) | ||||
118 | or croak "Couldn't launch [$cmdline]: $!/$^E"; | ||||||
119 | 0 | $self->pid( $pid ); | |||||
120 | 0 | sleep 2; # Java... | |||||
121 | }; | ||||||
122 | } | ||||||
123 | |||||||
124 | sub url { | ||||||
125 | # Should return URI instead | ||||||
126 | 0 | 0 | 0 | my( $self, $type )= @_; | |||
127 | 0 | 0 | $type||= 'text'; | ||||
128 | |||||||
129 | my $url= { | ||||||
130 | text => 'rmeta', | ||||||
131 | test => 'tika', # but GET instead of PUT | ||||||
132 | meta => 'rmeta', | ||||||
133 | #all => 'all', | ||||||
134 | language => 'language/string', | ||||||
135 | all => 'rmeta', | ||||||
136 | # unpack | ||||||
137 | 0 | }->{ $type }; | |||||
138 | |||||||
139 | 0 | sprintf | |||||
140 | 'http://127.0.0.1:%s/%s', | ||||||
141 | $self->port, | ||||||
142 | $url | ||||||
143 | }; | ||||||
144 | |||||||
145 | |||||||
146 | 0 | 0 | 0 | sub await($) { | |||
0 | |||||||
147 | 0 | my $promise = $_[0]; | |||||
148 | 0 | my @res; | |||||
149 | 0 | 0 | if( $promise->is_unfulfilled ) { | ||||
150 | 0 | require AnyEvent; | |||||
151 | 0 | my $await = AnyEvent->condvar; | |||||
152 | 0 | 0 | $promise->then(sub{ $await->send(@_)}); | ||||
0 | |||||||
153 | 0 | @res = $await->recv; | |||||
154 | } else { | ||||||
155 | 0 | @res = @{ $promise->result } | |||||
0 | |||||||
156 | } | ||||||
157 | @res | ||||||
158 | 0 | }; | |||||
159 | |||||||
160 | # /rmeta | ||||||
161 | # /unpacker | ||||||
162 | # /all | ||||||
163 | # /tika | ||||||
164 | # /language | ||||||
165 | # hello world | ||||||
166 | sub fetch { | ||||||
167 | 0 | 0 | 0 | my( $self, %options )= @_; | |||
168 | 0 | 0 | $options{ type }||= 'text'; | ||||
169 | 0 | my $url= $self->url( $options{ type } ); | |||||
170 | |||||||
171 | 0 | 0 | 0 | if(! $options{ content } and $options{ filename }) { | |||
172 | # read $options{ filename } | ||||||
173 | open my $fh, '<', $options{ filename } | ||||||
174 | 0 | 0 | or croak "Couldn't read '$options{ filename }': $!"; | ||||
175 | 0 | binmode $fh; | |||||
176 | 0 | local $/; | |||||
177 | 0 | $options{ content } = <$fh>; | |||||
178 | }; | ||||||
179 | |||||||
180 | 0 | my $method; | |||||
181 | 0 | 0 | if( 'test' eq $options{ type } ) { | ||||
182 | 0 | $method= 'get'; | |||||
183 | |||||||
184 | } else { | ||||||
185 | 0 | $method= 'put'; | |||||
186 | ; | ||||||
187 | }; | ||||||
188 | |||||||
189 | 0 | 0 | my $headers = $options{ headers } || {}; | ||||
190 | |||||||
191 | my ($code,$res) = await | ||||||
192 | 0 | $self->ua->request( $method, $url, $options{ content }, %$headers ); | |||||
193 | 0 | my $info; | |||||
194 | 0 | 0 | 0 | if( 'all' eq $options{ type } | |||
0 | |||||||
195 | or 'text' eq $options{ type } | ||||||
196 | or 'meta' eq $options{ type } ) { | ||||||
197 | 0 | 0 | if( $code !~ /^2..$/ ) { | ||||
198 | 0 | croak "Got HTTP error code $code for '$options{ filename }'"; | |||||
199 | }; | ||||||
200 | 0 | my $item = $res->[0]; | |||||
201 | |||||||
202 | # Should/could this be lazy? | ||||||
203 | 0 | my $c = delete $item->{'X-TIKA:content'}; | |||||
204 | # Ghetto-strip HTML we don't want: | ||||||
205 | 0 | 0 | 0 | if( $c =~ m!(.*)!s or $c =~ m! | !) {|||
206 | 0 | $c = $1; | |||||
207 | |||||||
208 | 0 | 0 | 0 | if( $item->{"Content-Type"} and $item->{"Content-Type"} =~ m!^text/plain\b!) { | |||
209 | # Also strip the enclosing .. |
||||||
210 | 0 | $c =~ s!\A\s* (.*)\s* \s*\z!$1!s; |
|||||
211 | }; | ||||||
212 | } else { | ||||||
213 | 0 | warn "Couldn't find HTML body in response: $c"; | |||||
214 | }; | ||||||
215 | |||||||
216 | 0 | $info= Apache::Tika::DocInfo->new({ | |||||
217 | content => $c, | ||||||
218 | meta => $item, | ||||||
219 | }); | ||||||
220 | |||||||
221 | 0 | 0 | if( ! defined $info->{meta}->{"meta:language"} ) { | ||||
222 | # Yay. Two requests. | ||||||
223 | 0 | my $lang_meta = $self->fetch(%options, type => 'language', 'Content-Type' => $item->{'Content-Type'}); | |||||
224 | 0 | $info->{meta}->{"meta:language"} = $lang_meta->meta->{"info"}; | |||||
225 | }; | ||||||
226 | |||||||
227 | } else { | ||||||
228 | # Must be '/language' | ||||||
229 | 0 | 0 | if( $code !~ /^2..$/ ) { | ||||
230 | 0 | croak "Got HTTP error code $code"; | |||||
231 | }; | ||||||
232 | 0 | 0 | if( ref $res ) { | ||||
233 | 0 | $res = $res->[0]; | |||||
234 | } else { | ||||||
235 | 0 | $res = { info => $res }; | |||||
236 | }; | ||||||
237 | |||||||
238 | 0 | my $c = delete $res->{'X-TIKA:content'}; | |||||
239 | 0 | $info= Apache::Tika::DocInfo->new({ | |||||
240 | meta => $res, | ||||||
241 | content => undef, | ||||||
242 | }); | ||||||
243 | }; | ||||||
244 | 0 | $info | |||||
245 | } | ||||||
246 | |||||||
247 | sub DEMOLISH { | ||||||
248 | 0 | 0 | 0 | 0 | 0 | kill -9 => $_[0]->pid | |
249 | if( $_[0] and $_[0]->pid ); | ||||||
250 | } | ||||||
251 | |||||||
252 | __PACKAGE__->meta->make_immutable; | ||||||
253 | |||||||
254 | 1; | ||||||
255 | |||||||
256 | =head1 REPOSITORY | ||||||
257 | |||||||
258 | The public repository of this module is | ||||||
259 | L |
||||||
260 | |||||||
261 | =head1 SUPPORT | ||||||
262 | |||||||
263 | The public support forum of this module is | ||||||
264 | L |
||||||
265 | |||||||
266 | =head1 BUG TRACKER | ||||||
267 | |||||||
268 | Please report bugs in this module via the RT CPAN bug queue at | ||||||
269 | L |
||||||
270 | or via mail to L |
||||||
271 | |||||||
272 | =head1 AUTHOR | ||||||
273 | |||||||
274 | Max Maischein C |
||||||
275 | |||||||
276 | =head1 COPYRIGHT (c) | ||||||
277 | |||||||
278 | Copyright 2014-2019 by Max Maischein C |
||||||
279 | |||||||
280 | =head1 LICENSE | ||||||
281 | |||||||
282 | This module is released under the same terms as Perl itself. | ||||||
283 | |||||||
284 | =cut |