making your perl rest

Post on 17-May-2015

14.414 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

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

• Google

• Yahoo!

• Amazon

• Intuit

• Best Practical

• Facebook

• Socialtext

• Digg

• Twitter

• 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!

top related