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