Prefer-Push

Client-initiated Server Push for H2

Transclusion

OData $expand


                        GET /heroes/r2d2?$expand=hero.friends($select=id,name,height) HTTP/1.1
                    
⬇︎

HTTP/1.1 200 OK
Content-Type: application/json

{
  "hero": {
    "id": "/heroes/r2d2",
    "name": "R2-D2",
    "height": 1.09,
    "friends": [
      { "id": "/heroes/luke-skywalker", "name": "Luke Skywalker", "height": 1.72 },
      { "id": "/heroes/han-solo", "name": "Han Solo", "height": 1.85 },
      { "id": "/heroes/leia-organa", "name": "Leia Organa", "height": 1.54 }
    ]
  }
}
                    

GraphQL Sub-selection


POST /query HTTP/1.1
Content-Type: application/graphql

{
  hero {
    friends {
      id
      name
      height
    }
  }
}
                    
➡︎

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
            "id": "/heroes/luke-skywalker",
            "name": "Luke Skywalker",
            "height": 1.72
        },
        {
            "id": "/heroes/han-solo",
            "name": "Han Solo",
            "height": 1.85
        },
        …
      ]
    }
  }
}
                    

Apollo GraphQL Sub-selection with @defer


POST /query HTTP/1.1
Content-Type: application/graphql

{
  hero {
    friends @defer {
      id
      name
      height
    }
  }
}
                    
➡︎

HTTP/1.1 200 OK
Content-Type: multipart/mixed; boundary=hero

--hero
Content-Type: application/json

{ "data": { "hero": { "name": "R2-D2", "friends": null }}}
--hero
Content-Type: application/json

{
    "path": ["hero", "friends"],
    "data" : { "hero": { "id": "/heroes/luke-skywalker", "name": "Luke Skywalker", "height": 1.72 }}
}
--hero
…
--hero--

                    

Prefer-Push


GET /heroes/r2d2 HTTP/2.0
Prefer-Push: hero.friends
                        

HTTP/2.0 200 OK

{
  "hero": {
    "name": "R2-D2",
    "height": 1.09,
    "friends": [
      { "id": "/heroes/luke-skywalker" },
      { "id": "/heroes/han-solo" },
      { "id": "/heroes/leia-organa" }
    ]
  }
}
                        

:path /heroes/luke-skywalker

{
  "hero": {
    "id": "/heroes/luke-skywalker",
    "name": "Luke Skywalker",
    "height": 1.72
  }
}
                        

:path /heroes/han-solo

{
  "hero": {
    "id": "/heroes/han-solo",
    "name": "Han Solo",
    "height": 1.85
  }
}
                        

:path /heroes/leia-organa

…
                        

Why not preload?

  • Link relations and object graphs, not URIs
  • Possibly backwards compatible with HTTP/1.1

Performance

  • Naïve testing shows 20-30% improvement
  • Can coalesce to a single database query
  • Much more performant than inline expansion
  • URI for each pushed resource: Happy cache
  • Avoids stack overflows since identity (URI) is preserved

TODO

Thank You

@asbjornu