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