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