Making Your Perl REST

15,036 views
14,946 views

Published on

Published in: Technology
0 Comments
6 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
15,036
On SlideShare
0
From Embeds
0
Number of Embeds
4
Actions
Shares
0
Downloads
146
Comments
0
Likes
6
Embeds 0
No embeds

No notes for slide

Making Your Perl REST

  1. 1. Making Your Perl REST Sterling Hanenkamp, PPW 2008
  2. 2. What is REST?
  3. 3. What is REST? • Ever heard of...
  4. 4. What is REST? • Ever heard of... • SOAP?
  5. 5. What is REST? • Ever heard of... • SOAP? • RPC?
  6. 6. What is REST? • Ever heard of... • SOAP? • RPC? • XML-RPC?
  7. 7. What is REST? • Ever heard of... • SOAP? • RPC? • XML-RPC? • WebDAV?
  8. 8. What is REST? • Ever heard of... • SOAP? • RPC? • XML-RPC? • WebDAV? • It’s kind of like that.
  9. 9. What is REST? • Ever heard of... • Ever heard of... • SOAP? • RPC? • XML-RPC? • WebDAV? • It’s kind of like that.
  10. 10. What is REST? • Ever heard of... • Ever heard of... • SOAP? • HTTP? • RPC? • XML-RPC? • WebDAV? • It’s kind of like that.
  11. 11. What is REST? • Ever heard of... • Ever heard of... • SOAP? • HTTP? • RPC? • XML? • XML-RPC? • WebDAV? • It’s kind of like that.
  12. 12. What is REST? • Ever heard of... • Ever heard of... • SOAP? • HTTP? • RPC? • XML? • XML-RPC? • YAML? • WebDAV? • It’s kind of like that.
  13. 13. What is REST? • Ever heard of... • Ever heard of... • SOAP? • HTTP? • RPC? • XML? • XML-RPC? • YAML? • WebDAV? • JSON? • It’s kind of like that.
  14. 14. What is REST? • Ever heard of... • Ever heard of... • SOAP? • HTTP? • RPC? • XML? • XML-RPC? • YAML? • WebDAV? • JSON? • It’s kind of like that. • It’s kind of like that too
  15. 15. What are we NOT talking about?
  16. 16. What are we NOT talking about? • REST is a general purpose term
  17. 17. What are we NOT talking about? • REST is a general purpose term • I’m not talking about a general notion
  18. 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. 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. 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. 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. 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...
  23. 23. The REST Triangle
  24. 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. 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. 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. 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
  28. 28. REST Web Services
  29. 29. REST Web Services CRUD
  30. 30. REST Web Services CRUD Create
  31. 31. REST Web Services CRUD Create Read
  32. 32. REST Web Services CRUD Create Read Update
  33. 33. REST Web Services CRUD Create Read Update Delete
  34. 34. REST Web Services • Treat your data like a static web page CRUD Create Read Update Delete
  35. 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. 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. 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. 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. 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
  40. 40. Who Uses It
  41. 41. Who Uses It • Google • Socialtext • Yahoo! • Digg • Amazon • Twitter • Intuit • eBay • Best Practical • Technorati • Facebook • Too many others to mention...
  42. 42. What about other stuff?
  43. 43. What about other stuff? • RPC is a square peg
  44. 44. What about other stuff? • RPC is a square peg • REST is round hole
  45. 45. What about other stuff? • RPC is a square peg • REST is round hole
  46. 46. What about other stuff? • RPC is a square peg • REST is round hole • But it works!
  47. 47. What about other stuff? • RPC is a square peg • REST is round hole • But it works! • Getting Info? GET
  48. 48. What about other stuff? • RPC is a square peg • REST is round hole • But it works! • Getting Info? GET • Modifying Something? POST
  49. 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
  50. 50. ENOUGH BASICS. LET’S DO SOMETHING.
  51. 51. # Manage your books from the command-line. Woo-hoo! % ./book list
  52. 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
  53. 53. % ./book read 0-8024-8160-4
  54. 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
  55. 55. % ./book create reformation.yml
  56. 56. % ./book create reformation.yml 0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5
  57. 57. % ./book create reformation.yml 0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5 % ./book update 0-87552-183-5 reformation2.yml
  58. 58. % ./book create reformation.yml 0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5 % ./book update 0-87552-183-5 reformation2.yml Updated 0-87552-183-5
  59. 59. % ./book create reformation.yml 0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5 % ./book update 0-87552-183-5 reformation2.yml Updated 0-87552-183-5 % ./book delete 0-87552-183-5
  60. 60. % ./book create reformation.yml 0-87552-183-5: .../library.cgi/=/model/book/id/0-87552-183-5 % ./book update 0-87552-183-5 reformation2.yml Updated 0-87552-183-5 % ./book delete 0-87552-183-5 Deleted 0-87552-183-5
  61. 61. Okay... Putting it together But what did that do?
  62. 62. Explaining the Nouns (a.k.a. URLs)
  63. 63. Explaining the Nouns (a.k.a. URLs) Scheme Borrowed from Jifty
  64. 64. 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
  65. 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. 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. 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. 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. 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. 70. 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; } };
  71. 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. 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. 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. 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. 75. 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); };
  76. 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. 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. 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. 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. 80. 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; } };
  81. 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. 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. 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. 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. 85. 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;; } };
  86. 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. 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. 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. 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. 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. 91. 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; } };
  92. 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. 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. 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. 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. 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. 97. 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! # ...
  98. 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. 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. 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. 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. 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. 103. 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) ) ); };
  104. 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. 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. 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. 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. 108. 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; } };
  109. 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. 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. 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. 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. 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. 114. 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;); };
  115. 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. 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. 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. 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. 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. 120. 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; } };
  121. 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. 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. 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. 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. 125. 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;); };
  126. 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. 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. 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. 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. 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. 131. 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}); };
  132. 132. 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
  133. 133. 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/
  134. 134. Thank you!

×