making your perl rest
TRANSCRIPT
Making Your Perl REST Sterling Hanenkamp, PPW 2008
What is REST?
What is REST?
• Ever heard of...
What is REST?
• Ever heard of...
• SOAP?
What is REST?
• Ever heard of...
• SOAP?
• RPC?
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
• WebDAV?
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
• WebDAV?
• It’s kind of like that.
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
• WebDAV?
• It’s kind of like that.
• Ever heard of...
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
• WebDAV?
• It’s kind of like that.
• Ever heard of...
• HTTP?
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
• WebDAV?
• It’s kind of like that.
• Ever heard of...
• HTTP?
• XML?
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
• WebDAV?
• It’s kind of like that.
• Ever heard of...
• HTTP?
• XML?
• YAML?
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
• WebDAV?
• It’s kind of like that.
• Ever heard of...
• HTTP?
• XML?
• YAML?
• JSON?
What is REST?
• Ever heard of...
• SOAP?
• RPC?
• XML-RPC?
• WebDAV?
• It’s kind of like that.
• Ever heard of...
• HTTP?
• XML?
• YAML?
• JSON?
• It’s kind of like that too
What are we NOT talking about?
• REST is a general purpose term
What are we NOT talking about?
• REST is a general purpose term
• I’m not talking about a general notion
What are we NOT talking about?
• REST is a general purpose term
• I’m not talking about a general notion
• We’re talking about REST APIs
What are we NOT talking about?
• REST is a general purpose term
• I’m not talking about a general notion
• We’re talking about REST APIs
• Web Services
What are we NOT talking about?
• REST is a general purpose term
• I’m not talking about a general notion
• We’re talking about REST APIs
• Web Services
• Mashups!!!! Web 2.0!!! Yippee!!!
What are we NOT talking about?
• REST is a general purpose term
• I’m not talking about a general notion
• We’re talking about REST APIs
• Web Services
• Mashups!!!! Web 2.0!!! Yippee!!!
What are we NOT talking about?
• REST is a general purpose term
• I’m not talking about a general notion
• We’re talking about REST APIs
• Web Services
• Mashups!!!! Web 2.0!!! Yippee!!!
• I’ve used my lifetime supply of exclamation points...
What are we NOT talking about?
The REST Triangle
The REST Triangle
nouns http://www.onlamp.com
ver
bs
GET, P
OST, P
UT, DEL
ETE
content typesYAML, XML, JSON, etc.
The REST Triangle
• Nouns (=URL)nouns http://www.onlamp.com
ver
bs
GET, P
OST, P
UT, DEL
ETE
content typesYAML, XML, JSON, etc.
The REST Triangle
• Nouns (=URL)
• Verbs (=HTTP METHOD)
nouns http://www.onlamp.com
ver
bs
GET, P
OST, P
UT, DEL
ETE
content typesYAML, XML, JSON, etc.
The REST Triangle
• Nouns (=URL)
• Verbs (=HTTP METHOD)
• Content (=MIME Type)
nouns http://www.onlamp.com
ver
bs
GET, P
OST, P
UT, DEL
ETE
content typesYAML, XML, JSON, etc.
REST Web Services
REST Web Services
CRUD
REST Web Services
CRUD
Create
REST Web Services
CRUD
Create
Read
REST Web Services
CRUD
Create
Read
Update
REST Web Services
CRUD
Create
Read
Update
Delete
REST Web Services
• Treat your data like a static web page CRUD
Create
Read
Update
Delete
REST Web Services
• Treat your data like a static web page
• Add data by POSTing a web page
CRUD
Create
Read
Update
Delete
REST Web Services
• Treat your data like a static web page
• Add data by POSTing a web page
• Fetch data by GETting a web pageCRUD
Create
Read
Update
Delete
REST Web Services
• Treat your data like a static web page
• Add data by POSTing a web page
• Fetch data by GETting a web page
• Update data by PUTing a web page
CRUD
Create
Read
Update
Delete
REST Web Services
• Treat your data like a static web page
• Add data by POSTing a web page
• Fetch data by GETting a web page
• Update data by PUTing a web page
• Delete data by DELETing a web page
CRUD
Create
Read
Update
Delete
REST Web Services
• Treat your data like a static web page
• Add data by POSTing a web page
• Fetch data by GETting a web page
• Update data by PUTing a web page
• Delete data by DELETing a web page
• E... it’s missing and it bugs me...
CRUD
Create
Read
Update
Delete
Who Uses It
Who Uses It
• Yahoo!
• Amazon
• Intuit
• Best Practical
• Socialtext
• Digg
• eBay
• Technorati
• Too many others to mention...
What about other stuff?
• RPC is a square peg
What about other stuff?
• RPC is a square peg
• REST is round hole
What about other stuff?
• RPC is a square peg
• REST is round hole
What about other stuff?
• RPC is a square peg
• REST is round hole
• But it works!
What about other stuff?
• RPC is a square peg
• REST is round hole
• But it works!
• Getting Info? GET
What about other stuff?
• RPC is a square peg
• REST is round hole
• But it works!
• Getting Info? GET
• Modifying Something? POST
What about other stuff?
• RPC is a square peg
• REST is round hole
• But it works!
• Getting Info? GET
• Modifying Something? POST
• Both? Not sure? POST
What about other stuff?
ENOUGH BASICS. LET’S DO SOMETHING.
# Manage your books from the command-line. Woo-hoo!% ./book list
# Manage your books from the command-line. Woo-hoo!% ./book list0-8024-8160-4: .../library.cgi/=/model/book/id/0-8024-8160-40-85151-760-9: .../library.cgi/=/model/book/id/0-85151-760-90-936083-11-5: .../library.cgi/=/model/book/id/0-936083-11-5
% ./book read 0-8024-8160-4
---author: David Clotfeltercity: Chicagoid: 0-8024-8160-4isbn: 0-8024-8160-4publisher: Moody Publisherstitle: > Sinners in the Hands of a Good God: Reconciling Divine Judgment and Mercy'year: 2004
% ./book read 0-8024-8160-4
% ./book create reformation.yml
% ./book create reformation.yml0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5
% ./book create reformation.yml0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5
% ./book update 0-87552-183-5 reformation2.yml
% ./book create reformation.yml0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5
% ./book update 0-87552-183-5 reformation2.ymlUpdated 0-87552-183-5
% ./book create reformation.yml0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5
% ./book update 0-87552-183-5 reformation2.ymlUpdated 0-87552-183-5
% ./book delete 0-87552-183-5
Deleted 0-87552-183-5
% ./book create reformation.yml0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5
% ./book update 0-87552-183-5 reformation2.ymlUpdated 0-87552-183-5
% ./book delete 0-87552-183-5
Okay... But what did that do?
Putting it together
Explaining the Nouns (a.k.a. URLs)
Explaining the Nouns (a.k.a. URLs)
Scheme Borrowed from Jifty
Explaining the Nouns (a.k.a. URLs)
What Example Why?
Prefix /= Marker for REST
Kind of Thing /=/model More Kinds
Name of Thing /=/model/book More Models
Field of Thing /=/model/book/id Multiple Identifiers
Value of Field /=/mode/book/id/1 Multiple Things
Scheme Borrowed from Jifty
Explaining the Nouns (a.k.a. URLs)
What Example Why?
Prefix /= Marker for REST
Kind of Thing /=/model More Kinds
Name of Thing /=/model/book More Models
Field of Thing /=/model/book/id Multiple Identifiers
Value of Field /=/mode/book/id/1 Multiple Things
Scheme Borrowed from Jifty
Explaining the Nouns (a.k.a. URLs)
What Example Why?
Prefix /= Marker for REST
Kind of Thing /=/model More Kinds
Name of Thing /=/model/book More Models
Field of Thing /=/model/book/id Multiple Identifiers
Value of Field /=/mode/book/id/1 Multiple Things
Scheme Borrowed from Jifty
Explaining the Nouns (a.k.a. URLs)
What Example Why?
Prefix /= Marker for REST
Kind of Thing /=/model More Kinds
Name of Thing /=/model/book More Models
Field of Thing /=/model/book/id Multiple Identifiers
Value of Field /=/mode/book/id/1 Multiple Things
Scheme Borrowed from Jifty
Explaining the Nouns (a.k.a. URLs)
What Example Why?
Prefix /= Marker for REST
Kind of Thing /=/model More Kinds
Name of Thing /=/model/book More Models
Field of Thing /=/model/book/id Multiple Identifiers
Value of Field /=/mode/book/id/1 Multiple Things
Scheme Borrowed from Jifty
Explaining the Nouns (a.k.a. URLs)
What Example Why?
Prefix /= Marker for REST
Kind of Thing /=/model More Kinds
Name of Thing /=/model/book More Models
Field of Thing /=/model/book/id Multiple Identifiers
Value of Field /=/mode/book/id/1 Multiple Things
Scheme Borrowed from Jifty
# book list - lists all the books the server returnssubcommand 'list' => sub {
# GET /=/model/book my $response = $ua->request(GET HOST.'/=/model/book/id');
# On success, find the link and print them out if ($response->is_success) { my @links = $response->content =~ /\bhref="([^"]+)"/gm; for my $url (@links) { my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; } }
# On failure, barf else { barf $response; }};
Read (List)
# book list - lists all the books the server returnssubcommand 'list' => sub {
# GET /=/model/book my $response = $ua->request(GET HOST.'/=/model/book/id');
# On success, find the link and print them out if ($response->is_success) { my @links = $response->content =~ /\bhref="([^"]+)"/gm; for my $url (@links) { my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; } }
# On failure, barf else { barf $response; }};
Read (List)
# book list - lists all the books the server returnssubcommand 'list' => sub {
# GET /=/model/book my $response = $ua->request(GET HOST.'/=/model/book/id');
# On success, find the link and print them out if ($response->is_success) { my @links = $response->content =~ /\bhref="([^"]+)"/gm; for my $url (@links) { my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; } }
# On failure, barf else { barf $response; }};
Read (List)
# book list - lists all the books the server returnssubcommand 'list' => sub {
# GET /=/model/book my $response = $ua->request(GET HOST.'/=/model/book/id');
# On success, find the link and print them out if ($response->is_success) { my @links = $response->content =~ /\bhref="([^"]+)"/gm; for my $url (@links) { my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; } }
# On failure, barf else { barf $response; }};
Read (List)
# book list - lists all the books the server returnssubcommand 'list' => sub {
# GET /=/model/book my $response = $ua->request(GET HOST.'/=/model/book/id');
# On success, find the link and print them out if ($response->is_success) { my @links = $response->content =~ /\bhref="([^"]+)"/gm; for my $url (@links) { my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; } }
# On failure, barf else { barf $response; }};
Read (List)
# Get a whole list of available documentsGET qr{^/=/model/book/id$} => sub { print $q->header('text/html');
# Find all the files available my @items; for my $filename (glob get_local_path('*')) { my ($id) = $filename =~ m{([\d-]+)$}; next unless defined $id;
push @items, $q->li( $q->a({ href => absolute_url('/=/model/book/id/'.$id) }, $id), ); }
# List the items print $q->ul( @items);};
Read (List)
# Get a whole list of available documentsGET qr{^/=/model/book/id$} => sub { print $q->header('text/html');
# Find all the files available my @items; for my $filename (glob get_local_path('*')) { my ($id) = $filename =~ m{([\d-]+)$}; next unless defined $id;
push @items, $q->li( $q->a({ href => absolute_url('/=/model/book/id/'.$id) }, $id), ); }
# List the items print $q->ul( @items);};
Read (List)
# Get a whole list of available documentsGET qr{^/=/model/book/id$} => sub { print $q->header('text/html');
# Find all the files available my @items; for my $filename (glob get_local_path('*')) { my ($id) = $filename =~ m{([\d-]+)$}; next unless defined $id;
push @items, $q->li( $q->a({ href => absolute_url('/=/model/book/id/'.$id) }, $id), ); }
# List the items print $q->ul( @items);};
Read (List)
# Get a whole list of available documentsGET qr{^/=/model/book/id$} => sub { print $q->header('text/html');
# Find all the files available my @items; for my $filename (glob get_local_path('*')) { my ($id) = $filename =~ m{([\d-]+)$}; next unless defined $id;
push @items, $q->li( $q->a({ href => absolute_url('/=/model/book/id/'.$id) }, $id), ); }
# List the items print $q->ul( @items);};
Read (List)
# Get a whole list of available documentsGET qr{^/=/model/book/id$} => sub { print $q->header('text/html');
# Find all the files available my @items; for my $filename (glob get_local_path('*')) { my ($id) = $filename =~ m{([\d-]+)$}; next unless defined $id;
push @items, $q->li( $q->a({ href => absolute_url('/=/model/book/id/'.$id) }, $id), ); }
# List the items print $q->ul( @items);};
Read (List)
# book read <id> - reads the book file for <id>subcommand 'read' => sub { my $id = shift @ARGV;
# GET /=/model/book/id/[id] my $response = $ua->request(GET HOST.'/=/model/book/id/'
.$id);
# On success, print the file if ($response->is_success) { print $response->content; }
# On failure, barf else { barf $response; }};
Read (Single)
# book read <id> - reads the book file for <id>subcommand 'read' => sub { my $id = shift @ARGV;
# GET /=/model/book/id/[id] my $response = $ua->request(GET HOST.'/=/model/book/id/'
.$id);
# On success, print the file if ($response->is_success) { print $response->content; }
# On failure, barf else { barf $response; }};
Read (Single)
# book read <id> - reads the book file for <id>subcommand 'read' => sub { my $id = shift @ARGV;
# GET /=/model/book/id/[id] my $response = $ua->request(GET HOST.'/=/model/book/id/'
.$id);
# On success, print the file if ($response->is_success) { print $response->content; }
# On failure, barf else { barf $response; }};
Read (Single)
# book read <id> - reads the book file for <id>subcommand 'read' => sub { my $id = shift @ARGV;
# GET /=/model/book/id/[id] my $response = $ua->request(GET HOST.'/=/model/book/id/'
.$id);
# On success, print the file if ($response->is_success) { print $response->content; }
# On failure, barf else { barf $response; }};
Read (Single)
# book read <id> - reads the book file for <id>subcommand 'read' => sub { my $id = shift @ARGV;
# GET /=/model/book/id/[id] my $response = $ua->request(GET HOST.'/=/model/book/id/'
.$id);
# On success, print the file if ($response->is_success) { print $response->content; }
# On failure, barf else { barf $response; }};
Read (Single)
# Look up and read a resourceGET qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Look up the resource file my $filename = get_local_path($id); if (-f $filename) {
# Open and slurp up the file and output the resource open my $bookfh, $filename or barf 500, "I Am Broke", "Cannot open $filename: $!";
print $q->header('text/yaml'); print do { local $/; <$bookfh> }; }
# No such resource exists else{ barf 404, "Where is What?", "Book for $id does not exist."; }};
Read (Single)
# Look up and read a resourceGET qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Look up the resource file my $filename = get_local_path($id); if (-f $filename) {
# Open and slurp up the file and output the resource open my $bookfh, $filename or barf 500, "I Am Broke", "Cannot open $filename: $!";
print $q->header('text/yaml'); print do { local $/; <$bookfh> }; }
# No such resource exists else{ barf 404, "Where is What?", "Book for $id does not exist."; }};
Read (Single)
# Look up and read a resourceGET qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Look up the resource file my $filename = get_local_path($id); if (-f $filename) {
# Open and slurp up the file and output the resource open my $bookfh, $filename or barf 500, "I Am Broke", "Cannot open $filename: $!";
print $q->header('text/yaml'); print do { local $/; <$bookfh> }; }
# No such resource exists else{ barf 404, "Where is What?", "Book for $id does not exist."; }};
Read (Single)
# Look up and read a resourceGET qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Look up the resource file my $filename = get_local_path($id); if (-f $filename) {
# Open and slurp up the file and output the resource open my $bookfh, $filename or barf 500, "I Am Broke", "Cannot open $filename: $!";
print $q->header('text/yaml'); print do { local $/; <$bookfh> }; }
# No such resource exists else{ barf 404, "Where is What?", "Book for $id does not exist."; }};
Read (Single)
# Look up and read a resourceGET qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Look up the resource file my $filename = get_local_path($id); if (-f $filename) {
# Open and slurp up the file and output the resource open my $bookfh, $filename or barf 500, "I Am Broke", "Cannot open $filename: $!";
print $q->header('text/yaml'); print do { local $/; <$bookfh> }; }
# No such resource exists else{ barf 404, "Where is What?", "Book for $id does not exist."; }};
Read (Single)
# Look up and read a resourceGET qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Look up the resource file my $filename = get_local_path($id); if (-f $filename) {
# Open and slurp up the file and output the resource open my $bookfh, $filename or barf 500, "I Am Broke", "Cannot open $filename: $!";
print $q->header('text/yaml'); print do { local $/; <$bookfh> }; }
# No such resource exists else{ barf 404, "Where is What?", "Book for $id does not exist."; }};
Read (Single)
# book create <filename> - submits the book file in <filename> to the serversubcommand 'create' => sub { my $file = shift @ARGV;
# Slurp up the contents of the given filename my $book_data = slurp $file;
# POST /=/model/book my $response = $ua->request(POST HOST.'/=/model/book', 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, return the new ID assigned to the resource if ($response->is_success) { my $url = $response->header('Location'); my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; }
# On failure, barf else { barf $response; }};
Create
# book create <filename> - submits the book file in <filename> to the serversubcommand 'create' => sub { my $file = shift @ARGV;
# Slurp up the contents of the given filename my $book_data = slurp $file;
# POST /=/model/book my $response = $ua->request(POST HOST.'/=/model/book', 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, return the new ID assigned to the resource if ($response->is_success) { my $url = $response->header('Location'); my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; }
# On failure, barf else { barf $response; }};
Create
# book create <filename> - submits the book file in <filename> to the serversubcommand 'create' => sub { my $file = shift @ARGV;
# Slurp up the contents of the given filename my $book_data = slurp $file;
# POST /=/model/book my $response = $ua->request(POST HOST.'/=/model/book', 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, return the new ID assigned to the resource if ($response->is_success) { my $url = $response->header('Location'); my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; }
# On failure, barf else { barf $response; }};
Create
# book create <filename> - submits the book file in <filename> to the serversubcommand 'create' => sub { my $file = shift @ARGV;
# Slurp up the contents of the given filename my $book_data = slurp $file;
# POST /=/model/book my $response = $ua->request(POST HOST.'/=/model/book', 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, return the new ID assigned to the resource if ($response->is_success) { my $url = $response->header('Location'); my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; }
# On failure, barf else { barf $response; }};
Create
# book create <filename> - submits the book file in <filename> to the serversubcommand 'create' => sub { my $file = shift @ARGV;
# Slurp up the contents of the given filename my $book_data = slurp $file;
# POST /=/model/book my $response = $ua->request(POST HOST.'/=/model/book', 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, return the new ID assigned to the resource if ($response->is_success) { my $url = $response->header('Location'); my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; }
# On failure, barf else { barf $response; }};
Create
# book create <filename> - submits the book file in <filename> to the serversubcommand 'create' => sub { my $file = shift @ARGV;
# Slurp up the contents of the given filename my $book_data = slurp $file;
# POST /=/model/book my $response = $ua->request(POST HOST.'/=/model/book', 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, return the new ID assigned to the resource if ($response->is_success) { my $url = $response->header('Location'); my ($id) = $url =~ /([\d-]+)$/; print "$id: $url\n"; }
# On failure, barf else { barf $response; }};
Create
# Handle the creation of new booksPOST qr{^/=/model/book$} => sub {
# Check to make sure the input book is sane my $book= check_book( $q->param('POSTDATA') );
# If we have an ISBN (some books don't!), then die if we already have # it because we don't permit POST cannot for updates! if ($book->{isbn} and -f get_local_path($book->{isbn})) { barf 500, 'Not Gonna Do It', 'A POST may not be used to update an existing book.'; }
# Our data is sane!
# ...
Create (part 1)
# Handle the creation of new booksPOST qr{^/=/model/book$} => sub {
# Check to make sure the input book is sane my $book= check_book( $q->param('POSTDATA') );
# If we have an ISBN (some books don't!), then die if we already have # it because we don't permit POST cannot for updates! if ($book->{isbn} and -f get_local_path($book->{isbn})) { barf 500, 'Not Gonna Do It', 'A POST may not be used to update an existing book.'; }
# Our data is sane!
# ...
Create (part 1)
# Handle the creation of new booksPOST qr{^/=/model/book$} => sub {
# Check to make sure the input book is sane my $book= check_book( $q->param('POSTDATA') );
# If we have an ISBN (some books don't!), then die if we already have # it because we don't permit POST cannot for updates! if ($book->{isbn} and -f get_local_path($book->{isbn})) { barf 500, 'Not Gonna Do It', 'A POST may not be used to update an existing book.'; }
# Our data is sane!
# ...
Create (part 1)
# Handle the creation of new booksPOST qr{^/=/model/book$} => sub {
# Check to make sure the input book is sane my $book= check_book( $q->param('POSTDATA') );
# If we have an ISBN (some books don't!), then die if we already have # it because we don't permit POST cannot for updates! if ($book->{isbn} and -f get_local_path($book->{isbn})) { barf 500, 'Not Gonna Do It', 'A POST may not be used to update an existing book.'; }
# Our data is sane!
# ...
Create (part 1)
# Handle the creation of new booksPOST qr{^/=/model/book$} => sub {
# Check to make sure the input book is sane my $book= check_book( $q->param('POSTDATA') );
# If we have an ISBN (some books don't!), then die if we already have # it because we don't permit POST cannot for updates! if ($book->{isbn} and -f get_local_path($book->{isbn})) { barf 500, 'Not Gonna Do It', 'A POST may not be used to update an existing book.'; }
# Our data is sane!
# ...
Create (part 1)
# Handle the creation of new booksPOST qr{^/=/model/book$} => sub {
# Check to make sure the input book is sane my $book= check_book( $q->param('POSTDATA') );
# If we have an ISBN (some books don't!), then die if we already have # it because we don't permit POST cannot for updates! if ($book->{isbn} and -f get_local_path($book->{isbn})) { barf 500, 'Not Gonna Do It', 'A POST may not be used to update an existing book.'; }
# Our data is sane!
# ...
Create (part 1)
# ...
# Figure out an ID, this is either the ISBN or a generated ID my $id = $book->{isbn} ? $book->{isbn} : next_id;
# Store the ID for reference within the record $book->{id} = $id;
# Save the resource eval { YAML::DumpFile(get_local_path($id), $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user my $resource_url = absolute_url('/=/model/book/id/'.$id); print $q->header( -status => 201, -type => 'text/html', -location => $resource_url, ); print $q->h1("Created $book->{title}"); print $q->ul( $q->li( $q->a({ href => $resource_url }, $resource_url) ) );};
Create (part 2)
# ...
# Figure out an ID, this is either the ISBN or a generated ID my $id = $book->{isbn} ? $book->{isbn} : next_id;
# Store the ID for reference within the record $book->{id} = $id;
# Save the resource eval { YAML::DumpFile(get_local_path($id), $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user my $resource_url = absolute_url('/=/model/book/id/'.$id); print $q->header( -status => 201, -type => 'text/html', -location => $resource_url, ); print $q->h1("Created $book->{title}"); print $q->ul( $q->li( $q->a({ href => $resource_url }, $resource_url) ) );};
Create (part 2)
# ...
# Figure out an ID, this is either the ISBN or a generated ID my $id = $book->{isbn} ? $book->{isbn} : next_id;
# Store the ID for reference within the record $book->{id} = $id;
# Save the resource eval { YAML::DumpFile(get_local_path($id), $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user my $resource_url = absolute_url('/=/model/book/id/'.$id); print $q->header( -status => 201, -type => 'text/html', -location => $resource_url, ); print $q->h1("Created $book->{title}"); print $q->ul( $q->li( $q->a({ href => $resource_url }, $resource_url) ) );};
Create (part 2)
# ...
# Figure out an ID, this is either the ISBN or a generated ID my $id = $book->{isbn} ? $book->{isbn} : next_id;
# Store the ID for reference within the record $book->{id} = $id;
# Save the resource eval { YAML::DumpFile(get_local_path($id), $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user my $resource_url = absolute_url('/=/model/book/id/'.$id); print $q->header( -status => 201, -type => 'text/html', -location => $resource_url, ); print $q->h1("Created $book->{title}"); print $q->ul( $q->li( $q->a({ href => $resource_url }, $resource_url) ) );};
Create (part 2)
# ...
# Figure out an ID, this is either the ISBN or a generated ID my $id = $book->{isbn} ? $book->{isbn} : next_id;
# Store the ID for reference within the record $book->{id} = $id;
# Save the resource eval { YAML::DumpFile(get_local_path($id), $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user my $resource_url = absolute_url('/=/model/book/id/'.$id); print $q->header( -status => 201, -type => 'text/html', -location => $resource_url, ); print $q->h1("Created $book->{title}"); print $q->ul( $q->li( $q->a({ href => $resource_url }, $resource_url) ) );};
Create (part 2)
# book update <id> <filename> - updates the book file <id> using <filename>subcommand 'update' => sub { my $id = shift @ARGV; my $file = shift @ARGV;
# Slurp up the given file name my $book_data = slurp $file;
# PUT /=/model/book/id/[id] my $response = $ua->request(PUT HOST.'/=/model/book/id/'.$id, 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, just announce success if ($response->is_success) { print "Updated $id\n"; }
# On failure, barf else { barf $response; }};
Update
# book update <id> <filename> - updates the book file <id> using <filename>subcommand 'update' => sub { my $id = shift @ARGV; my $file = shift @ARGV;
# Slurp up the given file name my $book_data = slurp $file;
# PUT /=/model/book/id/[id] my $response = $ua->request(PUT HOST.'/=/model/book/id/'.$id, 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, just announce success if ($response->is_success) { print "Updated $id\n"; }
# On failure, barf else { barf $response; }};
Update
# book update <id> <filename> - updates the book file <id> using <filename>subcommand 'update' => sub { my $id = shift @ARGV; my $file = shift @ARGV;
# Slurp up the given file name my $book_data = slurp $file;
# PUT /=/model/book/id/[id] my $response = $ua->request(PUT HOST.'/=/model/book/id/'.$id, 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, just announce success if ($response->is_success) { print "Updated $id\n"; }
# On failure, barf else { barf $response; }};
Update
# book update <id> <filename> - updates the book file <id> using <filename>subcommand 'update' => sub { my $id = shift @ARGV; my $file = shift @ARGV;
# Slurp up the given file name my $book_data = slurp $file;
# PUT /=/model/book/id/[id] my $response = $ua->request(PUT HOST.'/=/model/book/id/'.$id, 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, just announce success if ($response->is_success) { print "Updated $id\n"; }
# On failure, barf else { barf $response; }};
Update
# book update <id> <filename> - updates the book file <id> using <filename>subcommand 'update' => sub { my $id = shift @ARGV; my $file = shift @ARGV;
# Slurp up the given file name my $book_data = slurp $file;
# PUT /=/model/book/id/[id] my $response = $ua->request(PUT HOST.'/=/model/book/id/'.$id, 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, just announce success if ($response->is_success) { print "Updated $id\n"; }
# On failure, barf else { barf $response; }};
Update
# book update <id> <filename> - updates the book file <id> using <filename>subcommand 'update' => sub { my $id = shift @ARGV; my $file = shift @ARGV;
# Slurp up the given file name my $book_data = slurp $file;
# PUT /=/model/book/id/[id] my $response = $ua->request(PUT HOST.'/=/model/book/id/'.$id, 'Content-Type' => 'text/yaml', Content => $book_data, );
# On success, just announce success if ($response->is_success) { print "Updated $id\n"; }
# On failure, barf else { barf $response; }};
Update
# Handle updates to booksPUT qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Check to make sure the input book is sane my $book = check_book( $q->param('PUTDATA') );
# Make sure the book already exists or barf my $resource_path= get_local_path($id); unless(-f $resource_path) { barf 500, 'Not Gonna Do It', 'Cannot use PUTs for creating a new resource.'; }
# Make sure the ID is set $book->{id} = $id;
# Save the resource eval { YAML::DumpFile($resource_path, $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user print $q->header('text/html'); print $q->h1("Updated $book->{title}");};
Update
# Handle updates to booksPUT qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Check to make sure the input book is sane my $book = check_book( $q->param('PUTDATA') );
# Make sure the book already exists or barf my $resource_path= get_local_path($id); unless(-f $resource_path) { barf 500, 'Not Gonna Do It', 'Cannot use PUTs for creating a new resource.'; }
# Make sure the ID is set $book->{id} = $id;
# Save the resource eval { YAML::DumpFile($resource_path, $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user print $q->header('text/html'); print $q->h1("Updated $book->{title}");};
Update
# Handle updates to booksPUT qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Check to make sure the input book is sane my $book = check_book( $q->param('PUTDATA') );
# Make sure the book already exists or barf my $resource_path= get_local_path($id); unless(-f $resource_path) { barf 500, 'Not Gonna Do It', 'Cannot use PUTs for creating a new resource.'; }
# Make sure the ID is set $book->{id} = $id;
# Save the resource eval { YAML::DumpFile($resource_path, $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user print $q->header('text/html'); print $q->h1("Updated $book->{title}");};
Update
# Handle updates to booksPUT qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Check to make sure the input book is sane my $book = check_book( $q->param('PUTDATA') );
# Make sure the book already exists or barf my $resource_path= get_local_path($id); unless(-f $resource_path) { barf 500, 'Not Gonna Do It', 'Cannot use PUTs for creating a new resource.'; }
# Make sure the ID is set $book->{id} = $id;
# Save the resource eval { YAML::DumpFile($resource_path, $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user print $q->header('text/html'); print $q->h1("Updated $book->{title}");};
Update
# Handle updates to booksPUT qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Check to make sure the input book is sane my $book = check_book( $q->param('PUTDATA') );
# Make sure the book already exists or barf my $resource_path= get_local_path($id); unless(-f $resource_path) { barf 500, 'Not Gonna Do It', 'Cannot use PUTs for creating a new resource.'; }
# Make sure the ID is set $book->{id} = $id;
# Save the resource eval { YAML::DumpFile($resource_path, $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user print $q->header('text/html'); print $q->h1("Updated $book->{title}");};
Update
# Handle updates to booksPUT qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Check to make sure the input book is sane my $book = check_book( $q->param('PUTDATA') );
# Make sure the book already exists or barf my $resource_path= get_local_path($id); unless(-f $resource_path) { barf 500, 'Not Gonna Do It', 'Cannot use PUTs for creating a new resource.'; }
# Make sure the ID is set $book->{id} = $id;
# Save the resource eval { YAML::DumpFile($resource_path, $book) }; barf 500, 'I Am Broke', $@ if $@;
# Note the success to the end-user print $q->header('text/html'); print $q->h1("Updated $book->{title}");};
Update
# book delete <id> - deletes the book resource with ID <id>subcommand 'delete' => sub { my $id = shift @ARGV;
# DELETE /=/model/book/id/[id] my $response = $ua->request( HTTP::Request->new( DELETE => HOST.'/=/model/book/id/'.$id) );
# On success, announce it if ($response->is_success) { print "Deleted $id\n"; }
# On failure, barf else { barf $response; }};
Delete
# book delete <id> - deletes the book resource with ID <id>subcommand 'delete' => sub { my $id = shift @ARGV;
# DELETE /=/model/book/id/[id] my $response = $ua->request( HTTP::Request->new( DELETE => HOST.'/=/model/book/id/'.$id) );
# On success, announce it if ($response->is_success) { print "Deleted $id\n"; }
# On failure, barf else { barf $response; }};
Delete
# book delete <id> - deletes the book resource with ID <id>subcommand 'delete' => sub { my $id = shift @ARGV;
# DELETE /=/model/book/id/[id] my $response = $ua->request( HTTP::Request->new( DELETE => HOST.'/=/model/book/id/'.$id) );
# On success, announce it if ($response->is_success) { print "Deleted $id\n"; }
# On failure, barf else { barf $response; }};
Delete
# book delete <id> - deletes the book resource with ID <id>subcommand 'delete' => sub { my $id = shift @ARGV;
# DELETE /=/model/book/id/[id] my $response = $ua->request( HTTP::Request->new( DELETE => HOST.'/=/model/book/id/'.$id) );
# On success, announce it if ($response->is_success) { print "Deleted $id\n"; }
# On failure, barf else { barf $response; }};
Delete
# book delete <id> - deletes the book resource with ID <id>subcommand 'delete' => sub { my $id = shift @ARGV;
# DELETE /=/model/book/id/[id] my $response = $ua->request( HTTP::Request->new( DELETE => HOST.'/=/model/book/id/'.$id) );
# On success, announce it if ($response->is_success) { print "Deleted $id\n"; }
# On failure, barf else { barf $response; }};
Delete
DELETE qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Make sure the book actually exists my $resource_path = get_local_path($id); unless (-f $resource_path) { barf 404, 'Where is What?', 'Nothing here to delete.'; }
# Baleted! unlink $resource_path;
# Tell me about it. print $q->header('text/html'); print $q->h1("Deleted $id");};
Delete
DELETE qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Make sure the book actually exists my $resource_path = get_local_path($id); unless (-f $resource_path) { barf 404, 'Where is What?', 'Nothing here to delete.'; }
# Baleted! unlink $resource_path;
# Tell me about it. print $q->header('text/html'); print $q->h1("Deleted $id");};
Delete
DELETE qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Make sure the book actually exists my $resource_path = get_local_path($id); unless (-f $resource_path) { barf 404, 'Where is What?', 'Nothing here to delete.'; }
# Baleted! unlink $resource_path;
# Tell me about it. print $q->header('text/html'); print $q->h1("Deleted $id");};
Delete
DELETE qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Make sure the book actually exists my $resource_path = get_local_path($id); unless (-f $resource_path) { barf 404, 'Where is What?', 'Nothing here to delete.'; }
# Baleted! unlink $resource_path;
# Tell me about it. print $q->header('text/html'); print $q->h1("Deleted $id");};
Delete
DELETE qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Make sure the book actually exists my $resource_path = get_local_path($id); unless (-f $resource_path) { barf 404, 'Where is What?', 'Nothing here to delete.'; }
# Baleted! unlink $resource_path;
# Tell me about it. print $q->header('text/html'); print $q->h1("Deleted $id");};
Delete
DELETE qr{^/=/model/book/id/([\d-]+)$} => sub { my $id= $1;
# Make sure the book actually exists my $resource_path = get_local_path($id); unless (-f $resource_path) { barf 404, 'Where is What?', 'Nothing here to delete.'; }
# Baleted! unlink $resource_path;
# Tell me about it. print $q->header('text/html'); print $q->h1("Deleted $id");};
Delete
# Provide some nice documentationGET qr{^/=$} => sub { print $q->header('text/html');
print $q->h1('REST API Documentation');
print $q->p('Here is a list of what you can do:');
print $q->dl( $q->dt('GET /=/model/book/id'), $q->dd('Returns a list of available book IDs.'),
$q->dt('GET /=/model/book/id/[ID]'), $q->dd('ID may be a number or the ISBN. Returns the book.'),
$q->dt('POST /=/model/book'), $q->dd('Create a new book record. Returns the new URL to fetch with.'),
$q->dt('PUT /=/model/book/id/[ID]'), $q->dd('Update a book by posting a complete book file.'),
$q->dt('DELETE /=/model/book/id/[ID]'), $q->dd('Delete a book.'), );
print $q->p('All book resources are stored or fetched in YAML format. The list of books will be fetched in HTML with each LI in the returned listing containing a link to a book resource.');
print $q->p('Here is a sample book. The "title" field is the only required field for books. The "isbn" field should be equal to the "id" field, if the "isbn" is present. The "id" field should be the [ID] used to fetch, updated, or delete the record.');
print $q->pre(q{isbn: 0-7852-1155-1title: "The New Strong's Exhaustive Concordance of the Bible"author: James Strong, LL.D., S.T.D.publisher: Thomas Nelson Publisherscity: Nashville, Tennesseeyear: 1995});};
Built-in Documentation
• Use whatever content types are most appropriate to your audience: XML, YAML, JSON, HTML, RSS/Atom, SQL, CSV, vFiles, PDF
• Don’t be afraid to offer multiple formats using the Accept: headers or even file name suffixes
• Use the full range of HTTP response codes to give clear responses
• Include additional X-blah: headers for metadata
Exercises for the Audience
Recommended Resources
• Sample Code:http://contentment.org/files/onlamp/library.cgihttp://contentment.org/files/onlamp/book
• Original Articles:http://www.onlamp.com/pub/a/onlamp/2008/02/19/developing-restful-web-services-in-perl.htmlhttp://contentment.org/2008/08/developing-restful-web-service.html
• OpenResty - Nice REST middleware server by Agent Zhang:http://search.cpan.org/dist/OpenResty/
• Jifty - I ripped off the style of the REST interface of Jifty for this demo:http://search.cpan.org/dist/Jifty/
• HTTP Specification: http://www.w3.org/Protocols/rfc2616/rfc2616.html
• REST Wiki: http://rest.blueoxen.net/
Thank you!