File Coverage

blib/lib/LWP/Protocol/PSGI.pm
Criterion Covered Total %
statement 77 83 92.7
branch 24 28 85.7
condition n/a
subroutine 22 23 95.6
pod 4 6 66.6
total 127 140 90.7


line stmt bran cond sub pod time code
1             package LWP::Protocol::PSGI;
2              
3 4     4   703890 use strict;
  4         16  
  4         97  
4 4     4   115 use 5.008_001;
  4         13  
5             our $VERSION = '0.11';
6              
7 4     4   463 use parent qw(LWP::Protocol);
  4         273  
  4         23  
8 4     4   50556 use HTTP::Message::PSGI qw( req_to_psgi res_from_psgi );
  4         38691  
  4         237  
9 4     4   28 use Carp;
  4         8  
  4         1836  
10              
11             my @protocols = qw( http https );
12             my %orig;
13              
14             my @apps;
15              
16             sub register {
17 20     20 1 7837 my $class = shift;
18              
19 20         72 my $app = LWP::Protocol::PSGI::App->new(@_);
20 20         44 unshift @apps, $app;
21              
22             # register this guy (as well as saving original code) once
23 20 100       57 if (! scalar keys %orig) {
24 4         12 for my $proto (@protocols) {
25 8 100       66 if (my $orig = LWP::Protocol::implementor($proto)) {
26 4         77961 $orig{$proto} = $orig;
27 4         16 LWP::Protocol::implementor($proto, $class);
28             } else {
29 4 50       987 Carp::carp("LWP::Protocol::$proto is unavailable. Skip registering overrides for it.") if $^W;
30             }
31             }
32             }
33              
34 20 100       101 if (defined wantarray) {
35             return LWP::Protocol::PSGI::Guard->new(sub {
36 18     18   53 $class->unregister_app($app);
37 18         92 });
38             }
39             }
40              
41             sub unregister_app {
42 18     18 0 35 my ($class, $app) = @_;
43              
44 18         25 my $i = 0;
45 18         32 foreach my $stored_app (@apps) {
46 18 50       40 if ($app == $stored_app) {
47 18         25 splice @apps, $i, 1;
48 18         82 return;
49             }
50 0         0 $i++;
51             }
52             }
53            
54              
55             sub unregister {
56 1     1 1 4231 my $class = shift;
57 1         4 for my $proto (@protocols) {
58 2 100       14 if ($orig{$proto}) {
59 1         4 LWP::Protocol::implementor($proto, $orig{$proto});
60             }
61             }
62 1         7 @apps = ();
63 1         3 %orig = ();
64             }
65              
66             sub request {
67 15     15 1 47558 my($self, $request, $proxy, $arg, @rest) = @_;
68              
69 15 50       37 if (my $app = $self->handles($request)) {
70 15         45 my $env = req_to_psgi $request;
71 15         10322 my $response = res_from_psgi $app->app->($env);
72 15         2775 my $content = $response->content;
73 15         160 $response->content('');
74 15         257 $self->collect_once($arg, $response, $content);
75             } else {
76 0         0 $orig{$self->{scheme}}->new($self->{scheme}, $self->{ua})->request($request, $proxy, $arg, @rest);
77             }
78             }
79              
80             # for testing
81             sub create {
82 0     0 1 0 my $class = shift;
83 0         0 push @apps, LWP::Protocol::PSGI::App->new(@_);
84 0         0 $class->new;
85             }
86              
87             sub handles {
88 26     26 0 1843 my($self, $request) = @_;
89              
90 26         46 foreach my $app (@apps) {
91 27 100       54 if ($app->match($request)) {
92 23         123 return $app;
93             }
94             }
95             }
96              
97             package
98             LWP::Protocol::PSGI::Guard;
99 4     4   26 use strict;
  4         8  
  4         299  
100              
101             sub new {
102 18     18   39 my($class, $code) = @_;
103 18         48 bless $code, $class;
104             }
105              
106             sub DESTROY {
107 18     18   9257 my $self = shift;
108 18         44 $self->();
109             }
110              
111             package
112             LWP::Protocol::PSGI::App;
113 4     4   21 use strict;
  4         8  
  4         1443  
114              
115             sub new {
116 20     20   52 my ($class, $app, %options) = @_;
117 20         65 bless { app => $app, options => \%options }, $class;
118             }
119              
120 15     15   43 sub app { $_[0]->{app} }
121 27     27   55 sub options { $_[0]->{options} }
122             sub match {
123 27     27   41 my ($self, $request) = @_;
124 27         46 my $options = $self->options;
125              
126 27 100       67 if ($options->{host}) {
    100          
127 13         33 my $matcher = $self->_matcher($options->{host});
128 13 100       887 $matcher->($request->uri->host) || $matcher->($request->uri->host_port);
129             } elsif ($options->{uri}) {
130 4         9 $self->_matcher($options->{uri})->($request->uri);
131             } else {
132 10         24 1;
133             }
134             }
135              
136             sub _matcher {
137 17     17   50 my($self, $stuff) = @_;
138 17 100       52 if (ref $stuff eq 'Regexp') {
    100          
    50          
139 3     4   8 sub { $_[0] =~ $stuff };
  4         129  
140             } elsif (ref $stuff eq 'CODE') {
141 3         6 $stuff;
142             } elsif (!ref $stuff) {
143 11     14   37 sub { $_[0] eq $stuff };
  14         565  
144             } else {
145 0           Carp::croak("Don't know how to match: ", ref $stuff);
146             }
147             }
148              
149             1;
150             __END__
151              
152             =encoding utf-8
153              
154             =for stopwords
155              
156             =head1 NAME
157              
158             LWP::Protocol::PSGI - Override LWP's HTTP/HTTPS backend with your own PSGI application
159              
160             =head1 SYNOPSIS
161              
162             use LWP::UserAgent;
163             use LWP::Protocol::PSGI;
164              
165             # $app can be any PSGI application: Mojolicious, Catalyst or your own
166             my $app = do {
167             use Dancer;
168             set apphandler => 'PSGI';
169             get '/search' => sub {
170             return 'searching for ' . params->{q};
171             };
172             dance;
173             };
174              
175             # Register the $app to handle all LWP requests
176             LWP::Protocol::PSGI->register($app);
177              
178             # can hijack any code or module that uses LWP::UserAgent underneath, with no changes
179             my $ua = LWP::UserAgent->new;
180             my $res = $ua->get("http://www.google.com/search?q=bar");
181             print $res->content; # "searching for bar"
182              
183             # Only hijacks specific host (and port)
184             LWP::Protocol::PSGI->register($psgi_app, host => 'localhost:3000');
185              
186             my $ua = LWP::UserAgent->new;
187             $ua->get("http://localhost:3000/app"); # this routes $app
188             $ua->get("http://google.com/api"); # this doesn't - handled with actual HTTP requests
189              
190             =head1 DESCRIPTION
191              
192             LWP::Protocol::PSGI is a module to hijack B<any> code that uses
193             L<LWP::UserAgent> underneath such that any HTTP or HTTPS requests can
194             be routed to your own PSGI application.
195              
196             Because it works with any code that uses LWP, you can override various
197             WWW::*, Net::* or WebService::* modules such as L<WWW::Mechanize>,
198             without modifying the calling code or its internals.
199              
200             use WWW::Mechanize;
201             use LWP::Protocol::PSGI;
202              
203             LWP::Protocol::PSGI->register($my_psgi_app);
204              
205             my $mech = WWW::Mechanize->new;
206             $mech->get("http://amazon.com/"); # $my_psgi_app runs
207              
208             =head1 TESTING
209              
210             This module is extremely handy if you have tests that run HTTP
211             requests against your application and want them to work with both
212             internal and external instances.
213              
214             # in your .t file
215             use Test::More;
216             use LWP::UserAgent;
217              
218             unless ($ENV{TEST_LIVE}) {
219             require LWP::Protocol::PSGI;
220             my $app = Plack::Util::load_psgi("app.psgi");
221             LWP::Protocol::PSGI->register($app);
222             }
223              
224             my $ua = LWP::UserAgent->new;
225             my $res = $ua->get("http://myapp.example.com/");
226             is $res->code, 200;
227             like $res->content, qr/Hello/;
228              
229             This test script will by default route all HTTP requests to your own
230             PSGI app defined in C<$app>, but with the environment variable
231             C<TEST_LIVE> set, runs the requests against the live server.
232              
233             You can also combine L<Plack::App::Proxy> with L<LWP::Protocol::PSGI>
234             to route all requests made in your test against a specific server.
235              
236             use LWP::Protocol::PSGI;
237             use Plack::App::Proxy;
238              
239             my $app = Plack::App::Proxy->new(remote => "http://testapp.local:3000")->to_app;
240             LWP::Protocol::PSGI->register($app);
241              
242             my $ua = LWP::UserAgent->new;
243             my $res = $ua->request("http://testapp.com"); # this hits testapp.local:3000
244              
245             =head1 METHODS
246              
247             =over 4
248              
249             =item register
250              
251             LWP::Protocol::PSGI->register($app, %options);
252             my $guard = LWP::Protocol::PSGI->register($app, %options);
253              
254             Registers an override hook to hijack HTTP requests. If called in a
255             non-void context, returns a guard object that automatically resets
256             the override when it goes out of context.
257              
258             {
259             my $guard = LWP::Protocol::PSGI->register($app);
260             # hijack the code using LWP with $app
261             }
262              
263             # now LWP uses the original HTTP implementations
264              
265             When C<%options> is specified, the option limits which URL and hosts
266             this handler overrides. You can either pass C<host> or C<uri> to match
267             requests, and if it doesn't match, the handler falls back to the
268             original LWP HTTP protocol implementor.
269              
270             LWP::Protocol::PSGI->register($app, host => 'www.google.com');
271             LWP::Protocol::PSGI->register($app, host => qr/\.google\.com$/);
272             LWP::Protocol::PSGI->register($app, uri => sub { my $uri = shift; ... });
273              
274             The options can take either a string, where it does a complete match, a
275             regular expression or a subroutine reference that returns boolean
276             given the value of C<host> (only the hostname) or C<uri> (the whole
277             URI, including query parameters).
278              
279             =item unregister
280              
281             LWP::Protocol::PSGI->unregister;
282              
283             Resets all the overrides for LWP. If you use the guard interface
284             described above, it will be automatically called for you.
285              
286             =back
287              
288             =head1 DIFFERENCES WITH OTHER MODULES
289              
290             =head2 Mock vs Protocol handlers
291              
292             There are similar modules on CPAN that allows you to emulate LWP
293             requests and responses. Most of them are implemented as a mock
294             library, which means it doesn't go through the LWP guts and just gives
295             you a wrapper for receiving HTTP::Request and returning HTTP::Response
296             back.
297              
298             LWP::Protocol::PSGI is implemented as an LWP protocol handler and it
299             allows you to use most of the LWP extensions to add capabilities such
300             as manipulating headers and parsing cookies.
301              
302             =head2 Test::LWP::UserAgent
303              
304             L<Test::LWP::UserAgent> has the similar concept of overriding LWP
305             request method with particular PSGI applications. It has more features
306             and options such as passing through the requests to the native LWP
307             handler, while LWP::Protocol::PSGI only allows one to map certain hosts
308             and ports.
309              
310             Test::LWP::UserAgent requires you to change the instantiation of
311             UserAgent from C<< LWP::UserAgent->new >> to C<<
312             Test::LWP::UserAgent->new >> somehow and it's your responsibility to
313             do so. This mechanism gives you more control which requests should go
314             through the PSGI app, and it might not be difficult if the creation is
315             done in one place in your code base. However it might be hard or even
316             impossible when you are dealing with third party modules that calls
317             LWP::UserAgent inside.
318              
319             LWP::Protocol::PSGI affects the LWP calling code more globally, while
320             having an option to enable it only in a specific block, thus there's
321             no need to change the UserAgent object manually, whether it is in your
322             code or CPAN modules.
323              
324             =head1 AUTHOR
325              
326             Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
327              
328             =head1 COPYRIGHT
329              
330             Copyright 2011- Tatsuhiko Miyagawa
331              
332             =head1 LICENSE
333              
334             This library is free software; you can redistribute it and/or modify
335             it under the same terms as Perl itself.
336              
337             =head1 SEE ALSO
338              
339             L<Plack::Client> L<LWP::UserAgent>
340              
341             =cut