HTTP ALL THE THINGS


Simplifying Rich Applications by
Respecting the Rules of the Web

Or:

You're Still Doing HTTP Rong

Me

  • Nate Abele
  • Former lead developer, CakePHP
  • Founder & current lead developer, Lithium
  • Member, AngularUI
  • @nateabele

Thanks!

  • Amazingly, I get paid for this stuff
  • OS is magical because of people
  • I'm not a peasant

Life Lessons

  • Be humble
  • Don't be afraid to feel stupid
  • The night is young

History

1998: SOAP & XML-RPC

SOAP


          <?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Header></soap:Header>
  <soap:Body>
    <m:SomeCommand xmlns:m="...">
      <m:BodyOfMessage>...</m:BodyOfMessage>
    </m:SomeCommand>
  </soap:Body>
</soap:Envelope>
          

Or...

XML-RPC


          <?xml version="1.0"?>
<methodCall>
  <methodName>examples.getStateName</methodName>
  <params>
    <param>
        <value><i4>40</i4></value>
    </param>
  </params>
</methodCall>
          

:-(

  • POST *
  • Invocaction details in body
  • Format lock-in
  • No relationships
  • No discoverability

2000: REST

REST Constraints

  • Client-Server
  • Stateless
  • Cacheable
  • Uniform Interface
  • Opaque Layering
  • Code-on-Demand

The 4 Levels

  • RPC over HTTP (i.e. SOAP)
  • Resources
  • Verbs
  • Hypermedia Controls

Now: Hypermedia / HATEOAS

Hypermedia?

Content negotiation

application/json ...?

 

Content negotiation

application/json

FALE

Content negotiation

Better:

application/rss+xml

Content negotiation

Better:

application/rss+xml

Content negotiation

application/vnd.twitter.stream+json


// I can decode this!
json_decode()
/**
 * I can understand this!
 */
class Tweet {
   protected $username;
   protected $text;
   // ...
}

State traversal

Client: “What's next?”

We already do this!

Browsers


          <link rel="stylesheet" href="/css/app.css" />
<link rel="next" href="/next" />
<link
   rel="alternate"
   type="application/rss+xml"
   href="/posts.rss"
/>
          

Users

(with the help of browsers)


<a href="/next" />Next</a>
<form></form>
          

Atom?


<link
  rel="foo"
  type="text/foo"
  href="http://foo"
/>
          

JSON!

[{
  title: "Finish the demo",
  completed: false,
  $links: {
    self: { href: "http://my.app/tasks/1138" },
    owner: { href: "http://my.app/users/nate" },
    subtasks: { href: "http://my.app/tasks/1138/subtasks"}
  }
}]

Why?

AngularJS

Valid alternatives

  • EmberJS
  • KnockoutJS
  • Backbone (kind of, not really)

AngularJS

  • Higher level of abstraction
  • Simple, well-designed architecture
  • Ridiculously simple unit testing

Event binding

Event binding

jQuery

<input type="text" id="name" />

Hello!

<script type="text/javascript"> $(document).ready(function() { $('#name').keydown(function(e) { $('h1').html("Hello " + e.target.value + "!") }); }); </script>

Event binding

AngularJS

<input type="text" ng-model="name" />
<h1>Hello {{ name }}!</h1>

Hello {{ name }}!

Iteration

jQuery

    <script type="text/javascript"> $(["Hello", "Hola", "Ciao"]).each(function(k, v) { $(".greeting").append("
  • " + v + " World
  • "); }); </script>

    Iteration

    AngularJS

    <ul>
      <li ng-repeat="greet in ['Hello', 'Hola', 'Ciao']">
        {{ greet }} World
      </li>
    </ul>
    
    • {{ greet }} World
    <ul>
      <li ng-repeat="greet in ['Hello', 'Hola', 'Ciao']">
        {{ greet }} World
      </li>
    </ul>
    
    • {{ greet }} {{ greetName || 'World' }}

    Organization

    AngularJS


    Item: Qty: Price: Total:
    {{ item.qty * item.price | currency: "$" }}

    Total: {{ total() | currency: "$" }}

    Organization

    AngularJS

    Item: Qty: Price: Total:
    {{ item.qty * item.price | currency: "$" }}

    Total: {{ total() | currency: "$" }}

    Organization

    AngularJS

    function CheckoutController($scope) {
      $scope.items = $scope.items || [{ price: 0, qty: 0 }];
    
      $scope.total = function() {
        if ($scope.items[$scope.items.length - 1].qty) {
          $scope.items.push({ price: 0, qty: 0 });
        }
        return $scope.items.map(function(item) {
          return item.qty * item.price;
        }).reduce(function(a, b) {
          return a + b;
        });
      };
    }

    Organization

    jQuery

    Organization

    jQuery




    Ummm...?

    Organization

    jQuery




    *shrug*

    Testability

    AngularJS

    describe("Shopping cart", function() {
    
      describe("Checkout widget", function() {
    
        it("should create a default element", function() {
          var scope = {},
          controller = new CheckoutController(scope);
    
          expect(scope.items.length).toBe(1);
          expect(scope.items[0].qty).toBe(0);
          expect(scope.items[0].price).toBe(0);
        });
      });
    });

    Testability

    AngularJS

    describe("Shopping cart", function() {
    
      describe("Checkout widget", function() {
        // ...
    
        it("should calculate order total", function() {
          var scope = { items: [
            { price: 2, qty: 4 }, { price: 10, qty: 1 }
          ]};
          var controller = new CheckoutController(scope);
    
          expect(scope.total()).toBe(18);
        });
      });
    });

    jQuery...?

    AngularJS + HTTP Resources

    $resource()

    var Task = $resource("http://my.api/tasks/:id", {
      id: "@id"
    });
    
    var newTask = new Task({
      title: "New Task",
      description: "..."
    });
    
    /* POST /tasks { "title": "New Task", ... } */
    newPost.$save();
    /* GET /tasks/5 */
    var oneTask = Task.get({ id: 5 });
    
    /* GET /tasks?completed=false&due=1381158984 */
    var current = Task.query({
      completed: false,
      description: "..."
    });

    ¡No me gusta!

    Tight coupling

    • Client-side URL templates
    • Excessive parameters
    • No links!

    Intent

    /tasks?
    completed=false
    &due=1381158984

    No meaning

    /tasks/current

    /tasks/:id

    $resource("/posts/:id", {id: "@id"});

    {
      id: 5,
      title: "Something New",
      slug: "something-new"
    }

    /posts/something-new

    ...?

    What to do?

    uor/angular-model

    (on GitHub)




    modelProvider.model("Tasks", {
      $instance: {
        finish: function() {
          this.completed = true;
          return this.$save();
        },
        isCompleted: function() {
          return !!this.completed;
        }
      }
    });

    Where's the URL?

    3 options

    Automatic: /tasks

    <link
      rel="resource"
      name="Tasks"
      href="<?=$this->url('Tasks'); ?>"
    />

    GET /



    Accept:
    application/vnd.resource-def+json
    {
      "Tasks": "http://my.app/tasks",
      "Users": "http://my.app/users"
    }

    OPTIONS /tasks

    (Deprecated)



    Accept:
    application/vnd.resource-schema+json
    {
      "title": "string",
      "completed": "boolean",
      ...
    }

    Buzzword Detour:
    "Service Oriented Architecture"

    Good Ideas Apply Fractally

    UI === App

    URLs as Medium of Communication

    More HTTP goodies

    ...that you don't need to reinvent

    HTTP Range



    GET /rickroll.mp4

    Range: bytes=100-99999

    HEAD /posts HTTP/1.1

    ...

    HTTP 200 OK

    Accept-Ranges: posts

    GET /posts HTTP/1.1

    Range: posts=1-20

    HTTP Auth

    Always use protection!

    Caching

    Implement any invalidation strategy you can imagine

    JSONSpec.org

    JSONSpec.org

    PHP?

    nateabele/li3_resources

    (on GitHub)

    • Automatic CRUD manipulation with verbs
    • Parameter mapping
    • Links from model relationships
    • Schemas with OPTIONS
    • Proxies for mapping database values

    Coming soon...

    • Range pagination
    • Smart search with ?q=...
    • ...Tests (runs away)
    Akihito Koriyama

    koriym/BEAR.Sunday

    Questions?

    Thanks/Contact