File Coverage

lib/Catalyst/TraitFor/Request/REST/ForBrowsers.pm
Criterion Covered Total %
statement 19 19 100.0
branch 12 12 100.0
condition 5 6 83.3
subroutine 3 3 100.0
pod n/a
total 39 40 97.5


line stmt bran cond sub pod time code
1             package Catalyst::TraitFor::Request::REST::ForBrowsers;
2             $Catalyst::TraitFor::Request::REST::ForBrowsers::VERSION = '1.21';
3 7     7   3530 use Moose::Role;
  7         15  
  7         97  
4 7     7   30780 use namespace::autoclean;
  7         14  
  7         38  
5              
6             with 'Catalyst::TraitFor::Request::REST';
7              
8             has _determined_real_method => (
9                 is => 'rw',
10                 isa => 'Bool',
11             );
12              
13             has looks_like_browser => (
14                 is => 'rw',
15                 isa => 'Bool',
16                 lazy => 1,
17                 builder => '_build_looks_like_browser',
18                 init_arg => undef,
19             );
20              
21             # All this would be much less gross if Catalyst::Request used a builder to
22             # determine the method. Then we could just wrap the builder.
23             around method => sub {
24                 my $orig = shift;
25                 my $self = shift;
26              
27                 return $self->$orig(@_)
28                     if @_ || $self->_determined_real_method;
29              
30                 my $method = $self->$orig();
31              
32                 my $tunneled;
33                 if ( defined $method && uc $method eq 'POST' ) {
34                     $tunneled = $self->param('x-tunneled-method')
35                         || $self->header('x-http-method-override');
36                 }
37              
38                 $self->$orig( defined $tunneled ? uc $tunneled : $method );
39              
40                 $self->_determined_real_method(1);
41              
42                 return $self->$orig();
43             };
44              
45             {
46                 my %HTMLTypes = map { $_ => 1 } qw(
47             text/html
48             application/xhtml+xml
49             );
50              
51                 sub _build_looks_like_browser {
52 21     21   31         my $self = shift;
53              
54 21         52         my $with = $self->header('x-requested-with');
55                     return 0
56 21 100 66     1519             if $with && grep { $with eq $_ }
  8         132  
57                             qw( HTTP.Request XMLHttpRequest );
58              
59 17 100       51         if ( uc $self->method eq 'GET' ) {
60 16         133             my $forced_type = $self->param('content-type');
61                         return 0
62 16 100 100     1172                 if $forced_type && !$HTMLTypes{$forced_type};
63                     }
64              
65             # IE7 does not say it accepts any form of html, but _does_
66             # accept */* (helpful ;)
67 15 100       59         return 1
68                         if $self->accepts('*/*');
69              
70                     return 1
71 13 100       34             if grep { $self->accepts($_) } keys %HTMLTypes;
  26         104  
72              
73                     return 0
74 6 100       12             if @{ $self->accepted_content_types() };
  6         160  
75              
76             # If the client did not specify any content types at all,
77             # assume they are a browser.
78 2         59         return 1;
79                 }
80             }
81              
82             1;
83              
84             __END__
85            
86             =pod
87            
88             =head1 NAME
89            
90             Catalyst::TraitFor::Request::REST::ForBrowsers - A request trait for REST and browsers
91            
92             =head1 SYNOPSIS
93            
94             package MyApp;
95             use Moose;
96             use namespace::autoclean;
97            
98             use Catalyst;
99             use CatalystX::RoleApplicator;
100            
101             extends 'Catalyst';
102            
103             __PACKAGE__->apply_request_class_roles(qw[
104             Catalyst::TraitFor::Request::REST::ForBrowsers
105             ]);
106            
107             =head1 DESCRIPTION
108            
109             Writing REST-y apps is a good thing, but if you're also trying to support web
110             browsers, you're probably going to need some hackish workarounds. This module
111             provides those workarounds for you.
112            
113             Specifically, it lets you do two things. First, it lets you "tunnel" PUT and
114             DELETE requests across a POST, since most browsers do not support PUT or
115             DELETE actions (as of early 2009, at least).
116            
117             Second, it provides a heuristic to check if the client is a web browser,
118             regardless of what content types it claims to accept. The reason for this is
119             that while a browser might claim to accept the "application/xml" content type,
120             it's really not going to do anything useful with it, and you're best off
121             giving it HTML.
122            
123             =head1 METHODS
124            
125             This class provides the following methods:
126            
127             =head2 $request->method
128            
129             This method works just like C<< Catalyst::Request->method() >> except it
130             allows for tunneling of PUT and DELETE requests via a POST.
131            
132             Specifically, you can provide a form element named "x-tunneled-method" which
133             can override the request method for a POST. This I<only> works for a POST, not
134             a GET.
135            
136             You can also use a header named "x-http-method-override" instead (Google uses
137             this header for its APIs).
138            
139             =head2 $request->looks_like_browser
140            
141             This attribute provides a heuristic to determine whether or not the request
142             I<appears> to come from a browser. You can use this however you want. I
143             usually use it to determine whether or not to give the client a full HTML page
144             or some sort of serialized data.
145            
146             This is a heuristic, and like any heuristic, it is probably wrong
147             sometimes. Here is how it works:
148            
149             =over 4
150            
151             =item *
152            
153             If the request includes a header "X-Request-With" set to either "HTTP.Request"
154             or "XMLHttpRequest", this returns false. The assumption is that if you're
155             doing XHR, you don't want the request treated as if it comes from a browser.
156            
157             =item *
158            
159             If the client makes a GET request with a query string parameter
160             "content-type", and that type is I<not> an HTML type, it is I<not> a browser.
161            
162             =item *
163            
164             If the client provides an Accept header which includes "*/*" as an accepted
165             content type, the client is a browser. Specifically, it is IE7, which submits
166             an Accept header of "*/*". IE7's Accept header does not include any html types
167             like "text/html".
168            
169             =item *
170            
171             If the client provides an Accept header and accepts either "text/html" or
172             "application/xhtml+xml" it is a browser.
173            
174             =item *
175            
176             If it provides an Accept header of any sort that doesn't match one of the
177             above criteria, it is I<not> a browser.
178            
179             =item *
180            
181             The default is that the client is a browser.
182            
183             =back
184            
185             This all works well for my apps, but read it carefully to make sure it meets
186             your expectations before using it.
187            
188             =head1 AUTHOR
189            
190             Dave Rolsky, C<< <autarch@urth.org> >>
191            
192             =head1 BUGS
193            
194             Please report any bugs or feature requests to
195             C<bug-catalyst-action-rest@rt.cpan.org>, or through the web interface at
196             L<http://rt.cpan.org>. We will be notified, and then you'll automatically be
197             notified of progress on your bug as I make changes.
198            
199             =head1 COPYRIGHT & LICENSE
200            
201             Copyright 2008-2010 Dave Rolsky, All Rights Reserved.
202            
203             This program is free software; you can redistribute it and/or modify it under
204             the same terms as Perl itself.
205            
206             =cut
207