File Coverage

blib/lib/Browsermob/Proxy.pm
Criterion Covered Total %
statement 49 76 64.4
branch 10 32 31.2
condition 2 6 33.3
subroutine 13 20 65.0
pod 8 10 80.0
total 82 144 56.9


line stmt bran cond sub pod time code
1             package Browsermob::Proxy;
2             $Browsermob::Proxy::VERSION = '0.17';
3             # ABSTRACT: Perl client for the proxies created by the Browsermob server
4 2     2   106580 use Moo;
  2         34399  
  2         13  
5 2     2   2965 use Carp;
  2         5  
  2         134  
6 2     2   1937 use JSON;
  2         26907  
  2         10  
7 2     2   1920 use Net::HTTP::Spore;
  2         3846790  
  2         79  
8 2     2   1907 use Net::HTTP::Spore::Middleware::DefaultParams;
  2         24213  
  2         64  
9 2     2   1751 use Net::HTTP::Spore::Middleware::Format::Text;
  2         43236  
  2         2556  
10              
11              
12             my $spec = {
13             name => 'BrowserMob Proxy',
14             formats => ['json'],
15             version => '0.01',
16             # server name and port are constructed in the _spore builder
17             # base_url => '/proxy',
18             methods => {
19             get_proxies => {
20             method => 'GET',
21             path => '/',
22             description => 'Get a list of ports attached to ProxyServer instances managed by ProxyManager'
23             },
24             create => {
25             method => 'POST',
26             path => '/',
27             optional_params => [
28             'port'
29             ],
30             description => 'Create a new proxy. Returns a JSON object {"port": your_port} on success"'
31             },
32             delete_proxy => {
33             method => 'DELETE',
34             path => '/:port',
35             required_params => [
36             'port'
37             ],
38             description => 'Shutdown the proxy and close the port'
39             },
40             create_new_har => {
41             method => 'PUT',
42             path => '/:port/har',
43             optional_params => [
44             'initialPageRef',
45             'captureHeaders',
46             'captureContent',
47             'captureBinaryContent'
48             ],
49             required_params => [
50             'port'
51             ],
52             description => 'creates a new HAR attached to the proxy and returns the HAR content if there was a previous HAR.'
53             },
54             retrieve_har => {
55             method => 'GET',
56             path => '/:port/har',
57             required_params => [
58             'port'
59             ],
60             description => 'returns the JSON/HAR content representing all the HTTP traffic passed through the proxy'
61             },
62             auth_basic => {
63             method => 'POST',
64             path => '/:port/auth/basic/:domain',
65             required_params => [
66             'port',
67             'domain'
68             ],
69             description => 'Sets automatic basic authentication for the specified domain'
70             },
71             filter_request => {
72             method => 'POST',
73             path => '/:port/filter/request',
74             required_params => [
75             'port'
76             ],
77             description => 'Modify request/payload with javascript'
78             },
79             set_timeout => {
80             method => 'PUT',
81             path => '/:port/timeout',
82             required_params => [
83             'port',
84             ],
85             optional_params => [
86             'requestTimeout',
87             'readTimeout',
88             'connectionTimeout',
89             'dnsCacheTimeout'
90             ],
91             description => 'Handles different proxy timeouts'
92             }
93             }
94             };
95              
96              
97             has server_addr => (
98             is => 'rw',
99             default => sub { '127.0.0.1' }
100             );
101              
102              
103              
104             has server_port => (
105             is => 'rw',
106             default => sub { 8080 }
107             );
108              
109              
110             has port => (
111             is => 'rw',
112             lazy => 1,
113             predicate => 'has_port',
114             default => sub { '' }
115             );
116              
117              
118             has trace => (
119             is => 'ro',
120             default => sub { 0 }
121             );
122              
123             has mock => (
124             is => 'rw',
125             lazy => 1,
126             predicate => 'has_mock',
127             default => sub { '' }
128             );
129              
130             has _spore => (
131             is => 'ro',
132             lazy => 1,
133             handles => [keys %{ $spec->{methods} }],
134             builder => sub {
135 3     3   1441 my $self = shift;
136 3         28 my $client = Net::HTTP::Spore->new_from_string(
137             to_json($self->_spec),
138             trace => $self->trace
139             );
140              
141 3         186630 $self->_set_middlewares($client, 'json');
142              
143 3         35717 return $client;
144             }
145             );
146              
147             around filter_request => sub {
148             my ($orig, $self, @args) = @_;
149             my $client = $self->_spore;
150             $self->_set_middlewares($client, 'text');
151             $orig->($self, @args);
152             $self->_set_middlewares($client, 'json');
153             };
154              
155             sub _set_middlewares {
156 8     8   808 my ($self, $client, $type) = @_;
157 8         69 $client->reset_middlewares;
158              
159 8 100       515 if ($type eq 'json') {
    50          
160 7         40 $client->enable('Format::JSON');
161             }
162             elsif ($type eq 'text') {
163 1         4 $client->enable('Format::Text');
164             }
165              
166 8 100       35849 if ($self->has_port) {
167 5         99 $client->enable('DefaultParams', default_params => {
168             port => $self->port
169             });
170             }
171              
172 8 50       908 if ($self->has_mock) {
173             # The Mock middleware ignores any middleware enabled after
174             # it; make sure to enable everything else first.
175 8         136 $client->enable('Mock', tests => $self->mock);
176             }
177             }
178              
179             has _spec => (
180             is => 'ro',
181             lazy => 1,
182             builder => sub {
183 3     3   729 my $self = shift;
184 3         41 $spec->{base_url} = 'http://' . $self->server_addr . ':' . $self->server_port . '/proxy';
185 3         22 return $spec;
186             }
187             );
188              
189             sub BUILD {
190 3     3 0 24 my ($self, $args) = @_;
191 3         28 my $res = $self->create;
192              
193 3 50       51020 unless ($self->has_port) {
194 3         14 $self->port($res->body->{port});
195 3         1092 $self->_set_middlewares( $self->_spore, 'json' );
196             }
197             }
198              
199              
200             sub new_har {
201 0     0 1 0 my ($self, $initial_page_ref) = @_;
202 0         0 my $payload = {};
203              
204 0 0       0 croak "You need to create a proxy first!" unless $self->has_port;
205 0 0       0 if (defined $initial_page_ref) {
206 0         0 $payload->{initialPageRef} = $initial_page_ref;
207             }
208              
209 0         0 $self->_spore->create_new_har(payload => $payload);
210             }
211              
212              
213             sub har {
214 0     0 1 0 my ($self) = @_;
215              
216 0 0       0 croak "You need to create a proxy first!" unless $self->has_port;
217 0         0 return $self->_spore->retrieve_har->body;
218             }
219              
220              
221             sub selenium_proxy {
222 0     0 1 0 my ($self, $initiate_manually) = @_;
223 0 0       0 $self->new_har unless $initiate_manually;
224              
225             return {
226 0         0 proxyType => 'manual',
227             httpProxy => 'http://' . $self->server_addr . ':' . $self->port,
228             sslProxy => 'http://' . $self->server_addr . ':' . $self->port
229             };
230             }
231              
232              
233             sub firefox_proxy {
234 0     0 1 0 my ($self, $initiate_manually) = @_;
235 0 0       0 $self->new_har unless $initiate_manually;
236              
237             return {
238 0         0 'network.proxy.type' => 1,
239             'network.proxy.http' => $self->server_addr,
240             'network.proxy.http_port' => $self->port,
241             'network.proxy.ssl' => $self->server_addr,
242             'network.proxy.ssl_port' => $self->port
243             };
244             }
245              
246              
247             sub ua_proxy {
248 0     0 1 0 my ($self, $initiate_manually) = @_;
249 0 0       0 $self->new_har unless $initiate_manually;
250              
251 0         0 return ('http', 'http://' . $self->server_addr . ':' . $self->port);
252             }
253              
254              
255             sub set_env_proxy {
256 0     0 1 0 my ($self, $initiate_manually) = @_;
257 0 0       0 $self->new_har unless $initiate_manually;
258              
259 0         0 $ENV{http_proxy} = 'http://' . $self->server_addr . ':' . $self->port;
260 0         0 $ENV{https_proxy} = 'http://' . $self->server_addr . ':' . $self->port;
261 0         0 $ENV{ssl_proxy} = 'http://' . $self->server_addr . ':' . $self->port;
262             }
263              
264              
265             sub add_basic_auth {
266 0     0 1 0 my ($self, $args) = @_;
267 0         0 foreach (qw/domain username password/) {
268             croak "$_ is a required parameter for add_basic_auth"
269 0 0       0 unless exists $args->{$_};
270             }
271              
272             $self->auth_basic(
273             domain => delete $args->{domain},
274 0         0 payload => $args
275             );
276             }
277              
278              
279             sub set_request_header {
280 1     1 1 700 my ($self, $header, $value) = @_;
281 1 50 33     10 croak 'Please pass a ($header, $value) as arguments when setting a header'
282             unless $header and $value;
283              
284 1         4 $self->_set_header('request', $header, $value);
285             }
286              
287             sub _set_header {
288 1     1   3 my ($self, $type, $header, $value) = @_;
289              
290 1         36 $self->filter_request(
291             payload => "
292             $type.headers().remove('$header');
293             $type.headers().add('$header', '$value');
294             "
295             );
296             }
297              
298              
299              
300             sub DEMOLISH {
301 1     1 0 1263 my ($self, $gd) = @_;
302 1 50       5 return if $gd;
303              
304 1         3 eval { $self->delete_proxy };
  1         6  
305 1 50 33     3696 warn $@ if $@ and $self->trace;
306             }
307              
308             1;
309              
310             __END__
311              
312             =pod
313              
314             =encoding UTF-8
315              
316             =head1 NAME
317              
318             Browsermob::Proxy - Perl client for the proxies created by the Browsermob server
319              
320             =for markdown [![Build Status](https://travis-ci.org/gempesaw/Browsermob-Proxy.svg?branch=master)](https://travis-ci.org/gempesaw/Browsermob-Proxy)
321              
322             =head1 VERSION
323              
324             version 0.17
325              
326             =head1 SYNOPSIS
327              
328             Standalone:
329              
330             my $proxy = Browsermob::Proxy->new(
331             server_port => 9090
332             # port => 9092
333             );
334              
335             print $proxy->port;
336             $proxy->new_har('Google');
337             # create network traffic across your port
338             $proxy->har; # returns a HAR as a hashref, converted from JSON
339              
340             with L<Browsermob::Server>:
341              
342             my $server = Browsermob::Server->new(
343             server_port => 9090
344             );
345             $server->start; # ignore if your server is already running
346              
347             my $proxy = $server->create_proxy;
348             $proxy->new_har('proxy from server!');
349              
350             =head1 DESCRIPTION
351              
352             From L<http://bmp.lightbody.net/>:
353              
354             =over 4
355              
356             BrowserMob proxy is based on technology developed in the Selenium open
357             source project and a commercial load testing and monitoring service
358             originally called BrowserMob and now part of Neustar.
359              
360             It can capture performance data for web apps (via the HAR format), as
361             well as manipulate browser behavior and traffic, such as whitelisting
362             and blacklisting content, simulating network traffic and latency, and
363             rewriting HTTP requests and responses.
364              
365             =back
366              
367             This module is a Perl client interface to interact with the server and
368             its proxies. It uses L<Net::HTTP::Spore>. You can use
369             L<Browsermob::Server> to manage the server itself in addition to using
370             this module to handle the proxies.
371              
372             =head2 INSTALLATION
373              
374             We depend on L<Net::HTTP::Spore> to set up our communication with the
375             Browsermob server. Unfortunately, there hasn't been a recent release
376             and due to breaking changes in new versions of its dependencies, you
377             might run in to problems installing its current CPAN version
378             v0.06. And, thus installing this module may be difficult.
379              
380             We're using a fork of L<Net::HTTP::Spore> that is kept slightly ahead
381             of master with the bug fixes merged in; installation via
382             L<App::cpanminus> looks like:
383              
384             cpanm git://github.com/gempesaw/net-http-spore.git@build/master
385              
386             =head1 ATTRIBUTES
387              
388             =head2 server_addr
389              
390             Optional: specify where the proxy server is; defaults to 127.0.0.1
391              
392             my $proxy = Browsermob::Proxy->new(server_addr => '127.0.0.1');
393              
394             =head2 server_port
395              
396             Optional: Indicate at what port we should expect a Browsermob Server
397             to be running; defaults to 8080
398              
399             my $proxy = Browsermob::Proxy->new(server_port => 8080);
400              
401             =head2 port
402              
403             Optional: When instantiating a proxy, you can choose the proxy port on
404             your own, or let the server automatically assign you an unused port.
405              
406             my $proxy = Browsermob::Proxy->new(port => 9091);
407              
408             =head2 trace
409              
410             Set Net::HTTP::Spore's trace option; defaults to 0; set it to 1 to see
411             headers and 2 to see headers and responses. This can only be set during
412             construction; changing it afterwards will have no impact.
413              
414             my $proxy = Browsermob::Proxy->new( trace => 2 );
415              
416             =head1 METHODS
417              
418             =head2 new_har
419              
420             After creating a proxy, C<new_har> creates a new HAR attached to the
421             proxy and returns the HAR content if there was a previous one. If no
422             argument is passed, the initial page ref will be "Page 1"; you can
423             also pass a string to choose your own initial page ref.
424              
425             $proxy->new_har;
426             $proxy->new_har('Google');
427              
428             This convenience method is just a helper around the actual endpoint
429             method C</create_new_har>; it uses the defaults of not capturing
430             headers, request/response bodies, or binary content. If you'd like to
431             capture those items, you can use C<create_new_har> as follows:
432              
433             $proxy->create_new_har(
434             payload => {
435             initialPageRef => 'payload is optional'
436             },
437             captureHeaders => 'true',
438             captureContent => 'true',
439             captureBinaryContent => 'true'
440             );
441              
442             =head2 har
443              
444             After creating a proxy and initiating a L<new_har>, you can retrieve
445             the contents of the current HAR with this method. It returns a hashref
446             HAR, and may in the future return an isntance of L<Archive::HAR>.
447              
448             my $har = $proxy->har;
449             print Dumper $har->{log}->{entries}->[0];
450              
451             =head2 selenium_proxy
452              
453             Generate the proper capabilities for use in the constructor of a new
454             Selenium::Remote::Driver object.
455              
456             my $proxy = Browsermob::Proxy->new;
457             my $driver = Selenium::Remote::Driver->new(
458             browser_name => 'chrome',
459             proxy => $proxy->selenium_proxy
460             );
461             $driver->get('http://www.google.com');
462             print Dumper $proxy->har;
463              
464             N.B.: C<selenium_proxy> will AUTOMATICALLY call L</new_har> for you
465             initiating an unnamed har, unless you pass it something truthy.
466              
467             my $proxy = Browsermob::Proxy->new;
468             my $driver = Selenium::Remote::Driver->new(
469             browser_name => 'chrome',
470             proxy => $proxy->selenium_proxy(1)
471             );
472             # later
473             $proxy->new_har;
474             $driver->get('http://www.google.com');
475             print Dumper $proxy->har;
476              
477             =head2 firefox_proxy
478              
479             Generate a hash with the proper keys and values that for use in
480             setting preferences for a
481             L<Selenium::Remote::Driver::Firefox::Profile>. This method returns a
482             hashref; dereference it when you pass it to
483             L<Selenium::Remote::Driver::Firefox::Profile/set_preference>:
484              
485             my $profile = Selenium::Remote::Driver::Firefox::Profile->new;
486              
487             my $firefox_pref = $proxy->firefox_proxy;
488             $profile->set_preference( %{ $firefox_pref } );
489              
490             my $driver = Selenium::Remote::Driver->new_from_caps(
491             desired_capabilities => {
492             browserName => 'Firefox',
493             firefox_profile => $profile->_encode
494             }
495             );
496              
497             N.B.: C<firefox_proxy> will AUTOMATICALLY call L</new_har> for you
498             initiating an unnamed har, unless you pass it something truthy.
499              
500             =head2 ua_proxy
501              
502             Generate the proper arguments for the proxy method of
503             L<LWP::UserAgent>. By default, C<ua_proxy> will initiate a new har for
504             you automatically, the same as L</selenium_proxy> does. If you want to
505             initialize the har yourself, pass in something truthy.
506              
507             my $proxy = Browsermob::Proxy->new;
508             my $ua = LWP::UserAgent->new;
509             $ua->proxy($proxy->ua_proxy);
510              
511             =head2 set_env_proxy
512              
513             Export to C<%ENV> the properties of this proxy's port. This can be
514             used in tandem with <LWP::UserAgent/env_proxy>. This will set the
515             appropriate environment variables, and then your C<$ua> will pick it
516             up when its C<env_proxy> method is invoked aftewards. As usual, this
517             will create a new HAR unless you deliberately inhibit it.
518              
519             $proxy->set_env_proxy;
520             $ua->env_proxy;
521              
522             In particular, we set C<http_proxy>, C<https_proxy>, and C<ssl_proxy>
523             to the appropriate server and port by defining them as keys in C<%ENV>.
524              
525             =head2 add_basic_auth
526              
527             Set up automatic Basic authentication for a specified domain. Accepts
528             as input a HASHREF with the keys C<domain>, C<username>, and
529             C<password>. For example,
530              
531             $proxy->add_basic_auth({
532             domain => '.google.com',
533             username => 'username',
534             password => 'password'
535             });
536              
537             =head2 set_request_header ( $header, $value )
538              
539             Takes two STRINGs as arguments. (Unhelpfully) returns a
540             Net::HTTP::Spore::Response. With this method, we will remove the
541             specified C<$header> from every request the proxy sees, and replace it
542             with the C<$header> C<$value> pair that you pass in.
543              
544             $proxy->set_request_header( 'User-Agent', 'superwoman' );
545              
546             Under the covers, we are using L</filter_request> with a Javascript
547             Rhino payload.
548              
549             =head2 set_timeout ( $timeoutType => $milliseconds )
550              
551             Set different time outs on the instantiated proxy. You can set
552             multiple timeouts at once, if you like.
553              
554             $proxy->timeout(
555             requestTimeout => 5000,
556             readTimeout => 6000
557             );
558              
559             =over 4
560              
561             =item *
562              
563             requestTimeout
564              
565             Request timeout in milliseconds. A timeout value of -1 is interpreted
566             as infinite timeout. It equals -1 by default.
567              
568             =item *
569              
570             readTimeout
571              
572             Read timeout is the timeout for waiting for data or, put differently,
573             a maximum period inactivity between two consecutive data packets. A
574             timeout value of zero is interpreted as an infinite timeout. It equals
575             60000 by default.
576              
577             =item *
578              
579             connectionTimeout
580              
581             Determines the timeout in milliseconds until a connection is
582             established. A timeout value of zero is interpreted as an infinite
583             timeout. It eqauls 60000 by default.
584              
585             =item *
586              
587             dnsCacheTimeout
588              
589             Sets the maximum length of time that records will be stored in this
590             Cache. A nonpositive value disables this feature (that is, sets no
591             limit). It equals 0 by default.
592              
593             =back
594              
595             =head2 delete_proxy
596              
597             Delete the proxy off of the server, shutting down the port. Although
598             we do try to do this in our DEMOLISH method, we can't do anything if
599             the C<$proxy> object is kept around during global destruction. If
600             you're noticing that your BMP server has leftover proxies, you should
601             start either explicitly C<undef>ing the `$proxy` object or invoking
602             this method.
603              
604             # calls ->delete_proxy in our DEMOLISH method, explicitly not
605             # during global destruction!
606             undef $proxy;
607              
608             # manually delete the proxy from the BMP server
609             $proxy->delete_proxy;
610              
611             After deleting the proxy, invoking any other method will probably lead
612             to a C<die> from inside the Net::HTTP::Spore module somewhere.
613              
614             =head1 SEE ALSO
615              
616             Please see those modules/websites for more information related to this module.
617              
618             =over 4
619              
620             =item *
621              
622             L<http://bmp.lightbody.net/|http://bmp.lightbody.net/>
623              
624             =item *
625              
626             L<https://github.com/lightbody/browsermob-proxy|https://github.com/lightbody/browsermob-proxy>
627              
628             =item *
629              
630             L<Browsermob::Server|Browsermob::Server>
631              
632             =back
633              
634             =head1 BUGS
635              
636             Please report any bugs or feature requests on the bugtracker website
637             https://github.com/gempesaw/Browsermob-Proxy/issues
638              
639             When submitting a bug or request, please include a test-file or a
640             patch to an existing test-file that illustrates the bug or desired
641             feature.
642              
643             =head1 AUTHOR
644              
645             Daniel Gempesaw <gempesaw@gmail.com>
646              
647             =head1 COPYRIGHT AND LICENSE
648              
649             This software is copyright (c) 2014 by Daniel Gempesaw.
650              
651             This is free software; you can redistribute it and/or modify it under
652             the same terms as the Perl 5 programming language system itself.
653              
654             =cut