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