Slide 1

Slide 1 text

REST from the trenches Stevan Little YAPC::NA 2012 [email protected]

Slide 2

Slide 2 text

YAPC::NA 2011 So last year at YAPC 2011 I gave a talk ...

Slide 3

Slide 3 text

In that talk I was discussing a framework I was working on at $work which I had given the name Jackalope.

Slide 4

Slide 4 text

In the talk I mentioned that this it was part 1 of 2, largely because Jackalope was a very young framework and still had “lots of assembly required” to make it work. So I punted the rest of the talk to YAPC::NA 2012, this was partially because I knew Jackalope wasn’t ready and hadn’t really lived enough in the real world, and partially because ...

Slide 5

Slide 5 text

well to be honest, ... cause I hoped I wouldn’t ever have to give part 2

Slide 6

Slide 6 text

YAPC::NA But since that didn’t happen, I guess I will have to make the best of it.

Slide 7

Slide 7 text

In that talk, for those of you who weren’t there, I spoke about a few things, first was the state of Web-Services ...

Slide 8

Slide 8 text

Level 3 Web Services Richardson Maturity Model Level 0 - throwing feces over HTTP Level 1 - well at least they are using URLs Level 2 - using HTTP verbs and response codes (this is where most people stop) Level 3 - HATEOAS

Slide 9

Slide 9 text

HATEOAS Hypermedia As The Engine Of Application State Hateoas is a concept described by Roy Fielding in his thesis in which he described the REST architectural style. One key thing to keep in mind, REST is an “architectural style”, which is more akin to a design pattern, which is yet another misunderstood idea, which perhaps explains why REST is so misunderstood (trust me, I know, I misunderstood it a few times). So after spending some time on theory, I then ...

Slide 10

Slide 10 text

Jackalope is tEh awesum!!!1! ... then I spent a bunch of time talking about how cool Jackalope was ...

Slide 11

Slide 11 text

now fast forward a year and ...

Slide 12

Slide 12 text

Things I got wrong Well, yeah, ... not so much ...

Slide 13

Slide 13 text

Things I got wrong ‣ Schema Languages are bad ‣ ‣ ‣ ‣ “Be strict in what you emit, liberal in what you accept” There are good points to this and bad points, but the reality is that if you want people to use your API, this is probably a good philosophy to follow. Schema languages are just overhead, both computationally and conceptually, and they create a tight coupling between the client and server, by being more liberal in what you accept it is easier to evolve your service.

Slide 14

Slide 14 text

Things I got wrong ‣ Schema Languages are bad ‣ REST != CRUD ‣ ‣ ‣ REST is often associated with CRUD, partially because the ATOM Publishing protocol is a great example of REST, and partially cause CRUD is a simple thing to map too. Reality is that not all your web-services are CRUD and so anything that makes that assumption fails. In particular Jackalope got to the point where it insisted on CRUD and anything non-CRUD had to work around it.

Slide 15

Slide 15 text

Things I got wrong ‣ Schema Languages are bad ‣ REST != CRUD ‣ You can’t selectively use HTTP ‣ ‣ HTTP is hard, there is a lot of stuff to think about. But if you really are intent on using it, then you should use it all, not just a handful of status codes and a few methods. A perfect example of where Jackalope got this wrong is that it stuffed metadata into the body (the ID (should have been the Location header), the version (should have been in the ETag header) and links (should have been in the Links header)). Later on I will demonstrate this and how it made certain things just work.

Slide 16

Slide 16 text

Things I got wrong ‣ Schema Languages are bad ‣ REST != CRUD ‣ You can’t selectively use HTTP ‣ Front-end guys hate HATEOAS ‣ HATEOAS is a great concept, I still believe in it, but not for all cases. Writing discoverable code is a really cool idea, but fact is that a stable UI needs to be able to rely on certain things. So there has to be a balance here, which means, support both.

Slide 17

Slide 17 text

Things I got wrong ‣ Schema Languages are bad ‣ REST != CRUD ‣ You can’t selectively use HTTP ‣ Front-end guys hate HATEOAS ‣ Content-Type = application/json JSON is great, I love it. However, when all you emit is application/json, you are missing out. Content-types are important and content-negotiation is even more so. A lot can be said through custom content-types, including the ability to “downgrade” and deal with plain old JSON.

Slide 18

Slide 18 text

So basically it was back to the drawing board ... and after some thinking, I decided that for my next effort I needed to keep a couple things in mind.

Slide 19

Slide 19 text

‣ K.I.S.S. First it that things had to be simple. It should be easy to create new endpoints for my API and not have to jump through various hoops of complexity.

Slide 20

Slide 20 text

‣ K.I.S.S. ‣ Discoverability Next was discoverability, which is not just for computers, but for humans too. It should be possible to explore an API with something as low-level as Curl and to be able to navigate it as a human (albiet a human who knows HTTP), and then program a computer to do the same. The key to this I believe is actually being strict about your HTTP, which gives you a common language, which is hugely important.

Slide 21

Slide 21 text

‣ K.I.S.S. ‣ Discoverability ‣ Hackability And lastly hackability. I have long held the belief that a good architecutre has something I call a “Hack Layer” in it. This is a reasonably isolated layer in which you stuff all the bits which make no logical sense, but do make business sense (cause we all know business is not logical). So from here I started doing some research ...

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

... then I saw this ...

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Web::Machine https://github.com/stevan/webmachine-perl And so all of this lead me to port Web-Machine to Perl. Web-Machine is really made up of two parts, ...

Slide 32

Slide 32 text

Web::Machine::FSM in_conflict is_authorized known_content_type known_method last_modified_is_greater_than_if_modified_since last_modified_is_greater_than_if_unmodified_since malformed_request method_allowed method_is_delete method_is_get_or_head method_is_options method_is_post method_is_put moved_permanently moved_temporarily multiple_choices multiple_representations new_resource previously_existed redirect request_entity_too_large resource_exists response_body_includes_entity service_available uri_too_long acceptable_media_type_available accept_charset_choice_available accept_charset_exists accept_encoding_choice_available accept_encoding_exists accept_header_exists accept_language_choice_available accept_language_header_exists allow_post_to_missing_resource content_headers_okay delete_enacted_immediately did_delete_complete etag_in_if_match_list etag_in_if_none_match forbidden if_match_exists if_match_exists_and_if_match_is_wildcard if_match_is_wildcard if_modified_since_exists if_modified_since_greater_than_now if_modified_since_is_valid_date if_none_match_exists if_none_match_is_wildcard if_unmodified_since_exists if_unmodified_since_is_valid_date The FSM has 57 states that basically ask 50 different possible questions that are asked of a resource, a request and a response to move it through the states machine.

Slide 33

Slide 33 text

Predicates service_available is_authorized forbidden allow_missing_post malformed_request uri_too_long known_content_type valid_content_headers valid_entity_length delete_completed post_is_create is_conflict multiple_choices previously_existed moved_permanently moved_temporarily Informational base_uri allowed_methods known_methods charsets_provided languages_provided encodings_provided variances last_modified expires generate_etag Actions options resource_exists content_types_provided content_types_accepted delete_resource create_path process_post finish_request Web::Machine::Resource A resource has 34 methods you can override to produce functionality, I have basically grouped them into three categories here. - Predicates are (for the most part) yes/no questions which guide through the state diagram - Informational methods provide Web::Machine with information about your resource - Action methods are the places where you typically do the actual work

Slide 34

Slide 34 text

Link Content-Type Accept Accept-Charset Accept-Encoding Accept-Language Date Client-Date Expires Last-Modified If-Unmodified-Since If-Modified-Since WWW-Authenticate Authentication-Info Authorization HTTP::Headers::ActionPack::LinkList HTTP::Headers::ActionPack::MediaType HTTP::Headers::ActionPack::MediaTypeList HTTP::Headers::ActionPack::PriorityList HTTP::Headers::ActionPack::PriorityList HTTP::Headers::ActionPack::PriorityList HTTP::Headers::ActionPack::DateHeader HTTP::Headers::ActionPack::DateHeader HTTP::Headers::ActionPack::DateHeader HTTP::Headers::ActionPack::DateHeader HTTP::Headers::ActionPack::DateHeader HTTP::Headers::ActionPack::DateHeader HTTP::Headers::ActionPack::WWWAuthenticate HTTP::Headers::ActionPack::AuthenticationInfo HTTP::Headers::ActionPack::Authorization HTTP::Headers::ActionPack https://github.com/stevan/http-headers-actionpack

Slide 35

Slide 35 text

Examples

Slide 36

Slide 36 text

use 5.16.0; package YAPC::NA::2012::Example000::Resource { use JSON::XS qw[ encode_json ]; use parent 'Web::Machine::Resource'; sub content_types_provided { [{ 'application/json' => 'to_json' }] } sub to_json { encode_json( { message => 'Hello World' } ) } } Web::Machine->new( resource => 'YAPC::NA::2012::Example000::Resource' )->to_app; curl -v http://0:5000/ # fails with a 406 curl -v http://0:5000/ -H 'Accept: image/jpeg'

Slide 37

Slide 37 text

Y U NO MOOSE?!?!?!

Slide 38

Slide 38 text

% curl -v http://0:5000/ > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 01:23:58 GMT < Server: HTTP::Server::PSGI < Content-Length: 25 < Content-Type: application/json < {"message":"Hello World"}

Slide 39

Slide 39 text

% curl -v http://0:5000/ -H 'Accept: text/html' > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: text/html > < HTTP/1.0 406 Not Acceptable < Date: Thu, 07 Jun 2012 01:29:55 GMT < Server: HTTP::Server::PSGI < Content-Length: 14 < Not Acceptable

Slide 40

Slide 40 text

% curl -v -X DELETE http://0:5000/ > DELETE / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 405 Method Not Allowed < Date: Sun, 10 Jun 2012 19:13:54 GMT < Server: HTTP::Server::PSGI < Allow: GET, HEAD < Content-Length: 18 < Method Not Allowed

Slide 41

Slide 41 text

use 5.16.0; package YAPC::NA::2012::Example001::Resource { use JSON::XS qw[ encode_json ]; use parent 'Web::Machine::Resource'; sub content_types_provided { [ { 'application/json' => 'to_json' }, { 'text/html' => 'to_html' }, ] } sub to_json { encode_json( { message => 'Hello World' } ) } sub to_html { '

Hello World

' } } Web::Machine->new( resource => 'YAPC::NA::2012::Example001::Resource' )->to_app; This test shows that the order of content_types_provided is actually important if you do not specify a media-type. # JSON is the default ... curl -v http://0:5000/ # you must ask specifically for HTML curl -v http://0:5000/ -H 'Accept: text/html' # but open in a browser and you get HTML open http://0:5000/

Slide 42

Slide 42 text

% curl -v http://0:5000/ > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 01:23:58 GMT < Server: HTTP::Server::PSGI < Vary: Accept < Content-Length: 25 < Content-Type: application/json < {"message":"Hello World"} Note the Vary header here, this is important header when doing caching, it basically says “the Accept header can have variances, so be careful what you cache”.

Slide 43

Slide 43 text

% curl -v http://0:5000/ -H 'Accept: text/html' > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: text/html > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 01:31:23 GMT < Server: HTTP::Server::PSGI < Vary: Accept < Content-Length: 46 < Content-Type: text/html <

Hello World

Slide 44

Slide 44 text

use 5.16.0; package YAPC::NA::2012::Example002::Resource { use JSON::XS qw[ encode_json ]; use parent 'Web::Machine::Resource'; sub content_types_provided { [ { 'text/html' => 'to_html' }, { 'application/json' => 'to_json' }, ] } sub to_json { encode_json( { message => 'Hello World' } ) } sub to_html { '

Hello World

' } } Web::Machine->new( resource => 'YAPC::NA::2012::Example002::Resource' )->to_app; And showing preference is just as simple as changing the order of items in content_types_provided # now HTML is the default curl -v http://0:5000/ # and you must ask specifically for JSON curl -v http://0:5000/ -H 'Accept: application/json'

Slide 45

Slide 45 text

% curl -v http://0:5000/ > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 01:31:23 GMT < Server: HTTP::Server::PSGI < Vary: Accept < Content-Length: 46 < Content-Type: text/html <

Hello World

Slide 46

Slide 46 text

% curl -v http://0:5000/ -H 'Accept: application/json' > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: application/json > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 01:23:58 GMT < Server: HTTP::Server::PSGI < Vary: Accept < Content-Length: 25 < Content-Type: application/json < {"message":"Hello World"}

Slide 47

Slide 47 text

use 5.16.0; package YAPC::NA::2012::Example010::Resource { use JSON::XS (); use parent 'Web::Machine::Resource'; sub content_types_provided { [{ 'application/json' => 'to_json' }] } sub to_json { my $self = shift; JSON::XS->new->pretty->encode([ map { +{ $_->[0] => $_->[1]->type } } $self->request->header('Accept')->iterable ]) } } Web::Machine->new( resource => 'YAPC::NA::2012::Example010::Resource' )->to_app; Curl by default, it accepts anything, as you can see when we run this. curl -v http://0:5000/ However, web browsers are more sophisticated creatures and have more complicated needs. open http://0:5000/ You can see that since we only provide JSON, that we end up matching the */* at the end.

Slide 48

Slide 48 text

% curl -v http://0:5000/ > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 01:59:15 GMT < Server: HTTP::Server::PSGI < Content-Length: 32 < Content-Type: application/json < [ { "1" : "*/*" } ]

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

use 5.16.0; package YAPC::NA::2012::Example011::Resource { use JSON::XS (); use parent 'Web::Machine::Resource'; sub content_types_provided { [ { 'application/json' => 'to_json' }, { 'text/html' => 'to_html' } ] } sub to_json { ... } sub to_html { my $self = shift; '
    ' . (join "" => map { '
  • ' . $_->[0] . ' — ' . $_->[1]->type . '
  • ' } $self->request->header('Accept')->iterable) . '
' } } Web::Machine->new( resource => 'YAPC::NA::2012::Example011::Resource' )->to_app; So what happens then if we provide HTML as well? open http://0:5000/ Now we prefer HTML over JSON, even though JSON is the default here. If you call curl, you get the expected JSON. curl -v http://0:5000/

Slide 51

Slide 51 text

% curl -v http://0:5000/ > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 01:59:15 GMT < Server: HTTP::Server::PSGI < Content-Length: 32 < Content-Type: application/json < [ { "1" : "*/*" } ]

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

use 5.16.0; package YAPC::NA::2012::Example012::Resource { use GD::Simple; use parent 'Web::Machine::Resource'; sub content_types_provided { [ { 'image/gif' => 'to_gif' }, { 'text/html' => 'to_html' }, ] } sub to_html { '
    ' . (join "" => map { '
  • ' . $_->[0] . ' — ' . $_->[1]->type . '
  • ' } (shift)->request->header('Accept')->iterable) . '

' } sub to_gif { my $img = GD::Simple->new( 130, 20 ); $img->fgcolor('red'); $img->moveTo(15, 15); $img->string( (shift)->request->path_info ); $img->gif; } } Web::Machine->new( resource => 'YAPC::NA::2012::Example012::Resource' )->to_app; And of course, you don't have to just provide text based results ...

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

use 5.16.0; package YAPC::NA::2012::Example020::Resource { use Web::Machine::Util qw[ create_header ]; use parent 'Web::Machine::Resource'; sub content_types_provided { [ { 'text/html' => 'to_html' } ] } sub to_html { '

Hello World

' } sub is_authorized { my ($self, $auth_header) = @_; if ( $auth_header ) { return 1 if $auth_header->username eq 'foo' && $auth_header->password eq 'bar'; } return create_header( 'WWWAuthenticate' => [ 'Basic' => ( realm => 'Webmachine' ) ] ); } } Web::Machine->new( resource => 'YAPC::NA::2012::Example020::Resource' )->to_app;

Slide 57

Slide 57 text

% curl -v http://0:5000/ > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 401 Unauthorized < Date: Thu, 07 Jun 2012 02:22:42 GMT < Server: HTTP::Server::PSGI < WWW-Authenticate: Basic realm="Webmachine" < Content-Length: 12 < Unauthorized

Slide 58

Slide 58 text

% curl -v http://0:5000/ -H 'Authorization: Basic Zm9vOmJhcg==' > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > Authorization: Basic Zm9vOmJhcg== > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 02:25:31 GMT < Server: HTTP::Server::PSGI < Content-Length: 46 < Content-Type: text/html <

Hello World

Slide 59

Slide 59 text

% curl -v http://0:5000/ -H 'Authorization: Basic ZOMGBBQLOL==' > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > Authorization: Basic ZOMGBBQLOL== > < HTTP/1.0 401 Unauthorized < Date: Sun, 10 Jun 2012 19:26:31 GMT < Server: HTTP::Server::PSGI < WWW-Authenticate: Basic realm="Webmachine" < Content-Length: 12 < Unauthorized

Slide 60

Slide 60 text

use 5.16.0; package YAPC::NA::2012::Example030::Resource { use parent 'Web::Machine::Resource'; sub save_message { ... } sub get_messages { ... } sub allowed_methods { [qw[ GET POST ]] } sub content_types_provided { [ { 'text/html' => 'to_html' } ] } sub to_html { my $self = shift; '' . '
    ' . (join '' => map { '
  • ' . $_ . '
  • ' } $self->get_messages) . '
' } sub process_post { my $self = shift; $self->save_message( $self->request->param('message') ); $self->response->header('Location' => '/'); return \301; } } Web::Machine->new( resource => 'YAPC::NA::2012::Example030::Resource' )->to_app;

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

use 5.16.0; package YAPC::NA::2012::Example031::Resource { use JSON::XS (); use parent 'YAPC::NA::2012::Example030::Resource'; sub allowed_methods { [qw[ GET PUT POST ]] } sub content_types_accepted { [ { 'application/json' => 'from_json' } ] } sub from_json { my $self = shift; $self->save_message( JSON::XS->new->allow_nonref->decode( $self->request->content ) ); } sub process_post { my $self = shift; return \415 unless $self->request->header('Content-Type') ->match('application/x-www-form-urlencoded'); $self->SUPER::process_post; } } Web::Machine->new( resource => 'YAPC::NA::2012::Example031::Resource' )->to_app;

Slide 65

Slide 65 text

% curl -v -X PUT http://0:5000/ -H 'Content-Type: application/json' -d '"bar"' > PUT / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > Content-Type: application/json > Content-Length: 5 > < HTTP/1.0 204 No Content < Date: Sun, 10 Jun 2012 19:36:49 GMT < Server: HTTP::Server::PSGI < Content-Type: text/html <

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

% curl -v -X POST http://0:5000/ -H 'Content-Type: application/json' -d '"bar"' > POST / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > Content-Type: application/json > Content-Length: 5 > < HTTP/1.0 415 Unsupported Media Type < Date: Sun, 10 Jun 2012 19:49:21 GMT < Server: HTTP::Server::PSGI < Content-Type: text/html < Content-Length: 22 < Unsupported Media Type

Slide 68

Slide 68 text

use 5.16.0; package YAPC::NA::2012::Example032::Resource { use Web::Machine::Util qw[ create_header ]; use parent 'YAPC::NA::2012::Example031::Resource'; sub is_authorized { my ($self, $auth_header) = @_; return 1 if $self->request->method ne 'PUT'; if ( $auth_header ) { return 1 if $auth_header->username eq 'foo' && $auth_header->password eq 'bar'; } return create_header( 'WWWAuthenticate' => [ 'Basic' => ( realm => 'Webmachine' ) ] ); } } Web::Machine->new( resource => 'YAPC::NA::2012::Example032::Resource' )->to_app;

Slide 69

Slide 69 text

% curl -v -X PUT http://0:5000/ -H 'Content-Type: application/json' -d '"baz"' > PUT / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > Content-Type: application/json > Content-Length: 5 > < HTTP/1.0 401 Unauthorized < Date: Sun, 10 Jun 2012 20:07:57 GMT < Server: HTTP::Server::PSGI < WWW-Authenticate: Basic realm="Webmachine" < Content-Length: 12 < Unauthorized

Slide 70

Slide 70 text

% curl -v -X PUT http://0:5000/ -H 'Content-Type: application/json' -H 'Authorization: Zm9vOmJhcg==' -d '"baz"' > PUT / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > Content-Type: application/json > Authorization: Basic Zm9vOmJhcg== > Content-Length: 5 > < HTTP/1.0 204 No Content < Date: Sun, 10 Jun 2012 20:08:24 GMT < Server: HTTP::Server::PSGI < Location: / < Content-Type: text/html < Content-Length: 0 <

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

use 5.16.0; package YAPC::NA::2012::Example033::Resource { use JSON::XS qw[ encode_json ]; use Web::Machine::Util qw[ create_header ]; use parent 'YAPC::NA::2012::Example032::Resource'; sub content_types_provided { my $self = shift; my $types = $self->SUPER::content_types_provided; push @$types => { 'application/json' => 'to_json' }; $types; } sub to_json { my $self = shift; $self->response->header( 'Link' => create_header( 'LinkHeader' => [ '/', ('content-type' => 'text/html') ] ) ); encode_json([ $self->get_messages ]); } } Web::Machine->new( resource => 'YAPC::NA::2012::Example033::Resource' )->to_app;

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

% curl -v http://0:5000 -H 'Accept: application/json' > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: application/json > < HTTP/1.0 200 OK < Date: Sun, 10 Jun 2012 21:44:11 GMT < Server: HTTP::Server::PSGI < Vary: Accept < Content-Length: 19 < Content-Type: application/json < Link: ; content-type="text/html" < ["foo","bar","baz"]

Slide 75

Slide 75 text

use 5.16.0; package YAPC::NA::2012::Example100::Resource { use Web::Machine::Util qw[ create_date ]; use parent 'Web::Machine::Resource'; sub content_types_provided { [{ 'text/html' => 'to_html' }] } sub last_modified { create_date('Sun, 27 May 2012 17:35:00 EDT') } sub generate_etag { '0xDEADBEEF' } sub to_html { '

Hello World

' } } Web::Machine->new( resource => 'YAPC::NA::2012::Example100::Resource' )->to_app; curl -v http://0:5000/ -H 'If-Modified-Since: Sun, 27 May 2012 17:34:59 EDT' curl -v http://0:5000/ -H 'If-Modified-Since: Sun, 27 May 2012 17:35:00 EDT'

Slide 76

Slide 76 text

% curl -v http://0:5000/ -H 'If-Modified-Since: Sun, 27 May 2012 17:34:59 EDT' > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > If-Modified-Since: Sun, 27 May 2012 17:34:59 EDT > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 02:28:55 GMT < Server: HTTP::Server::PSGI < ETag: "0xDEADBEEF" < Content-Length: 46 < Content-Type: text/html < Last-Modified: Sun, 27 May 2012 21:35:00 GMT <

Hello World

Slide 77

Slide 77 text

% curl -v http://0:5000/ -H 'If-Modified-Since: Sun, 27 May 2012 17:35:00 EDT' > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > If-Modified-Since: Sun, 27 May 2012 17:35:00 EDT > < HTTP/1.0 304 Not Modified < Date: Thu, 07 Jun 2012 02:30:02 GMT < Server: HTTP::Server::PSGI < ETag: "0xDEADBEEF" < Last-Modified: Sun, 27 May 2012 21:35:00 GMT <

Slide 78

Slide 78 text

use 5.16.0; package YAPC::NA::2012::Example110::Resource { use parent 'Web::Machine::Resource'; sub content_types_provided { [ { 'text/html' => 'to_html' }, ] } sub to_html { '

Hello World

' } sub service_available { my $self = shift; return 1 unless -e './site_down'; $self->response->body([ '

Service Unavailable

' ]); 0; } } Web::Machine->new( resource => 'YAPC::NA::2012::Example110::Resource' )->to_app; This demostrates how you can easily handle situations like the site being down in a reasonably elegant way. touch site_down rm site_down

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

% touch site_down

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

use 5.16.0; package YAPC::NA::2012::Example120::Resource { use Web::Machine::Util qw[ bind_path ]; use parent 'Web::Machine::Resource'; sub content_types_provided { [{ 'text/html' => 'to_html' }] } sub to_html { my $self = shift; if ( my ($action, $id) = bind_path('/:action/:id', $self->request->path_info) ) { return "

action('$action') id('$id')

"; } else { return \404; } } } Web::Machine->new( resource => 'YAPC::NA::2012::Example120::Resource' )->to_app; curl -v http://0:5000/ curl -v http://0:5000/edit/100

Slide 83

Slide 83 text

% curl -v http://0:5000/ > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 404 Not Found < Date: Thu, 07 Jun 2012 02:33:19 GMT < Server: HTTP::Server::PSGI < Content-Type: text/html < Content-Length: 9 < Not Found

Slide 84

Slide 84 text

% curl -v http://0:5000/edit/100 > GET /edit/100 HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 02:34:00 GMT < Server: HTTP::Server::PSGI < Content-Length: 59 < Content-Type: text/html < action('edit') id('100')

Slide 85

Slide 85 text

use 5.16.0; package YAPC::NA::2012::Example130::Resource { use parent 'Web::Machine::Resource'; sub content_types_provided { [ { 'text/html' => 'to_html' } ] } sub to_html { '

Hello World

' } } Web::Machine->new( resource => 'YAPC::NA::2012::Example130::Resource', tracing => 1 )->to_app;

Slide 86

Slide 86 text

% curl -v http://0:5000/ > GET / HTTP/1.1 > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) ... > Host: 0:5000 > Accept: */* > < HTTP/1.0 200 OK < Date: Thu, 07 Jun 2012 02:35:17 GMT < Server: HTTP::Server::PSGI < Content-Length: 46 < Content-Type: text/html < X-Web-Machine-Trace: b13,b12,b11,b10,b9,b8,b7,b6,b5,b4,b3,c3,c4,d4,e5,f6,g7,g8, h10,i12,l13,m16,n16,o16,o18,o18b <

Hello World

Slide 87

Slide 87 text

No content

Slide 88

Slide 88 text

% export WM_DEBUG=1; % plackup examples/yapc-talk-examples/130-tracing-header.psgi HTTP::Server::PSGI: Accepting connections at http://0:5000/ entering b13 (service_available) -> transitioning to b12 entering b12 (known_method) -> transitioning to b11 entering b11 (uri_too_long) -> transitioning to b10 entering b10 (method_allowed) -> transitioning to b9 entering b9 (malformed_request) -> transitioning to b8 entering b8 (is_authorized) -> transitioning to b7 entering b7 (forbidden) -> transitioning to b6 entering b6 (content_headers_okay) -> transitioning to b5 entering b5 (known_content_type) -> transitioning to b4 entering b4 (request_entity_too_large) -> transitioning to b3 entering b3 (method_is_options) -> transitioning to c3 entering c3 (accept_header_exists) -> transitioning to c4 entering c4 (acceptable_media_type_available) -> transitioning to d4 entering d4 (accept_language_header_exists) -> transitioning to e5 entering e5 (accept_charset_exists) -> transitioning to f6

Slide 89

Slide 89 text

entering o18 (multiple_representations) -> transitioning to o18b entering o18b (multiple_choices) .. terminating with 200 $VAR1 = { 'psgi.multiprocess' => '', 'SCRIPT_NAME' => '', 'SERVER_NAME' => 0, 'PATH_INFO' => '/', 'HTTP_ACCEPT' => '*/*', 'REQUEST_METHOD' => 'GET', 'psgi.multithread' => '', 'HTTP_USER_AGENT' => 'curl/7.21.4 (universal-apple- darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5', 'QUERY_STRING' => '', 'SERVER_PORT' => 5000, 'psgix.input.buffered' => 1, 'REMOTE_ADDR' => '127.0.0.1', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'psgi.streaming' => 1, 'psgi.errors' => *::STDERR, 'REQUEST_URI' => '/', 'psgi.version' => [ 1, 1 ], 'psgi.nonblocking' => '', 'psgix.io' => bless( \*Symbol::GEN1, 'IO::Socket::INET' ), 'psgi.url_scheme' => 'http', 'psgi.run_once' => '', 'HTTP_HOST' => '0:5000', 'psgi.input' => \*{'HTTP::Server::PSGI::$input'} }; $VAR1 = [

Slide 90

Slide 90 text

'psgi.run_once' => '', 'HTTP_HOST' => '0:5000', 'psgi.input' => \*{'HTTP::Server::PSGI::$input'} }; $VAR1 = [ 200, [ 'Content-Length', 46, 'Content-Type', 'text/html' ], [ '

Hello World

' ] ]; 127.0.0.1 - - [10/Jun/2012:16:21:59 -0400] "GET / HTTP/1.1" 200 46 "-" "curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5"

Slide 91

Slide 91 text

Conclusions ‣ ‣ ‣

Slide 92

Slide 92 text

Conclusions ‣ The Web is messy ‣ ‣ The reality is that the web is a very messy place, it is important that your web-service can live in this world. The best way to deal with this, is to use HTTP to it’s fullest, it is the common language. This increases discoverability too, and is just an all around good idea.

Slide 93

Slide 93 text

Conclusions ‣ The Web is messy ‣ Use REST toolkits not frameworks ‣ Frameworks are basically a big ball of opinion with a few bits left undone for you to complete. REST does not lend itself to this, instead I recommend using a Toolkit, meaning, a set of libraries with which you accomplish REST.

Slide 94

Slide 94 text

Conclusions ‣ The Web is messy ‣ Use REST toolkits not frameworks ‣ Simple and Direct Web services often layer over complex systems, therefore keeping the web-layer simple is a good thing.

Slide 95

Slide 95 text

The End

Slide 96

Slide 96 text

Questions?