Download - Efficient HTTP Apis

Transcript
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