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