$30 off During Our Annual Pro Sale. View Details »

REST from the trenches

REST from the trenches

This is a talk I gave at YAPC::NA 2012 in Madison, Wisconsin

Stevan Little

June 13, 2012
Tweet

More Decks by Stevan Little

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

  4. 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 ...

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. 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

    View Slide

  9. 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 ...

    View Slide

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

    View Slide

  11. now fast forward a year and ...

    View Slide

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

    View Slide

  13. 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.

    View Slide

  14. 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.

    View Slide

  15. 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.

    View Slide

  16. 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.

    View Slide

  17. 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.

    View Slide

  18. 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.

    View Slide

  19. ‣ 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.

    View Slide

  20. ‣ 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.

    View Slide

  21. ‣ 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 ...

    View Slide

  22. View Slide

  23. ... then I saw this ...

    View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. 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, ...

    View Slide

  32. 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.

    View Slide

  33. 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

    View Slide

  34. 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

    View Slide

  35. Examples

    View Slide

  36. 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'

    View Slide

  37. Y U NO MOOSE?!?!?!

    View Slide

  38. % 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"}

    View Slide

  39. % 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

    View Slide

  40. % 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

    View Slide

  41. 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/

    View Slide

  42. % 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”.

    View Slide

  43. % 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

    View Slide

  44. 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'

    View Slide

  45. % 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

    View Slide

  46. % 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"}

    View Slide

  47. 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.

    View Slide

  48. % 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" : "*/*"
    }
    ]

    View Slide

  49. View Slide

  50. 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/

    View Slide

  51. % 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" : "*/*"
    }
    ]

    View Slide

  52. View Slide

  53. 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 ...

    View Slide

  54. View Slide

  55. View Slide

  56. 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;

    View Slide

  57. % 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

    View Slide

  58. % 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

    View Slide

  59. % 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

    View Slide

  60. 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;

    View Slide

  61. View Slide

  62. View Slide

  63. View Slide

  64. 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;

    View Slide

  65. % 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
    <

    View Slide

  66. View Slide

  67. % 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

    View Slide

  68. 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;

    View Slide

  69. % 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

    View Slide

  70. % 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
    <

    View Slide

  71. View Slide

  72. 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;

    View Slide

  73. View Slide

  74. % 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"]

    View Slide

  75. 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'

    View Slide

  76. % 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

    View Slide

  77. % 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
    <

    View Slide

  78. 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

    View Slide

  79. View Slide

  80. % touch site_down

    View Slide

  81. View Slide

  82. 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

    View Slide

  83. % 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

    View Slide

  84. % 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')

    View Slide

  85. 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;

    View Slide

  86. % 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

    View Slide

  87. View Slide

  88. % 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

    View Slide

  89. 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 = [

    View Slide

  90. '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"

    View Slide

  91. Conclusions



    View Slide

  92. 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.

    View Slide

  93. 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.

    View Slide

  94. 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.

    View Slide

  95. The End

    View Slide

  96. Questions?

    View Slide