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