File Coverage

blib/lib/LWP/Protocol/PSGI.pm
Criterion Covered Total %
statement 68 79 86.0
branch 21 28 75.0
condition n/a
subroutine 21 23 91.3
pod 4 6 66.6
total 114 136 83.8


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