fix missing v1.15
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Controller / REST.pm
CommitLineData
256c894f 1package Catalyst::Controller::REST;
930013e6 2use Moose;
3use namespace::autoclean;
256c894f 4
cc188065 5# VERSION
832e768d 6
398c5a1b 7=head1 NAME
8
db8bb647 9Catalyst::Controller::REST - A RESTful controller
398c5a1b 10
11=head1 SYNOPSIS
12
13 package Foo::Controller::Bar;
5cb5f6bb 14 use Moose;
15 use namespace::autoclean;
259c53c7 16
5cb5f6bb 17 BEGIN { extends 'Catalyst::Controller::REST' }
398c5a1b 18
19 sub thing : Local : ActionClass('REST') { }
20
21 # Answer GET requests to "thing"
22 sub thing_GET {
23 my ( $self, $c ) = @_;
db8bb647 24
398c5a1b 25 # Return a 200 OK, with the data in entity
db8bb647 26 # serialized in the body
398c5a1b 27 $self->status_ok(
db8bb647 28 $c,
398c5a1b 29 entity => {
30 some => 'data',
31 foo => 'is real bar-y',
32 },
33 );
34 }
35
36 # Answer PUT requests to "thing"
db8bb647 37 sub thing_PUT {
ace04991 38 my ( $self, $c ) = @_;
39
fcf45ed9 40 $radiohead = $c->req->data->{radiohead};
259c53c7 41
10bcd217 42 $self->status_created(
43 $c,
259c53c7 44 location => $c->req->uri,
10bcd217 45 entity => {
46 radiohead => $radiohead,
47 }
48 );
259c53c7 49 }
398c5a1b 50
51=head1 DESCRIPTION
52
53Catalyst::Controller::REST implements a mechanism for building
54RESTful services in Catalyst. It does this by extending the
db8bb647 55normal Catalyst dispatch mechanism to allow for different
56subroutines to be called based on the HTTP Method requested,
398c5a1b 57while also transparently handling all the serialization/deserialization for
58you.
59
60This is probably best served by an example. In the above
61controller, we have declared a Local Catalyst action on
db8bb647 62"sub thing", and have used the ActionClass('REST').
398c5a1b 63
64Below, we have declared "thing_GET" and "thing_PUT". Any
db8bb647 65GET requests to thing will be dispatched to "thing_GET",
66while any PUT requests will be dispatched to "thing_PUT".
398c5a1b 67
e601adda 68Any unimplemented HTTP methods will be met with a "405 Method Not Allowed"
69response, automatically containing the proper list of available methods. You
70can override this behavior through implementing a custom
db8bb647 71C<thing_not_implemented> method.
e601adda 72
73If you do not provide an OPTIONS handler, we will respond to any OPTIONS
74requests with a "200 OK", populating the Allowed header automatically.
75
76Any data included in C<< $c->stash->{'rest'} >> will be serialized for you.
77The serialization format will be selected based on the content-type
78of the incoming request. It is probably easier to use the L<STATUS HELPERS>,
79which are described below.
398c5a1b 80
10bcd217 81"The HTTP POST, PUT, and OPTIONS methods will all automatically
82L<deserialize|Catalyst::Action::Deserialize> the contents of
259c53c7 83C<< $c->request->body >> into the C<< $c->request->data >> hashref", based on
10bcd217 84the request's C<Content-type> header. A list of understood serialization
85formats is L<below|/AVAILABLE SERIALIZERS>.
398c5a1b 86
e601adda 87If we do not have (or cannot run) a serializer for a given content-type, a 415
db8bb647 88"Unsupported Media Type" error is generated.
398c5a1b 89
90To make your Controller RESTful, simply have it
91
5cb5f6bb 92 BEGIN { extends 'Catalyst::Controller::REST' }
398c5a1b 93
9cd203c9 94=head1 CONFIGURATION
95
96See L<Catalyst::Action::Serialize/CONFIGURATION>. Note that the C<serialize>
97key has been deprecated.
98
398c5a1b 99=head1 SERIALIZATION
100
101Catalyst::Controller::REST will automatically serialize your
e601adda 102responses, and deserialize any POST, PUT or OPTIONS requests. It evaluates
103which serializer to use by mapping a content-type to a Serialization module.
db8bb647 104We select the content-type based on:
e601adda 105
5cb5f6bb 106=over
e601adda 107
108=item B<The Content-Type Header>
109
110If the incoming HTTP Request had a Content-Type header set, we will use it.
111
112=item B<The content-type Query Parameter>
113
114If this is a GET request, you can supply a content-type query parameter.
115
116=item B<Evaluating the Accept Header>
117
118Finally, if the client provided an Accept header, we will evaluate
db8bb647 119it and use the best-ranked choice.
e601adda 120
121=back
122
123=head1 AVAILABLE SERIALIZERS
124
125A given serialization mechanism is only available if you have the underlying
126modules installed. For example, you can't use XML::Simple if it's not already
db8bb647 127installed.
e601adda 128
95318468 129In addition, each serializer has its quirks in terms of what sorts of data
e601adda 130structures it will properly handle. L<Catalyst::Controller::REST> makes
db8bb647 131no attempt to save you from yourself in this regard. :)
e601adda 132
133=over 2
134
95318468 135=item * C<text/x-yaml> => C<YAML::Syck>
e601adda 136
137Returns YAML generated by L<YAML::Syck>.
138
95318468 139=item * C<text/html> => C<YAML::HTML>
e601adda 140
141This uses L<YAML::Syck> and L<URI::Find> to generate YAML with all URLs turned
26b59bcb 142to hyperlinks. Only usable for Serialization.
e601adda 143
95318468 144=item * C<application/json> => C<JSON>
e601adda 145
db8bb647 146Uses L<JSON> to generate JSON output. It is strongly advised to also have
e540a1fa 147L<JSON::XS> installed. The C<text/x-json> content type is supported but is
148deprecated and you will receive warnings in your log.
e601adda 149
838f49dc 150You can also add a hash in your controller config to pass options to the json object.
151For instance, to relax permissions when deserializing input, add:
152 __PACKAGE__->config(
153 json_options => { relaxed => 1 }
154 )
155
d0d292d4 156=item * C<text/javascript> => C<JSONP>
157
158If a callback=? parameter is passed, this returns javascript in the form of: $callback($serializedJSON);
159
92d78e8f 160Note - this is disabled by default as it can be a security risk if you are unaware.
161
162The usual MIME types for this serialization format are: 'text/javascript', 'application/x-javascript',
163'application/javascript'.
164
95318468 165=item * C<text/x-data-dumper> => C<Data::Serializer>
e601adda 166
167Uses the L<Data::Serializer> module to generate L<Data::Dumper> output.
168
95318468 169=item * C<text/x-data-denter> => C<Data::Serializer>
e601adda 170
171Uses the L<Data::Serializer> module to generate L<Data::Denter> output.
172
95318468 173=item * C<text/x-data-taxi> => C<Data::Serializer>
e601adda 174
175Uses the L<Data::Serializer> module to generate L<Data::Taxi> output.
176
95318468 177=item * C<text/x-config-general> => C<Data::Serializer>
e601adda 178
179Uses the L<Data::Serializer> module to generate L<Config::General> output.
180
95318468 181=item * C<text/x-php-serialization> => C<Data::Serializer>
e601adda 182
183Uses the L<Data::Serializer> module to generate L<PHP::Serialization> output.
184
95318468 185=item * C<text/xml> => C<XML::Simple>
e601adda 186
187Uses L<XML::Simple> to generate XML output. This is probably not suitable
188for any real heavy XML work. Due to L<XML::Simple>s requirement that the data
189you serialize be a HASHREF, we transform outgoing data to be in the form of:
190
191 { data => $yourdata }
192
95318468 193=item * L<View>
9a76221e 194
db8bb647 195Uses a regular Catalyst view. For example, if you wanted to have your
3d8a0645 196C<text/html> and C<text/xml> views rendered by TT, set:
197
198 __PACKAGE__->config(
199 map => {
200 'text/html' => [ 'View', 'TT' ],
201 'text/xml' => [ 'View', 'XML' ],
202 }
5cb5f6bb 203 );
3d8a0645 204
205Your views should have a C<process> method like this:
206
207 sub process {
208 my ( $self, $c, $stash_key ) = @_;
5cb5f6bb 209
3d8a0645 210 my $output;
211 eval {
212 $output = $self->serialize( $c->stash->{$stash_key} );
213 };
214 return $@ if $@;
5cb5f6bb 215
3d8a0645 216 $c->response->body( $output );
217 return 1; # important
218 }
259c53c7 219
3d8a0645 220 sub serialize {
221 my ( $self, $data ) = @_;
5cb5f6bb 222
3d8a0645 223 my $serialized = ... process $data here ...
5cb5f6bb 224
3d8a0645 225 return $serialized;
226 }
9a76221e 227
178f8470 228=item * Callback
229
230For infinite flexibility, you can provide a callback for the
231deserialization/serialization steps.
232
233 __PACKAGE__->config(
234 map => {
235 'text/xml' => [ 'Callback', { deserialize => \&parse_xml, serialize => \&render_xml } ],
236 }
237 );
238
239The C<deserialize> callback is passed a string that is the body of the
240request and is expected to return a scalar value that results from
241the deserialization. The C<serialize> callback is passed the data
242structure that needs to be serialized and must return a string suitable
243for returning in the HTTP response. In addition to receiving the scalar
244to act on, both callbacks are passed the controller object and the context
245(i.e. C<$c>) as the second and third arguments.
246
e601adda 247=back
248
259c53c7 249By default, L<Catalyst::Controller::REST> will return a
95318468 250C<415 Unsupported Media Type> response if an attempt to use an unsupported
251content-type is made. You can ensure that something is always returned by
252setting the C<default> config option:
398c5a1b 253
5cb5f6bb 254 __PACKAGE__->config(default => 'text/x-yaml');
398c5a1b 255
95318468 256would make it always fall back to the serializer plugin defined for
257C<text/x-yaml>.
398c5a1b 258
e601adda 259=head1 CUSTOM SERIALIZERS
260
95318468 261Implementing new Serialization formats is easy! Contributions
259c53c7 262are most welcome! If you would like to implement a custom serializer,
95318468 263you should create two new modules in the L<Catalyst::Action::Serialize>
264and L<Catalyst::Action::Deserialize> namespace. Then assign your new
265class to the content-type's you want, and you're done.
266
259c53c7 267See L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>
95318468 268for more information.
e601adda 269
398c5a1b 270=head1 STATUS HELPERS
271
e601adda 272Since so much of REST is in using HTTP, we provide these Status Helpers.
273Using them will ensure that you are responding with the proper codes,
274headers, and entities.
275
398c5a1b 276These helpers try and conform to the HTTP 1.1 Specification. You can
db8bb647 277refer to it at: L<http://www.w3.org/Protocols/rfc2616/rfc2616.txt>.
398c5a1b 278These routines are all implemented as regular subroutines, and as
279such require you pass the current context ($c) as the first argument.
280
5cb5f6bb 281=over
398c5a1b 282
283=cut
284
930013e6 285BEGIN { extends 'Catalyst::Controller' }
d4611771 286use Params::Validate qw(SCALAR OBJECT);
256c894f 287
288__PACKAGE__->mk_accessors(qw(serialize));
289
290__PACKAGE__->config(
e540a1fa 291 'stash_key' => 'rest',
292 'map' => {
e540a1fa 293 'text/xml' => 'XML::Simple',
e540a1fa 294 'application/json' => 'JSON',
295 'text/x-json' => 'JSON',
e540a1fa 296 },
256c894f 297);
298
e540a1fa 299sub begin : ActionClass('Deserialize') { }
5511d1ff 300
0ba73721 301sub end : ActionClass('Serialize') { }
302
398c5a1b 303=item status_ok
304
305Returns a "200 OK" response. Takes an "entity" to serialize.
306
307Example:
308
309 $self->status_ok(
db8bb647 310 $c,
398c5a1b 311 entity => {
312 radiohead => "Is a good band!",
313 }
314 );
315
316=cut
317
318sub status_ok {
319 my $self = shift;
e601adda 320 my $c = shift;
d4611771 321 my %p = Params::Validate::validate( @_, { entity => 1, }, );
398c5a1b 322
323 $c->response->status(200);
e601adda 324 $self->_set_entity( $c, $p{'entity'} );
398c5a1b 325 return 1;
326}
327
328=item status_created
329
330Returns a "201 CREATED" response. Takes an "entity" to serialize,
331and a "location" where the created object can be found.
332
333Example:
334
335 $self->status_created(
db8bb647 336 $c,
259c53c7 337 location => $c->req->uri,
398c5a1b 338 entity => {
339 radiohead => "Is a good band!",
340 }
341 );
342
343In the above example, we use the requested URI as our location.
344This is probably what you want for most PUT requests.
345
346=cut
bb4130f6 347
5511d1ff 348sub status_created {
349 my $self = shift;
e601adda 350 my $c = shift;
d4611771 351 my %p = Params::Validate::validate(
e601adda 352 @_,
5511d1ff 353 {
e601adda 354 location => { type => SCALAR | OBJECT },
355 entity => { optional => 1 },
5511d1ff 356 },
357 );
256c894f 358
5511d1ff 359 $c->response->status(201);
259c53c7 360 $c->response->header( 'Location' => $p{location} );
e601adda 361 $self->_set_entity( $c, $p{'entity'} );
bb4130f6 362 return 1;
363}
364
398c5a1b 365=item status_accepted
366
367Returns a "202 ACCEPTED" response. Takes an "entity" to serialize.
259c53c7 368Also takes optional "location" for queue type scenarios.
398c5a1b 369
370Example:
371
372 $self->status_accepted(
db8bb647 373 $c,
259c53c7 374 location => $c->req->uri,
398c5a1b 375 entity => {
376 status => "queued",
377 }
378 );
379
380=cut
e601adda 381
398c5a1b 382sub status_accepted {
bb4130f6 383 my $self = shift;
e601adda 384 my $c = shift;
259c53c7 385 my %p = Params::Validate::validate(
386 @_,
387 {
388 location => { type => SCALAR | OBJECT, optional => 1 },
389 entity => 1,
390 },
391 );
bb4130f6 392
398c5a1b 393 $c->response->status(202);
259c53c7 394 $c->response->header( 'Location' => $p{location} ) if exists $p{location};
e601adda 395 $self->_set_entity( $c, $p{'entity'} );
bb4130f6 396 return 1;
397}
398
bbf0feae 399=item status_no_content
400
401Returns a "204 NO CONTENT" response.
402
403=cut
404
405sub status_no_content {
406 my $self = shift;
407 my $c = shift;
408 $c->response->status(204);
409 $self->_set_entity( $c, undef );
042656b6 410 return 1;
bbf0feae 411}
412
bdff70a9 413=item status_multiple_choices
414
415Returns a "300 MULTIPLE CHOICES" response. Takes an "entity" to serialize, which should
416provide list of possible locations. Also takes optional "location" for preferred choice.
417
418=cut
419
420sub status_multiple_choices {
421 my $self = shift;
422 my $c = shift;
423 my %p = Params::Validate::validate(
424 @_,
425 {
426 entity => 1,
427 location => { type => SCALAR | OBJECT, optional => 1 },
428 },
429 );
430
bdff70a9 431 $c->response->status(300);
259c53c7 432 $c->response->header( 'Location' => $p{location} ) if exists $p{'location'};
bdff70a9 433 $self->_set_entity( $c, $p{'entity'} );
434 return 1;
435}
436
e52456a4 437=item status_found
438
439Returns a "302 FOUND" response. Takes an "entity" to serialize.
259c53c7 440Also takes optional "location".
e52456a4 441
442=cut
443
444sub status_found {
445 my $self = shift;
446 my $c = shift;
447 my %p = Params::Validate::validate(
448 @_,
449 {
450 entity => 1,
451 location => { type => SCALAR | OBJECT, optional => 1 },
452 },
453 );
454
e52456a4 455 $c->response->status(302);
259c53c7 456 $c->response->header( 'Location' => $p{location} ) if exists $p{'location'};
e52456a4 457 $self->_set_entity( $c, $p{'entity'} );
458 return 1;
459}
460
398c5a1b 461=item status_bad_request
462
463Returns a "400 BAD REQUEST" response. Takes a "message" argument
464as a scalar, which will become the value of "error" in the serialized
465response.
466
467Example:
468
469 $self->status_bad_request(
db8bb647 470 $c,
33e5de96 471 message => "Cannot do what you have asked!",
398c5a1b 472 );
473
474=cut
e601adda 475
cc186a5b 476sub status_bad_request {
477 my $self = shift;
e601adda 478 my $c = shift;
d4611771 479 my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
cc186a5b 480
481 $c->response->status(400);
faf5c20b 482 $c->log->debug( "Status Bad Request: " . $p{'message'} ) if $c->debug;
e601adda 483 $self->_set_entity( $c, { error => $p{'message'} } );
cc186a5b 484 return 1;
485}
486
550807bc 487=item status_forbidden
488
489Returns a "403 FORBIDDEN" response. Takes a "message" argument
490as a scalar, which will become the value of "error" in the serialized
491response.
492
493Example:
494
495 $self->status_forbidden(
496 $c,
497 message => "access denied",
498 );
499
500=cut
501
502sub status_forbidden {
503 my $self = shift;
504 my $c = shift;
505 my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
506
507 $c->response->status(403);
508 $c->log->debug( "Status Forbidden: " . $p{'message'} ) if $c->debug;
509 $self->_set_entity( $c, { error => $p{'message'} } );
510 return 1;
511}
512
398c5a1b 513=item status_not_found
514
515Returns a "404 NOT FOUND" response. Takes a "message" argument
516as a scalar, which will become the value of "error" in the serialized
517response.
518
519Example:
520
521 $self->status_not_found(
db8bb647 522 $c,
33e5de96 523 message => "Cannot find what you were looking for!",
398c5a1b 524 );
525
526=cut
e601adda 527
bb4130f6 528sub status_not_found {
529 my $self = shift;
e601adda 530 my $c = shift;
d4611771 531 my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
bb4130f6 532
533 $c->response->status(404);
faf5c20b 534 $c->log->debug( "Status Not Found: " . $p{'message'} ) if $c->debug;
e601adda 535 $self->_set_entity( $c, { error => $p{'message'} } );
bb4130f6 536 return 1;
537}
538
bbf0feae 539=item gone
540
541Returns a "41O GONE" response. Takes a "message" argument as a scalar,
542which will become the value of "error" in the serialized response.
543
544Example:
545
546 $self->status_gone(
547 $c,
548 message => "The document have been deleted by foo",
549 );
550
551=cut
552
553sub status_gone {
554 my $self = shift;
555 my $c = shift;
556 my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
557
558 $c->response->status(410);
559 $c->log->debug( "Status Gone " . $p{'message'} ) if $c->debug;
560 $self->_set_entity( $c, { error => $p{'message'} } );
561 return 1;
562}
563
0aceaa9b 564=item status_see_other
565
566Returns a "303 See Other" response. Takes an optional "entity" to serialize,
567and a "location" where the client should redirect to.
568
569Example:
570
571 $self->status_see_other(
572 $c,
573 location => $some_other_url,
574 entity => {
575 radiohead => "Is a good band!",
576 }
577 );
578
579=cut
580
581sub status_see_other {
582 my $self = shift;
583 my $c = shift;
584 my %p = Params::Validate::validate(
585 @_,
586 {
587 location => { type => SCALAR | OBJECT },
588 entity => { optional => 1 },
589 },
590 );
591
592 $c->response->status(303);
593 $c->response->header( 'Location' => $p{location} );
594 $self->_set_entity( $c, $p{'entity'} );
595 return 1;
596}
597
598=item status_moved
599
600Returns a "301 MOVED" response. Takes an "entity" to serialize, and a
601"location" where the created object can be found.
602
603Example:
604
605 $self->status_moved(
606 $c,
607 location => '/somewhere/else',
608 entity => {
609 radiohead => "Is a good band!",
610 },
611 );
612
613=cut
614
615sub status_moved {
616 my $self = shift;
617 my $c = shift;
618 my %p = Params::Validate::validate(
619 @_,
620 {
621 location => { type => SCALAR | OBJECT },
622 entity => { optional => 1 },
623 },
624 );
625
626 my $location = ref $p{location}
627 ? $p{location}->as_string
628 : $p{location}
629 ;
630
631 $c->response->status(301);
632 $c->response->header( Location => $location );
633 $self->_set_entity($c, $p{entity});
634 return 1;
635}
636
bb4130f6 637sub _set_entity {
e601adda 638 my $self = shift;
639 my $c = shift;
bb4130f6 640 my $entity = shift;
e601adda 641 if ( defined($entity) ) {
faf5c20b 642 $c->stash->{ $self->{'stash_key'} } = $entity;
5511d1ff 643 }
644 return 1;
eccb2137 645}
256c894f 646
398c5a1b 647=back
648
649=head1 MANUAL RESPONSES
650
651If you want to construct your responses yourself, all you need to
652do is put the object you want serialized in $c->stash->{'rest'}.
653
e601adda 654=head1 IMPLEMENTATION DETAILS
655
656This Controller ties together L<Catalyst::Action::REST>,
657L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>. It should be suitable for most applications. You should be aware that it:
658
659=over 4
660
661=item Configures the Serialization Actions
662
663This class provides a default configuration for Serialization. It is currently:
664
665 __PACKAGE__->config(
95318468 666 'stash_key' => 'rest',
667 'map' => {
668 'text/html' => 'YAML::HTML',
669 'text/xml' => 'XML::Simple',
670 'text/x-yaml' => 'YAML',
671 'application/json' => 'JSON',
672 'text/x-json' => 'JSON',
673 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
674 'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
675 'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ],
676 'application/x-storable' => [ 'Data::Serializer', 'Storable' ],
677 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ],
678 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ],
679 'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
680 },
e601adda 681 );
682
683You can read the full set of options for this configuration block in
684L<Catalyst::Action::Serialize>.
685
686=item Sets a C<begin> and C<end> method for you
687
688The C<begin> method uses L<Catalyst::Action::Deserialize>. The C<end>
689method uses L<Catalyst::Action::Serialize>. If you want to override
690either behavior, simply implement your own C<begin> and C<end> actions
355d4385 691and forward to another action with the Serialize and/or Deserialize
692action classes:
e601adda 693
10bcd217 694 package Foo::Controller::Monkey;
695 use Moose;
696 use namespace::autoclean;
355d4385 697
10bcd217 698 BEGIN { extends 'Catalyst::Controller::REST' }
e601adda 699
355d4385 700 sub begin : Private {
e601adda 701 my ($self, $c) = @_;
db8bb647 702 ... do things before Deserializing ...
355d4385 703 $c->forward('deserialize');
e601adda 704 ... do things after Deserializing ...
db8bb647 705 }
e601adda 706
355d4385 707 sub deserialize : ActionClass('Deserialize') {}
708
e601adda 709 sub end :Private {
710 my ($self, $c) = @_;
db8bb647 711 ... do things before Serializing ...
355d4385 712 $c->forward('serialize');
e601adda 713 ... do things after Serializing ...
714 }
715
355d4385 716 sub serialize : ActionClass('Serialize') {}
717
8bf1f20e 718If you need to deserialize multipart requests (i.e. REST data in
719one part and file uploads in others) you can do so by using the
720L<Catalyst::Action::DeserializeMultiPart> action class.
721
e540a1fa 722=back
723
e601adda 724=head1 A MILD WARNING
725
726I have code in production using L<Catalyst::Controller::REST>. That said,
727it is still under development, and it's possible that things may change
d6ece98c 728between releases. I promise to not break things unnecessarily. :)
e601adda 729
398c5a1b 730=head1 SEE ALSO
731
732L<Catalyst::Action::REST>, L<Catalyst::Action::Serialize>,
733L<Catalyst::Action::Deserialize>
734
735For help with REST in general:
736
737The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt
738
739Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer
740
741The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage
742
5cb5f6bb 743=head1 AUTHORS
e540a1fa 744
5cb5f6bb 745See L<Catalyst::Action::REST> for authors.
e540a1fa 746
398c5a1b 747=head1 LICENSE
748
749You may distribute this code under the same terms as Perl itself.
750
751=cut
752
24748286 753__PACKAGE__->meta->make_immutable;
754
256c894f 7551;