16. What are we NOT talking about?
• REST is a general purpose term
17. What are we NOT talking about?
• REST is a general purpose term
• I’m not talking about a general notion
18. 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
19. 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
20. 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!!!
21. 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!!!
22. 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...
24. The REST Triangle
no
un
ht s
tp
://
ww
w.
on
lam
p.c
om
content types
YAML, XML, JSON, etc.
T E
LE
DE
U T,
b s ST, P
er PO
v ,
G ET
25. The REST Triangle
no • Nouns (=URL)
un
ht s
tp
://
ww
w.
on
lam
p.c
om
content types
YAML, XML, JSON, etc.
T E
LE
DE
U T,
b s ST, P
er PO
v ,
G ET
26. The REST Triangle
no • Nouns (=URL)
un
ht s
tp
://
ww • Verbs (=HTTP METHOD)
w.
on
lam
p.c
om
content types
YAML, XML, JSON, etc.
T E
LE
DE
U T,
b s ST, P
er PO
v ,
G ET
27. The REST Triangle
no • Nouns (=URL)
un
ht s
tp
://
ww • Verbs (=HTTP METHOD)
w.
on
lam
p.c
om • Content (=MIME Type)
content types
YAML, XML, JSON, etc.
T E
LE
DE
U T,
b s ST, P
er PO
v ,
G ET
34. REST Web Services
• Treat your data like a static web page
CRUD
Create
Read
Update
Delete
35. REST Web Services
• Treat your data like a static web page
CRUD
• Add data by POSTing a web page Create
Read
Update
Delete
36. REST Web Services
• Treat your data like a static web page
CRUD
• Add data by POSTing a web page Create
• Fetch data by GETting a web page Read
Update
Delete
37. REST Web Services
• Treat your data like a static web page
CRUD
• Add data by POSTing a web page Create
• Fetch data by GETting a web page Read
• Update data by PUTing a web page
Update
Delete
38. REST Web Services
• Treat your data like a static web page
CRUD
• Add data by POSTing a web page Create
• Fetch data by GETting a web page Read
• Update data by PUTing a web page
Update
• Delete data by DELETing a web page
Delete
39. REST Web Services
• Treat your data like a static web page
CRUD
• Add data by POSTing a web page Create
• Fetch data by GETting a web page Read
• Update data by PUTing a web page
Update
• Delete data by DELETing a web page
• E... it’s missing and it bugs me...
Delete
41. Who Uses It
• Google • Socialtext
• Yahoo! • Digg
• Amazon • Twitter
• Intuit • eBay
• Best Practical • Technorati
• Facebook • Too many others to mention...
44. What about other stuff?
• RPC is a square peg
• REST is round hole
45. What about other stuff?
• RPC is a square peg
• REST is round hole
46. What about other stuff?
• RPC is a square peg
• REST is round hole
• But it works!
47. What about other stuff?
• RPC is a square peg
• REST is round hole
• But it works!
• Getting Info? GET
48. What about other stuff?
• RPC is a square peg
• REST is round hole
• But it works!
• Getting Info? GET
• Modifying Something? POST
49. 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
51. # Manage your books from the command-line. Woo-hoo!
% ./book list
52. # Manage your books from the command-line. Woo-hoo!
% ./book list
0-8024-8160-4: .../library.cgi/=/model/book/id/0-8024-8160-4
0-85151-760-9: .../library.cgi/=/model/book/id/0-85151-760-9
0-936083-11-5: .../library.cgi/=/model/book/id/0-936083-11-5
54. % ./book read 0-8024-8160-4
---
author: David Clotfelter
city: Chicago
id: 0-8024-8160-4
isbn: 0-8024-8160-4
publisher: Moody Publishers
title: >
Sinners in the Hands of a Good God:
Reconciling Divine Judgment and Mercy'
year: 2004
65. Explaining the Nouns (a.k.a. URLs)
Scheme Borrowed from Jifty
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
66. Explaining the Nouns (a.k.a. URLs)
Scheme Borrowed from Jifty
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
67. Explaining the Nouns (a.k.a. URLs)
Scheme Borrowed from Jifty
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
68. Explaining the Nouns (a.k.a. URLs)
Scheme Borrowed from Jifty
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
69. Explaining the Nouns (a.k.a. URLs)
Scheme Borrowed from Jifty
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
70. Explaining the Nouns (a.k.a. URLs)
Scheme Borrowed from Jifty
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
71. Read (List)
# book list - lists all the books the server returns
subcommand '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=quot;([^quot;]+)quot;/gm;
for my $url (@links) {
my ($id) = $url =~ /([d-]+)$/;
print quot;$id: $urlnquot;;
}
}
# On failure, barf
else {
barf $response;
}
};
72. Read (List)
# book list - lists all the books the server returns
subcommand '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=quot;([^quot;]+)quot;/gm;
for my $url (@links) {
my ($id) = $url =~ /([d-]+)$/;
print quot;$id: $urlnquot;;
}
}
# On failure, barf
else {
barf $response;
}
};
73. Read (List)
# book list - lists all the books the server returns
subcommand '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=quot;([^quot;]+)quot;/gm;
for my $url (@links) {
my ($id) = $url =~ /([d-]+)$/;
print quot;$id: $urlnquot;;
}
}
# On failure, barf
else {
barf $response;
}
};
74. Read (List)
# book list - lists all the books the server returns
subcommand '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=quot;([^quot;]+)quot;/gm;
for my $url (@links) {
my ($id) = $url =~ /([d-]+)$/;
print quot;$id: $urlnquot;;
}
}
# On failure, barf
else {
barf $response;
}
};
75. Read (List)
# book list - lists all the books the server returns
subcommand '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=quot;([^quot;]+)quot;/gm;
for my $url (@links) {
my ($id) = $url =~ /([d-]+)$/;
print quot;$id: $urlnquot;;
}
}
# On failure, barf
else {
barf $response;
}
};
76. Read (List)
# Get a whole list of available documents
GET 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);
};
77. Read (List)
# Get a whole list of available documents
GET 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);
};
78. Read (List)
# Get a whole list of available documents
GET 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);
};
79. Read (List)
# Get a whole list of available documents
GET 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);
};
80. Read (List)
# Get a whole list of available documents
GET 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);
};
81. 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;
}
};
82. 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;
}
};
83. 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;
}
};
84. 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;
}
};
85. 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;
}
};
86. Read (Single)
# Look up and read a resource
GET 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, quot;I Am Brokequot;, quot;Cannot open $filename: $!quot;;
print $q->header('text/yaml');
print do { local $/; <$bookfh> };
}
# No such resource exists
else{
barf 404, quot;Where is What?quot;, quot;Book for $id does not exist.quot;;
}
};
87. Read (Single)
# Look up and read a resource
GET 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, quot;I Am Brokequot;, quot;Cannot open $filename: $!quot;;
print $q->header('text/yaml');
print do { local $/; <$bookfh> };
}
# No such resource exists
else{
barf 404, quot;Where is What?quot;, quot;Book for $id does not exist.quot;;
}
};
88. Read (Single)
# Look up and read a resource
GET 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, quot;I Am Brokequot;, quot;Cannot open $filename: $!quot;;
print $q->header('text/yaml');
print do { local $/; <$bookfh> };
}
# No such resource exists
else{
barf 404, quot;Where is What?quot;, quot;Book for $id does not exist.quot;;
}
};
89. Read (Single)
# Look up and read a resource
GET 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, quot;I Am Brokequot;, quot;Cannot open $filename: $!quot;;
print $q->header('text/yaml');
print do { local $/; <$bookfh> };
}
# No such resource exists
else{
barf 404, quot;Where is What?quot;, quot;Book for $id does not exist.quot;;
}
};
90. Read (Single)
# Look up and read a resource
GET 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, quot;I Am Brokequot;, quot;Cannot open $filename: $!quot;;
print $q->header('text/yaml');
print do { local $/; <$bookfh> };
}
# No such resource exists
else{
barf 404, quot;Where is What?quot;, quot;Book for $id does not exist.quot;;
}
};
91. Read (Single)
# Look up and read a resource
GET 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, quot;I Am Brokequot;, quot;Cannot open $filename: $!quot;;
print $q->header('text/yaml');
print do { local $/; <$bookfh> };
}
# No such resource exists
else{
barf 404, quot;Where is What?quot;, quot;Book for $id does not exist.quot;;
}
};
92. Create
# book create <filename> - submits the book file in <filename> to the server
subcommand '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 quot;$id: $urlnquot;;
}
# On failure, barf
else {
barf $response;
}
};
93. Create
# book create <filename> - submits the book file in <filename> to the server
subcommand '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 quot;$id: $urlnquot;;
}
# On failure, barf
else {
barf $response;
}
};
94. Create
# book create <filename> - submits the book file in <filename> to the server
subcommand '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 quot;$id: $urlnquot;;
}
# On failure, barf
else {
barf $response;
}
};
95. Create
# book create <filename> - submits the book file in <filename> to the server
subcommand '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 quot;$id: $urlnquot;;
}
# On failure, barf
else {
barf $response;
}
};
96. Create
# book create <filename> - submits the book file in <filename> to the server
subcommand '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 quot;$id: $urlnquot;;
}
# On failure, barf
else {
barf $response;
}
};
97. Create
# book create <filename> - submits the book file in <filename> to the server
subcommand '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 quot;$id: $urlnquot;;
}
# On failure, barf
else {
barf $response;
}
};
98. Create (part 1)
# Handle the creation of new books
POST 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!
# ...
99. Create (part 1)
# Handle the creation of new books
POST 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!
# ...
100. Create (part 1)
# Handle the creation of new books
POST 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!
# ...
101. Create (part 1)
# Handle the creation of new books
POST 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!
# ...
102. Create (part 1)
# Handle the creation of new books
POST 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!
# ...
103. Create (part 1)
# Handle the creation of new books
POST 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!
# ...
104. 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(quot;Created $book->{title}quot;);
print $q->ul(
$q->li(
$q->a({ href => $resource_url }, $resource_url)
)
);
};
105. 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(quot;Created $book->{title}quot;);
print $q->ul(
$q->li(
$q->a({ href => $resource_url }, $resource_url)
)
);
};
106. 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(quot;Created $book->{title}quot;);
print $q->ul(
$q->li(
$q->a({ href => $resource_url }, $resource_url)
)
);
};
107. 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(quot;Created $book->{title}quot;);
print $q->ul(
$q->li(
$q->a({ href => $resource_url }, $resource_url)
)
);
};
108. 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(quot;Created $book->{title}quot;);
print $q->ul(
$q->li(
$q->a({ href => $resource_url }, $resource_url)
)
);
};
109. 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 quot;Updated $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
110. 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 quot;Updated $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
111. 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 quot;Updated $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
112. 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 quot;Updated $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
113. 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 quot;Updated $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
114. 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 quot;Updated $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
115. Update
# Handle updates to books
PUT 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(quot;Updated $book->{title}quot;);
};
116. Update
# Handle updates to books
PUT 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(quot;Updated $book->{title}quot;);
};
117. Update
# Handle updates to books
PUT 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(quot;Updated $book->{title}quot;);
};
118. Update
# Handle updates to books
PUT 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(quot;Updated $book->{title}quot;);
};
119. Update
# Handle updates to books
PUT 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(quot;Updated $book->{title}quot;);
};
120. Update
# Handle updates to books
PUT 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(quot;Updated $book->{title}quot;);
};
121. 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 quot;Deleted $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
122. 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 quot;Deleted $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
123. 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 quot;Deleted $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
124. 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 quot;Deleted $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
125. 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 quot;Deleted $idnquot;;
}
# On failure, barf
else {
barf $response;
}
};
126. 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(quot;Deleted $idquot;);
};
127. 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(quot;Deleted $idquot;);
};
128. 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(quot;Deleted $idquot;);
};
129. 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(quot;Deleted $idquot;);
};
130. 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(quot;Deleted $idquot;);
};
131. 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(quot;Deleted $idquot;);
};
132. Built-in Documentation
# Provide some nice documentation
GET 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 quot;titlequot; field is the only required field for books. The
quot;isbnquot; field should be equal to the quot;idquot; field, if the quot;isbnquot; is present. The quot;idquot; field should be the
[ID] used to fetch, updated, or delete the record.');
print $q->pre(q{isbn: 0-7852-1155-1
title: quot;The New Strong's Exhaustive Concordance of the Biblequot;
author: James Strong, LL.D., S.T.D.
publisher: Thomas Nelson Publishers
city: Nashville, Tennessee
year: 1995});
};
133. Exercises for the Audience
• 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
134. Recommended Resources
• Sample Code:
http://contentment.org/files/onlamp/library.cgi
http://contentment.org/files/onlamp/book
• Original Articles:
http://www.onlamp.com/pub/a/onlamp/2008/02/19/developing-restful-web-
services-in-perl.html
http://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/