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