57
Efficient HTTP Apis A walk through http/2 via okhttp @adrianfcole

Efficient HTTP Apis

Embed Size (px)

DESCRIPTION

Developers choose HTTP for its ubiquity. HTTP's semantics are cherry-picked or embraced in the myriad of apis we develop and consume. Efficiency discussions are commonplace: Does this design imply N+1 requests? Should we denormalize the model? How do consumers discover changes in state? How many connections are needed to effectively use this api? Meanwhile, HTTP 1.1 is a choice, as opposed to constant. SPDY and HTTP/2 implementations surface, simultaneously retaining semantics and dramatically changing performance implications. We can choose treat these new protocols as more efficient versions HTTP 1.1 or buy into new patterns such as server-side push. This session walks you through these topics via an open source project from Square called okhttp. You'll understand how okhttp addresses portability so that you can develop against something as familiar as java's HTTPUrlConnection. We'll review how to use new protocol features and constraints to keep in mind along the way. You'll learn how to sandbox ideas with okhttp's mock server, so that you can begin experimenting with SPDY and HTTP/2 today!

Citation preview

Page 1: Efficient HTTP Apis

Efficient HTTP ApisA walk through http/2 via okhttp

@adrianfcole

Page 2: Efficient HTTP Apis

introduction

hello http api!

hello okhttp/2!

uh oh.. scale!

hello http/2!

wrapping up

Page 3: Efficient HTTP Apis

introduction

hello http api!

hello okhttp/2!

uh oh.. scale!

hello http/2!

wrapping up

Page 4: Efficient HTTP Apis

* Can be blamed for the http/2 defects in okhttp!

@adrianfcole

• staff engineer at Twitter• founded apache jclouds• focus on cloud computing

Page 5: Efficient HTTP Apis

okhttp• HttpURLConnection compatible• designed for java and android• BDFL: Jesse Wilson from square

https://github.com/square/okhttp

Page 6: Efficient HTTP Apis

introduction

hello http api!

hello okhttp/2!

uh oh.. scale!

hello http/2!

wrapping up

Page 7: Efficient HTTP Apis

$ curl https://apihost/things \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Accept: application/json’[ { "id": 1, "owner_id": 0, "name": "Able" },... { "id": 26, "owner_id": 0,

$ curl https://apihost/things/2 \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Accept: application/json’{ "id": 2, "owner_id": 0, "name": "Beatific"}

$ curl -X POST https://apihost/things \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Content-Type: application/json’ -d’{ "name": "Open-minded"}’

$ curl -X DELETE https://apihost/things/2 \-H ‘SecurityToken: b08c85073c1a2d02’

json apis are so simple

Page 8: Efficient HTTP Apis

$ curl https://apihost/things \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Accept: application/json’[ { "id": 1, "owner_id": 0, "name": "Able" },... { "id": 26, "owner_id": 0, "name": "Zest" }

$ curl https://apihost/things/2 \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Accept: application/json’{ "id": 2, "owner_id": 0, "name": "Beatific"}

$ curl -X POST https://apihost/things \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Content-Type: application/json’ -d’{ "name": "Open-minded" }’

$ curl -X DELETE https://apihost/things/2 \-H ‘SecurityToken: b08c85073c1a2d02’

so many bytes?!

1642!

Page 9: Efficient HTTP Apis

$ curl --compress https://apihost/things \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Accept: application/json’-H ‘Accept-encoding: gzip, deflate’[ { "id": 1, "owner_id": 0, "name": "Able" },... { "id": 26, "owner_id": 0, "name": "Zest"

$ curl https://apihost/things/2 \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Accept: application/json’{ "id": 2, "owner_id": 0, "name": "Beatific"}

$ curl -X POST https://apihost/things \-H ‘SecurityToken: b08c85073c1a2d02’ \-H ‘Content-Type: application/json’ -d’{ "name": "Open-minded" }’

$ curl -X DELETE https://apihost/things/2 \-H ‘SecurityToken: b08c85073c1a2d02’

now with gzip

318

Page 10: Efficient HTTP Apis

java + gsonurl = new URL(“https://apihost/things”);connection = (HttpURLConnection) url.openConnection();connection.setRequestProperty(“SecurityToken”, “b08c85073c1a2d02”);connection.setRequestProperty(“Accept”, “application/json”);connection.setRequestProperty(“Accept-Encoding”, "gzip, deflate");is = connection.getInputStream();

isr = new InputStreamReader(new GZIPInputStream(is));things = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){});

Page 11: Efficient HTTP Apis

okhttp + gsonclient = new OkHttpClient();url = new URL(“https://apihost/things”);connection = new OkUrlFactory(client).open(url);connection.setRequestProperty(“SecurityToken”, “b08c85073c1a2d02”);connection.setRequestProperty(“Accept”, “application/json”);connection.setRequestProperty(“Accept-Encoding”, "gzip, deflate");is = connection.getInputStream();

isr = new InputStreamReader(is); // automatic gunzipthings = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){});is.close();

Page 12: Efficient HTTP Apis

We won!

• List body reduced from 1642 to 318 bytes!

• We saved some lines using OkHttp

• Concession: cpu for compression, curl is a little verbose.

Page 13: Efficient HTTP Apis

introduction

hello http api!

hello okhttp/2!

uh oh.. scale!

hello http/2!

wrapping up

Page 14: Efficient HTTP Apis

okhttp2• A year in the making• New API, okio, samples• Bridge with v1.6; current v2.1

https://github.com/square/okhttp

Page 15: Efficient HTTP Apis

okhttp 2client = new OkHttpClient();request = new Request.Builder() .url(“https://apihost/things”) .header(“SecurityToken”, “b08c85073c1a2d02”) .header(“Accept”, “application/json”); .header(“Accept-Encoding”, "gzip, deflate").build(); // look ma.. I’m immutable!

reader = client.call(request).execute().body().charStream();things = new Gson().fromJson(reader, new TypeToken<List<Thing>>(){});

• OkHttp has its own api, which has a concise way to synchronously get bytes or chars (via okio).

Page 16: Efficient HTTP Apis

okhttp callclient = new OkHttpClient();request = new Request.Builder() .url(“https://apihost/things”) .header(“SecurityToken”, “b08c85073c1a2d02”) .header(“Accept”, “application/json”); .header(“Accept-Encoding”, "gzip, deflate").build();

call = client.call(request);call.enqueue(new ParseThingsCallback());call.cancel(); // changed my mind

• OkHttp also has an async api, which (also) supports cancel!

Page 17: Efficient HTTP Apis

• # Explicity trust certs for your properties• new CertificatePinner.Builder()• .add(“api.my.biz”, publicKeySHA1)• .build()

• # Respond to authentication challenges• new Authenticator() {• public Request authenticate(Proxy p, Response rsp) {• return rsp.request().newBuilder()• .header(“Authorization", “Bearer …”)

• # Set your own floor for TLS• new ConnectionSpec.Builder(MODERN_TLS)• .tlsVersions(TlsVersion.TLS_1_2).build()

okhttpsecurity

Page 18: Efficient HTTP Apis

okio• ByteString• Unbounded Buffer• Source/Sink abstraction

https://github.com/square/okio

Page 19: Efficient HTTP Apis

// Okio is (more than) a prettier DataInputStreamBufferedSink sink = Okio.buffer(Okio.sink(file));sink.writeUtf8("Hello, java.io file!”);sink.writeLong(1000000000000000L);sink.close();

// Streaming writes with no housekeepingnew RequestBody() { @Override public void writeTo(BufferedSink sink) throws IOException { sink.writeUtf8("Numbers\n"); sink.writeUtf8("-------\n"); for (int i = 2; i <= 997; i++) { sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i))); } }—snip—

// Lazy, composable bodiesnew MultipartBuilder("123") .addPart(RequestBody.create(null, "Quick")) .addPart(new StreamingBody("Brown"))

okiosamples

Page 20: Efficient HTTP Apis

samples• Starters like web crawler• How-to guide w/ recipes

https://github.com/square/okhttp/tree/master/samples

Page 21: Efficient HTTP Apis

introduction

hello http api!

hello okhttp/2!

uh oh.. scale!

hello http/2!

wrapping up

Page 22: Efficient HTTP Apis

Hey.. List #1 is slow!

368!

Page 23: Efficient HTTP Apis

Ask Ilya why!

• TCP connections need 3-way handshake.

• TLS requires up to 2 more round-trips.

• Read High Performance Browser Networking

http://chimera.labs.oreilly.com/books/1230000000545

Page 24: Efficient HTTP Apis

HttpUrlConnection

• http.keepAlive - should connections should be pooled at all? Default is true.

• http.maxConnections - maximum number of idle connections to each host in the pool. Default is 5.

• get[Input|Error]Stream().close() - recycles the connection, if fully read.

• disconnect() - removes the connection.

Page 25: Efficient HTTP Apis

Don’t forget to read!...

is = tryGetInputStream(connection);

isr = new InputStreamReader(is);things = new Gson().fromJson(isr, new TypeToken<List<Thing>>(){});...

InputStream tryGetInputStream(HttpUrlConnection connection) throws IOException {try { return connection.getInputStream();} catch (IOException e) { InputStream err = connection.getErrorStream();  while (in.read() != -1); // skip err.close(); throw e;}

Page 26: Efficient HTTP Apis

or just use okhttp2try (ResponseBody body = client.call(request).execute().body()) { // connection will be cleaned on close.}

client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { // body is implicitly closed on failure }

@Override public void onResponse(Response response) { // make sure you call response.body().close() when done }});

Page 27: Efficient HTTP Apis

We won!

• Recycled socket requests are much faster and have less impact on the server.

• Concessions: must read responses, concurrency is still bounded by sockets.

Page 28: Efficient HTTP Apis

Let’s make List #2 free

Cache-Control: private, max-age=60, s-maxage=0Vary: SecurityToken

Step 1: add a little magic to your server response

Step 2: make sure you are using a cache!

client.setCache(new Cache(dir, 10_MB));

Step 3: pray your developers don’t get clever

// TODO: stupid server sends stale data without thisnew Request.Builder().url(”https://apihost/things?time=” + currentTimeMillis());

Page 29: Efficient HTTP Apis

// Limit cache durationthisMessageWillSelfDestruct = new Request.Builder() .url(“https://foo.com/weather”) .cacheControl(new CacheControl.Builder().maxAge(12, HOURS).build()) .build();

// Opt-out of response cachingnotStored = new Request.Builder() .url(“https://foo.com/private”) .cacheControl(CacheControl.FORCE_NETWORK) .build();

// Offline modeoffline = new Request.Builder() .url(“https://foo.com/image”) .cacheControl(CacheControl.FORCE_CACHE) .build();

okhttp cache recipes

Page 30: Efficient HTTP Apis

We won again!

• No time or bandwidth used for cached responses

• No application-specific cache bugs code.

• Concessions: only supports “safe methods”, caching needs to be tuned.

Page 31: Efficient HTTP Apis

introduction

hello http api!

hello okhttp/2!

uh oh.. scale!

hello http/2!

wrapping up

Page 32: Efficient HTTP Apis

http/1.1• rfc2616 - June 1999• text-based framing• defined semantics of the web

http://www.w3.org/Protocols/rfc2616/rfc2616.html

Page 33: Efficient HTTP Apis

FYI: RFC 2616 is dead!

• RFC 7230-5 replaces RFC 2616.

• Checkout details on www.mnot.net/blog

Page 34: Efficient HTTP Apis

spdy/3.1

http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1

• google - Sep 2013• binary framing• retains http/1.1 semantics• concurrent multiplexed streams

Page 35: Efficient HTTP Apis

http/2

https://github.com/http2/http2-spec

• ietf draft 16 - June 2014• binary framing• retains http/1.1 semantics• concurrent multiplexed streams

Page 36: Efficient HTTP Apis

multiplexing

header compression

flow control

priority

server push

http/2 headline features

Page 37: Efficient HTTP Apis

multiplexing

header compression

server push

http/2 headline features

Page 38: Efficient HTTP Apis

Looking at the whole message

Request: request line, headers, and body

Response: status line, headers, and body

Page 39: Efficient HTTP Apis

http/1.1 round-trip

GZIPPED DATA

Content-Length: 318Cache-Control: private, max-age=60, s-maxage=0Vary: SecurityToken Date: Sun, 02 Feb 2014 20:30:38 GMTContent-Type: application/jsonContent-Encoding: gzip

Host: apihostSecurityToken: b08c85073c1a2d02Accept: application/jsonAccept-encoding: gzip, deflate

GET /things HTTP/1.1

HTTP/1.1 200 OK

Page 40: Efficient HTTP Apis

http/2 round-trip

GZIPPED DATA

:status: 200content-length: 318cache-control: private, max-age=60, s-maxage=0vary: SecurityToken date: Sun, 02 Feb 2014 20:30:38 GMTcontent-type: application/json

:method: GET:authority: apihost:path: /thingssecuritytoken: b08c85073c1a2d02accept: application/jsonaccept-encoding: gzip, deflate

HEADERS

Stream: 3

Flags: END_HEADERS, END_STREAM

HEADERS

Stream: 3

Flags: END_HEADERS

DATA

Stream: 3

Flags: END_STREAM

Page 41: Efficient HTTP Apis

interleaving

HEADERS

Stream: 5

Flags: END_HEADERS, END_STREAM

HEADERS

Stream: 3

Flags: END_HEADERS

DATA

Stream: 5

Flags:

DATA

Stream: 5

Flags: END_STREAM

HEADERS

Stream: 3

Flags: END_HEADERS, END_STREAM

HEADERS

Stream: 5

Flags: END_HEADERS

DATA

Stream: 3

Flags: END_STREAM

Page 42: Efficient HTTP Apis

Canceling a Stream

HEADERS

Stream: 5

Flags: END_HEADERS, END_STREAM

HEADERS

Stream: 3

Flags: END_HEADERS

DATA

Stream: 5

Flags:

HEADERS

Stream: 3

Flags: END_HEADERS, END_STREAM

HEADERS

Stream: 5

Flags: END_HEADERS

DATA

Stream: 3

Flags: END_STREAM

RST_STREAM

Stream: 5

ErrorCode: CANCEL

Page 43: Efficient HTTP Apis

control frames

HEADERS

Stream: 5

HEADERS

Stream: 3

DATA

Stream: 5

DATA

Stream: 3

HEADERS

Stream: 3

HEADERS

Stream: 5

SETTINGS

Stream: 0

SETTINGS

Stream: 0

DATA

Stream: 5

Page 44: Efficient HTTP Apis

OkHttp/2 Architecture

Frame ReaderThread

Frame WriterLock

Control Frame Queue

Page 45: Efficient HTTP Apis

multiplexing

header compression

server push

http/2 headline features

Page 46: Efficient HTTP Apis

http/1.1 headers

GZIPPED DATA

Content-Length: 318Cache-Control: private, max-age=60, s-maxage=0Vary: SecurityToken Date: Sun, 02 Feb 2014 20:30:38 GMTContent-Type: application/jsonContent-Encoding: gzip

Host: apihostSecurityToken: b08c85073c1a2d02Accept: application/jsonAccept-encoding: gzip, deflate

GET /things HTTP/1.1

HTTP/1.1 200 OK

168!

195!

318

• You can gzip data, but not headers!

Page 47: Efficient HTTP Apis

header compression

GZIPPED DATA

:status: 200content-length: 318cache-control: private, max-age=60, s-maxage=0vary: SecurityToken date: Sun, 02 Feb 2014 20:30:38 GMTcontent-type: application/jsoncontent-encoding: gzip

:method: GET:authority: apihost:path: /thingssecuritytoken: b08c85073c1a2d02accept: application/jsonaccept-encoding: gzip, deflate

HEADERS

Stream: 3

Flags: END_HEADERS, END_STREAM

HEADERS

Stream: 3

Flags: END_HEADERS

DATA

Stream: 3

Flags: END_STREAM

8 bytes

52 bytes compressed

8 bytes

85 bytes compressed

• 161 byte overhead instead of 363 8 bytes

Page 48: Efficient HTTP Apis

indexed headers!

GZIPPED DATA

:status: 200content-length: 318cache-control: private, max-age=60, s-maxage=0vary: SecurityToken date: Sun, 02 Feb 2014 20:30:39 GMTcontent-type: application/jsoncontent-encoding: gzip

:method: GET:authority: apihost:path: /thingssecuritytoken: b08c85073c1a2d02accept: application/jsonaccept-encoding: gzip, deflate

HEADERS

Stream: 3

Flags: END_HEADERS, END_STREAM

HEADERS

Stream: 3

Flags: END_HEADERS

DATA

Stream: 3

Flags: END_STREAM

8 bytes

28 bytes compressed

8 bytes

30 bytes compressed

• 82 byte overhead instead of 363 8 bytes

Page 49: Efficient HTTP Apis

hpack

http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10

• ietf draft 10 - Dec 2014• static and dynamic table• huffman encoding

Page 50: Efficient HTTP Apis

multiplexing

header compression

server push

http/2 headline features

Page 51: Efficient HTTP Apis

push promise

:method: GET:path: /things...

HEADERS

Stream: 3

HEADERS

Stream: 3

DATA

Stream: 4

:method: GET:path: /users/0...

PUSH_PROMISE

Stream: 3

Promised-Stream: 4

HEADERS

Stream: 4

push response goes into

cache

DATA

Stream: 3

Server guesses a future request or indicates a cache invalidation

Page 52: Efficient HTTP Apis

okhttp + http/2

• OkHttp 2.1 supports http/2 on TLS connections.

• Works out of the box on Android.

• For Java, add jetty’s NPN to your bootclasspath.

java -Xbootclasspath/p:/path/to/npn-boot-8.1.2.v20120308.jar ...

Page 53: Efficient HTTP Apis

We won!

• 1 socket for 100+ concurrent streams.

• Advanced features like flow control, cancellation and cache push.

• Dramatically less header overhead.

Page 54: Efficient HTTP Apis

Getting started

• Connect to twitter or use OkHttp’s MockWebServer.

https://github.com/square/okhttp

Page 55: Efficient HTTP Apis

introduction

hello http api!

hello okhttp/2!

uh oh.. scale!

hello http/2!

wrapping up

Page 56: Efficient HTTP Apis

Engage!

• Work with http/2, or at least spdy!

• Investigate http/2 api practices like libchan.

• Work on okhttp with us, and try version 2!

https://github.com/docker/libchan

https://code.google.com/p/mod-spdy/source/list

https://github.com/square/okio

Page 57: Efficient HTTP Apis

Thank you!

yes, twitter is hiring!yes, twitter runs http/2!

github square/okhttp@adrianfcole