40a9a39fb309fd473a9ba6572c4fd3a8c1e05f92
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Request / REST.pm
1 #
2 # REST.pm
3 # Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
4 # Created on: 10/13/2006 03:54:33 PM PDT
5 #
6 # $Id: $
7
8 package Catalyst::Request::REST;
9
10 use strict;
11 use warnings;
12
13 use base 'Catalyst::Request';
14 use HTTP::Headers::Util qw(split_header_words);
15
16
17 =head1 NAME
18
19 Catalyst::Request::REST - A REST-y subclass of Catalyst::Request
20
21 =head1 SYNOPSIS
22
23      if ( $c->request->accepts('application/json') ) {
24          ...
25      }
26
27      my $types = $c->request->accepted_content_types();
28
29 =head1 DESCRIPTION
30
31 This is a subclass of C<Catalyst::Request> that adds a few methods to
32 the request object to faciliate writing REST-y code. Currently, these
33 methods are all related to the content types accepted by the client.
34
35
36 =head1 METHODS
37
38 =over 4 data
39
40 If the request went through the Deserializer action, this method will
41 returned the deserialized data structure.
42
43 =cut
44
45 __PACKAGE__->mk_accessors(qw(data accept_only));
46
47 =item accepted_content_types
48
49 Returns an array reference of content types accepted by the
50 client.
51
52 The list of types is created by looking at the following sources:
53
54 =over 8
55
56 =item * Content-type header
57
58 If this exists, this will always be the first type in the list.
59
60 =item * content-type parameter
61
62 If the request is a GET request and there is a "content-type"
63 parameter in the query string, this will come before any types in the
64 Accept header.
65
66 =item * Accept header
67
68 This will be parsed and the types found will be ordered by the
69 relative quality specified for each type.
70
71 =back
72
73 If a type appears in more than one of these places, it is ordered based on
74 where it is first found.
75
76 =cut
77
78 sub accepted_content_types {
79     my $self = shift;
80
81     return $self->{content_types} if $self->{content_types};
82
83     my %types;
84
85     # First, we use the content type in the HTTP Request.  It wins all.
86     $types{ $self->content_type } = 3
87         if $self->content_type;
88
89     if ($self->method eq "GET" && $self->param('content-type')) {
90         $types{ $self->param('content-type') } = 2;
91     }
92
93     # Third, we parse the Accept header, and see if the client
94     # takes a format we understand.
95     #
96     # This is taken from chansen's Apache2::UploadProgress.
97     if ( $self->header('Accept') ) {
98         $self->accept_only(1) unless keys %types;
99
100         my $accept_header = $self->header('Accept');
101         my $counter       = 0;
102
103         foreach my $pair ( split_header_words($accept_header) ) {
104             my ( $type, $qvalue ) = @{$pair}[ 0, 3 ];
105             next if $types{$type};
106
107             unless ( defined $qvalue ) {
108                 $qvalue = 1 - ( ++$counter / 1000 );
109             }
110
111             $types{$type} = sprintf( '%.3f', $qvalue );
112         }
113     }
114
115     return $self->{content_types} =
116         [ sort { $types{$b} <=> $types{$a} } keys %types ];
117 }
118
119 =item preferred_content_type
120
121 This returns the first content type found. It is shorthand for:
122
123   $request->accepted_content_types->[0]
124
125 =cut
126
127 sub preferred_content_type { $_[0]->accepted_content_types->[0] }
128
129 =item accepts($type)
130
131 Given a content type, this returns true if the type is accepted.
132
133 Note that this does not do any wildcard expansion of types.
134
135 =cut
136
137 sub accepts {
138     my $self = shift;
139     my $type = shift;
140
141     return grep { $_ eq $type } @{ $self->accepted_content_types };
142 }
143
144 =head1 AUTHOR
145
146 Adam Jacob <adam@stalecoffee.org>, with lots of help from mst and jrockway
147
148 =head1 LICENSE
149
150 You may distribute this code under the same terms as Perl itself.
151
152 =cut
153
154 1;