v1.21
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / TraitFor / Request / REST / ForBrowsers.pm
1 package Catalyst::TraitFor::Request::REST::ForBrowsers;
2
3 use Moose::Role;
4 use namespace::autoclean;
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         my $self = shift;
53
54         my $with = $self->header('x-requested-with');
55         return 0
56             if $with && grep { $with eq $_ }
57                 qw( HTTP.Request XMLHttpRequest );
58
59         if ( uc $self->method eq 'GET' ) {
60             my $forced_type = $self->param('content-type');
61             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         return 1
68             if $self->accepts('*/*');
69
70         return 1
71             if grep { $self->accepts($_) } keys %HTMLTypes;
72
73         return 0
74             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         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