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