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.19';
3 7     7   5365 use Moose::Role;
  7         15  
  7         78  
4 7     7   33900 use namespace::autoclean;
  7         19  
  7         67  
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   28 my $self = shift;
53              
54 21         73 my $with = $self->header('x-requested-with');
55 8         176 return 0
56 21 100 66     1496 if $with && grep { $with eq $_ }
57             qw( HTTP.Request XMLHttpRequest );
58              
59 17 100       52 if ( uc $self->method eq 'GET' ) {
60 16         131 my $forced_type = $self->param('content-type');
61 16 100 100     1082 return 0
62             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       66 return 1
68             if $self->accepts('*/*');
69              
70 26         60 return 1
71 13 100       36 if grep { $self->accepts($_) } keys %HTMLTypes;
72              
73 6         181 return 0
74 6 100       9 if @{ $self->accepted_content_types() };
75              
76             # If the client did not specify any content types at all,
77             # assume they are a browser.
78 2         66 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