convert to Dist::Zilla
[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;
3accd912 94 use Moose;
95 use namespace::autoclean;
85aa4e18 96
3accd912 97 use Catalyst;
98 use CatalystX::RoleApplicator;
85aa4e18 99
3accd912 100 extends 'Catalyst';
85aa4e18 101
3accd912 102 __PACKAGE__->apply_request_class_roles(qw[
103 Catalyst::TraitFor::Request::REST::ForBrowsers
104 ]);
85aa4e18 105
106=head1 DESCRIPTION
107
108Writing REST-y apps is a good thing, but if you're also trying to support web
109browsers, you're probably going to need some hackish workarounds. This module
110provides those workarounds for you.
111
112Specifically, it lets you do two things. First, it lets you "tunnel" PUT and
113DELETE requests across a POST, since most browsers do not support PUT or
114DELETE actions (as of early 2009, at least).
115
116Second, it provides a heuristic to check if the client is a web browser,
117regardless of what content types it claims to accept. The reason for this is
118that while a browser might claim to accept the "application/xml" content type,
119it's really not going to do anything useful with it, and you're best off
120giving it HTML.
121
122=head1 METHODS
123
124This class provides the following methods:
125
126=head2 $request->method
127
128This method works just like C<< Catalyst::Request->method() >> except it
129allows for tunneling of PUT and DELETE requests via a POST.
130
131Specifically, you can provide a form element named "x-tunneled-method" which
132can override the request method for a POST. This I<only> works for a POST, not
133a GET.
134
135You can also use a header named "x-http-method-override" instead (Google uses
136this header for its APIs).
137
138=head2 $request->looks_like_browser
139
140This attribute provides a heuristic to determine whether or not the request
141I<appears> to come from a browser. You can use this however you want. I
142usually use it to determine whether or not to give the client a full HTML page
143or some sort of serialized data.
144
145This is a heuristic, and like any heuristic, it is probably wrong
146sometimes. Here is how it works:
147
148=over 4
149
150=item *
151
152If the request includes a header "X-Request-With" set to either "HTTP.Request"
153or "XMLHttpRequest", this returns false. The assumption is that if you're
154doing XHR, you don't want the request treated as if it comes from a browser.
155
156=item *
157
158If the client makes a GET request with a query string parameter
159"content-type", and that type is I<not> an HTML type, it is I<not> a browser.
160
161=item *
162
163If the client provides an Accept header which includes "*/*" as an accepted
164content type, the client is a browser. Specifically, it is IE7, which submits
165an Accept header of "*/*". IE7's Accept header does not include any html types
166like "text/html".
167
168=item *
169
170If the client provides an Accept header and accepts either "text/html" or
171"application/xhtml+xml" it is a browser.
172
173=item *
174
78d66407 175If it provides an Accept header of any sort that doesn't match one of the
176above criteria, it is I<not> a browser.
85aa4e18 177
178=item *
179
180The default is that the client is a browser.
181
182=back
183
184This all works well for my apps, but read it carefully to make sure it meets
185your expectations before using it.
186
187=head1 AUTHOR
188
189Dave Rolsky, C<< <autarch@urth.org> >>
190
191=head1 BUGS
192
193Please report any bugs or feature requests to
194C<bug-catalyst-action-rest@rt.cpan.org>, or through the web interface at
195L<http://rt.cpan.org>. We will be notified, and then you'll automatically be
196notified of progress on your bug as I make changes.
197
198=head1 COPYRIGHT & LICENSE
199
200Copyright 2008-2010 Dave Rolsky, All Rights Reserved.
201
202This program is free software; you can redistribute it and/or modify it under
203the same terms as Perl itself.
204
205=cut